Skip to content
Snippets Groups Projects
Commit 5a8cfdd8 authored by Tim Emiola's avatar Tim Emiola
Browse files

Merge pull request #1097 from murgatroid99/node_general_interface

Node general interface
parents 71d2f559 94c5e713
No related branches found
No related tags found
No related merge requests found
......@@ -56,7 +56,7 @@ function loadObject(value) {
});
return result;
} else if (value.className === 'Service') {
return client.makeClientConstructor(value);
return client.makeProtobufClientConstructor(value);
} else if (value.className === 'Message' || value.className === 'Enum') {
return value.build();
} else {
......@@ -119,7 +119,7 @@ exports.load = load;
/**
* See docs for server.makeServerConstructor
*/
exports.buildServer = server.makeServerConstructor;
exports.buildServer = server.makeProtobufServerConstructor;
/**
* Status name to code number mapping
......@@ -141,3 +141,7 @@ exports.Credentials = grpc.Credentials;
exports.ServerCredentials = grpc.ServerCredentials;
exports.getGoogleAuthDelegate = getGoogleAuthDelegate;
exports.makeGenericClientConstructor = client.makeClientConstructor;
exports.makeGenericServerConstructor = server.makeServerConstructor;
......@@ -35,9 +35,6 @@
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node');
var common = require('./common.js');
......@@ -463,13 +460,18 @@ var requester_makers = {
};
/**
* Creates a constructor for clients for the given service
* @param {ProtoBuf.Reflect.Service} service The service to generate a client
* for
* Creates a constructor for a client with the given methods. The methods object
* maps method name to an object with the following keys:
* path: The path on the server for accessing the method. For example, for
* protocol buffers, we use "/service_name/method_name"
* requestStream: bool indicating whether the client sends a stream
* resonseStream: bool indicating whether the server sends a stream
* requestSerialize: function to serialize request objects
* responseDeserialize: function to deserialize response objects
* @param {Object} methods An object mapping method names to method attributes
* @return {function(string, Object)} New client constructor
*/
function makeClientConstructor(service) {
var prefix = '/' + common.fullyQualifiedName(service) + '/';
function makeClientConstructor(methods) {
/**
* Create a client with the given methods
* @constructor
......@@ -489,30 +491,41 @@ function makeClientConstructor(service) {
this.channel = new grpc.Channel(address, options);
}
_.each(service.children, function(method) {
_.each(methods, function(attrs, name) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
if (attrs.requestStream) {
if (attrs.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
if (attrs.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
var serialize = common.serializeCls(method.resolvedRequestType.build());
var deserialize = common.deserializeCls(
method.resolvedResponseType.build());
Client.prototype[decapitalize(method.name)] = requester_makers[method_type](
prefix + capitalize(method.name), serialize, deserialize);
Client.prototype[decapitalize(method.name)].serialize = serialize;
Client.prototype[decapitalize(method.name)].deserialize = deserialize;
var serialize = attrs.requestSerialize;
var deserialize = attrs.responseDeserialize;
Client.prototype[name] = requester_makers[method_type](
attrs.path, serialize, deserialize);
Client.prototype[name].serialize = serialize;
Client.prototype[name].deserialize = deserialize;
});
return Client;
}
/**
* Creates a constructor for clients for the given service
* @param {ProtoBuf.Reflect.Service} service The service to generate a client
* for
* @return {function(string, Object)} New client constructor
*/
function makeProtobufClientConstructor(service) {
var method_attrs = common.getProtobufServiceAttrs(service);
var Client = makeClientConstructor(method_attrs);
Client.service = service;
return Client;
......@@ -520,6 +533,8 @@ function makeClientConstructor(service) {
exports.makeClientConstructor = makeClientConstructor;
exports.makeProtobufClientConstructor = makeProtobufClientConstructor;
/**
* See docs for client.status
*/
......
......@@ -36,6 +36,7 @@
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
/**
* Get a function that deserializes a specific type of protobuf.
......@@ -109,6 +110,26 @@ function wrapIgnoreNull(func) {
};
}
/**
* Return a map from method names to method attributes for the service.
* @param {ProtoBuf.Reflect.Service} service The service to get attributes for
* @return {Object} The attributes map
*/
function getProtobufServiceAttrs(service) {
var prefix = '/' + fullyQualifiedName(service) + '/';
return _.object(_.map(service.children, function(method) {
return [decapitalize(method.name), {
path: prefix + capitalize(method.name),
requestStream: method.requestStream,
responseStream: method.responseStream,
requestSerialize: serializeCls(method.resolvedRequestType.build()),
requestDeserialize: deserializeCls(method.resolvedRequestType.build()),
responseSerialize: serializeCls(method.resolvedResponseType.build()),
responseDeserialize: deserializeCls(method.resolvedResponseType.build())
}];
}));
}
/**
* See docs for deserializeCls
*/
......@@ -128,3 +149,5 @@ exports.fullyQualifiedName = fullyQualifiedName;
* See docs for wrapIgnoreNull
*/
exports.wrapIgnoreNull = wrapIgnoreNull;
exports.getProtobufServiceAttrs = getProtobufServiceAttrs;
......@@ -35,9 +35,6 @@
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node');
var common = require('./common');
......@@ -532,26 +529,20 @@ Server.prototype.bind = function(port, creds) {
};
/**
* Creates a constructor for servers with a service defined by the methods
* object. The methods object has string keys and values of this form:
* {serialize: function, deserialize: function, client_stream: bool,
* server_stream: bool}
* @param {Object} methods Method descriptor for each method the server should
* expose
* @param {string} prefix The prefex to prepend to each method name
* @return {function(Object, Object)} New server constructor
* Create a constructor for servers with services defined by service_attr_map.
* That is an object that maps (namespaced) service names to objects that in
* turn map method names to objects with the following keys:
* path: The path on the server for accessing the method. For example, for
* protocol buffers, we use "/service_name/method_name"
* requestStream: bool indicating whether the client sends a stream
* resonseStream: bool indicating whether the server sends a stream
* requestDeserialize: function to deserialize request objects
* responseSerialize: function to serialize response objects
* @param {Object} service_attr_map An object mapping service names to method
* attribute map objects
* @return {function(Object, function, Object=)} New server constructor
*/
function makeServerConstructor(services) {
var qual_names = [];
_.each(services, function(service) {
_.each(service.children, function(method) {
var name = common.fullyQualifiedName(method);
if (_.indexOf(qual_names, name) !== -1) {
throw new Error('Method ' + name + ' exposed by more than one service');
}
qual_names.push(name);
});
});
function makeServerConstructor(service_attr_map) {
/**
* Create a server with the given handlers for all of the methods.
* @constructor
......@@ -565,41 +556,34 @@ function makeServerConstructor(services) {
function SurfaceServer(service_handlers, getMetadata, options) {
var server = new Server(getMetadata, options);
this.inner_server = server;
_.each(services, function(service) {
var service_name = common.fullyQualifiedName(service);
_.each(service_attr_map, function(service_attrs, service_name) {
if (service_handlers[service_name] === undefined) {
throw new Error('Handlers for service ' +
service_name + ' not provided.');
}
var prefix = '/' + common.fullyQualifiedName(service) + '/';
_.each(service.children, function(method) {
_.each(service_attrs, function(attrs, name) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
if (attrs.requestStream) {
if (attrs.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
if (attrs.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
if (service_handlers[service_name][decapitalize(method.name)] ===
undefined) {
throw new Error('Method handler for ' +
common.fullyQualifiedName(method) + ' not provided.');
if (service_handlers[service_name][name] === undefined) {
throw new Error('Method handler for ' + attrs.path +
' not provided.');
}
var serialize = common.serializeCls(
method.resolvedResponseType.build());
var deserialize = common.deserializeCls(
method.resolvedRequestType.build());
server.register(
prefix + capitalize(method.name),
service_handlers[service_name][decapitalize(method.name)],
serialize, deserialize, method_type);
var serialize = attrs.responseSerialize;
var deserialize = attrs.requestDeserialize;
server.register(attrs.path, service_handlers[service_name][name],
serialize, deserialize, method_type);
});
}, this);
}
......@@ -635,7 +619,40 @@ function makeServerConstructor(services) {
return SurfaceServer;
}
/**
* Create a constructor for servers that serve the given services.
* @param {Array<ProtoBuf.Reflect.Service>} services The services that the
* servers will serve
* @return {function(Object, function, Object=)} New server constructor
*/
function makeProtobufServerConstructor(services) {
var qual_names = [];
var service_attr_map = {};
_.each(services, function(service) {
var service_name = common.fullyQualifiedName(service);
_.each(service.children, function(method) {
var name = common.fullyQualifiedName(method);
if (_.indexOf(qual_names, name) !== -1) {
throw new Error('Method ' + name + ' exposed by more than one service');
}
qual_names.push(name);
});
var method_attrs = common.getProtobufServiceAttrs(service);
if (!service_attr_map.hasOwnProperty(service_name)) {
service_attr_map[service_name] = {};
}
service_attr_map[service_name] = _.extend(service_attr_map[service_name],
method_attrs);
});
return makeServerConstructor(service_attr_map);
}
/**
* See documentation for makeServerConstructor
*/
exports.makeServerConstructor = makeServerConstructor;
/**
* See documentation for makeProtobufServerConstructor
*/
exports.makeProtobufServerConstructor = makeProtobufServerConstructor;
......@@ -45,6 +45,8 @@ var math_proto = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
var mathService = math_proto.lookup('math.Math');
var capitalize = require('underscore.string/capitalize');
describe('Surface server constructor', function() {
it('Should fail with conflicting method names', function() {
assert.throws(function() {
......@@ -75,6 +77,55 @@ describe('Surface server constructor', function() {
}, /math.Math/);
});
});
describe('Generic client and server', function() {
function toString(val) {
return val.toString();
}
function toBuffer(str) {
return new Buffer(str);
}
var string_service_attrs = {
'capitalize' : {
path: '/string/capitalize',
requestStream: false,
responseStream: false,
requestSerialize: toBuffer,
requestDeserialize: toString,
responseSerialize: toBuffer,
responseDeserialize: toString
}
};
describe('String client and server', function() {
var client;
var server;
before(function() {
var Server = grpc.makeGenericServerConstructor({
string: string_service_attrs
});
server = new Server({
string: {
capitalize: function(call, callback) {
callback(null, capitalize(call.request));
}
}
});
var port = server.bind('localhost:0');
server.listen();
var Client = grpc.makeGenericClientConstructor(string_service_attrs);
client = new Client('localhost:' + port);
});
after(function() {
server.shutdown();
});
it('Should respond with a capitalized string', function(done) {
client.capitalize('abc', function(err, response) {
assert.ifError(err);
assert.strictEqual(response, 'Abc');
done();
});
});
});
});
describe('Cancelling surface client', function() {
var client;
var server;
......@@ -89,7 +140,7 @@ describe('Cancelling surface client', function() {
}
});
var port = server.bind('localhost:0');
var Client = surface_client.makeClientConstructor(mathService);
var Client = surface_client.makeProtobufClientConstructor(mathService);
client = new Client('localhost:' + port);
});
after(function() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment