aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/node
diff options
context:
space:
mode:
authorGravatar David Garcia Quintas <dgq@google.com>2015-08-24 12:05:13 -0700
committerGravatar David Garcia Quintas <dgq@google.com>2015-08-24 12:08:38 -0700
commitc43648f250dd6cb0f086e2366e468372a6de26ae (patch)
tree0353cb50e39de0338553b97e5fcb1276f48cf4a5 /src/node
parentbeac88ca56f4710e86668f2cbbd80e02e0607f9c (diff)
parent04715888e60c6195a2c1d9d6b31f7a82f0d717e2 (diff)
Merge branch 'master' of github.com:grpc/grpc into compression-accept-encoding
Diffstat (limited to 'src/node')
-rw-r--r--src/node/README.md32
-rw-r--r--src/node/binding.gyp3
-rw-r--r--src/node/examples/perf_test.js4
-rw-r--r--src/node/examples/qps_test.js2
-rw-r--r--src/node/examples/route_guide_server.js2
-rw-r--r--src/node/examples/stock_server.js2
-rw-r--r--src/node/ext/call.cc55
-rw-r--r--src/node/ext/call.h13
-rw-r--r--src/node/ext/channel.cc85
-rw-r--r--src/node/ext/channel.h8
-rw-r--r--src/node/ext/completion_queue_async_worker.cc6
-rw-r--r--src/node/ext/node_grpc.cc50
-rw-r--r--src/node/ext/server.cc14
-rw-r--r--src/node/ext/server_credentials.cc63
-rw-r--r--src/node/health_check/health.js10
-rw-r--r--src/node/health_check/health.proto5
-rw-r--r--src/node/index.js10
-rw-r--r--src/node/interop/interop_client.js37
-rw-r--r--src/node/interop/interop_server.js6
-rw-r--r--src/node/src/client.js126
-rw-r--r--src/node/src/server.js27
-rw-r--r--src/node/test/call_test.js5
-rw-r--r--src/node/test/channel_test.js87
-rw-r--r--src/node/test/constant_test.js37
-rw-r--r--src/node/test/end_to_end_test.js8
-rw-r--r--src/node/test/health_test.js24
-rw-r--r--src/node/test/server_test.js8
-rw-r--r--src/node/test/surface_test.js241
28 files changed, 789 insertions, 181 deletions
diff --git a/src/node/README.md b/src/node/README.md
index 7d3d8c7fa1..b6411537c7 100644
--- a/src/node/README.md
+++ b/src/node/README.md
@@ -5,11 +5,35 @@ Alpha : Ready for early adopters
## PREREQUISITES
- `node`: This requires `node` to be installed. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
-- [homebrew][] on Mac OS X, [linuxbrew][] on Linux. These simplify the installation of the gRPC C core.
+- [homebrew][] on Mac OS X. These simplify the installation of the gRPC C core.
## INSTALLATION
-On Mac OS X, install [homebrew][]. On Linux, install [linuxbrew][].
-Run the following command to install gRPC Node.js.
+
+**Linux (Debian):**
+
+Add [Debian unstable][] to your `sources.list` file. Example:
+
+```sh
+echo "deb http://ftp.us.debian.org/debian unstable main contrib non-free" | \
+sudo tee -a /etc/apt/sources.list
+```
+
+Install the gRPC Debian package
+
+```sh
+sudo apt-get update
+sudo apt-get install libgrpc-dev
+```
+
+Install the gRPC NPM package
+
+```sh
+npm install grpc
+```
+
+**Mac OS X**
+
+Install [homebrew][]. Run the following command to install gRPC Node.js.
```sh
$ curl -fsSL https://goo.gl/getgrpc | bash -s nodejs
```
@@ -88,5 +112,5 @@ ServerCredentials
An object with factory methods for creating credential objects for servers.
[homebrew]:http://brew.sh
-[linuxbrew]:https://github.com/Homebrew/linuxbrew#installation
[gRPC install script]:https://raw.githubusercontent.com/grpc/homebrew-grpc/master/scripts/install
+[Debian unstable]:https://www.debian.org/releases/sid/
diff --git a/src/node/binding.gyp b/src/node/binding.gyp
index 6ba233388a..734dc8410b 100644
--- a/src/node/binding.gyp
+++ b/src/node/binding.gyp
@@ -11,7 +11,8 @@
'-pedantic',
'-g',
'-zdefs',
- '-Werror'
+ '-Werror',
+ '-Wno-error=deprecated-declarations'
],
'ldflags': [
'-g'
diff --git a/src/node/examples/perf_test.js b/src/node/examples/perf_test.js
index 0f38725f72..ba8fbf88d2 100644
--- a/src/node/examples/perf_test.js
+++ b/src/node/examples/perf_test.js
@@ -40,7 +40,7 @@ var interop_server = require('../interop/interop_server.js');
function runTest(iterations, callback) {
var testServer = interop_server.getServer(0, false);
- testServer.server.listen();
+ testServer.server.start();
var client = new testProto.TestService('localhost:' + testServer.port,
grpc.Credentials.createInsecure());
@@ -63,7 +63,7 @@ function runTest(iterations, callback) {
var timeDiff = process.hrtime(startTime);
intervals[i] = timeDiff[0] * 1000000 + timeDiff[1] / 1000;
next(i+1);
- }, {}, deadline);
+ }, {}, {deadline: deadline});
}
}
next(0);
diff --git a/src/node/examples/qps_test.js b/src/node/examples/qps_test.js
index 1ce4dbe070..ec968b8540 100644
--- a/src/node/examples/qps_test.js
+++ b/src/node/examples/qps_test.js
@@ -60,7 +60,7 @@ var interop_server = require('../interop/interop_server.js');
*/
function runTest(concurrent_calls, seconds, callback) {
var testServer = interop_server.getServer(0, false);
- testServer.server.listen();
+ testServer.server.start();
var client = new testProto.TestService('localhost:' + testServer.port,
grpc.Credentials.createInsecure());
diff --git a/src/node/examples/route_guide_server.js b/src/node/examples/route_guide_server.js
index bb8e79b5bd..465b32f54f 100644
--- a/src/node/examples/route_guide_server.js
+++ b/src/node/examples/route_guide_server.js
@@ -248,7 +248,7 @@ if (require.main === module) {
throw err;
}
feature_list = JSON.parse(data);
- routeServer.listen();
+ routeServer.start();
});
}
diff --git a/src/node/examples/stock_server.js b/src/node/examples/stock_server.js
index dfcfe30eb4..12e5479584 100644
--- a/src/node/examples/stock_server.js
+++ b/src/node/examples/stock_server.js
@@ -81,7 +81,7 @@ stockServer.addProtoService(examples.Stock.service, {
if (require.main === module) {
stockServer.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
- stockServer.listen();
+ stockServer.start();
}
module.exports = stockServer;
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index fe585a0d4f..18858fa334 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -207,6 +207,13 @@ class SendMessageOp : public Op {
if (!::node::Buffer::HasInstance(value)) {
return false;
}
+ Handle<Object> object_value = value->ToObject();
+ if (object_value->HasOwnProperty(NanNew("grpcWriteFlags"))) {
+ Handle<Value> flag_value = object_value->Get(NanNew("grpcWriteFlags"));
+ if (flag_value->IsUint32()) {
+ out->flags = flag_value->Uint32Value() & GRPC_WRITE_USED_MASK;
+ }
+ }
out->data.send_message = BufferToByteBuffer(value);
Persistent<Value> *handle = new Persistent<Value>();
NanAssignPersistent(*handle, value);
@@ -457,10 +464,6 @@ void Call::Init(Handle<Object> exports) {
NanNew<FunctionTemplate>(GetPeer)->GetFunction());
NanAssignPersistent(fun_tpl, tpl);
Handle<Function> ctr = tpl->GetFunction();
- ctr->Set(NanNew("WRITE_BUFFER_HINT"),
- NanNew<Uint32, uint32_t>(GRPC_WRITE_BUFFER_HINT));
- ctr->Set(NanNew("WRITE_NO_COMPRESS"),
- NanNew<Uint32, uint32_t>(GRPC_WRITE_NO_COMPRESS));
exports->Set(NanNew("Call"), ctr);
constructor = new NanCallback(ctr);
}
@@ -502,6 +505,22 @@ NAN_METHOD(Call::New) {
return NanThrowTypeError(
"Call's third argument must be a date or a number");
}
+ // These arguments are at the end because they are optional
+ grpc_call *parent_call = NULL;
+ if (Call::HasInstance(args[4])) {
+ Call *parent_obj = ObjectWrap::Unwrap<Call>(args[4]->ToObject());
+ parent_call = parent_obj->wrapped_call;
+ } else if (!(args[4]->IsUndefined() || args[4]->IsNull())) {
+ return NanThrowTypeError(
+ "Call's fifth argument must be another call, if provided");
+ }
+ gpr_uint32 propagate_flags = GRPC_PROPAGATE_DEFAULTS;
+ if (args[5]->IsUint32()) {
+ propagate_flags = args[5]->Uint32Value();
+ } else if (!(args[5]->IsUndefined() || args[5]->IsNull())) {
+ return NanThrowTypeError(
+ "Call's sixth argument must be propagate flags, if provided");
+ }
Handle<Object> channel_object = args[0]->ToObject();
Channel *channel = ObjectWrap::Unwrap<Channel>(channel_object);
if (channel->GetWrappedChannel() == NULL) {
@@ -510,10 +529,21 @@ NAN_METHOD(Call::New) {
NanUtf8String method(args[1]);
double deadline = args[2]->NumberValue();
grpc_channel *wrapped_channel = channel->GetWrappedChannel();
- grpc_call *wrapped_call = grpc_channel_create_call(
- wrapped_channel, NULL, GRPC_PROPAGATE_DEFAULTS,
- CompletionQueueAsyncWorker::GetQueue(), *method, channel->GetHost(),
- MillisecondsToTimespec(deadline));
+ grpc_call *wrapped_call;
+ if (args[3]->IsString()) {
+ NanUtf8String host_override(args[3]);
+ wrapped_call = grpc_channel_create_call(
+ wrapped_channel, parent_call, propagate_flags,
+ CompletionQueueAsyncWorker::GetQueue(), *method,
+ *host_override, MillisecondsToTimespec(deadline), NULL);
+ } else if (args[3]->IsUndefined() || args[3]->IsNull()) {
+ wrapped_call = grpc_channel_create_call(
+ wrapped_channel, parent_call, propagate_flags,
+ CompletionQueueAsyncWorker::GetQueue(), *method,
+ NULL, MillisecondsToTimespec(deadline), NULL);
+ } else {
+ return NanThrowTypeError("Call's fourth argument must be a string");
+ }
call = new Call(wrapped_call);
args.This()->SetHiddenValue(NanNew("channel_"), channel_object);
}
@@ -554,6 +584,7 @@ NAN_METHOD(Call::StartBatch) {
uint32_t type = keys->Get(i)->Uint32Value();
ops[i].op = static_cast<grpc_op_type>(type);
ops[i].flags = 0;
+ ops[i].reserved = NULL;
switch (type) {
case GRPC_OP_SEND_INITIAL_METADATA:
op.reset(new SendMetadataOp());
@@ -590,9 +621,9 @@ NAN_METHOD(Call::StartBatch) {
NanCallback *callback = new NanCallback(callback_func);
grpc_call_error error = grpc_call_start_batch(
call->wrapped_call, &ops[0], nops, new struct tag(
- callback, op_vector.release(), resources));
+ callback, op_vector.release(), resources), NULL);
if (error != GRPC_CALL_OK) {
- return NanThrowError("startBatch failed", error);
+ return NanThrowError(nanErrorWithCode("startBatch failed", error));
}
CompletionQueueAsyncWorker::Next();
NanReturnUndefined();
@@ -604,9 +635,9 @@ NAN_METHOD(Call::Cancel) {
return NanThrowTypeError("cancel can only be called on Call objects");
}
Call *call = ObjectWrap::Unwrap<Call>(args.This());
- grpc_call_error error = grpc_call_cancel(call->wrapped_call);
+ grpc_call_error error = grpc_call_cancel(call->wrapped_call, NULL);
if (error != GRPC_CALL_OK) {
- return NanThrowError("cancel failed", error);
+ return NanThrowError(nanErrorWithCode("cancel failed", error));
}
NanReturnUndefined();
}
diff --git a/src/node/ext/call.h b/src/node/ext/call.h
index 6acda76197..ef6e5fcd21 100644
--- a/src/node/ext/call.h
+++ b/src/node/ext/call.h
@@ -51,6 +51,19 @@ namespace node {
using std::unique_ptr;
using std::shared_ptr;
+/**
+ * Helper function for throwing errors with a grpc_call_error value.
+ * Modified from the answer by Gus Goose to
+ * http://stackoverflow.com/questions/31794200.
+ */
+inline v8::Local<v8::Value> nanErrorWithCode(const char *msg,
+ grpc_call_error code) {
+ NanEscapableScope();
+ v8::Local<v8::Object> err = NanError(msg).As<v8::Object>();
+ err->Set(NanNew("code"), NanNew<v8::Uint32>(code));
+ return NanEscapeScope(err);
+}
+
v8::Handle<v8::Value> ParseMetadata(const grpc_metadata_array *metadata_array);
class PersistentHolder {
diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc
index d02ed95672..a61c830099 100644
--- a/src/node/ext/channel.cc
+++ b/src/node/ext/channel.cc
@@ -33,12 +33,17 @@
#include <vector>
+#include "grpc/support/log.h"
+
#include <node.h>
#include <nan.h>
#include "grpc/grpc.h"
#include "grpc/grpc_security.h"
+#include "call.h"
#include "channel.h"
+#include "completion_queue_async_worker.h"
#include "credentials.h"
+#include "timeval.h"
namespace grpc {
namespace node {
@@ -51,6 +56,7 @@ using v8::Handle;
using v8::HandleScope;
using v8::Integer;
using v8::Local;
+using v8::Number;
using v8::Object;
using v8::Persistent;
using v8::String;
@@ -59,14 +65,12 @@ using v8::Value;
NanCallback *Channel::constructor;
Persistent<FunctionTemplate> Channel::fun_tpl;
-Channel::Channel(grpc_channel *channel, NanUtf8String *host)
- : wrapped_channel(channel), host(host) {}
+Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {}
Channel::~Channel() {
if (wrapped_channel != NULL) {
grpc_channel_destroy(wrapped_channel);
}
- delete host;
}
void Channel::Init(Handle<Object> exports) {
@@ -78,6 +82,12 @@ void Channel::Init(Handle<Object> exports) {
NanNew<FunctionTemplate>(Close)->GetFunction());
NanSetPrototypeTemplate(tpl, "getTarget",
NanNew<FunctionTemplate>(GetTarget)->GetFunction());
+ NanSetPrototypeTemplate(
+ tpl, "getConnectivityState",
+ NanNew<FunctionTemplate>(GetConnectivityState)->GetFunction());
+ NanSetPrototypeTemplate(
+ tpl, "watchConnectivityState",
+ NanNew<FunctionTemplate>(WatchConnectivityState)->GetFunction());
NanAssignPersistent(fun_tpl, tpl);
Handle<Function> ctr = tpl->GetFunction();
constructor = new NanCallback(ctr);
@@ -91,8 +101,6 @@ bool Channel::HasInstance(Handle<Value> val) {
grpc_channel *Channel::GetWrappedChannel() { return this->wrapped_channel; }
-char *Channel::GetHost() { return **this->host; }
-
NAN_METHOD(Channel::New) {
NanScope();
@@ -103,8 +111,7 @@ NAN_METHOD(Channel::New) {
}
grpc_channel *wrapped_channel;
// Owned by the Channel object
- NanUtf8String *host = new NanUtf8String(args[0]);
- NanUtf8String *host_override = NULL;
+ NanUtf8String host(args[0]);
grpc_credentials *creds;
if (!Credentials::HasInstance(args[1])) {
return NanThrowTypeError(
@@ -116,12 +123,9 @@ NAN_METHOD(Channel::New) {
grpc_channel_args *channel_args_ptr;
if (args[2]->IsUndefined()) {
channel_args_ptr = NULL;
- wrapped_channel = grpc_insecure_channel_create(**host, NULL);
+ wrapped_channel = grpc_insecure_channel_create(*host, NULL, NULL);
} else if (args[2]->IsObject()) {
Handle<Object> args_hash(args[2]->ToObject()->Clone());
- if (args_hash->HasOwnProperty(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))) {
- host_override = new NanUtf8String(args_hash->Get(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG)));
- }
Handle<Array> keys(args_hash->GetOwnPropertyNames());
grpc_channel_args channel_args;
channel_args.num_args = keys->Length();
@@ -153,20 +157,16 @@ NAN_METHOD(Channel::New) {
return NanThrowTypeError("Channel expects a string and an object");
}
if (creds == NULL) {
- wrapped_channel = grpc_insecure_channel_create(**host, channel_args_ptr);
+ wrapped_channel = grpc_insecure_channel_create(*host, channel_args_ptr,
+ NULL);
} else {
wrapped_channel =
- grpc_secure_channel_create(creds, **host, channel_args_ptr);
+ grpc_secure_channel_create(creds, *host, channel_args_ptr);
}
if (channel_args_ptr != NULL) {
free(channel_args_ptr->args);
}
- Channel *channel;
- if (host_override == NULL) {
- channel = new Channel(wrapped_channel, host);
- } else {
- channel = new Channel(wrapped_channel, host_override);
- }
+ Channel *channel = new Channel(wrapped_channel);
channel->Wrap(args.This());
NanReturnValue(args.This());
} else {
@@ -198,5 +198,52 @@ NAN_METHOD(Channel::GetTarget) {
NanReturnValue(NanNew(grpc_channel_get_target(channel->wrapped_channel)));
}
+NAN_METHOD(Channel::GetConnectivityState) {
+ NanScope();
+ if (!HasInstance(args.This())) {
+ return NanThrowTypeError(
+ "getConnectivityState can only be called on Channel objects");
+ }
+ Channel *channel = ObjectWrap::Unwrap<Channel>(args.This());
+ int try_to_connect = (int)args[0]->Equals(NanTrue());
+ NanReturnValue(grpc_channel_check_connectivity_state(channel->wrapped_channel,
+ try_to_connect));
+}
+
+NAN_METHOD(Channel::WatchConnectivityState) {
+ NanScope();
+ if (!HasInstance(args.This())) {
+ return NanThrowTypeError(
+ "watchConnectivityState can only be called on Channel objects");
+ }
+ if (!args[0]->IsUint32()) {
+ return NanThrowTypeError(
+ "watchConnectivityState's first argument must be a channel state");
+ }
+ if (!(args[1]->IsNumber() || args[1]->IsDate())) {
+ return NanThrowTypeError(
+ "watchConnectivityState's second argument must be a date or a number");
+ }
+ if (!args[2]->IsFunction()) {
+ return NanThrowTypeError(
+ "watchConnectivityState's third argument must be a callback");
+ }
+ grpc_connectivity_state last_state =
+ static_cast<grpc_connectivity_state>(args[0]->Uint32Value());
+ double deadline = args[1]->NumberValue();
+ Handle<Function> callback_func = args[2].As<Function>();
+ NanCallback *callback = new NanCallback(callback_func);
+ Channel *channel = ObjectWrap::Unwrap<Channel>(args.This());
+ unique_ptr<OpVec> ops(new OpVec());
+ grpc_channel_watch_connectivity_state(
+ channel->wrapped_channel, last_state, MillisecondsToTimespec(deadline),
+ CompletionQueueAsyncWorker::GetQueue(),
+ new struct tag(callback,
+ ops.release(),
+ shared_ptr<Resources>(nullptr)));
+ CompletionQueueAsyncWorker::Next();
+ NanReturnUndefined();
+}
+
} // namespace node
} // namespace grpc
diff --git a/src/node/ext/channel.h b/src/node/ext/channel.h
index 6725ebb03f..458f71d093 100644
--- a/src/node/ext/channel.h
+++ b/src/node/ext/channel.h
@@ -53,11 +53,8 @@ class Channel : public ::node::ObjectWrap {
/* Returns the grpc_channel struct that this object wraps */
grpc_channel *GetWrappedChannel();
- /* Return the hostname that this channel connects to */
- char *GetHost();
-
private:
- explicit Channel(grpc_channel *channel, NanUtf8String *host);
+ explicit Channel(grpc_channel *channel);
~Channel();
// Prevent copying
@@ -67,11 +64,12 @@ class Channel : public ::node::ObjectWrap {
static NAN_METHOD(New);
static NAN_METHOD(Close);
static NAN_METHOD(GetTarget);
+ static NAN_METHOD(GetConnectivityState);
+ static NAN_METHOD(WatchConnectivityState);
static NanCallback *constructor;
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
grpc_channel *wrapped_channel;
- NanUtf8String *host;
};
} // namespace node
diff --git a/src/node/ext/completion_queue_async_worker.cc b/src/node/ext/completion_queue_async_worker.cc
index 1215c97e19..bf2cd946a5 100644
--- a/src/node/ext/completion_queue_async_worker.cc
+++ b/src/node/ext/completion_queue_async_worker.cc
@@ -63,9 +63,9 @@ CompletionQueueAsyncWorker::~CompletionQueueAsyncWorker() {}
void CompletionQueueAsyncWorker::Execute() {
result =
- grpc_completion_queue_next(queue, gpr_inf_future(GPR_CLOCK_REALTIME));
+ grpc_completion_queue_next(queue, gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
if (!result.success) {
- SetErrorMessage("The batch encountered an error");
+ SetErrorMessage("The async function encountered an error");
}
}
@@ -85,7 +85,7 @@ void CompletionQueueAsyncWorker::Init(Handle<Object> exports) {
NanScope();
current_threads = 0;
waiting_next_calls = 0;
- queue = grpc_completion_queue_create();
+ queue = grpc_completion_queue_create(NULL);
}
void CompletionQueueAsyncWorker::HandleOKCallback() {
diff --git a/src/node/ext/node_grpc.cc b/src/node/ext/node_grpc.cc
index 4e31cbaa27..0cf30da922 100644
--- a/src/node/ext/node_grpc.cc
+++ b/src/node/ext/node_grpc.cc
@@ -159,12 +159,62 @@ void InitOpTypeConstants(Handle<Object> exports) {
op_type->Set(NanNew("RECV_CLOSE_ON_SERVER"), RECV_CLOSE_ON_SERVER);
}
+void InitPropagateConstants(Handle<Object> exports) {
+ NanScope();
+ Handle<Object> propagate = NanNew<Object>();
+ exports->Set(NanNew("propagate"), propagate);
+ Handle<Value> DEADLINE(NanNew<Uint32, uint32_t>(GRPC_PROPAGATE_DEADLINE));
+ propagate->Set(NanNew("DEADLINE"), DEADLINE);
+ Handle<Value> CENSUS_STATS_CONTEXT(
+ NanNew<Uint32, uint32_t>(GRPC_PROPAGATE_CENSUS_STATS_CONTEXT));
+ propagate->Set(NanNew("CENSUS_STATS_CONTEXT"), CENSUS_STATS_CONTEXT);
+ Handle<Value> CENSUS_TRACING_CONTEXT(
+ NanNew<Uint32, uint32_t>(GRPC_PROPAGATE_CENSUS_TRACING_CONTEXT));
+ propagate->Set(NanNew("CENSUS_TRACING_CONTEXT"), CENSUS_TRACING_CONTEXT);
+ Handle<Value> CANCELLATION(
+ NanNew<Uint32, uint32_t>(GRPC_PROPAGATE_CANCELLATION));
+ propagate->Set(NanNew("CANCELLATION"), CANCELLATION);
+ Handle<Value> DEFAULTS(NanNew<Uint32, uint32_t>(GRPC_PROPAGATE_DEFAULTS));
+ propagate->Set(NanNew("DEFAULTS"), DEFAULTS);
+}
+
+void InitConnectivityStateConstants(Handle<Object> exports) {
+ NanScope();
+ Handle<Object> channel_state = NanNew<Object>();
+ exports->Set(NanNew("connectivityState"), channel_state);
+ Handle<Value> IDLE(NanNew<Uint32, uint32_t>(GRPC_CHANNEL_IDLE));
+ channel_state->Set(NanNew("IDLE"), IDLE);
+ Handle<Value> CONNECTING(NanNew<Uint32, uint32_t>(GRPC_CHANNEL_CONNECTING));
+ channel_state->Set(NanNew("CONNECTING"), CONNECTING);
+ Handle<Value> READY(NanNew<Uint32, uint32_t>(GRPC_CHANNEL_READY));
+ channel_state->Set(NanNew("READY"), READY);
+ Handle<Value> TRANSIENT_FAILURE(
+ NanNew<Uint32, uint32_t>(GRPC_CHANNEL_TRANSIENT_FAILURE));
+ channel_state->Set(NanNew("TRANSIENT_FAILURE"), TRANSIENT_FAILURE);
+ Handle<Value> FATAL_FAILURE(
+ NanNew<Uint32, uint32_t>(GRPC_CHANNEL_FATAL_FAILURE));
+ channel_state->Set(NanNew("FATAL_FAILURE"), FATAL_FAILURE);
+}
+
+void InitWriteFlags(Handle<Object> exports) {
+ NanScope();
+ Handle<Object> write_flags = NanNew<Object>();
+ exports->Set(NanNew("writeFlags"), write_flags);
+ Handle<Value> BUFFER_HINT(NanNew<Uint32, uint32_t>(GRPC_WRITE_BUFFER_HINT));
+ write_flags->Set(NanNew("BUFFER_HINT"), BUFFER_HINT);
+ Handle<Value> NO_COMPRESS(NanNew<Uint32, uint32_t>(GRPC_WRITE_NO_COMPRESS));
+ write_flags->Set(NanNew("NO_COMPRESS"), NO_COMPRESS);
+}
+
void init(Handle<Object> exports) {
NanScope();
grpc_init();
InitStatusConstants(exports);
InitCallErrorConstants(exports);
InitOpTypeConstants(exports);
+ InitPropagateConstants(exports);
+ InitConnectivityStateConstants(exports);
+ InitWriteFlags(exports);
grpc::node::Call::Init(exports);
grpc::node::Channel::Init(exports);
diff --git a/src/node/ext/server.cc b/src/node/ext/server.cc
index 1dc179db3d..01217bce79 100644
--- a/src/node/ext/server.cc
+++ b/src/node/ext/server.cc
@@ -113,8 +113,8 @@ class NewCallOp : public Op {
};
Server::Server(grpc_server *server) : wrapped_server(server) {
- shutdown_queue = grpc_completion_queue_create();
- grpc_server_register_completion_queue(server, shutdown_queue);
+ shutdown_queue = grpc_completion_queue_create(NULL);
+ grpc_server_register_completion_queue(server, shutdown_queue, NULL);
}
Server::~Server() {
@@ -158,7 +158,7 @@ void Server::ShutdownServer() {
this->shutdown_queue,
NULL);
grpc_completion_queue_pluck(this->shutdown_queue, NULL,
- gpr_inf_future(GPR_CLOCK_REALTIME));
+ gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
this->wrapped_server = NULL;
}
}
@@ -176,7 +176,7 @@ NAN_METHOD(Server::New) {
grpc_server *wrapped_server;
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
if (args[0]->IsUndefined()) {
- wrapped_server = grpc_server_create(NULL);
+ wrapped_server = grpc_server_create(NULL, NULL);
} else if (args[0]->IsObject()) {
Handle<Object> args_hash(args[0]->ToObject());
Handle<Array> keys(args_hash->GetOwnPropertyNames());
@@ -205,12 +205,12 @@ NAN_METHOD(Server::New) {
return NanThrowTypeError("Arg values must be strings");
}
}
- wrapped_server = grpc_server_create(&channel_args);
+ wrapped_server = grpc_server_create(&channel_args, NULL);
free(channel_args.args);
} else {
return NanThrowTypeError("Server expects an object");
}
- grpc_server_register_completion_queue(wrapped_server, queue);
+ grpc_server_register_completion_queue(wrapped_server, queue, NULL);
Server *server = new Server(wrapped_server);
server->Wrap(args.This());
NanReturnValue(args.This());
@@ -235,7 +235,7 @@ NAN_METHOD(Server::RequestCall) {
new struct tag(new NanCallback(args[0].As<Function>()), ops.release(),
shared_ptr<Resources>(nullptr)));
if (error != GRPC_CALL_OK) {
- return NanThrowError("requestCall failed", error);
+ return NanThrowError(nanErrorWithCode("requestCall failed", error));
}
CompletionQueueAsyncWorker::Next();
NanReturnUndefined();
diff --git a/src/node/ext/server_credentials.cc b/src/node/ext/server_credentials.cc
index 1b8e7b43fb..6e17197e16 100644
--- a/src/node/ext/server_credentials.cc
+++ b/src/node/ext/server_credentials.cc
@@ -41,6 +41,7 @@
namespace grpc {
namespace node {
+using v8::Array;
using v8::Exception;
using v8::External;
using v8::Function;
@@ -52,6 +53,7 @@ using v8::Local;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
+using v8::String;
using v8::Value;
NanCallback *ServerCredentials::constructor;
@@ -122,25 +124,66 @@ NAN_METHOD(ServerCredentials::CreateSsl) {
// TODO: have the node API support multiple key/cert pairs.
NanScope();
char *root_certs = NULL;
- grpc_ssl_pem_key_cert_pair key_cert_pair;
if (::node::Buffer::HasInstance(args[0])) {
root_certs = ::node::Buffer::Data(args[0]);
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
return NanThrowTypeError(
"createSSl's first argument must be a Buffer if provided");
}
- if (!::node::Buffer::HasInstance(args[1])) {
- return NanThrowTypeError("createSsl's second argument must be a Buffer");
+ if (!args[1]->IsArray()) {
+ return NanThrowTypeError(
+ "createSsl's second argument must be a list of objects");
+ }
+ int force_client_auth = 0;
+ if (args[2]->IsBoolean()) {
+ force_client_auth = (int)args[2]->BooleanValue();
+ } else if (!(args[2]->IsUndefined() || args[2]->IsNull())) {
+ return NanThrowTypeError(
+ "createSsl's third argument must be a boolean if provided");
}
- key_cert_pair.private_key = ::node::Buffer::Data(args[1]);
- if (!::node::Buffer::HasInstance(args[2])) {
- return NanThrowTypeError("createSsl's third argument must be a Buffer");
+ Handle<Array> pair_list = Local<Array>::Cast(args[1]);
+ uint32_t key_cert_pair_count = pair_list->Length();
+ grpc_ssl_pem_key_cert_pair *key_cert_pairs = new grpc_ssl_pem_key_cert_pair[
+ key_cert_pair_count];
+
+ Handle<String> key_key = NanNew("private_key");
+ Handle<String> cert_key = NanNew("cert_chain");
+
+ for(uint32_t i = 0; i < key_cert_pair_count; i++) {
+ if (!pair_list->Get(i)->IsObject()) {
+ delete key_cert_pairs;
+ return NanThrowTypeError("Key/cert pairs must be objects");
+ }
+ Handle<Object> pair_obj = pair_list->Get(i)->ToObject();
+ if (!pair_obj->HasOwnProperty(key_key)) {
+ delete key_cert_pairs;
+ return NanThrowTypeError(
+ "Key/cert pairs must have a private_key and a cert_chain");
+ }
+ if (!pair_obj->HasOwnProperty(cert_key)) {
+ delete key_cert_pairs;
+ return NanThrowTypeError(
+ "Key/cert pairs must have a private_key and a cert_chain");
+ }
+ if (!::node::Buffer::HasInstance(pair_obj->Get(key_key))) {
+ delete key_cert_pairs;
+ return NanThrowTypeError("private_key must be a Buffer");
+ }
+ if (!::node::Buffer::HasInstance(pair_obj->Get(cert_key))) {
+ delete key_cert_pairs;
+ return NanThrowTypeError("cert_chain must be a Buffer");
+ }
+ key_cert_pairs[i].private_key = ::node::Buffer::Data(
+ pair_obj->Get(key_key));
+ key_cert_pairs[i].cert_chain = ::node::Buffer::Data(
+ pair_obj->Get(cert_key));
}
- key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]);
- // TODO Add a force_client_auth parameter and pass it as the last parameter
- // here.
grpc_server_credentials *creds =
- grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1, 0);
+ grpc_ssl_server_credentials_create(root_certs,
+ key_cert_pairs,
+ key_cert_pair_count,
+ force_client_auth);
+ delete key_cert_pairs;
if (creds == NULL) {
NanReturnNull();
}
diff --git a/src/node/health_check/health.js b/src/node/health_check/health.js
index 87e00197fe..84d7e0568e 100644
--- a/src/node/health_check/health.js
+++ b/src/node/health_check/health.js
@@ -45,17 +45,13 @@ function HealthImplementation(statusMap) {
this.statusMap = _.clone(statusMap);
}
-HealthImplementation.prototype.setStatus = function(host, service, status) {
- if (!this.statusMap[host]) {
- this.statusMap[host] = {};
- }
- this.statusMap[host][service] = status;
+HealthImplementation.prototype.setStatus = function(service, status) {
+ this.statusMap[service] = status;
};
HealthImplementation.prototype.check = function(call, callback){
- var host = call.request.host;
var service = call.request.service;
- var status = _.get(this.statusMap, [host, service], null);
+ var status = _.get(this.statusMap, service, null);
if (status === null) {
callback({code:grpc.status.NOT_FOUND});
} else {
diff --git a/src/node/health_check/health.proto b/src/node/health_check/health.proto
index d31df1e0a7..57f4aaa9c0 100644
--- a/src/node/health_check/health.proto
+++ b/src/node/health_check/health.proto
@@ -32,8 +32,7 @@ syntax = "proto3";
package grpc.health.v1alpha;
message HealthCheckRequest {
- string host = 1;
- string service = 2;
+ string service = 1;
}
message HealthCheckResponse {
@@ -47,4 +46,4 @@ message HealthCheckResponse {
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
-} \ No newline at end of file
+}
diff --git a/src/node/index.js b/src/node/index.js
index b26ab35f2c..889b0ac0e9 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -135,11 +135,21 @@ exports.Server = server.Server;
exports.status = grpc.status;
/**
+ * Propagate flag name to number mapping
+ */
+exports.propagate = grpc.propagate;
+
+/**
* Call error name to code number mapping
*/
exports.callError = grpc.callError;
/**
+ * Write flag name to code number mapping
+ */
+exports.writeFlags = grpc.writeFlags;
+
+/**
* Credentials factories
*/
exports.Credentials = grpc.Credentials;
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index 236b36616c..612dcf01f6 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -67,11 +67,8 @@ function zeroBuffer(size) {
* primarily for use with mocha
*/
function emptyUnary(client, done) {
- var call = client.emptyCall({}, function(err, resp) {
+ client.emptyCall({}, function(err, resp) {
assert.ifError(err);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -92,13 +89,10 @@ function largeUnary(client, done) {
body: zeroBuffer(271828)
}
};
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -115,9 +109,6 @@ function clientStreaming(client, done) {
var call = client.streamingInputCall(function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.aggregated_payload_size, 74922);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
@@ -268,12 +259,14 @@ function cancelAfterFirstResponse(client, done) {
function timeoutOnSleepingServer(client, done) {
var deadline = new Date();
deadline.setMilliseconds(deadline.getMilliseconds() + 1);
- var call = client.fullDuplexCall(null, deadline);
+ var call = client.fullDuplexCall(null, {deadline: deadline});
call.write({
payload: {body: zeroBuffer(27182)}
});
call.on('error', function(error) {
- assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED);
+
+ assert(error.code === grpc.status.DEADLINE_EXCEEDED ||
+ error.code === grpc.status.INTERNAL);
done();
});
}
@@ -302,15 +295,14 @@ function authTest(expected_user, scope, client, done) {
fill_username: true,
fill_oauth_scope: true
};
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, 'COMPRESSABLE');
assert.strictEqual(resp.payload.body.length, 314159);
assert.strictEqual(resp.username, expected_user);
- assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
+ if (scope) {
+ assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
+ }
if (done) {
done();
}
@@ -340,17 +332,14 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
};
var makeTestCall = function(error, client_metadata) {
assert.ifError(error);
- var call = client.unaryCall(arg, function(err, resp) {
+ client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.username, expected_user);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
- });
- call.on('status', function(status) {
- assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
- });
+ }, client_metadata);
};
if (per_rpc) {
updateMetadata('', {}, makeTestCall);
@@ -358,7 +347,6 @@ function oauth2Test(expected_user, scope, per_rpc, client, done) {
client.updateMetadata = updateMetadata;
makeTestCall(null, {});
}
-
});
});
}
@@ -409,6 +397,7 @@ function runTest(address, host_override, test_case, tls, test_ca, done) {
creds = grpc.Credentials.createSsl(ca_data);
if (host_override) {
options['grpc.ssl_target_name_override'] = host_override;
+ options['grpc.default_authority'] = host_override;
}
} else {
creds = grpc.Credentials.createInsecure();
diff --git a/src/node/interop/interop_server.js b/src/node/interop/interop_server.js
index ece22cce31..99155e9958 100644
--- a/src/node/interop/interop_server.js
+++ b/src/node/interop/interop_server.js
@@ -169,8 +169,8 @@ function getServer(port, tls) {
var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path);
server_creds = grpc.ServerCredentials.createSsl(null,
- key_data,
- pem_data);
+ [{private_key: key_data,
+ cert_chain: pem_data}]);
} else {
server_creds = grpc.ServerCredentials.createInsecure();
}
@@ -194,7 +194,7 @@ if (require.main === module) {
});
var server_obj = getServer(argv.port, argv.use_tls === 'true');
console.log('Server attaching to port ' + argv.port);
- server_obj.server.listen();
+ server_obj.server.start();
}
/**
diff --git a/src/node/src/client.js b/src/node/src/client.js
index b2b4423707..7b7eae51d2 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -79,13 +79,19 @@ function ClientWritableStream(call, serialize) {
* implementation of a method needed for implementing stream.Writable.
* @access private
* @param {Buffer} chunk The chunk to write
- * @param {string} encoding Ignored
+ * @param {string} encoding Used to pass write flags
* @param {function(Error=)} callback Called when the write is complete
*/
function _write(chunk, encoding, callback) {
/* jshint validthis: true */
var batch = {};
- batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk);
+ var message = this.serialize(chunk);
+ if (_.isFinite(encoding)) {
+ /* Attach the encoding if it is a finite number. This is the closest we
+ * can get to checking that it is valid flags */
+ message.grpcWriteFlags = encoding;
+ }
+ batch[grpc.opType.SEND_MESSAGE] = message;
this.call.startBatch(batch, function(err, event) {
if (err) {
// Something has gone wrong. Stop writing by failing to call callback
@@ -208,6 +214,30 @@ ClientWritableStream.prototype.getPeer = getPeer;
ClientDuplexStream.prototype.getPeer = getPeer;
/**
+ * Get a call object built with the provided options. Keys for options are
+ * 'deadline', which takes a date or number, and 'host', which takes a string
+ * and overrides the hostname to connect to.
+ * @param {Object} options Options map.
+ */
+function getCall(channel, method, options) {
+ var deadline;
+ var host;
+ var parent;
+ var propagate_flags;
+ if (options) {
+ deadline = options.deadline;
+ host = options.host;
+ parent = _.get(options, 'parent.call');
+ propagate_flags = options.propagate_flags;
+ }
+ if (deadline === undefined) {
+ deadline = Infinity;
+ }
+ return new grpc.Call(channel, method, deadline, host,
+ parent, propagate_flags);
+}
+
+/**
* Get a function that can make unary requests to the specified method.
* @param {string} method The name of the method to request
* @param {function(*):Buffer} serialize The serialization function for inputs
@@ -226,17 +256,13 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
* response is received
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object=} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeUnaryRequest(argument, callback, metadata, deadline) {
+ function makeUnaryRequest(argument, callback, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
var emitter = new EventEmitter();
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -253,8 +279,12 @@ function makeUnaryRequestFunction(method, serialize, deserialize) {
return;
}
var client_batch = {};
+ var message = serialize(argument);
+ if (options) {
+ message.grpcWriteFlags = options.flags;
+ }
client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
- client_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
+ client_batch[grpc.opType.SEND_MESSAGE] = message;
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
client_batch[grpc.opType.RECV_MESSAGE] = true;
@@ -300,16 +330,12 @@ function makeClientStreamRequestFunction(method, serialize, deserialize) {
* response is received
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object=} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeClientStreamRequest(callback, metadata, deadline) {
+ function makeClientStreamRequest(callback, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -374,16 +400,12 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
* serialize
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Object} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeServerStreamRequest(argument, metadata, deadline) {
+ function makeServerStreamRequest(argument, metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -395,9 +417,13 @@ function makeServerStreamRequestFunction(method, serialize, deserialize) {
return;
}
var start_batch = {};
+ var message = serialize(argument);
+ if (options) {
+ message.grpcWriteFlags = options.flags;
+ }
start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
- start_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
+ start_batch[grpc.opType.SEND_MESSAGE] = message;
start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(start_batch, function(err, response) {
if (err) {
@@ -446,16 +472,12 @@ function makeBidiStreamRequestFunction(method, serialize, deserialize) {
* @this {SurfaceClient} Client object. Must have a channel member.
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
- * @param {(number|Date)=} deadline The deadline for processing this request.
- * Defaults to infinite future
+ * @param {Options} options Options map
* @return {EventEmitter} An event emitter for stream related events
*/
- function makeBidiStreamRequest(metadata, deadline) {
+ function makeBidiStreamRequest(metadata, options) {
/* jshint validthis: true */
- if (deadline === undefined) {
- deadline = Infinity;
- }
- var call = new grpc.Call(this.channel, method, deadline);
+ var call = getCall(this.channel, method, options);
if (metadata === null || metadata === undefined) {
metadata = {};
}
@@ -523,7 +545,7 @@ var requester_makers = {
* requestSerialize: function to serialize request objects
* responseDeserialize: function to deserialize response objects
* @param {Object} methods An object mapping method names to method attributes
- * @param {string} serviceName The name of the service
+ * @param {string} serviceName The fully qualified name of the service
* @return {function(string, Object)} New client constructor
*/
exports.makeClientConstructor = function(methods, serviceName) {
@@ -548,11 +570,42 @@ exports.makeClientConstructor = function(methods, serviceName) {
}
options['grpc.primary_user_agent'] = 'grpc-node/' + version;
this.channel = new grpc.Channel(address, credentials, options);
- this.server_address = address.replace(/\/$/, '');
- this.auth_uri = this.server_address + '/' + serviceName;
+ // Remove the optional DNS scheme, trailing port, and trailing backslash
+ address = address.replace(/^(dns:\/{3})?([^:\/]+)(:\d+)?\/?$/, '$2');
+ this.server_address = address;
+ this.auth_uri = 'https://' + this.server_address + '/' + serviceName;
this.updateMetadata = updateMetadata;
}
+ /**
+ * Wait for the client to be ready. The callback will be called when the
+ * client has successfully connected to the server, and it will be called
+ * with an error if the attempt to connect to the server has unrecoverablly
+ * failed or if the deadline expires. This function will make the channel
+ * start connecting if it has not already done so.
+ * @param {(Date|Number)} deadline When to stop waiting for a connection. Pass
+ * Infinity to wait forever.
+ * @param {function(Error)} callback The callback to call when done attempting
+ * to connect.
+ */
+ Client.prototype.$waitForReady = function(deadline, callback) {
+ var self = this;
+ var checkState = function(err) {
+ if (err) {
+ callback(new Error('Failed to connect before the deadline'));
+ }
+ var new_state = self.channel.getConnectivityState(true);
+ if (new_state === grpc.connectivityState.READY) {
+ callback();
+ } else if (new_state === grpc.connectivityState.FATAL_FAILURE) {
+ callback(new Error('Failed to connect to server'));
+ } else {
+ self.channel.watchConnectivityState(new_state, deadline, checkState);
+ }
+ };
+ checkState();
+ };
+
_.each(methods, function(attrs, name) {
var method_type;
if (attrs.requestStream) {
@@ -587,7 +640,8 @@ exports.makeClientConstructor = function(methods, serviceName) {
*/
exports.makeProtobufClientConstructor = function(service) {
var method_attrs = common.getProtobufServiceAttrs(service, service.name);
- var Client = exports.makeClientConstructor(method_attrs);
+ var Client = exports.makeClientConstructor(
+ method_attrs, common.fullyQualifiedName(service));
Client.service = service;
return Client;
};
diff --git a/src/node/src/server.js b/src/node/src/server.js
index 5c62f5990c..5037abae43 100644
--- a/src/node/src/server.js
+++ b/src/node/src/server.js
@@ -115,8 +115,10 @@ function waitForCancel(call, emitter) {
* @param {function(*):Buffer=} serialize Serialization function for the
* response
* @param {Object=} metadata Optional trailing metadata to send with status
+ * @param {number=} flags Flags for modifying how the message is sent.
+ * Defaults to 0.
*/
-function sendUnaryResponse(call, value, serialize, metadata) {
+function sendUnaryResponse(call, value, serialize, metadata, flags) {
var end_batch = {};
var status = {
code: grpc.status.OK,
@@ -130,7 +132,9 @@ function sendUnaryResponse(call, value, serialize, metadata) {
end_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
call.metadataSent = true;
}
- end_batch[grpc.opType.SEND_MESSAGE] = serialize(value);
+ var message = serialize(value);
+ message.grpcWriteFlags = flags;
+ end_batch[grpc.opType.SEND_MESSAGE] = message;
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
call.startBatch(end_batch, function (){});
}
@@ -254,7 +258,7 @@ function ServerWritableStream(call, serialize) {
* for implementing stream.Writable.
* @access private
* @param {Buffer} chunk The chunk of data to write
- * @param {string} encoding Ignored
+ * @param {string} encoding Used to pass write flags
* @param {function(Error=)} callback Callback to indicate that the write is
* complete
*/
@@ -265,7 +269,13 @@ function _write(chunk, encoding, callback) {
batch[grpc.opType.SEND_INITIAL_METADATA] = {};
this.call.metadataSent = true;
}
- batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk);
+ var message = this.serialize(chunk);
+ if (_.isFinite(encoding)) {
+ /* Attach the encoding if it is a finite number. This is the closest we
+ * can get to checking that it is valid flags */
+ message.grpcWriteFlags = encoding;
+ }
+ batch[grpc.opType.SEND_MESSAGE] = message;
this.call.startBatch(batch, function(err, value) {
if (err) {
this.emit('error', err);
@@ -432,6 +442,7 @@ function handleUnary(call, handler, metadata) {
});
emitter.metadata = metadata;
waitForCancel(call, emitter);
+ emitter.call = call;
var batch = {};
batch[grpc.opType.RECV_MESSAGE] = true;
call.startBatch(batch, function(err, result) {
@@ -449,14 +460,14 @@ function handleUnary(call, handler, metadata) {
if (emitter.cancelled) {
return;
}
- handler.func(emitter, function sendUnaryData(err, value, trailer) {
+ handler.func(emitter, function sendUnaryData(err, value, trailer, flags) {
if (err) {
if (trailer) {
err.metadata = trailer;
}
handleError(call, err);
} else {
- sendUnaryResponse(call, value, handler.serialize, trailer);
+ sendUnaryResponse(call, value, handler.serialize, trailer, flags);
}
});
});
@@ -513,7 +524,7 @@ function handleClientStreaming(call, handler, metadata) {
});
waitForCancel(call, stream);
stream.metadata = metadata;
- handler.func(stream, function(err, value, trailer) {
+ handler.func(stream, function(err, value, trailer, flags) {
stream.terminate();
if (err) {
if (trailer) {
@@ -521,7 +532,7 @@ function handleClientStreaming(call, handler, metadata) {
}
handleError(call, err);
} else {
- sendUnaryResponse(call, value, handler.serialize, trailer);
+ sendUnaryResponse(call, value, handler.serialize, trailer, flags);
}
});
}
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index 48d859a8ec..8d0f20b074 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -84,6 +84,11 @@ describe('call', function() {
new grpc.Call(channel, 'method', 0);
});
});
+ it('should accept an optional fourth string parameter', function() {
+ assert.doesNotThrow(function() {
+ new grpc.Call(channel, 'method', new Date(), 'host_override');
+ });
+ });
it('should fail with a closed channel', function() {
var local_channel = new grpc.Channel('hostname', insecureCreds);
local_channel.close();
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
index c991d7b25b..d81df2a36d 100644
--- a/src/node/test/channel_test.js
+++ b/src/node/test/channel_test.js
@@ -36,6 +36,26 @@
var assert = require('assert');
var grpc = require('bindings')('grpc.node');
+/**
+ * This is used for testing functions with multiple asynchronous calls that
+ * can happen in different orders. This should be passed the number of async
+ * function invocations that can occur last, and each of those should call this
+ * function's return value
+ * @param {function()} done The function that should be called when a test is
+ * complete.
+ * @param {number} count The number of calls to the resulting function if the
+ * test passes.
+ * @return {function()} The function that should be called at the end of each
+ * sequence of asynchronous functions.
+ */
+function multiDone(done, count) {
+ return function() {
+ count -= 1;
+ if (count <= 0) {
+ done();
+ }
+ };
+}
var insecureCreds = grpc.Credentials.createInsecure();
describe('channel', function() {
@@ -86,14 +106,16 @@ describe('channel', function() {
});
});
describe('close', function() {
+ var channel;
+ beforeEach(function() {
+ channel = new grpc.Channel('hostname', insecureCreds, {});
+ });
it('should succeed silently', function() {
- var channel = new grpc.Channel('hostname', insecureCreds, {});
assert.doesNotThrow(function() {
channel.close();
});
});
it('should be idempotent', function() {
- var channel = new grpc.Channel('hostname', insecureCreds, {});
assert.doesNotThrow(function() {
channel.close();
channel.close();
@@ -101,9 +123,68 @@ describe('channel', function() {
});
});
describe('getTarget', function() {
+ var channel;
+ beforeEach(function() {
+ channel = new grpc.Channel('hostname', insecureCreds, {});
+ });
it('should return a string', function() {
- var channel = new grpc.Channel('localhost', insecureCreds, {});
assert.strictEqual(typeof channel.getTarget(), 'string');
});
});
+ describe('getConnectivityState', function() {
+ var channel;
+ beforeEach(function() {
+ channel = new grpc.Channel('hostname', insecureCreds, {});
+ });
+ it('should return IDLE for a new channel', function() {
+ assert.strictEqual(channel.getConnectivityState(),
+ grpc.connectivityState.IDLE);
+ });
+ });
+ describe('watchConnectivityState', function() {
+ var channel;
+ beforeEach(function() {
+ channel = new grpc.Channel('localhost', insecureCreds, {});
+ });
+ afterEach(function() {
+ channel.close();
+ });
+ it('should time out if called alone', function(done) {
+ var old_state = channel.getConnectivityState();
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ channel.watchConnectivityState(old_state, deadline, function(err, value) {
+ assert(err);
+ done();
+ });
+ });
+ it('should complete if a connection attempt is forced', function(done) {
+ var old_state = channel.getConnectivityState();
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ channel.watchConnectivityState(old_state, deadline, function(err, value) {
+ assert.ifError(err);
+ assert.notEqual(value.new_state, old_state);
+ done();
+ });
+ channel.getConnectivityState(true);
+ });
+ it('should complete twice if called twice', function(done) {
+ done = multiDone(done, 2);
+ var old_state = channel.getConnectivityState();
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ channel.watchConnectivityState(old_state, deadline, function(err, value) {
+ assert.ifError(err);
+ assert.notEqual(value.new_state, old_state);
+ done();
+ });
+ channel.watchConnectivityState(old_state, deadline, function(err, value) {
+ assert.ifError(err);
+ assert.notEqual(value.new_state, old_state);
+ done();
+ });
+ channel.getConnectivityState(true);
+ });
+ });
});
diff --git a/src/node/test/constant_test.js b/src/node/test/constant_test.js
index ecc98ec443..fa06ad4e4d 100644
--- a/src/node/test/constant_test.js
+++ b/src/node/test/constant_test.js
@@ -78,6 +78,31 @@ var callErrorNames = [
'INVALID_FLAGS'
];
+/**
+ * List of all propagate flag names
+ * @const
+ * @type {Array.<string>}
+ */
+var propagateFlagNames = [
+ 'DEADLINE',
+ 'CENSUS_STATS_CONTEXT',
+ 'CENSUS_TRACING_CONTEXT',
+ 'CANCELLATION',
+ 'DEFAULTS'
+];
+/*
+ * List of all connectivity state names
+ * @const
+ * @type {Array.<string>}
+ */
+var connectivityStateNames = [
+ 'IDLE',
+ 'CONNECTING',
+ 'READY',
+ 'TRANSIENT_FAILURE',
+ 'FATAL_FAILURE'
+];
+
describe('constants', function() {
it('should have all of the status constants', function() {
for (var i = 0; i < statusNames.length; i++) {
@@ -91,4 +116,16 @@ describe('constants', function() {
'call error missing: ' + callErrorNames[i]);
}
});
+ it('should have all of the propagate flags', function() {
+ for (var i = 0; i < propagateFlagNames.length; i++) {
+ assert(grpc.propagate.hasOwnProperty(propagateFlagNames[i]),
+ 'call error missing: ' + propagateFlagNames[i]);
+ }
+ });
+ it('should have all of the connectivity states', function() {
+ for (var i = 0; i < connectivityStateNames.length; i++) {
+ assert(grpc.connectivityState.hasOwnProperty(connectivityStateNames[i]),
+ 'connectivity status missing: ' + connectivityStateNames[i]);
+ }
+ });
});
diff --git a/src/node/test/end_to_end_test.js b/src/node/test/end_to_end_test.js
index ea41dfc28c..7574d98b8a 100644
--- a/src/node/test/end_to_end_test.js
+++ b/src/node/test/end_to_end_test.js
@@ -74,8 +74,6 @@ describe('end-to-end', function() {
});
it('should start and end a request without error', function(complete) {
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
@@ -126,8 +124,6 @@ describe('end-to-end', function() {
});
it('should successfully send and receive metadata', function(complete) {
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
@@ -184,8 +180,6 @@ describe('end-to-end', function() {
var req_text = 'client_request';
var reply_text = 'server_response';
var done = multiDone(complete, 2);
- var deadline = new Date();
- deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'success';
var call = new grpc.Call(channel,
'dummy_method',
@@ -241,8 +235,6 @@ 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',
diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js
index be4ef1d251..04959f5f55 100644
--- a/src/node/test/health_test.js
+++ b/src/node/test/health_test.js
@@ -41,13 +41,9 @@ var grpc = require('../');
describe('Health Checking', function() {
var statusMap = {
- '': {
- '': 'SERVING',
- 'grpc.test.TestService': 'NOT_SERVING',
- },
- virtual_host: {
- 'grpc.test.TestService': 'SERVING'
- }
+ '': 'SERVING',
+ 'grpc.test.TestServiceNotServing': 'NOT_SERVING',
+ 'grpc.test.TestServiceServing': 'SERVING'
};
var healthServer = new grpc.Server();
healthServer.addProtoService(health.service,
@@ -71,15 +67,15 @@ describe('Health Checking', function() {
});
});
it('should say that a disabled service is NOT_SERVING', function(done) {
- healthClient.check({service: 'grpc.test.TestService'},
+ healthClient.check({service: 'grpc.test.TestServiceNotServing'},
function(err, response) {
assert.ifError(err);
assert.strictEqual(response.status, 'NOT_SERVING');
done();
});
});
- it('should say that a service on another host is SERVING', function(done) {
- healthClient.check({host: 'virtual_host', service: 'grpc.test.TestService'},
+ it('should say that an enabled service is SERVING', function(done) {
+ healthClient.check({service: 'grpc.test.TestServiceServing'},
function(err, response) {
assert.ifError(err);
assert.strictEqual(response.status, 'SERVING');
@@ -93,12 +89,4 @@ describe('Health Checking', function() {
done();
});
});
- it('should get NOT_FOUND if the host is not registered', function(done) {
- healthClient.check({host: 'wrong_host', service: 'grpc.test.TestService'},
- function(err, response) {
- assert(err);
- assert.strictEqual(err.code, grpc.status.NOT_FOUND);
- done();
- });
- });
});
diff --git a/src/node/test/server_test.js b/src/node/test/server_test.js
index a9df43909e..78bac8da29 100644
--- a/src/node/test/server_test.js
+++ b/src/node/test/server_test.js
@@ -70,7 +70,9 @@ describe('server', function() {
var pem_path = path.join(__dirname, '../test/data/server1.pem');
var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path);
- var creds = grpc.ServerCredentials.createSsl(null, key_data, pem_data);
+ var creds = grpc.ServerCredentials.createSsl(null,
+ [{private_key: key_data,
+ cert_chain: pem_data}]);
assert.doesNotThrow(function() {
port = server.addHttp2Port('0.0.0.0:0', creds);
});
@@ -83,7 +85,7 @@ describe('server', function() {
server = new grpc.Server();
});
});
- describe('listen', function() {
+ describe('start', function() {
var server;
before(function() {
server = new grpc.Server();
@@ -92,7 +94,7 @@ describe('server', function() {
after(function() {
server.shutdown();
});
- it('should listen without error', function() {
+ it('should start 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 dda2f8d127..ec7ed87728 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -47,6 +47,27 @@ var mathService = math_proto.lookup('math.Math');
var _ = require('lodash');
+/**
+ * This is used for testing functions with multiple asynchronous calls that
+ * can happen in different orders. This should be passed the number of async
+ * function invocations that can occur last, and each of those should call this
+ * function's return value
+ * @param {function()} done The function that should be called when a test is
+ * complete.
+ * @param {number} count The number of calls to the resulting function if the
+ * test passes.
+ * @return {function()} The function that should be called at the end of each
+ * sequence of asynchronous functions.
+ */
+function multiDone(done, count) {
+ return function() {
+ count -= 1;
+ if (count <= 0) {
+ done();
+ }
+ };
+}
+
var server_insecure_creds = grpc.ServerCredentials.createInsecure();
describe('File loader', function() {
@@ -112,6 +133,58 @@ describe('Server.prototype.addProtoService', function() {
});
});
});
+describe('Client#$waitForReady', function() {
+ var server;
+ var port;
+ var Client;
+ var client;
+ before(function() {
+ server = new grpc.Server();
+ port = server.bind('localhost:0', grpc.ServerCredentials.createInsecure());
+ server.start();
+ Client = surface_client.makeProtobufClientConstructor(mathService);
+ });
+ beforeEach(function() {
+ client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
+ });
+ after(function() {
+ server.shutdown();
+ });
+ it('should complete when called alone', function(done) {
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ done();
+ });
+ });
+ it('should complete when a call is initiated', function(done) {
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ done();
+ });
+ var call = client.div({}, function(err, response) {});
+ call.cancel();
+ });
+ it('should complete if called more than once', function(done) {
+ done = multiDone(done, 2);
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ done();
+ });
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ done();
+ });
+ });
+ it('should complete if called when already ready', function(done) {
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ client.$waitForReady(Infinity, function(error) {
+ assert.ifError(error);
+ done();
+ });
+ });
+ });
+});
describe('Echo service', function() {
var server;
var client;
@@ -272,12 +345,14 @@ describe('Echo metadata', function() {
});
});
describe('Other conditions', function() {
+ var test_service;
+ var Client;
var client;
var server;
var port;
before(function() {
var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
- var test_service = test_proto.lookup('TestService');
+ test_service = test_proto.lookup('TestService');
server = new grpc.Server();
server.addProtoService(test_service, {
unary: function(call, cb) {
@@ -339,7 +414,7 @@ describe('Other conditions', function() {
}
});
port = server.bind('localhost:0', server_insecure_creds);
- var Client = surface_client.makeProtobufClientConstructor(test_service);
+ Client = surface_client.makeProtobufClientConstructor(test_service);
client = new Client('localhost:' + port, grpc.Credentials.createInsecure());
server.start();
});
@@ -592,6 +667,168 @@ describe('Other conditions', function() {
});
});
});
+ describe('Call propagation', function() {
+ var proxy;
+ var proxy_impl;
+ beforeEach(function() {
+ proxy = new grpc.Server();
+ proxy_impl = {
+ unary: function(call) {},
+ clientStream: function(stream) {},
+ serverStream: function(stream) {},
+ bidiStream: function(stream) {}
+ };
+ });
+ afterEach(function() {
+ console.log('Shutting down server');
+ proxy.shutdown();
+ });
+ describe('Cancellation', function() {
+ it('With a unary call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.unary = function(parent, callback) {
+ client.unary(parent.request, function(err, value) {
+ try {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.CANCELLED);
+ } finally {
+ callback(err, value);
+ done();
+ }
+ }, null, {parent: parent});
+ call.cancel();
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var call = proxy_client.unary({}, function(err, value) {
+ done();
+ });
+ });
+ it('With a client stream call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.clientStream = function(parent, callback) {
+ client.clientStream(function(err, value) {
+ try {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.CANCELLED);
+ } finally {
+ callback(err, value);
+ done();
+ }
+ }, null, {parent: parent});
+ call.cancel();
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var call = proxy_client.clientStream(function(err, value) {
+ done();
+ });
+ });
+ it('With a server stream call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.serverStream = function(parent) {
+ var child = client.serverStream(parent.request, null,
+ {parent: parent});
+ child.on('error', function(err) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.CANCELLED);
+ done();
+ });
+ call.cancel();
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var call = proxy_client.serverStream({});
+ call.on('error', function(err) {
+ done();
+ });
+ });
+ it('With a bidi stream call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.bidiStream = function(parent) {
+ var child = client.bidiStream(null, {parent: parent});
+ child.on('error', function(err) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.CANCELLED);
+ done();
+ });
+ call.cancel();
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var call = proxy_client.bidiStream();
+ call.on('error', function(err) {
+ done();
+ });
+ });
+ });
+ describe('Deadline', function() {
+ /* jshint bitwise:false */
+ var deadline_flags = (grpc.propagate.DEFAULTS &
+ ~grpc.propagate.CANCELLATION);
+ it('With a client stream call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.clientStream = function(parent, callback) {
+ client.clientStream(function(err, value) {
+ try {
+ assert(err);
+ assert(err.code === grpc.status.DEADLINE_EXCEEDED ||
+ err.code === grpc.status.INTERNAL);
+ } finally {
+ callback(err, value);
+ done();
+ }
+ }, null, {parent: parent, propagate_flags: deadline_flags});
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ proxy_client.clientStream(function(err, value) {
+ done();
+ }, null, {deadline: deadline});
+ });
+ it('With a bidi stream call', function(done) {
+ done = multiDone(done, 2);
+ proxy_impl.bidiStream = function(parent) {
+ var child = client.bidiStream(
+ null, {parent: parent, propagate_flags: deadline_flags});
+ child.on('error', function(err) {
+ assert(err);
+ assert(err.code === grpc.status.DEADLINE_EXCEEDED ||
+ err.code === grpc.status.INTERNAL);
+ done();
+ });
+ };
+ proxy.addProtoService(test_service, proxy_impl);
+ var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
+ proxy.start();
+ var proxy_client = new Client('localhost:' + proxy_port,
+ grpc.Credentials.createInsecure());
+ var deadline = new Date();
+ deadline.setSeconds(deadline.getSeconds() + 1);
+ var call = proxy_client.bidiStream(null, {deadline: deadline});
+ call.on('error', function(err) {
+ done();
+ });
+ });
+ });
+ });
});
describe('Cancelling surface client', function() {
var client;