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) { ...@@ -56,7 +56,7 @@ function loadObject(value) {
}); });
return result; return result;
} else if (value.className === 'Service') { } else if (value.className === 'Service') {
return client.makeClientConstructor(value); return client.makeProtobufClientConstructor(value);
} else if (value.className === 'Message' || value.className === 'Enum') { } else if (value.className === 'Message' || value.className === 'Enum') {
return value.build(); return value.build();
} else { } else {
...@@ -119,7 +119,7 @@ exports.load = load; ...@@ -119,7 +119,7 @@ exports.load = load;
/** /**
* See docs for server.makeServerConstructor * See docs for server.makeServerConstructor
*/ */
exports.buildServer = server.makeServerConstructor; exports.buildServer = server.makeProtobufServerConstructor;
/** /**
* Status name to code number mapping * Status name to code number mapping
...@@ -141,3 +141,7 @@ exports.Credentials = grpc.Credentials; ...@@ -141,3 +141,7 @@ exports.Credentials = grpc.Credentials;
exports.ServerCredentials = grpc.ServerCredentials; exports.ServerCredentials = grpc.ServerCredentials;
exports.getGoogleAuthDelegate = getGoogleAuthDelegate; exports.getGoogleAuthDelegate = getGoogleAuthDelegate;
exports.makeGenericClientConstructor = client.makeClientConstructor;
exports.makeGenericServerConstructor = server.makeServerConstructor;
...@@ -35,9 +35,6 @@ ...@@ -35,9 +35,6 @@
var _ = require('underscore'); var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node'); var grpc = require('bindings')('grpc.node');
var common = require('./common.js'); var common = require('./common.js');
...@@ -463,13 +460,18 @@ var requester_makers = { ...@@ -463,13 +460,18 @@ var requester_makers = {
}; };
/** /**
* Creates a constructor for clients for the given service * Creates a constructor for a client with the given methods. The methods object
* @param {ProtoBuf.Reflect.Service} service The service to generate a client * maps method name to an object with the following keys:
* for * 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 * @return {function(string, Object)} New client constructor
*/ */
function makeClientConstructor(service) { function makeClientConstructor(methods) {
var prefix = '/' + common.fullyQualifiedName(service) + '/';
/** /**
* Create a client with the given methods * Create a client with the given methods
* @constructor * @constructor
...@@ -489,30 +491,41 @@ function makeClientConstructor(service) { ...@@ -489,30 +491,41 @@ function makeClientConstructor(service) {
this.channel = new grpc.Channel(address, options); this.channel = new grpc.Channel(address, options);
} }
_.each(service.children, function(method) { _.each(methods, function(attrs, name) {
var method_type; var method_type;
if (method.requestStream) { if (attrs.requestStream) {
if (method.responseStream) { if (attrs.responseStream) {
method_type = 'bidi'; method_type = 'bidi';
} else { } else {
method_type = 'client_stream'; method_type = 'client_stream';
} }
} else { } else {
if (method.responseStream) { if (attrs.responseStream) {
method_type = 'server_stream'; method_type = 'server_stream';
} else { } else {
method_type = 'unary'; method_type = 'unary';
} }
} }
var serialize = common.serializeCls(method.resolvedRequestType.build()); var serialize = attrs.requestSerialize;
var deserialize = common.deserializeCls( var deserialize = attrs.responseDeserialize;
method.resolvedResponseType.build()); Client.prototype[name] = requester_makers[method_type](
Client.prototype[decapitalize(method.name)] = requester_makers[method_type]( attrs.path, serialize, deserialize);
prefix + capitalize(method.name), serialize, deserialize); Client.prototype[name].serialize = serialize;
Client.prototype[decapitalize(method.name)].serialize = serialize; Client.prototype[name].deserialize = deserialize;
Client.prototype[decapitalize(method.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; Client.service = service;
return Client; return Client;
...@@ -520,6 +533,8 @@ function makeClientConstructor(service) { ...@@ -520,6 +533,8 @@ function makeClientConstructor(service) {
exports.makeClientConstructor = makeClientConstructor; exports.makeClientConstructor = makeClientConstructor;
exports.makeProtobufClientConstructor = makeProtobufClientConstructor;
/** /**
* See docs for client.status * See docs for client.status
*/ */
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
var _ = require('underscore'); var _ = require('underscore');
var capitalize = require('underscore.string/capitalize'); var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
/** /**
* Get a function that deserializes a specific type of protobuf. * Get a function that deserializes a specific type of protobuf.
...@@ -109,6 +110,26 @@ function wrapIgnoreNull(func) { ...@@ -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 * See docs for deserializeCls
*/ */
...@@ -128,3 +149,5 @@ exports.fullyQualifiedName = fullyQualifiedName; ...@@ -128,3 +149,5 @@ exports.fullyQualifiedName = fullyQualifiedName;
* See docs for wrapIgnoreNull * See docs for wrapIgnoreNull
*/ */
exports.wrapIgnoreNull = wrapIgnoreNull; exports.wrapIgnoreNull = wrapIgnoreNull;
exports.getProtobufServiceAttrs = getProtobufServiceAttrs;
...@@ -35,9 +35,6 @@ ...@@ -35,9 +35,6 @@
var _ = require('underscore'); var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node'); var grpc = require('bindings')('grpc.node');
var common = require('./common'); var common = require('./common');
...@@ -532,26 +529,20 @@ Server.prototype.bind = function(port, creds) { ...@@ -532,26 +529,20 @@ Server.prototype.bind = function(port, creds) {
}; };
/** /**
* Creates a constructor for servers with a service defined by the methods * Create a constructor for servers with services defined by service_attr_map.
* object. The methods object has string keys and values of this form: * That is an object that maps (namespaced) service names to objects that in
* {serialize: function, deserialize: function, client_stream: bool, * turn map method names to objects with the following keys:
* server_stream: bool} * path: The path on the server for accessing the method. For example, for
* @param {Object} methods Method descriptor for each method the server should * protocol buffers, we use "/service_name/method_name"
* expose * requestStream: bool indicating whether the client sends a stream
* @param {string} prefix The prefex to prepend to each method name * resonseStream: bool indicating whether the server sends a stream
* @return {function(Object, Object)} New server constructor * 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) { function makeServerConstructor(service_attr_map) {
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);
});
});
/** /**
* Create a server with the given handlers for all of the methods. * Create a server with the given handlers for all of the methods.
* @constructor * @constructor
...@@ -565,41 +556,34 @@ function makeServerConstructor(services) { ...@@ -565,41 +556,34 @@ function makeServerConstructor(services) {
function SurfaceServer(service_handlers, getMetadata, options) { function SurfaceServer(service_handlers, getMetadata, options) {
var server = new Server(getMetadata, options); var server = new Server(getMetadata, options);
this.inner_server = server; this.inner_server = server;
_.each(services, function(service) { _.each(service_attr_map, function(service_attrs, service_name) {
var service_name = common.fullyQualifiedName(service);
if (service_handlers[service_name] === undefined) { if (service_handlers[service_name] === undefined) {
throw new Error('Handlers for service ' + throw new Error('Handlers for service ' +
service_name + ' not provided.'); service_name + ' not provided.');
} }
var prefix = '/' + common.fullyQualifiedName(service) + '/'; _.each(service_attrs, function(attrs, name) {
_.each(service.children, function(method) {
var method_type; var method_type;
if (method.requestStream) { if (attrs.requestStream) {
if (method.responseStream) { if (attrs.responseStream) {
method_type = 'bidi'; method_type = 'bidi';
} else { } else {
method_type = 'client_stream'; method_type = 'client_stream';
} }
} else { } else {
if (method.responseStream) { if (attrs.responseStream) {
method_type = 'server_stream'; method_type = 'server_stream';
} else { } else {
method_type = 'unary'; method_type = 'unary';
} }
} }
if (service_handlers[service_name][decapitalize(method.name)] === if (service_handlers[service_name][name] === undefined) {
undefined) { throw new Error('Method handler for ' + attrs.path +
throw new Error('Method handler for ' + ' not provided.');
common.fullyQualifiedName(method) + ' not provided.');
} }
var serialize = common.serializeCls( var serialize = attrs.responseSerialize;
method.resolvedResponseType.build()); var deserialize = attrs.requestDeserialize;
var deserialize = common.deserializeCls( server.register(attrs.path, service_handlers[service_name][name],
method.resolvedRequestType.build()); serialize, deserialize, method_type);
server.register(
prefix + capitalize(method.name),
service_handlers[service_name][decapitalize(method.name)],
serialize, deserialize, method_type);
}); });
}, this); }, this);
} }
...@@ -635,7 +619,40 @@ function makeServerConstructor(services) { ...@@ -635,7 +619,40 @@ function makeServerConstructor(services) {
return SurfaceServer; 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 * See documentation for makeServerConstructor
*/ */
exports.makeServerConstructor = makeServerConstructor; exports.makeServerConstructor = makeServerConstructor;
/**
* See documentation for makeProtobufServerConstructor
*/
exports.makeProtobufServerConstructor = makeProtobufServerConstructor;
...@@ -45,6 +45,8 @@ var math_proto = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto'); ...@@ -45,6 +45,8 @@ var math_proto = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
var mathService = math_proto.lookup('math.Math'); var mathService = math_proto.lookup('math.Math');
var capitalize = require('underscore.string/capitalize');
describe('Surface server constructor', function() { describe('Surface server constructor', function() {
it('Should fail with conflicting method names', function() { it('Should fail with conflicting method names', function() {
assert.throws(function() { assert.throws(function() {
...@@ -75,6 +77,55 @@ describe('Surface server constructor', function() { ...@@ -75,6 +77,55 @@ describe('Surface server constructor', function() {
}, /math.Math/); }, /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() { describe('Cancelling surface client', function() {
var client; var client;
var server; var server;
...@@ -89,7 +140,7 @@ describe('Cancelling surface client', function() { ...@@ -89,7 +140,7 @@ describe('Cancelling surface client', function() {
} }
}); });
var port = server.bind('localhost:0'); var port = server.bind('localhost:0');
var Client = surface_client.makeClientConstructor(mathService); var Client = surface_client.makeProtobufClientConstructor(mathService);
client = new Client('localhost:' + port); client = new Client('localhost:' + port);
}); });
after(function() { 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