From e023e98d9a2513f61a7dbf0555e54da7bbdbdfd1 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Wed, 18 Mar 2015 17:17:33 -0700 Subject: Added functions to create generic servers and clients --- src/node/index.js | 8 +++- src/node/src/client.js | 53 +++++++++++++++--------- src/node/src/common.js | 23 +++++++++++ src/node/src/server.js | 96 +++++++++++++++++++++++++------------------ src/node/test/surface_test.js | 2 +- 5 files changed, 119 insertions(+), 63 deletions(-) diff --git a/src/node/index.js b/src/node/index.js index ad3dd96af7..0b768edc6b 100644 --- a/src/node/index.js +++ b/src/node/index.js @@ -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; diff --git a/src/node/src/client.js b/src/node/src/client.js index 54b8dbdc9c..c46f7d0526 100644 --- a/src/node/src/client.js +++ b/src/node/src/client.js @@ -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 */ diff --git a/src/node/src/common.js b/src/node/src/common.js index eec8f0f987..55a6b13782 100644 --- a/src/node/src/common.js +++ b/src/node/src/common.js @@ -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; diff --git a/src/node/src/server.js b/src/node/src/server.js index b72d110666..e214d76dc5 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -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,37 @@ function makeServerConstructor(services) { return SurfaceServer; } +/** + * Create a constructor for servers that serve the given services. + * @param {Array} 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; + +exports.makeProtobufServerConstructor = makeProtobufServerConstructor; diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js index 91d8197bee..6fae483e6c 100644 --- a/src/node/test/surface_test.js +++ b/src/node/test/surface_test.js @@ -89,7 +89,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() { -- cgit v1.2.3 From e0f24dc2add401ecf970bff48ae592866d8a6af1 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 19 Mar 2015 11:16:48 -0700 Subject: Added a test for generic client and server constructors --- src/node/test/surface_test.js | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js index 6fae483e6c..96b47815e1 100644 --- a/src/node/test/surface_test.js +++ b/src/node/test/surface_test.js @@ -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; -- cgit v1.2.3 From 94c5e7135b74a7f5ddb578f7482bb3f09efbe81c Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Mon, 23 Mar 2015 10:46:05 -0700 Subject: Added a comment to server.js --- src/node/src/server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/node/src/server.js b/src/node/src/server.js index e214d76dc5..8a26a43606 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -652,4 +652,7 @@ function makeProtobufServerConstructor(services) { */ exports.makeServerConstructor = makeServerConstructor; +/** + * See documentation for makeProtobufServerConstructor + */ exports.makeProtobufServerConstructor = makeProtobufServerConstructor; -- cgit v1.2.3