aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/node')
-rw-r--r--src/node/README.md6
-rw-r--r--src/node/examples/qps_test.js136
-rw-r--r--src/node/ext/byte_buffer.cc2
-rw-r--r--src/node/ext/call.cc34
-rw-r--r--src/node/ext/completion_queue_async_worker.cc1
-rw-r--r--src/node/index.js8
-rw-r--r--src/node/interop/interop_client.js12
-rw-r--r--src/node/package.json4
-rw-r--r--src/node/src/client.js53
-rw-r--r--src/node/src/common.js23
-rw-r--r--src/node/src/server.js99
-rw-r--r--src/node/test/call_test.js4
-rw-r--r--src/node/test/end_to_end_test.js87
-rw-r--r--src/node/test/server_test.js94
-rw-r--r--src/node/test/surface_test.js53
15 files changed, 520 insertions, 96 deletions
diff --git a/src/node/README.md b/src/node/README.md
index 5b3de6b4f6..b1d2310ede 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -10,9 +10,9 @@ This requires `node` to be installed. If you instead have the `nodejs` executabl
## Installation
-First, clone this repository (NPM package coming soon). Then follow the instructions in the `INSTALL` file in the root of the repository to install the C core library that this package depends on.
-
-Then, simply run `npm install` in or referencing this directory.
+ 1. Clone [the grpc repository](https://github.com/grpc/grpc).
+ 2. Follow the instructions in the `INSTALL` file in the root of that repository to install the C core library that this package depends on.
+ 3. Run `npm install`.
## Tests
diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js
new file mode 100644
index 0000000000..00293b464a
--- /dev/null
+++ b/src/node/examples/qps_test.js
@@ -0,0 +1,136 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/**
+ * This script runs a QPS test. It sends requests for a specified length of time
+ * with a specified number pending at any one time. It then outputs the measured
+ * QPS. Usage:
+ * node qps_test.js [--concurrent=count] [--time=seconds]
+ * concurrent defaults to 100 and time defaults to 10
+ */
+
+'use strict';
+
+var async = require('async');
+var parseArgs = require('minimist');
+
+var grpc = require('..');
+var testProto = grpc.load(__dirname + '/../interop/test.proto').grpc.testing;
+var interop_server = require('../interop/interop_server.js');
+
+/**
+ * Runs the QPS test. Sends requests constantly for the given number of seconds,
+ * and keeps concurrent_calls requests pending at all times. When the test ends,
+ * the callback is called with the number of calls that completed within the
+ * time limit.
+ * @param {number} concurrent_calls The number of calls to have pending
+ * simultaneously
+ * @param {number} seconds The number of seconds to run the test for
+ * @param {function(Error, number)} callback Callback for test completion
+ */
+function runTest(concurrent_calls, seconds, callback) {
+ var testServer = interop_server.getServer(0, false);
+ testServer.server.listen();
+ var client = new testProto.TestService('localhost:' + testServer.port);
+
+ var warmup_num = 100;
+
+ /**
+ * Warms up the client to avoid counting startup time in the test result
+ * @param {function(Error)} callback Called when warmup is complete
+ */
+ function warmUp(callback) {
+ var pending = warmup_num;
+ function startCall() {
+ client.emptyCall({}, function(err, resp) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ pending--;
+ if (pending === 0) {
+ callback(null);
+ }
+ });
+ }
+ for (var i = 0; i < warmup_num; i++) {
+ startCall();
+ }
+ }
+ /**
+ * Run the QPS test. Starts concurrent_calls requests, then starts a new
+ * request whenever one completes until time runs out.
+ * @param {function(Error, number)} callback Called when the test is complete.
+ * The second argument is the number of calls that finished within the
+ * time limit
+ */
+ function run(callback) {
+ var running = 0;
+ var count = 0;
+ var start = process.hrtime();
+ function responseCallback(err, resp) {
+ if (process.hrtime(start)[0] < seconds) {
+ count += 1;
+ client.emptyCall({}, responseCallback);
+ } else {
+ running -= 1;
+ if (running <= 0) {
+ callback(null, count);
+ }
+ }
+ }
+ for (var i = 0; i < concurrent_calls; i++) {
+ running += 1;
+ client.emptyCall({}, responseCallback);
+ }
+ }
+ async.waterfall([warmUp, run], function(err, count) {
+ testServer.server.shutdown();
+ callback(err, count);
+ });
+}
+
+if (require.main === module) {
+ var argv = parseArgs(process.argv.slice(2), {
+ default: {'concurrent': 100,
+ 'time': 10}
+ });
+ runTest(argv.concurrent, argv.time, function(err, count) {
+ if (err) {
+ throw err;
+ }
+ console.log('Concurrent calls:', argv.concurrent);
+ console.log('Time:', argv.time, 'seconds');
+ console.log('QPS:', (count/argv.time));
+ });
+}
diff --git a/src/node/ext/byte_buffer.cc b/src/node/ext/byte_buffer.cc
index 5235c8e083..82b54b518c 100644
--- a/src/node/ext/byte_buffer.cc
+++ b/src/node/ext/byte_buffer.cc
@@ -65,7 +65,7 @@ grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
NanEscapableScope();
if (buffer == NULL) {
- return NanNull();
+ return NanEscapeScope(NanNull());
}
size_t length = grpc_byte_buffer_length(buffer);
char *result = reinterpret_cast<char *>(calloc(length, sizeof(char)));
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index afb6541783..8cc3e38cd9 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -75,6 +75,9 @@ using v8::Value;
NanCallback *Call::constructor;
Persistent<FunctionTemplate> Call::fun_tpl;
+bool EndsWith(const char *str, const char *substr) {
+ return strcmp(str+strlen(str)-strlen(substr), substr) == 0;
+}
bool CreateMetadataArray(Handle<Object> metadata, grpc_metadata_array *array,
shared_ptr<Resources> resources) {
@@ -99,14 +102,19 @@ bool CreateMetadataArray(Handle<Object> metadata, grpc_metadata_array *array,
Handle<Value> value = values->Get(j);
grpc_metadata *current = &array->metadata[array->count];
current->key = **utf8_key;
- if (::node::Buffer::HasInstance(value)) {
- current->value = ::node::Buffer::Data(value);
- current->value_length = ::node::Buffer::Length(value);
- Persistent<Value> *handle = new Persistent<Value>();
- NanAssignPersistent(*handle, value);
- resources->handles.push_back(unique_ptr<PersistentHolder>(
- new PersistentHolder(handle)));
- } else if (value->IsString()) {
+ // Only allow binary headers for "-bin" keys
+ if (EndsWith(current->key, "-bin")) {
+ if (::node::Buffer::HasInstance(value)) {
+ current->value = ::node::Buffer::Data(value);
+ current->value_length = ::node::Buffer::Length(value);
+ Persistent<Value> *handle = new Persistent<Value>();
+ NanAssignPersistent(*handle, value);
+ resources->handles.push_back(unique_ptr<PersistentHolder>(
+ new PersistentHolder(handle)));
+ continue;
+ }
+ }
+ if (value->IsString()) {
Handle<String> string_value = value->ToString();
NanUtf8String *utf8_value = new NanUtf8String(string_value);
resources->strings.push_back(unique_ptr<NanUtf8String>(utf8_value));
@@ -146,9 +154,13 @@ Handle<Value> ParseMetadata(const grpc_metadata_array *metadata_array) {
array = NanNew<Array>(size_map[elem->key]);
metadata_object->Set(key_string, array);
}
- array->Set(index_map[elem->key],
- MakeFastBuffer(
- NanNewBufferHandle(elem->value, elem->value_length)));
+ if (EndsWith(elem->key, "-bin")) {
+ array->Set(index_map[elem->key],
+ MakeFastBuffer(
+ NanNewBufferHandle(elem->value, elem->value_length)));
+ } else {
+ array->Set(index_map[elem->key], NanNew(elem->value));
+ }
index_map[elem->key] += 1;
}
return NanEscapeScope(metadata_object);
diff --git a/src/node/ext/completion_queue_async_worker.cc b/src/node/ext/completion_queue_async_worker.cc
index ca22527e6f..cd7acd1d1b 100644
--- a/src/node/ext/completion_queue_async_worker.cc
+++ b/src/node/ext/completion_queue_async_worker.cc
@@ -80,7 +80,6 @@ void CompletionQueueAsyncWorker::HandleOKCallback() {
NanScope();
NanCallback *callback = GetTagCallback(result->tag);
Handle<Value> argv[] = {NanNull(), GetTagNodeValue(result->tag)};
-
callback->Call(2, argv);
DestroyTag(result->tag);
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/interop/interop_client.js b/src/node/interop/interop_client.js
index 8060baf827..77804cf595 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -35,6 +35,7 @@
var fs = require('fs');
var path = require('path');
+var _ = require('underscore');
var grpc = require('..');
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
var GoogleAuth = require('google-auth-library');
@@ -45,6 +46,8 @@ var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
var AUTH_USER = ('155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk' +
'@developer.gserviceaccount.com');
+var COMPUTE_ENGINE_USER = ('155450119199-r5aaqa2vqoa9g5mv2m6s3m1l293rlmel' +
+ '@developer.gserviceaccount.com');
/**
* Create a buffer filled with size zeroes
@@ -265,11 +268,12 @@ function cancelAfterFirstResponse(client, done) {
/**
* Run one of the authentication tests.
+ * @param {string} expected_user The expected username in the response
* @param {Client} client The client to test against
* @param {function} done Callback to call when the test is completed. Included
* primarily for use with mocha
*/
-function authTest(client, done) {
+function authTest(expected_user, client, done) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
assert.ifError(err);
if (credential.createScopedRequired()) {
@@ -290,7 +294,7 @@ function authTest(client, done) {
assert.strictEqual(resp.payload.type, testProto.PayloadType.COMPRESSABLE);
assert.strictEqual(resp.payload.body.limit - resp.payload.body.offset,
314159);
- assert.strictEqual(resp.username, AUTH_USER);
+ assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
});
call.on('status', function(status) {
@@ -314,8 +318,8 @@ var test_cases = {
empty_stream: emptyStream,
cancel_after_begin: cancelAfterBegin,
cancel_after_first_response: cancelAfterFirstResponse,
- compute_engine_creds: authTest,
- service_account_creds: authTest
+ compute_engine_creds: _.partial(authTest, AUTH_USER),
+ service_account_creds: _.partial(authTest, COMPUTE_ENGINE_USER)
};
/**
diff --git a/src/node/package.json b/src/node/package.json
index 20eb21fc47..9f52f8c988 100644
--- a/src/node/package.json
+++ b/src/node/package.json
@@ -1,6 +1,6 @@
{
"name": "grpc",
- "version": "0.5.2",
+ "version": "0.6.0",
"author": "Google Inc.",
"description": "gRPC Library for Node",
"homepage": "http://www.grpc.io/",
@@ -26,7 +26,7 @@
"dependencies": {
"bindings": "^1.2.0",
"nan": "^1.5.0",
- "protobufjs": "murgatroid99/ProtoBuf.js",
+ "protobufjs": "^4.0.0-b2",
"underscore": "^1.6.0",
"underscore.string": "^3.0.0"
},
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.
@@ -110,6 +111,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
*/
exports.deserializeCls = 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..8a26a43606 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);
}
@@ -636,6 +620,39 @@ function makeServerConstructor(services) {
}
/**
+ * 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;
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index 7b2b36ae37..98158ffff3 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -142,8 +142,8 @@ describe('call', function() {
assert.doesNotThrow(function() {
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = {
- 'key1': [new Buffer('value1')],
- 'key2': [new Buffer('value2')]
+ 'key1-bin': [new Buffer('value1')],
+ 'key2-bin': [new Buffer('value2')]
};
call.startBatch(batch, function(err, resp) {
assert.ifError(err);
diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js
index 1cc1928691..60e9861bc8 100644
--- a/src/node/test/end_to_end_test.js
+++ b/src/node/test/end_to_end_test.js
@@ -138,21 +138,21 @@ describe('end-to-end', function() {
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
assert.ifError(err);
- assert(response['send metadata']);
- assert(response['client close']);
- assert(response.hasOwnProperty('metadata'));
- assert.strictEqual(response.metadata.server_key[0].toString(),
- 'server_value');
- assert.deepEqual(response.status, {'code': grpc.status.OK,
- 'details': status_text,
- 'metadata': {}});
+ assert.deepEqual(response,{
+ 'send metadata': true,
+ 'client close': true,
+ metadata: {server_key: ['server_value']},
+ status: {'code': grpc.status.OK,
+ 'details': status_text,
+ 'metadata': {}}
+ });
done();
});
server.requestCall(function(err, call_details) {
var new_call = call_details['new call'];
assert.notEqual(new_call, null);
- assert.strictEqual(new_call.metadata.client_key[0].toString(),
+ assert.strictEqual(new_call.metadata.client_key[0],
'client_value');
var server_call = new_call.call;
assert.notEqual(server_call, null);
@@ -235,4 +235,73 @@ describe('end-to-end', function() {
});
});
});
+ it('should send multiple messages', function(complete) {
+ var done = multiDone(complete, 2);
+ var requests = ['req1', 'req2'];
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 3);
+ var status_text = 'xyz';
+ var call = new grpc.Call(channel,
+ 'dummy_method',
+ Infinity);
+ var client_batch = {};
+ client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+ client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[0]);
+ client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
+ call.startBatch(client_batch, function(err, response) {
+ assert.ifError(err);
+ assert.deepEqual(response, {
+ 'send metadata': true,
+ 'send message': true,
+ 'metadata': {}
+ });
+ var req2_batch = {};
+ req2_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[1]);
+ req2_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
+ req2_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+ call.startBatch(req2_batch, function(err, resp) {
+ assert.ifError(err);
+ assert.deepEqual(resp, {
+ 'send message': true,
+ 'client close': true,
+ 'status': {
+ 'code': grpc.status.OK,
+ 'details': status_text,
+ 'metadata': {}
+ }
+ });
+ done();
+ });
+ });
+
+ server.requestCall(function(err, call_details) {
+ var new_call = call_details['new call'];
+ assert.notEqual(new_call, null);
+ var server_call = new_call.call;
+ assert.notEqual(server_call, null);
+ var server_batch = {};
+ server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
+ server_batch[grpc.opType.RECV_MESSAGE] = true;
+ server_call.startBatch(server_batch, function(err, response) {
+ assert.ifError(err);
+ assert(response['send metadata']);
+ assert.strictEqual(response.read.toString(), requests[0]);
+ var end_batch = {};
+ end_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
+ end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
+ 'metadata': {},
+ 'code': grpc.status.OK,
+ 'details': status_text
+ };
+ end_batch[grpc.opType.RECV_MESSAGE] = true;
+ server_call.startBatch(end_batch, function(err, response) {
+ assert.ifError(err);
+ assert(response['send status']);
+ assert(!response.cancelled);
+ assert.strictEqual(response.read.toString(), requests[1]);
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js
new file mode 100644
index 0000000000..7cb34fa0cb
--- /dev/null
+++ b/src/node/test/server_test.js
@@ -0,0 +1,94 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+'use strict';
+
+var assert = require('assert');
+var grpc = require('bindings')('grpc.node');
+
+describe('server', function() {
+ describe('constructor', function() {
+ it('should work with no arguments', function() {
+ assert.doesNotThrow(function() {
+ new grpc.Server();
+ });
+ });
+ it('should work with an empty list argument', function() {
+ assert.doesNotThrow(function() {
+ new grpc.Server([]);
+ });
+ });
+ });
+ describe('addHttp2Port', function() {
+ var server;
+ before(function() {
+ server = new grpc.Server();
+ });
+ it('should bind to an unused port', function() {
+ var port;
+ assert.doesNotThrow(function() {
+ port = server.addHttp2Port('0.0.0.0:0');
+ });
+ assert(port > 0);
+ });
+ });
+ describe('addSecureHttp2Port', function() {
+ var server;
+ before(function() {
+ server = new grpc.Server();
+ });
+ it('should bind to an unused port with fake credentials', function() {
+ var port;
+ var creds = grpc.ServerCredentials.createFake();
+ assert.doesNotThrow(function() {
+ port = server.addSecureHttp2Port('0.0.0.0:0', creds);
+ });
+ assert(port > 0);
+ });
+ });
+ describe('listen', function() {
+ var server;
+ before(function() {
+ server = new grpc.Server();
+ server.addHttp2Port('0.0.0.0:0');
+ });
+ after(function() {
+ server.shutdown();
+ });
+ it('should listen without error', function() {
+ assert.doesNotThrow(function() {
+ server.start();
+ });
+ });
+ });
+});
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 91d8197bee..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;
@@ -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() {