mirror of https://github.com/grpc/grpc-node.git
Clean commit of Node.js library source
This commit is contained in:
commit
a9b57ed10f
|
@ -0,0 +1,12 @@
|
||||||
|
# Node.js GRPC extension
|
||||||
|
|
||||||
|
The package is built with
|
||||||
|
|
||||||
|
node-gyp configure
|
||||||
|
node-gyp build
|
||||||
|
|
||||||
|
or, for brevity
|
||||||
|
|
||||||
|
node-gyp configure build
|
||||||
|
|
||||||
|
The tests can be run with `npm test` on a dev install.
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"targets" : [
|
||||||
|
{
|
||||||
|
'include_dirs': [
|
||||||
|
"<!(node -e \"require('nan')\")"
|
||||||
|
],
|
||||||
|
'cxxflags': [
|
||||||
|
'-Wall',
|
||||||
|
'-pthread',
|
||||||
|
'-pedantic',
|
||||||
|
'-g',
|
||||||
|
'-zdefs'
|
||||||
|
'-Werror',
|
||||||
|
],
|
||||||
|
'ldflags': [
|
||||||
|
'-g',
|
||||||
|
'-L/usr/local/google/home/mlumish/grpc_dev/lib'
|
||||||
|
],
|
||||||
|
'link_settings': {
|
||||||
|
'libraries': [
|
||||||
|
'-lgrpc',
|
||||||
|
'-levent',
|
||||||
|
'-levent_pthreads',
|
||||||
|
'-levent_core',
|
||||||
|
'-lrt',
|
||||||
|
'-lgpr',
|
||||||
|
'-lpthread'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"target_name": "grpc",
|
||||||
|
"sources": [
|
||||||
|
"byte_buffer.cc",
|
||||||
|
"call.cc",
|
||||||
|
"channel.cc",
|
||||||
|
"completion_queue_async_worker.cc",
|
||||||
|
"credentials.cc",
|
||||||
|
"event.cc",
|
||||||
|
"node_grpc.cc",
|
||||||
|
"server.cc",
|
||||||
|
"server_credentials.cc",
|
||||||
|
"tag.cc",
|
||||||
|
"timeval.cc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/slice.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
#include "byte_buffer.h"
|
||||||
|
|
||||||
|
using ::node::Buffer;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
|
||||||
|
NanScope();
|
||||||
|
int length = Buffer::Length(buffer);
|
||||||
|
char *data = Buffer::Data(buffer);
|
||||||
|
gpr_slice slice = gpr_slice_malloc(length);
|
||||||
|
memcpy(GPR_SLICE_START_PTR(slice), data, length);
|
||||||
|
grpc_byte_buffer *byte_buffer(grpc_byte_buffer_create(&slice, 1));
|
||||||
|
gpr_slice_unref(slice);
|
||||||
|
return byte_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
|
||||||
|
NanEscapableScope();
|
||||||
|
if (buffer == NULL) {
|
||||||
|
NanReturnNull();
|
||||||
|
}
|
||||||
|
size_t length = grpc_byte_buffer_length(buffer);
|
||||||
|
char *result = reinterpret_cast<char*>(calloc(length, sizeof(char)));
|
||||||
|
size_t offset = 0;
|
||||||
|
grpc_byte_buffer_reader *reader = grpc_byte_buffer_reader_create(buffer);
|
||||||
|
gpr_slice next;
|
||||||
|
while (grpc_byte_buffer_reader_next(reader, &next) != 0) {
|
||||||
|
memcpy(result+offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next));
|
||||||
|
offset += GPR_SLICE_LENGTH(next);
|
||||||
|
}
|
||||||
|
return NanEscapeScope(NanNewBufferHandle(result, length));
|
||||||
|
}
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef NET_GRPC_NODE_BYTE_BUFFER_H_
|
||||||
|
#define NET_GRPC_NODE_BYTE_BUFFER_H_
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Convert a Node.js Buffer to grpc_byte_buffer. Requires that
|
||||||
|
::node::Buffer::HasInstance(buffer) */
|
||||||
|
grpc_byte_buffer *BufferToByteBuffer(v8::Handle<v8::Value> buffer);
|
||||||
|
|
||||||
|
/* Convert a grpc_byte_buffer to a Node.js Buffer */
|
||||||
|
v8::Handle<v8::Value> ByteBufferToBuffer(grpc_byte_buffer *buffer);
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_BYTE_BUFFER_H_
|
|
@ -0,0 +1,384 @@
|
||||||
|
#include <node.h>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/time.h"
|
||||||
|
#include "byte_buffer.h"
|
||||||
|
#include "call.h"
|
||||||
|
#include "channel.h"
|
||||||
|
#include "completion_queue_async_worker.h"
|
||||||
|
#include "timeval.h"
|
||||||
|
#include "tag.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using ::node::Buffer;
|
||||||
|
using v8::Arguments;
|
||||||
|
using v8::Array;
|
||||||
|
using v8::Exception;
|
||||||
|
using v8::External;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Integer;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Number;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::ObjectTemplate;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Uint32;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Persistent<Function> Call::constructor;
|
||||||
|
Persistent<FunctionTemplate> Call::fun_tpl;
|
||||||
|
|
||||||
|
Call::Call(grpc_call *call) : wrapped_call(call) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Call::~Call() {
|
||||||
|
grpc_call_destroy(wrapped_call);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Call::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(NanNew("Call"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
NanSetPrototypeTemplate(tpl, "addMetadata",
|
||||||
|
FunctionTemplate::New(AddMetadata)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "startInvoke",
|
||||||
|
FunctionTemplate::New(StartInvoke)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "serverAccept",
|
||||||
|
FunctionTemplate::New(ServerAccept)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(
|
||||||
|
tpl,
|
||||||
|
"serverEndInitialMetadata",
|
||||||
|
FunctionTemplate::New(ServerEndInitialMetadata)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "cancel",
|
||||||
|
FunctionTemplate::New(Cancel)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "startWrite",
|
||||||
|
FunctionTemplate::New(StartWrite)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(
|
||||||
|
tpl, "startWriteStatus",
|
||||||
|
FunctionTemplate::New(StartWriteStatus)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "writesDone",
|
||||||
|
FunctionTemplate::New(WritesDone)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "startReadMetadata",
|
||||||
|
FunctionTemplate::New(WritesDone)->GetFunction());
|
||||||
|
NanSetPrototypeTemplate(tpl, "startRead",
|
||||||
|
FunctionTemplate::New(StartRead)->GetFunction());
|
||||||
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
|
NanAssignPersistent(constructor, tpl->GetFunction());
|
||||||
|
constructor->Set(NanNew("WRITE_BUFFER_HINT"),
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_WRITE_BUFFER_HINT));
|
||||||
|
constructor->Set(NanNew("WRITE_NO_COMPRESS"),
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_WRITE_NO_COMPRESS));
|
||||||
|
exports->Set(String::NewSymbol("Call"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Call::HasInstance(Handle<Value> val) {
|
||||||
|
NanScope();
|
||||||
|
return NanHasInstance(fun_tpl, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> Call::WrapStruct(grpc_call *call) {
|
||||||
|
NanEscapableScope();
|
||||||
|
if (call == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
const int argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { External::New(reinterpret_cast<void*>(call)) };
|
||||||
|
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::New) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
if (args.IsConstructCall()) {
|
||||||
|
Call *call;
|
||||||
|
if (args[0]->IsExternal()) {
|
||||||
|
// This option is used for wrapping an existing call
|
||||||
|
grpc_call *call_value = reinterpret_cast<grpc_call*>(
|
||||||
|
External::Unwrap(args[0]));
|
||||||
|
call = new Call(call_value);
|
||||||
|
} else {
|
||||||
|
if (!Channel::HasInstance(args[0])) {
|
||||||
|
return NanThrowTypeError("Call's first argument must be a Channel");
|
||||||
|
}
|
||||||
|
if (!args[1]->IsString()) {
|
||||||
|
return NanThrowTypeError("Call's second argument must be a string");
|
||||||
|
}
|
||||||
|
if (!(args[2]->IsNumber() || args[2]->IsDate())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"Call's third argument must be a date or a number");
|
||||||
|
}
|
||||||
|
Handle<Object> channel_object = args[0]->ToObject();
|
||||||
|
Channel *channel = ObjectWrap::Unwrap<Channel>(channel_object);
|
||||||
|
if (channel->GetWrappedChannel() == NULL) {
|
||||||
|
return NanThrowError("Call cannot be created from a closed channel");
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
*method,
|
||||||
|
channel->GetHost(),
|
||||||
|
MillisecondsToTimespec(deadline));
|
||||||
|
call = new Call(wrapped_call);
|
||||||
|
args.This()->SetHiddenValue(String::NewSymbol("channel_"),
|
||||||
|
channel_object);
|
||||||
|
}
|
||||||
|
call->Wrap(args.This());
|
||||||
|
NanReturnValue(args.This());
|
||||||
|
} else {
|
||||||
|
const int argc = 4;
|
||||||
|
Local<Value> argv[argc] = { args[0], args[1], args[2], args[3] };
|
||||||
|
NanReturnValue(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::AddMetadata) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"addMetadata can only be called on Call objects");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
for (int i=0; !args[i]->IsUndefined(); i++) {
|
||||||
|
if (!args[i]->IsObject()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"addMetadata arguments must be objects with key and value");
|
||||||
|
}
|
||||||
|
Handle<Object> item = args[i]->ToObject();
|
||||||
|
Handle<Value> key = item->Get(NanNew("key"));
|
||||||
|
if (!key->IsString()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"objects passed to addMetadata must have key->string");
|
||||||
|
}
|
||||||
|
Handle<Value> value = item->Get(NanNew("value"));
|
||||||
|
if (!Buffer::HasInstance(value)) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"objects passed to addMetadata must have value->Buffer");
|
||||||
|
}
|
||||||
|
grpc_metadata metadata;
|
||||||
|
NanUtf8String utf8_key(key);
|
||||||
|
metadata.key = *utf8_key;
|
||||||
|
metadata.value = Buffer::Data(value);
|
||||||
|
metadata.value_length = Buffer::Length(value);
|
||||||
|
grpc_call_error error = grpc_call_add_metadata(call->wrapped_call,
|
||||||
|
&metadata,
|
||||||
|
0);
|
||||||
|
if (error != GRPC_CALL_OK) {
|
||||||
|
return NanThrowError("addMetadata failed", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::StartInvoke) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("startInvoke can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"StartInvoke's first argument must be a function");
|
||||||
|
}
|
||||||
|
if (!args[1]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"StartInvoke's second argument must be a function");
|
||||||
|
}
|
||||||
|
if (!args[2]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"StartInvoke's third argument must be a function");
|
||||||
|
}
|
||||||
|
if (!args[3]->IsUint32()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"StartInvoke's fourth argument must be integer flags");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
unsigned int flags = args[3]->Uint32Value();
|
||||||
|
grpc_call_error error = grpc_call_start_invoke(
|
||||||
|
call->wrapped_call,
|
||||||
|
CompletionQueueAsyncWorker::GetQueue(),
|
||||||
|
CreateTag(args[0], args.This()),
|
||||||
|
CreateTag(args[1], args.This()),
|
||||||
|
CreateTag(args[2], args.This()),
|
||||||
|
flags);
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("startInvoke failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::ServerAccept) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("accept can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"accept's first argument must be a function");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
grpc_call_error error = grpc_call_server_accept(
|
||||||
|
call->wrapped_call,
|
||||||
|
CompletionQueueAsyncWorker::GetQueue(),
|
||||||
|
CreateTag(args[0], args.This()));
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("serverAccept failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::ServerEndInitialMetadata) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"serverEndInitialMetadata can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsUint32()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"serverEndInitialMetadata's second argument must be integer flags");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
unsigned int flags = args[1]->Uint32Value();
|
||||||
|
grpc_call_error error = grpc_call_server_end_initial_metadata(
|
||||||
|
call->wrapped_call,
|
||||||
|
flags);
|
||||||
|
if (error != GRPC_CALL_OK) {
|
||||||
|
return NanThrowError("serverEndInitialMetadata failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::Cancel) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("startInvoke can only be called on Call objects");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
grpc_call_error error = grpc_call_cancel(call->wrapped_call);
|
||||||
|
if (error != GRPC_CALL_OK) {
|
||||||
|
return NanThrowError("cancel failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::StartWrite) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("startWrite can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!Buffer::HasInstance(args[0])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWrite's first argument must be a Buffer");
|
||||||
|
}
|
||||||
|
if (!args[1]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWrite's second argument must be a function");
|
||||||
|
}
|
||||||
|
if (!args[2]->IsUint32()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWrite's third argument must be integer flags");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
grpc_byte_buffer *buffer = BufferToByteBuffer(args[0]);
|
||||||
|
unsigned int flags = args[2]->Uint32Value();
|
||||||
|
grpc_call_error error = grpc_call_start_write(call->wrapped_call,
|
||||||
|
buffer,
|
||||||
|
CreateTag(args[1], args.This()),
|
||||||
|
flags);
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("startWrite failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::StartWriteStatus) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWriteStatus can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsUint32()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWriteStatus's first argument must be a status code");
|
||||||
|
}
|
||||||
|
if (!args[1]->IsString()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWriteStatus's second argument must be a string");
|
||||||
|
}
|
||||||
|
if (!args[2]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startWriteStatus's third argument must be a function");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
NanUtf8String details(args[1]);
|
||||||
|
grpc_call_error error = grpc_call_start_write_status(
|
||||||
|
call->wrapped_call,
|
||||||
|
(grpc_status_code)args[0]->Uint32Value(),
|
||||||
|
*details,
|
||||||
|
CreateTag(args[2], args.This()));
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("startWriteStatus failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::WritesDone) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("writesDone can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"writesDone's first argument must be a function");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
grpc_call_error error = grpc_call_writes_done(
|
||||||
|
call->wrapped_call,
|
||||||
|
CreateTag(args[0], args.This()));
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("writesDone failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Call::StartRead) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("startRead can only be called on Call objects");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"startRead's first argument must be a function");
|
||||||
|
}
|
||||||
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
|
grpc_call_error error = grpc_call_start_read(call->wrapped_call,
|
||||||
|
CreateTag(args[0], args.This()));
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("startRead failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,49 @@
|
||||||
|
#ifndef NET_GRPC_NODE_CALL_H_
|
||||||
|
#define NET_GRPC_NODE_CALL_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
#include "channel.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Wrapper class for grpc_call structs. */
|
||||||
|
class Call : public ::node::ObjectWrap {
|
||||||
|
public:
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
static bool HasInstance(v8::Handle<v8::Value> val);
|
||||||
|
/* Wrap a grpc_call struct in a javascript object */
|
||||||
|
static v8::Handle<v8::Value> WrapStruct(grpc_call *call);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Call(grpc_call *call);
|
||||||
|
~Call();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
Call(const Call&);
|
||||||
|
Call& operator=(const Call&);
|
||||||
|
|
||||||
|
static NAN_METHOD(New);
|
||||||
|
static NAN_METHOD(AddMetadata);
|
||||||
|
static NAN_METHOD(StartInvoke);
|
||||||
|
static NAN_METHOD(ServerAccept);
|
||||||
|
static NAN_METHOD(ServerEndInitialMetadata);
|
||||||
|
static NAN_METHOD(Cancel);
|
||||||
|
static NAN_METHOD(StartWrite);
|
||||||
|
static NAN_METHOD(StartWriteStatus);
|
||||||
|
static NAN_METHOD(WritesDone);
|
||||||
|
static NAN_METHOD(StartRead);
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
// Used for typechecking instances of this javascript class
|
||||||
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
grpc_call *wrapped_call;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_CALL_H_
|
|
@ -0,0 +1,155 @@
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
#include "channel.h"
|
||||||
|
#include "credentials.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using v8::Arguments;
|
||||||
|
using v8::Array;
|
||||||
|
using v8::Exception;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Integer;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Persistent<Function> Channel::constructor;
|
||||||
|
Persistent<FunctionTemplate> Channel::fun_tpl;
|
||||||
|
|
||||||
|
Channel::Channel(grpc_channel *channel, NanUtf8String *host)
|
||||||
|
: wrapped_channel(channel), host(host) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel::~Channel() {
|
||||||
|
if (wrapped_channel != NULL) {
|
||||||
|
grpc_channel_destroy(wrapped_channel);
|
||||||
|
}
|
||||||
|
delete host;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(NanNew("Channel"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
NanSetPrototypeTemplate(tpl, "close",
|
||||||
|
FunctionTemplate::New(Close)->GetFunction());
|
||||||
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
|
NanAssignPersistent(constructor, tpl->GetFunction());
|
||||||
|
exports->Set(NanNew("Channel"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::HasInstance(Handle<Value> val) {
|
||||||
|
NanScope();
|
||||||
|
return NanHasInstance(fun_tpl, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc_channel *Channel::GetWrappedChannel() {
|
||||||
|
return this->wrapped_channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *Channel::GetHost() {
|
||||||
|
return **this->host;
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Channel::New) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
if (args.IsConstructCall()) {
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
return NanThrowTypeError("Channel expects a string and an object");
|
||||||
|
}
|
||||||
|
grpc_channel *wrapped_channel;
|
||||||
|
// Owned by the Channel object
|
||||||
|
NanUtf8String *host = new NanUtf8String(args[0]);
|
||||||
|
if (args[1]->IsUndefined()) {
|
||||||
|
wrapped_channel = grpc_channel_create(**host, NULL);
|
||||||
|
} else if (args[1]->IsObject()) {
|
||||||
|
grpc_credentials *creds = NULL;
|
||||||
|
Handle<Object> args_hash(args[1]->ToObject()->Clone());
|
||||||
|
if (args_hash->HasOwnProperty(NanNew("credentials"))) {
|
||||||
|
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
|
||||||
|
if (!Credentials::HasInstance(creds_value)) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"credentials arg must be a Credentials object");
|
||||||
|
}
|
||||||
|
Credentials *creds_object = ObjectWrap::Unwrap<Credentials>(
|
||||||
|
creds_value->ToObject());
|
||||||
|
creds = creds_object->GetWrappedCredentials();
|
||||||
|
args_hash->Delete(NanNew("credentials"));
|
||||||
|
}
|
||||||
|
Handle<Array> keys(args_hash->GetOwnPropertyNames());
|
||||||
|
grpc_channel_args channel_args;
|
||||||
|
channel_args.num_args = keys->Length();
|
||||||
|
channel_args.args = reinterpret_cast<grpc_arg*>(
|
||||||
|
calloc(channel_args.num_args, sizeof(grpc_arg)));
|
||||||
|
/* These are used to keep all strings until then end of the block, then
|
||||||
|
destroy them */
|
||||||
|
std::vector<NanUtf8String*> key_strings(keys->Length());
|
||||||
|
std::vector<NanUtf8String*> value_strings(keys->Length());
|
||||||
|
for (unsigned int i = 0; i < channel_args.num_args; i++) {
|
||||||
|
Handle<String> current_key(keys->Get(i)->ToString());
|
||||||
|
Handle<Value> current_value(args_hash->Get(current_key));
|
||||||
|
key_strings[i] = new NanUtf8String(current_key);
|
||||||
|
channel_args.args[i].key = **key_strings[i];
|
||||||
|
if (current_value->IsInt32()) {
|
||||||
|
channel_args.args[i].type = GRPC_ARG_INTEGER;
|
||||||
|
channel_args.args[i].value.integer = current_value->Int32Value();
|
||||||
|
} else if (current_value->IsString()) {
|
||||||
|
channel_args.args[i].type = GRPC_ARG_STRING;
|
||||||
|
value_strings[i] = new NanUtf8String(current_value);
|
||||||
|
channel_args.args[i].value.string = **value_strings[i];
|
||||||
|
} else {
|
||||||
|
free(channel_args.args);
|
||||||
|
return NanThrowTypeError("Arg values must be strings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (creds == NULL) {
|
||||||
|
wrapped_channel = grpc_channel_create(**host, &channel_args);
|
||||||
|
} else {
|
||||||
|
wrapped_channel = grpc_secure_channel_create(creds,
|
||||||
|
**host,
|
||||||
|
&channel_args);
|
||||||
|
}
|
||||||
|
free(channel_args.args);
|
||||||
|
} else {
|
||||||
|
return NanThrowTypeError("Channel expects a string and an object");
|
||||||
|
}
|
||||||
|
Channel *channel = new Channel(wrapped_channel, host);
|
||||||
|
channel->Wrap(args.This());
|
||||||
|
NanReturnValue(args.This());
|
||||||
|
} else {
|
||||||
|
const int argc = 2;
|
||||||
|
Local<Value> argv[argc] = { args[0], args[1] };
|
||||||
|
NanReturnValue(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Channel::Close) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("close can only be called on Channel objects");
|
||||||
|
}
|
||||||
|
Channel *channel = ObjectWrap::Unwrap<Channel>(args.This());
|
||||||
|
if (channel->wrapped_channel != NULL) {
|
||||||
|
grpc_channel_destroy(channel->wrapped_channel);
|
||||||
|
channel->wrapped_channel = NULL;
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef NET_GRPC_NODE_CHANNEL_H_
|
||||||
|
#define NET_GRPC_NODE_CHANNEL_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Wrapper class for grpc_channel structs */
|
||||||
|
class Channel : public ::node::ObjectWrap {
|
||||||
|
public:
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
static bool HasInstance(v8::Handle<v8::Value> val);
|
||||||
|
/* This is used to typecheck javascript objects before converting them to
|
||||||
|
this type */
|
||||||
|
static v8::Persistent<v8::Value> prototype;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
~Channel();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
Channel(const Channel&);
|
||||||
|
Channel& operator=(const Channel&);
|
||||||
|
|
||||||
|
static NAN_METHOD(New);
|
||||||
|
static NAN_METHOD(Close);
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
grpc_channel *wrapped_channel;
|
||||||
|
NanUtf8String *host;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_CHANNEL_H_
|
|
@ -0,0 +1,176 @@
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
|
|
||||||
|
var Duplex = require('stream').Duplex;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
util.inherits(GrpcClientStream, Duplex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC client side stream as a Node stream. Extends
|
||||||
|
* from stream.Duplex.
|
||||||
|
* @constructor
|
||||||
|
* @param {grpc.Call} call Call object to proxy
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function GrpcClientStream(call, options) {
|
||||||
|
Duplex.call(this, options);
|
||||||
|
var self = this;
|
||||||
|
// Indicates that we can start reading and have not received a null read
|
||||||
|
var can_read = false;
|
||||||
|
// Indicates that a read is currently pending
|
||||||
|
var reading = false;
|
||||||
|
// Indicates that we can call startWrite
|
||||||
|
var can_write = false;
|
||||||
|
// Indicates that a write is currently pending
|
||||||
|
var writing = false;
|
||||||
|
this._call = call;
|
||||||
|
/**
|
||||||
|
* Callback to handle receiving a READ event. Pushes the data from that event
|
||||||
|
* onto the read queue and starts reading again if applicable.
|
||||||
|
* @param {grpc.Event} event The READ event object
|
||||||
|
*/
|
||||||
|
function readCallback(event) {
|
||||||
|
var data = event.data;
|
||||||
|
if (self.push(data)) {
|
||||||
|
if (data == null) {
|
||||||
|
// Disable starting to read after null read was received
|
||||||
|
can_read = false;
|
||||||
|
reading = false;
|
||||||
|
} else {
|
||||||
|
call.startRead(readCallback);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Indicate that reading can be resumed by calling startReading
|
||||||
|
reading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Initiate a read, which continues until self.push returns false (indicating
|
||||||
|
* that reading should be paused) or data is null (indicating that there is no
|
||||||
|
* more data to read).
|
||||||
|
*/
|
||||||
|
function startReading() {
|
||||||
|
call.startRead(readCallback);
|
||||||
|
}
|
||||||
|
// TODO(mlumish): possibly change queue implementation due to shift slowness
|
||||||
|
var write_queue = [];
|
||||||
|
/**
|
||||||
|
* Write the next chunk of data in the write queue if there is one. Otherwise
|
||||||
|
* indicate that there is no pending write. When the write succeeds, this
|
||||||
|
* function is called again.
|
||||||
|
*/
|
||||||
|
function writeNext() {
|
||||||
|
if (write_queue.length > 0) {
|
||||||
|
writing = true;
|
||||||
|
var next = write_queue.shift();
|
||||||
|
var writeCallback = function(event) {
|
||||||
|
next.callback();
|
||||||
|
writeNext();
|
||||||
|
};
|
||||||
|
call.startWrite(next.chunk, writeCallback, 0);
|
||||||
|
} else {
|
||||||
|
writing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
call.startInvoke(function(event) {
|
||||||
|
can_read = true;
|
||||||
|
can_write = true;
|
||||||
|
startReading();
|
||||||
|
writeNext();
|
||||||
|
}, function(event) {
|
||||||
|
self.emit('metadata', event.data);
|
||||||
|
}, function(event) {
|
||||||
|
self.emit('status', event.data);
|
||||||
|
}, 0);
|
||||||
|
this.on('finish', function() {
|
||||||
|
call.writesDone(function() {});
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Indicate that reads should start, and start them if the INVOKE_ACCEPTED
|
||||||
|
* event has been received.
|
||||||
|
*/
|
||||||
|
this._enableRead = function() {
|
||||||
|
if (!reading) {
|
||||||
|
reading = true;
|
||||||
|
if (can_read) {
|
||||||
|
startReading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Push the chunk onto the write queue, and write from the write queue if
|
||||||
|
* there is not a pending write
|
||||||
|
* @param {Buffer} chunk The chunk of data to write
|
||||||
|
* @param {function(Error=)} callback The callback to call when the write
|
||||||
|
* completes
|
||||||
|
*/
|
||||||
|
this._tryWrite = function(chunk, callback) {
|
||||||
|
write_queue.push({chunk: chunk, callback: callback});
|
||||||
|
if (can_write && !writing) {
|
||||||
|
writeNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start reading. This is an implementation of a method needed for implementing
|
||||||
|
* stream.Readable.
|
||||||
|
* @param {number} size Ignored
|
||||||
|
*/
|
||||||
|
GrpcClientStream.prototype._read = function(size) {
|
||||||
|
this._enableRead();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to write the given chunk. Calls the callback when done. This is an
|
||||||
|
* implementation of a method needed for implementing stream.Writable.
|
||||||
|
* @param {Buffer} chunk The chunk to write
|
||||||
|
* @param {string} encoding Ignored
|
||||||
|
* @param {function(Error=)} callback Ignored
|
||||||
|
*/
|
||||||
|
GrpcClientStream.prototype._write = function(chunk, encoding, callback) {
|
||||||
|
this._tryWrite(chunk, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a request on the channel to the given method with the given arguments
|
||||||
|
* @param {grpc.Channel} channel The channel on which to make the request
|
||||||
|
* @param {string} method The method to request
|
||||||
|
* @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.
|
||||||
|
* @return {stream=} The stream of responses
|
||||||
|
*/
|
||||||
|
function makeRequest(channel,
|
||||||
|
method,
|
||||||
|
metadata,
|
||||||
|
deadline) {
|
||||||
|
if (deadline === undefined) {
|
||||||
|
deadline = Infinity;
|
||||||
|
}
|
||||||
|
var call = new grpc.Call(channel, method, deadline);
|
||||||
|
if (metadata) {
|
||||||
|
call.addMetadata(metadata);
|
||||||
|
}
|
||||||
|
return new GrpcClientStream(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for makeRequest above
|
||||||
|
*/
|
||||||
|
exports.makeRequest = makeRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a client side gRPC channel associated with a single host.
|
||||||
|
*/
|
||||||
|
exports.Channel = grpc.Channel;
|
||||||
|
/**
|
||||||
|
* Status name to code number mapping
|
||||||
|
*/
|
||||||
|
exports.status = grpc.status;
|
||||||
|
/**
|
||||||
|
* Call error name to code number mapping
|
||||||
|
*/
|
||||||
|
exports.callError = grpc.callError;
|
|
@ -0,0 +1,29 @@
|
||||||
|
var _ = require('highland');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the given stream finishes without error, call the callback once. This
|
||||||
|
* will not be called until something begins to consume the stream.
|
||||||
|
* @param {function} callback The callback to call at stream end
|
||||||
|
* @param {stream} source The stream to watch
|
||||||
|
* @return {stream} The stream with the callback attached
|
||||||
|
*/
|
||||||
|
function onSuccessfulStreamEnd(callback, source) {
|
||||||
|
var error = false;
|
||||||
|
return source.consume(function(err, x, push, next) {
|
||||||
|
if (x === _.nil) {
|
||||||
|
if (!error) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
push(null, x);
|
||||||
|
} else if (err) {
|
||||||
|
error = true;
|
||||||
|
push(err);
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
push(err, x);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.onSuccessfulStreamEnd = onSuccessfulStreamEnd;
|
|
@ -0,0 +1,62 @@
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/time.h"
|
||||||
|
#include "completion_queue_async_worker.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "tag.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using v8::Function;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
grpc_completion_queue *CompletionQueueAsyncWorker::queue;
|
||||||
|
|
||||||
|
CompletionQueueAsyncWorker::CompletionQueueAsyncWorker() :
|
||||||
|
NanAsyncWorker(NULL) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletionQueueAsyncWorker::~CompletionQueueAsyncWorker() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompletionQueueAsyncWorker::Execute() {
|
||||||
|
result = grpc_completion_queue_next(queue, gpr_inf_future);
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc_completion_queue *CompletionQueueAsyncWorker::GetQueue() {
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompletionQueueAsyncWorker::Next() {
|
||||||
|
NanScope();
|
||||||
|
CompletionQueueAsyncWorker *worker = new CompletionQueueAsyncWorker();
|
||||||
|
NanAsyncQueueWorker(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompletionQueueAsyncWorker::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
queue = grpc_completion_queue_create();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompletionQueueAsyncWorker::HandleOKCallback() {
|
||||||
|
NanScope();
|
||||||
|
NanCallback event_callback(GetTagHandle(result->tag).As<Function>());
|
||||||
|
Handle<Value> argv[] = {
|
||||||
|
CreateEventObject(result)
|
||||||
|
};
|
||||||
|
|
||||||
|
DestroyTag(result->tag);
|
||||||
|
grpc_event_finish(result);
|
||||||
|
result = NULL;
|
||||||
|
|
||||||
|
event_callback.Call(1, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
|
||||||
|
#define NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
|
||||||
|
#include <nan.h>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* A worker that asynchronously calls completion_queue_next, and queues onto the
|
||||||
|
node event loop a call to the function stored in the event's tag. */
|
||||||
|
class CompletionQueueAsyncWorker : public NanAsyncWorker {
|
||||||
|
public:
|
||||||
|
CompletionQueueAsyncWorker();
|
||||||
|
|
||||||
|
~CompletionQueueAsyncWorker();
|
||||||
|
/* Calls completion_queue_next with the provided deadline, and stores the
|
||||||
|
event if there was one or sets an error message if there was not */
|
||||||
|
void Execute();
|
||||||
|
|
||||||
|
/* Returns the completion queue attached to this class */
|
||||||
|
static grpc_completion_queue *GetQueue();
|
||||||
|
|
||||||
|
/* Convenience function to create a worker with the given arguments and queue
|
||||||
|
it to run asynchronously */
|
||||||
|
static void Next();
|
||||||
|
|
||||||
|
/* Initialize the CompletionQueueAsyncWorker class */
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Called when Execute has succeeded (completed without setting an error
|
||||||
|
message). Calls the saved callback with the event that came from
|
||||||
|
completion_queue_next */
|
||||||
|
void HandleOKCallback();
|
||||||
|
|
||||||
|
private:
|
||||||
|
grpc_event *result;
|
||||||
|
|
||||||
|
static grpc_completion_queue *queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_COMPLETION_QUEUE_ASYNC_WORKER_H_
|
|
@ -0,0 +1,180 @@
|
||||||
|
#include <node.h>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
#include "grpc/support/log.h"
|
||||||
|
#include "credentials.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using ::node::Buffer;
|
||||||
|
using v8::Arguments;
|
||||||
|
using v8::Exception;
|
||||||
|
using v8::External;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Integer;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::ObjectTemplate;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Persistent<Function> Credentials::constructor;
|
||||||
|
Persistent<FunctionTemplate> Credentials::fun_tpl;
|
||||||
|
|
||||||
|
Credentials::Credentials(grpc_credentials *credentials)
|
||||||
|
: wrapped_credentials(credentials) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Credentials::~Credentials() {
|
||||||
|
gpr_log(GPR_DEBUG, "Destroying credentials object");
|
||||||
|
grpc_credentials_release(wrapped_credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Credentials::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(NanNew("Credentials"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
|
NanAssignPersistent(constructor, tpl->GetFunction());
|
||||||
|
constructor->Set(NanNew("createDefault"),
|
||||||
|
FunctionTemplate::New(CreateDefault)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createSsl"),
|
||||||
|
FunctionTemplate::New(CreateSsl)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createComposite"),
|
||||||
|
FunctionTemplate::New(CreateComposite)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createGce"),
|
||||||
|
FunctionTemplate::New(CreateGce)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createFake"),
|
||||||
|
FunctionTemplate::New(CreateFake)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createIam"),
|
||||||
|
FunctionTemplate::New(CreateIam)->GetFunction());
|
||||||
|
exports->Set(NanNew("Credentials"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Credentials::HasInstance(Handle<Value> val) {
|
||||||
|
NanScope();
|
||||||
|
return NanHasInstance(fun_tpl, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
|
||||||
|
NanEscapableScope();
|
||||||
|
if (credentials == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
const int argc = 1;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
External::New(reinterpret_cast<void*>(credentials)) };
|
||||||
|
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc_credentials *Credentials::GetWrappedCredentials() {
|
||||||
|
return wrapped_credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::New) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
if (args.IsConstructCall()) {
|
||||||
|
if (!args[0]->IsExternal()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"Credentials can only be created with the provided functions");
|
||||||
|
}
|
||||||
|
grpc_credentials *creds_value = reinterpret_cast<grpc_credentials*>(
|
||||||
|
External::Unwrap(args[0]));
|
||||||
|
Credentials *credentials = new Credentials(creds_value);
|
||||||
|
credentials->Wrap(args.This());
|
||||||
|
NanReturnValue(args.This());
|
||||||
|
} else {
|
||||||
|
const int argc = 1;
|
||||||
|
Local<Value> argv[argc] = { args[0] };
|
||||||
|
NanReturnValue(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateDefault) {
|
||||||
|
NanScope();
|
||||||
|
NanReturnValue(WrapStruct(grpc_default_credentials_create()));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateSsl) {
|
||||||
|
NanScope();
|
||||||
|
char *root_certs;
|
||||||
|
char *private_key = NULL;
|
||||||
|
char *cert_chain = NULL;
|
||||||
|
int root_certs_length, private_key_length = 0, cert_chain_length = 0;
|
||||||
|
if (!Buffer::HasInstance(args[0])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSsl's first argument must be a Buffer");
|
||||||
|
}
|
||||||
|
root_certs = Buffer::Data(args[0]);
|
||||||
|
root_certs_length = Buffer::Length(args[0]);
|
||||||
|
if (Buffer::HasInstance(args[1])) {
|
||||||
|
private_key = Buffer::Data(args[1]);
|
||||||
|
private_key_length = Buffer::Length(args[1]);
|
||||||
|
} else if (!(args[1]->IsNull() || args[1]->IsUndefined())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSSl's second argument must be a Buffer if provided");
|
||||||
|
}
|
||||||
|
if (Buffer::HasInstance(args[2])) {
|
||||||
|
cert_chain = Buffer::Data(args[2]);
|
||||||
|
cert_chain_length = Buffer::Length(args[2]);
|
||||||
|
} else if (!(args[2]->IsNull() || args[2]->IsUndefined())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSSl's third argument must be a Buffer if provided");
|
||||||
|
}
|
||||||
|
NanReturnValue(WrapStruct(grpc_ssl_credentials_create(
|
||||||
|
reinterpret_cast<unsigned char*>(root_certs), root_certs_length,
|
||||||
|
reinterpret_cast<unsigned char*>(private_key), private_key_length,
|
||||||
|
reinterpret_cast<unsigned char*>(cert_chain), cert_chain_length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateComposite) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args[0])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createComposite's first argument must be a Credentials object");
|
||||||
|
}
|
||||||
|
if (!HasInstance(args[1])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createComposite's second argument must be a Credentials object");
|
||||||
|
}
|
||||||
|
Credentials *creds1 = ObjectWrap::Unwrap<Credentials>(args[0]->ToObject());
|
||||||
|
Credentials *creds2 = ObjectWrap::Unwrap<Credentials>(args[1]->ToObject());
|
||||||
|
NanReturnValue(WrapStruct(grpc_composite_credentials_create(
|
||||||
|
creds1->wrapped_credentials, creds2->wrapped_credentials)));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateGce) {
|
||||||
|
NanScope();
|
||||||
|
NanReturnValue(WrapStruct(grpc_compute_engine_credentials_create()));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateFake) {
|
||||||
|
NanScope();
|
||||||
|
NanReturnValue(WrapStruct(grpc_fake_transport_security_credentials_create()));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Credentials::CreateIam) {
|
||||||
|
NanScope();
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createIam's first argument must be a string");
|
||||||
|
}
|
||||||
|
if (!args[1]->IsString()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createIam's second argument must be a string");
|
||||||
|
}
|
||||||
|
NanUtf8String auth_token(args[0]);
|
||||||
|
NanUtf8String auth_selector(args[1]);
|
||||||
|
NanReturnValue(WrapStruct(grpc_iam_credentials_create(*auth_token,
|
||||||
|
*auth_selector)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,48 @@
|
||||||
|
#ifndef NET_GRPC_NODE_CREDENTIALS_H_
|
||||||
|
#define NET_GRPC_NODE_CREDENTIALS_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Wrapper class for grpc_credentials structs */
|
||||||
|
class Credentials : public ::node::ObjectWrap {
|
||||||
|
public:
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
static bool HasInstance(v8::Handle<v8::Value> val);
|
||||||
|
/* Wrap a grpc_credentials struct in a javascript object */
|
||||||
|
static v8::Handle<v8::Value> WrapStruct(grpc_credentials *credentials);
|
||||||
|
|
||||||
|
/* Returns the grpc_credentials struct that this object wraps */
|
||||||
|
grpc_credentials *GetWrappedCredentials();
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Credentials(grpc_credentials *credentials);
|
||||||
|
~Credentials();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
Credentials(const Credentials&);
|
||||||
|
Credentials& operator=(const Credentials&);
|
||||||
|
|
||||||
|
static NAN_METHOD(New);
|
||||||
|
static NAN_METHOD(CreateDefault);
|
||||||
|
static NAN_METHOD(CreateSsl);
|
||||||
|
static NAN_METHOD(CreateComposite);
|
||||||
|
static NAN_METHOD(CreateGce);
|
||||||
|
static NAN_METHOD(CreateFake);
|
||||||
|
static NAN_METHOD(CreateIam);
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
// Used for typechecking instances of this javascript class
|
||||||
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
grpc_credentials *wrapped_credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_CREDENTIALS_H_
|
|
@ -0,0 +1,132 @@
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "byte_buffer.h"
|
||||||
|
#include "call.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "tag.h"
|
||||||
|
#include "timeval.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using v8::Array;
|
||||||
|
using v8::Date;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Number;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Handle<Value> GetEventData(grpc_event *event) {
|
||||||
|
NanEscapableScope();
|
||||||
|
size_t count;
|
||||||
|
grpc_metadata *items;
|
||||||
|
Handle<Array> metadata;
|
||||||
|
Handle<Object> status;
|
||||||
|
Handle<Object> rpc_new;
|
||||||
|
switch (event->type) {
|
||||||
|
case GRPC_READ:
|
||||||
|
return NanEscapeScope(ByteBufferToBuffer(event->data.read));
|
||||||
|
case GRPC_INVOKE_ACCEPTED:
|
||||||
|
return NanEscapeScope(NanNew<Number>(event->data.invoke_accepted));
|
||||||
|
case GRPC_WRITE_ACCEPTED:
|
||||||
|
return NanEscapeScope(NanNew<Number>(event->data.write_accepted));
|
||||||
|
case GRPC_FINISH_ACCEPTED:
|
||||||
|
return NanEscapeScope(NanNew<Number>(event->data.finish_accepted));
|
||||||
|
case GRPC_CLIENT_METADATA_READ:
|
||||||
|
count = event->data.client_metadata_read.count;
|
||||||
|
items = event->data.client_metadata_read.elements;
|
||||||
|
metadata = NanNew<Array>(static_cast<int>(count));
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
Handle<Object> item_obj = NanNew<Object>();
|
||||||
|
item_obj->Set(NanNew<String, const char *>("key"),
|
||||||
|
NanNew<String, char *>(items[i].key));
|
||||||
|
item_obj->Set(NanNew<String, const char *>("value"),
|
||||||
|
NanNew<String, char *>(
|
||||||
|
items[i].value,
|
||||||
|
static_cast<int>(items[i].value_length)));
|
||||||
|
metadata->Set(i, item_obj);
|
||||||
|
}
|
||||||
|
return NanEscapeScope(metadata);
|
||||||
|
case GRPC_FINISHED:
|
||||||
|
status = NanNew<Object>();
|
||||||
|
status->Set(NanNew("code"), NanNew<Number>(
|
||||||
|
event->data.finished.status));
|
||||||
|
if (event->data.finished.details != NULL) {
|
||||||
|
status->Set(NanNew("details"), String::New(
|
||||||
|
event->data.finished.details));
|
||||||
|
}
|
||||||
|
count = event->data.finished.metadata_count;
|
||||||
|
items = event->data.finished.metadata_elements;
|
||||||
|
metadata = NanNew<Array>(static_cast<int>(count));
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
Handle<Object> item_obj = NanNew<Object>();
|
||||||
|
item_obj->Set(NanNew<String, const char *>("key"),
|
||||||
|
NanNew<String, char *>(items[i].key));
|
||||||
|
item_obj->Set(NanNew<String, const char *>("value"),
|
||||||
|
NanNew<String, char *>(
|
||||||
|
items[i].value,
|
||||||
|
static_cast<int>(items[i].value_length)));
|
||||||
|
metadata->Set(i, item_obj);
|
||||||
|
}
|
||||||
|
status->Set(NanNew("metadata"), metadata);
|
||||||
|
return NanEscapeScope(status);
|
||||||
|
case GRPC_SERVER_RPC_NEW:
|
||||||
|
rpc_new = NanNew<Object>();
|
||||||
|
if (event->data.server_rpc_new.method == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
rpc_new->Set(NanNew<String, const char *>("method"),
|
||||||
|
NanNew<String, const char *>(
|
||||||
|
event->data.server_rpc_new.method));
|
||||||
|
rpc_new->Set(NanNew<String, const char *>("host"),
|
||||||
|
NanNew<String, const char *>(
|
||||||
|
event->data.server_rpc_new.host));
|
||||||
|
rpc_new->Set(NanNew<String, const char *>("absolute_deadline"),
|
||||||
|
NanNew<Date>(TimespecToMilliseconds(
|
||||||
|
event->data.server_rpc_new.deadline)));
|
||||||
|
count = event->data.server_rpc_new.metadata_count;
|
||||||
|
items = event->data.server_rpc_new.metadata_elements;
|
||||||
|
metadata = NanNew<Array>(static_cast<int>(count));
|
||||||
|
for (unsigned int i = 0; i < count; i++) {
|
||||||
|
Handle<Object> item_obj = Object::New();
|
||||||
|
item_obj->Set(NanNew<String, const char *>("key"),
|
||||||
|
NanNew<String, char *>(items[i].key));
|
||||||
|
item_obj->Set(NanNew<String, const char *>("value"),
|
||||||
|
NanNew<String, char *>(
|
||||||
|
items[i].value,
|
||||||
|
static_cast<int>(items[i].value_length)));
|
||||||
|
metadata->Set(i, item_obj);
|
||||||
|
}
|
||||||
|
rpc_new->Set(NanNew<String, const char *>("metadata"), metadata);
|
||||||
|
return NanEscapeScope(rpc_new);
|
||||||
|
default:
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CreateEventObject(grpc_event *event) {
|
||||||
|
NanEscapableScope();
|
||||||
|
if (event == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
Handle<Object> event_obj = NanNew<Object>();
|
||||||
|
Handle<Value> call;
|
||||||
|
if (TagHasCall(event->tag)) {
|
||||||
|
call = TagGetCall(event->tag);
|
||||||
|
} else {
|
||||||
|
call = Call::WrapStruct(event->call);
|
||||||
|
}
|
||||||
|
event_obj->Set(NanNew<String, const char *>("call"), call);
|
||||||
|
event_obj->Set(NanNew<String, const char *>("type"),
|
||||||
|
NanNew<Number>(event->type));
|
||||||
|
event_obj->Set(NanNew<String, const char *>("data"), GetEventData(event));
|
||||||
|
|
||||||
|
return NanEscapeScope(event_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef NET_GRPC_NODE_EVENT_H_
|
||||||
|
#define NET_GRPC_NODE_EVENT_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
v8::Handle<v8::Value> CreateEventObject(grpc_event *event);
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_EVENT_H_
|
|
@ -0,0 +1,25 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package math;
|
||||||
|
|
||||||
|
message DivArgs {
|
||||||
|
required int64 dividend = 1;
|
||||||
|
required int64 divisor = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DivReply {
|
||||||
|
required int64 quotient = 1;
|
||||||
|
required int64 remainder = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FibArgs {
|
||||||
|
optional int64 limit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Num {
|
||||||
|
required int64 num = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FibReply {
|
||||||
|
required int64 count = 1;
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var ProtoBuf = require('protobufjs');
|
||||||
|
var fs = require('fs');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var Transform = require('stream').Transform;
|
||||||
|
|
||||||
|
var builder = ProtoBuf.loadProtoFile(__dirname + '/math.proto');
|
||||||
|
var math = builder.build('math');
|
||||||
|
|
||||||
|
var makeConstructor = require('../surface_server.js').makeServerConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that deserializes a specific type of protobuf.
|
||||||
|
* @param {function()} cls The constructor of the message type to deserialize
|
||||||
|
* @return {function(Buffer):cls} The deserialization function
|
||||||
|
*/
|
||||||
|
function deserializeCls(cls) {
|
||||||
|
/**
|
||||||
|
* Deserialize a buffer to a message object
|
||||||
|
* @param {Buffer} arg_buf The buffer to deserialize
|
||||||
|
* @return {cls} The resulting object
|
||||||
|
*/
|
||||||
|
return function deserialize(arg_buf) {
|
||||||
|
return cls.decode(arg_buf);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that serializes objects to a buffer by protobuf class.
|
||||||
|
* @param {function()} Cls The constructor of the message type to serialize
|
||||||
|
* @return {function(Cls):Buffer} The serialization function
|
||||||
|
*/
|
||||||
|
function serializeCls(Cls) {
|
||||||
|
/**
|
||||||
|
* Serialize an object to a Buffer
|
||||||
|
* @param {Object} arg The object to serialize
|
||||||
|
* @return {Buffer} The serialized object
|
||||||
|
*/
|
||||||
|
return function serialize(arg) {
|
||||||
|
return new Buffer(new Cls(arg).encode().toBuffer());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function call creates a server constructor for servers that that expose
|
||||||
|
* the four specified methods. This specifies how to serialize messages that the
|
||||||
|
* server sends and deserialize messages that the client sends, and whether the
|
||||||
|
* client or the server will send a stream of messages, for each method. This
|
||||||
|
* also specifies a prefix that will be added to method names when sending them
|
||||||
|
* on the wire. This function call and all of the preceding code in this file
|
||||||
|
* are intended to approximate what the generated code will look like for the
|
||||||
|
* math service */
|
||||||
|
var Server = makeConstructor({
|
||||||
|
Div: {
|
||||||
|
serialize: serializeCls(math.DivReply),
|
||||||
|
deserialize: deserializeCls(math.DivArgs),
|
||||||
|
client_stream: false,
|
||||||
|
server_stream: false
|
||||||
|
},
|
||||||
|
Fib: {
|
||||||
|
serialize: serializeCls(math.Num),
|
||||||
|
deserialize: deserializeCls(math.FibArgs),
|
||||||
|
client_stream: false,
|
||||||
|
server_stream: true
|
||||||
|
},
|
||||||
|
Sum: {
|
||||||
|
serialize: serializeCls(math.Num),
|
||||||
|
deserialize: deserializeCls(math.Num),
|
||||||
|
client_stream: true,
|
||||||
|
server_stream: false
|
||||||
|
},
|
||||||
|
DivMany: {
|
||||||
|
serialize: serializeCls(math.DivReply),
|
||||||
|
deserialize: deserializeCls(math.DivArgs),
|
||||||
|
client_stream: true,
|
||||||
|
server_stream: true
|
||||||
|
}
|
||||||
|
}, '/Math/');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server function for division. Provides the /Math/DivMany and /Math/Div
|
||||||
|
* functions (Div is just DivMany with only one stream element). For each
|
||||||
|
* DivArgs parameter, responds with a DivReply with the results of the division
|
||||||
|
* @param {Object} call The object containing request and cancellation info
|
||||||
|
* @param {function(Error, *)} cb Response callback
|
||||||
|
*/
|
||||||
|
function mathDiv(call, cb) {
|
||||||
|
var req = call.request;
|
||||||
|
if (req.divisor == 0) {
|
||||||
|
cb(new Error('cannot divide by zero'));
|
||||||
|
}
|
||||||
|
cb(null, {
|
||||||
|
quotient: req.dividend / req.divisor,
|
||||||
|
remainder: req.dividend % req.divisor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server function for Fibonacci numbers. Provides the /Math/Fib function. Reads
|
||||||
|
* a single parameter that indicates the number of responses, and then responds
|
||||||
|
* with a stream of that many Fibonacci numbers.
|
||||||
|
* @param {stream} stream The stream for sending responses.
|
||||||
|
*/
|
||||||
|
function mathFib(stream) {
|
||||||
|
// Here, call is a standard writable Node object Stream
|
||||||
|
var previous = 0, current = 1;
|
||||||
|
for (var i = 0; i < stream.request.limit; i++) {
|
||||||
|
stream.write({num: current});
|
||||||
|
var temp = current;
|
||||||
|
current += previous;
|
||||||
|
previous = temp;
|
||||||
|
}
|
||||||
|
stream.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server function for summation. Provides the /Math/Sum function. Reads a
|
||||||
|
* stream of number parameters, then responds with their sum.
|
||||||
|
* @param {stream} call The stream of arguments.
|
||||||
|
* @param {function(Error, *)} cb Response callback
|
||||||
|
*/
|
||||||
|
function mathSum(call, cb) {
|
||||||
|
// Here, call is a standard readable Node object Stream
|
||||||
|
var sum = 0;
|
||||||
|
call.on('data', function(data) {
|
||||||
|
sum += data.num | 0;
|
||||||
|
});
|
||||||
|
call.on('end', function() {
|
||||||
|
cb(null, {num: sum});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mathDivMany(stream) {
|
||||||
|
// Here, call is a standard duplex Node object Stream
|
||||||
|
util.inherits(DivTransform, Transform);
|
||||||
|
function DivTransform() {
|
||||||
|
var options = {objectMode: true};
|
||||||
|
Transform.call(this, options);
|
||||||
|
}
|
||||||
|
DivTransform.prototype._transform = function(div_args, encoding, callback) {
|
||||||
|
if (div_args.divisor == 0) {
|
||||||
|
callback(new Error('cannot divide by zero'));
|
||||||
|
}
|
||||||
|
callback(null, {
|
||||||
|
quotient: div_args.dividend / div_args.divisor,
|
||||||
|
remainder: div_args.dividend % div_args.divisor
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var transform = new DivTransform();
|
||||||
|
stream.pipe(transform);
|
||||||
|
transform.pipe(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var server = new Server({
|
||||||
|
Div: mathDiv,
|
||||||
|
Fib: mathFib,
|
||||||
|
Sum: mathSum,
|
||||||
|
DivMany: mathDivMany
|
||||||
|
});
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
server.bind('localhost:7070').listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for server
|
||||||
|
*/
|
||||||
|
module.exports = server;
|
|
@ -0,0 +1,151 @@
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include <v8.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
#include "call.h"
|
||||||
|
#include "channel.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "completion_queue_async_worker.h"
|
||||||
|
#include "credentials.h"
|
||||||
|
#include "server_credentials.h"
|
||||||
|
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::Value;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Uint32;
|
||||||
|
using v8::String;
|
||||||
|
|
||||||
|
void InitStatusConstants(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Handle<Object> status = Object::New();
|
||||||
|
exports->Set(NanNew("status"), status);
|
||||||
|
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_STATUS_OK));
|
||||||
|
status->Set(NanNew("OK"), OK);
|
||||||
|
Handle<Value> CANCELLED(NanNew<Uint32, uint32_t>(GRPC_STATUS_CANCELLED));
|
||||||
|
status->Set(NanNew("CANCELLED"), CANCELLED);
|
||||||
|
Handle<Value> UNKNOWN(NanNew<Uint32, uint32_t>(GRPC_STATUS_UNKNOWN));
|
||||||
|
status->Set(NanNew("UNKNOWN"), UNKNOWN);
|
||||||
|
Handle<Value> INVALID_ARGUMENT(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_INVALID_ARGUMENT));
|
||||||
|
status->Set(NanNew("INVALID_ARGUMENT"), INVALID_ARGUMENT);
|
||||||
|
Handle<Value> DEADLINE_EXCEEDED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_DEADLINE_EXCEEDED));
|
||||||
|
status->Set(NanNew("DEADLINE_EXCEEDED"), DEADLINE_EXCEEDED);
|
||||||
|
Handle<Value> NOT_FOUND(NanNew<Uint32, uint32_t>(GRPC_STATUS_NOT_FOUND));
|
||||||
|
status->Set(NanNew("NOT_FOUND"), NOT_FOUND);
|
||||||
|
Handle<Value> ALREADY_EXISTS(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_ALREADY_EXISTS));
|
||||||
|
status->Set(NanNew("ALREADY_EXISTS"), ALREADY_EXISTS);
|
||||||
|
Handle<Value> PERMISSION_DENIED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_PERMISSION_DENIED));
|
||||||
|
status->Set(NanNew("PERMISSION_DENIED"), PERMISSION_DENIED);
|
||||||
|
Handle<Value> UNAUTHENTICATED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_UNAUTHENTICATED));
|
||||||
|
status->Set(NanNew("UNAUTHENTICATED"), UNAUTHENTICATED);
|
||||||
|
Handle<Value> RESOURCE_EXHAUSTED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_RESOURCE_EXHAUSTED));
|
||||||
|
status->Set(NanNew("RESOURCE_EXHAUSTED"), RESOURCE_EXHAUSTED);
|
||||||
|
Handle<Value> FAILED_PRECONDITION(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_FAILED_PRECONDITION));
|
||||||
|
status->Set(NanNew("FAILED_PRECONDITION"), FAILED_PRECONDITION);
|
||||||
|
Handle<Value> ABORTED(NanNew<Uint32, uint32_t>(GRPC_STATUS_ABORTED));
|
||||||
|
status->Set(NanNew("ABORTED"), ABORTED);
|
||||||
|
Handle<Value> OUT_OF_RANGE(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_OUT_OF_RANGE));
|
||||||
|
status->Set(NanNew("OUT_OF_RANGE"), OUT_OF_RANGE);
|
||||||
|
Handle<Value> UNIMPLEMENTED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_STATUS_UNIMPLEMENTED));
|
||||||
|
status->Set(NanNew("UNIMPLEMENTED"), UNIMPLEMENTED);
|
||||||
|
Handle<Value> INTERNAL(NanNew<Uint32, uint32_t>(GRPC_STATUS_INTERNAL));
|
||||||
|
status->Set(NanNew("INTERNAL"), INTERNAL);
|
||||||
|
Handle<Value> UNAVAILABLE(NanNew<Uint32, uint32_t>(GRPC_STATUS_UNAVAILABLE));
|
||||||
|
status->Set(NanNew("UNAVAILABLE"), UNAVAILABLE);
|
||||||
|
Handle<Value> DATA_LOSS(NanNew<Uint32, uint32_t>(GRPC_STATUS_DATA_LOSS));
|
||||||
|
status->Set(NanNew("DATA_LOSS"), DATA_LOSS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitCallErrorConstants(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Handle<Object> call_error = Object::New();
|
||||||
|
exports->Set(NanNew("callError"), call_error);
|
||||||
|
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_CALL_OK));
|
||||||
|
call_error->Set(NanNew("OK"), OK);
|
||||||
|
Handle<Value> ERROR(NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR));
|
||||||
|
call_error->Set(NanNew("ERROR"), ERROR);
|
||||||
|
Handle<Value> NOT_ON_SERVER(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_ON_SERVER));
|
||||||
|
call_error->Set(NanNew("NOT_ON_SERVER"), NOT_ON_SERVER);
|
||||||
|
Handle<Value> NOT_ON_CLIENT(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_ON_CLIENT));
|
||||||
|
call_error->Set(NanNew("NOT_ON_CLIENT"), NOT_ON_CLIENT);
|
||||||
|
Handle<Value> ALREADY_INVOKED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_ALREADY_INVOKED));
|
||||||
|
call_error->Set(NanNew("ALREADY_INVOKED"), ALREADY_INVOKED);
|
||||||
|
Handle<Value> NOT_INVOKED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_NOT_INVOKED));
|
||||||
|
call_error->Set(NanNew("NOT_INVOKED"), NOT_INVOKED);
|
||||||
|
Handle<Value> ALREADY_FINISHED(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_ALREADY_FINISHED));
|
||||||
|
call_error->Set(NanNew("ALREADY_FINISHED"), ALREADY_FINISHED);
|
||||||
|
Handle<Value> TOO_MANY_OPERATIONS(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_TOO_MANY_OPERATIONS));
|
||||||
|
call_error->Set(NanNew("TOO_MANY_OPERATIONS"),
|
||||||
|
TOO_MANY_OPERATIONS);
|
||||||
|
Handle<Value> INVALID_FLAGS(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CALL_ERROR_INVALID_FLAGS));
|
||||||
|
call_error->Set(NanNew("INVALID_FLAGS"), INVALID_FLAGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitOpErrorConstants(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Handle<Object> op_error = Object::New();
|
||||||
|
exports->Set(NanNew("opError"), op_error);
|
||||||
|
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_OP_OK));
|
||||||
|
op_error->Set(NanNew("OK"), OK);
|
||||||
|
Handle<Value> ERROR(NanNew<Uint32, uint32_t>(GRPC_OP_ERROR));
|
||||||
|
op_error->Set(NanNew("ERROR"), ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitCompletionTypeConstants(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Handle<Object> completion_type = Object::New();
|
||||||
|
exports->Set(NanNew("completionType"), completion_type);
|
||||||
|
Handle<Value> QUEUE_SHUTDOWN(NanNew<Uint32, uint32_t>(GRPC_QUEUE_SHUTDOWN));
|
||||||
|
completion_type->Set(NanNew("QUEUE_SHUTDOWN"), QUEUE_SHUTDOWN);
|
||||||
|
Handle<Value> READ(NanNew<Uint32, uint32_t>(GRPC_READ));
|
||||||
|
completion_type->Set(NanNew("READ"), READ);
|
||||||
|
Handle<Value> INVOKE_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_INVOKE_ACCEPTED));
|
||||||
|
completion_type->Set(NanNew("INVOKE_ACCEPTED"), INVOKE_ACCEPTED);
|
||||||
|
Handle<Value> WRITE_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_WRITE_ACCEPTED));
|
||||||
|
completion_type->Set(NanNew("WRITE_ACCEPTED"), WRITE_ACCEPTED);
|
||||||
|
Handle<Value> FINISH_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_FINISH_ACCEPTED));
|
||||||
|
completion_type->Set(NanNew("FINISH_ACCEPTED"), FINISH_ACCEPTED);
|
||||||
|
Handle<Value> CLIENT_METADATA_READ(
|
||||||
|
NanNew<Uint32, uint32_t>(GRPC_CLIENT_METADATA_READ));
|
||||||
|
completion_type->Set(NanNew("CLIENT_METADATA_READ"),
|
||||||
|
CLIENT_METADATA_READ);
|
||||||
|
Handle<Value> FINISHED(NanNew<Uint32, uint32_t>(GRPC_FINISHED));
|
||||||
|
completion_type->Set(NanNew("FINISHED"), FINISHED);
|
||||||
|
Handle<Value> SERVER_RPC_NEW(NanNew<Uint32, uint32_t>(GRPC_SERVER_RPC_NEW));
|
||||||
|
completion_type->Set(NanNew("SERVER_RPC_NEW"), SERVER_RPC_NEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
grpc_init();
|
||||||
|
InitStatusConstants(exports);
|
||||||
|
InitCallErrorConstants(exports);
|
||||||
|
InitOpErrorConstants(exports);
|
||||||
|
InitCompletionTypeConstants(exports);
|
||||||
|
|
||||||
|
grpc::node::Call::Init(exports);
|
||||||
|
grpc::node::Channel::Init(exports);
|
||||||
|
grpc::node::Server::Init(exports);
|
||||||
|
grpc::node::CompletionQueueAsyncWorker::Init(exports);
|
||||||
|
grpc::node::Credentials::Init(exports);
|
||||||
|
grpc::node::ServerCredentials::Init(exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_MODULE(grpc, init)
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "grpc",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "gRPC Library for Node",
|
||||||
|
"scripts": {
|
||||||
|
"test": "./node_modules/mocha/bin/mocha"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.2.1",
|
||||||
|
"nan": "~1.3.0",
|
||||||
|
"underscore": "^1.7.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "~1.21.0",
|
||||||
|
"highland": "~2.0.0",
|
||||||
|
"protobufjs": "~3.8.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
var net = require('net');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a free port that a server can bind to, in the format
|
||||||
|
* "address:port"
|
||||||
|
* @param {function(string)} cb The callback that should execute when the port
|
||||||
|
* is available
|
||||||
|
*/
|
||||||
|
function nextAvailablePort(cb) {
|
||||||
|
var server = net.createServer();
|
||||||
|
server.listen(function() {
|
||||||
|
var address = server.address();
|
||||||
|
server.close(function() {
|
||||||
|
cb(address.address + ':' + address.port.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.nextAvailablePort = nextAvailablePort;
|
|
@ -0,0 +1,212 @@
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
#include "call.h"
|
||||||
|
#include "completion_queue_async_worker.h"
|
||||||
|
#include "tag.h"
|
||||||
|
#include "server_credentials.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using v8::Arguments;
|
||||||
|
using v8::Array;
|
||||||
|
using v8::Boolean;
|
||||||
|
using v8::Exception;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Number;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Persistent<Function> Server::constructor;
|
||||||
|
Persistent<FunctionTemplate> Server::fun_tpl;
|
||||||
|
|
||||||
|
Server::Server(grpc_server *server) : wrapped_server(server) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Server::~Server() {
|
||||||
|
grpc_server_destroy(wrapped_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(String::NewSymbol("Server"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
NanSetPrototypeTemplate(tpl, "requestCall",
|
||||||
|
FunctionTemplate::New(RequestCall)->GetFunction());
|
||||||
|
|
||||||
|
NanSetPrototypeTemplate(tpl, "addHttp2Port",
|
||||||
|
FunctionTemplate::New(AddHttp2Port)->GetFunction());
|
||||||
|
|
||||||
|
NanSetPrototypeTemplate(tpl, "addSecureHttp2Port",
|
||||||
|
FunctionTemplate::New(
|
||||||
|
AddSecureHttp2Port)->GetFunction());
|
||||||
|
|
||||||
|
NanSetPrototypeTemplate(tpl, "start",
|
||||||
|
FunctionTemplate::New(Start)->GetFunction());
|
||||||
|
|
||||||
|
NanSetPrototypeTemplate(tpl, "shutdown",
|
||||||
|
FunctionTemplate::New(Shutdown)->GetFunction());
|
||||||
|
|
||||||
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
|
NanAssignPersistent(constructor, tpl->GetFunction());
|
||||||
|
exports->Set(String::NewSymbol("Server"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server::HasInstance(Handle<Value> val) {
|
||||||
|
return NanHasInstance(fun_tpl, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::New) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
/* If this is not a constructor call, make a constructor call and return
|
||||||
|
the result */
|
||||||
|
if (!args.IsConstructCall()) {
|
||||||
|
const int argc = 1;
|
||||||
|
Local<Value> argv[argc] = { args[0] };
|
||||||
|
NanReturnValue(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
grpc_server *wrapped_server;
|
||||||
|
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
|
||||||
|
if (args[0]->IsUndefined()) {
|
||||||
|
wrapped_server = grpc_server_create(queue, NULL);
|
||||||
|
} else if (args[0]->IsObject()) {
|
||||||
|
grpc_server_credentials *creds = NULL;
|
||||||
|
Handle<Object> args_hash(args[0]->ToObject()->Clone());
|
||||||
|
if (args_hash->HasOwnProperty(NanNew("credentials"))) {
|
||||||
|
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
|
||||||
|
if (!ServerCredentials::HasInstance(creds_value)) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"credentials arg must be a ServerCredentials object");
|
||||||
|
}
|
||||||
|
ServerCredentials *creds_object = ObjectWrap::Unwrap<ServerCredentials>(
|
||||||
|
creds_value->ToObject());
|
||||||
|
creds = creds_object->GetWrappedServerCredentials();
|
||||||
|
args_hash->Delete(NanNew("credentials"));
|
||||||
|
}
|
||||||
|
Handle<Array> keys(args_hash->GetOwnPropertyNames());
|
||||||
|
grpc_channel_args channel_args;
|
||||||
|
channel_args.num_args = keys->Length();
|
||||||
|
channel_args.args = reinterpret_cast<grpc_arg*>(
|
||||||
|
calloc(channel_args.num_args, sizeof(grpc_arg)));
|
||||||
|
/* These are used to keep all strings until then end of the block, then
|
||||||
|
destroy them */
|
||||||
|
std::vector<NanUtf8String*> key_strings(keys->Length());
|
||||||
|
std::vector<NanUtf8String*> value_strings(keys->Length());
|
||||||
|
for (unsigned int i = 0; i < channel_args.num_args; i++) {
|
||||||
|
Handle<String> current_key(keys->Get(i)->ToString());
|
||||||
|
Handle<Value> current_value(args_hash->Get(current_key));
|
||||||
|
key_strings[i] = new NanUtf8String(current_key);
|
||||||
|
channel_args.args[i].key = **key_strings[i];
|
||||||
|
if (current_value->IsInt32()) {
|
||||||
|
channel_args.args[i].type = GRPC_ARG_INTEGER;
|
||||||
|
channel_args.args[i].value.integer = current_value->Int32Value();
|
||||||
|
} else if (current_value->IsString()) {
|
||||||
|
channel_args.args[i].type = GRPC_ARG_STRING;
|
||||||
|
value_strings[i] = new NanUtf8String(current_value);
|
||||||
|
channel_args.args[i].value.string = **value_strings[i];
|
||||||
|
} else {
|
||||||
|
free(channel_args.args);
|
||||||
|
return NanThrowTypeError("Arg values must be strings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (creds == NULL) {
|
||||||
|
wrapped_server = grpc_server_create(queue,
|
||||||
|
&channel_args);
|
||||||
|
} else {
|
||||||
|
wrapped_server = grpc_secure_server_create(creds,
|
||||||
|
queue,
|
||||||
|
&channel_args);
|
||||||
|
}
|
||||||
|
free(channel_args.args);
|
||||||
|
} else {
|
||||||
|
return NanThrowTypeError("Server expects an object");
|
||||||
|
}
|
||||||
|
Server *server = new Server(wrapped_server);
|
||||||
|
server->Wrap(args.This());
|
||||||
|
NanReturnValue(args.This());
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::RequestCall) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("requestCall can only be called on a Server");
|
||||||
|
}
|
||||||
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
grpc_call_error error = grpc_server_request_call(
|
||||||
|
server->wrapped_server,
|
||||||
|
CreateTag(args[0], NanNull()));
|
||||||
|
if (error == GRPC_CALL_OK) {
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
|
} else {
|
||||||
|
return NanThrowError("requestCall failed", error);
|
||||||
|
}
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::AddHttp2Port) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("addHttp2Port can only be called on a Server");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
return NanThrowTypeError("addHttp2Port's argument must be a String");
|
||||||
|
}
|
||||||
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
NanReturnValue(NanNew<Boolean>(grpc_server_add_http2_port(
|
||||||
|
server->wrapped_server,
|
||||||
|
*NanUtf8String(args[0]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::AddSecureHttp2Port) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"addSecureHttp2Port can only be called on a Server");
|
||||||
|
}
|
||||||
|
if (!args[0]->IsString()) {
|
||||||
|
return NanThrowTypeError("addSecureHttp2Port's argument must be a String");
|
||||||
|
}
|
||||||
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
NanReturnValue(NanNew<Boolean>(grpc_server_add_secure_http2_port(
|
||||||
|
server->wrapped_server,
|
||||||
|
*NanUtf8String(args[0]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::Start) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("start can only be called on a Server");
|
||||||
|
}
|
||||||
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
grpc_server_start(server->wrapped_server);
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(Server::Shutdown) {
|
||||||
|
NanScope();
|
||||||
|
if (!HasInstance(args.This())) {
|
||||||
|
return NanThrowTypeError("shutdown can only be called on a Server");
|
||||||
|
}
|
||||||
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
grpc_server_shutdown(server->wrapped_server);
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,46 @@
|
||||||
|
#ifndef NET_GRPC_NODE_SERVER_H_
|
||||||
|
#define NET_GRPC_NODE_SERVER_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Wraps grpc_server as a JavaScript object. Provides a constructor
|
||||||
|
and wrapper methods for grpc_server_create, grpc_server_request_call,
|
||||||
|
grpc_server_add_http2_port, and grpc_server_start. */
|
||||||
|
class Server : public ::node::ObjectWrap {
|
||||||
|
public:
|
||||||
|
/* Initializes the Server class and exposes the constructor and
|
||||||
|
wrapper methods to JavaScript */
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
/* Tests whether the given value was constructed by this class's
|
||||||
|
JavaScript constructor */
|
||||||
|
static bool HasInstance(v8::Handle<v8::Value> val);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Server(grpc_server *server);
|
||||||
|
~Server();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
Server(const Server&);
|
||||||
|
Server& operator=(const Server&);
|
||||||
|
|
||||||
|
static NAN_METHOD(New);
|
||||||
|
static NAN_METHOD(RequestCall);
|
||||||
|
static NAN_METHOD(AddHttp2Port);
|
||||||
|
static NAN_METHOD(AddSecureHttp2Port);
|
||||||
|
static NAN_METHOD(Start);
|
||||||
|
static NAN_METHOD(Shutdown);
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
grpc_server *wrapped_server;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_SERVER_H_
|
|
@ -0,0 +1,228 @@
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
var common = require('./common');
|
||||||
|
|
||||||
|
var Duplex = require('stream').Duplex;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
util.inherits(GrpcServerStream, Duplex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC server side stream as a Node stream. Extends
|
||||||
|
* from stream.Duplex.
|
||||||
|
* @constructor
|
||||||
|
* @param {grpc.Call} call Call object to proxy
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function GrpcServerStream(call, options) {
|
||||||
|
Duplex.call(this, options);
|
||||||
|
this._call = call;
|
||||||
|
// Indicate that a status has been sent
|
||||||
|
var finished = false;
|
||||||
|
var self = this;
|
||||||
|
var status = {
|
||||||
|
'code' : grpc.status.OK,
|
||||||
|
'details' : 'OK'
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Send the pending status
|
||||||
|
*/
|
||||||
|
function sendStatus() {
|
||||||
|
call.startWriteStatus(status.code, status.details, function() {
|
||||||
|
});
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
this.on('finish', sendStatus);
|
||||||
|
/**
|
||||||
|
* Set the pending status to a given error status. If the error does not have
|
||||||
|
* code or details properties, the code will be set to grpc.status.INTERNAL
|
||||||
|
* and the details will be set to 'Unknown Error'.
|
||||||
|
* @param {Error} err The error object
|
||||||
|
*/
|
||||||
|
function setStatus(err) {
|
||||||
|
var code = grpc.status.INTERNAL;
|
||||||
|
var details = 'Unknown Error';
|
||||||
|
|
||||||
|
if (err.hasOwnProperty('code')) {
|
||||||
|
code = err.code;
|
||||||
|
if (err.hasOwnProperty('details')) {
|
||||||
|
details = err.details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status = {'code': code, 'details': details};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Terminate the call. This includes indicating that reads are done, draining
|
||||||
|
* all pending writes, and sending the given error as a status
|
||||||
|
* @param {Error} err The error object
|
||||||
|
* @this GrpcServerStream
|
||||||
|
*/
|
||||||
|
function terminateCall(err) {
|
||||||
|
// Drain readable data
|
||||||
|
this.on('data', function() {});
|
||||||
|
setStatus(err);
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
this.on('error', terminateCall);
|
||||||
|
// Indicates that a read is pending
|
||||||
|
var reading = false;
|
||||||
|
/**
|
||||||
|
* Callback to be called when a READ event is received. Pushes the data onto
|
||||||
|
* the read queue and starts reading again if applicable
|
||||||
|
* @param {grpc.Event} event READ event object
|
||||||
|
*/
|
||||||
|
function readCallback(event) {
|
||||||
|
if (finished) {
|
||||||
|
self.push(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = event.data;
|
||||||
|
if (self.push(data) && data != null) {
|
||||||
|
self._call.startRead(readCallback);
|
||||||
|
} else {
|
||||||
|
reading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Start reading if there is not already a pending read. Reading will
|
||||||
|
* continue until self.push returns false (indicating reads should slow
|
||||||
|
* down) or the read data is null (indicating that there is no more data).
|
||||||
|
*/
|
||||||
|
this.startReading = function() {
|
||||||
|
if (finished) {
|
||||||
|
self.push(null);
|
||||||
|
} else {
|
||||||
|
if (!reading) {
|
||||||
|
reading = true;
|
||||||
|
self._call.startRead(readCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start reading from the gRPC data source. This is an implementation of a
|
||||||
|
* method required for implementing stream.Readable
|
||||||
|
* @param {number} size Ignored
|
||||||
|
*/
|
||||||
|
GrpcServerStream.prototype._read = function(size) {
|
||||||
|
this.startReading();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start writing a chunk of data. This is an implementation of a method required
|
||||||
|
* for implementing stream.Writable.
|
||||||
|
* @param {Buffer} chunk The chunk of data to write
|
||||||
|
* @param {string} encoding Ignored
|
||||||
|
* @param {function(Error=)} callback Callback to indicate that the write is
|
||||||
|
* complete
|
||||||
|
*/
|
||||||
|
GrpcServerStream.prototype._write = function(chunk, encoding, callback) {
|
||||||
|
var self = this;
|
||||||
|
self._call.startWrite(chunk, function(event) {
|
||||||
|
callback();
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a server object that stores request handlers and delegates
|
||||||
|
* incoming requests to those handlers
|
||||||
|
* @constructor
|
||||||
|
* @param {Array} options Options that should be passed to the internal server
|
||||||
|
* implementation
|
||||||
|
*/
|
||||||
|
function Server(options) {
|
||||||
|
this.handlers = {};
|
||||||
|
var handlers = this.handlers;
|
||||||
|
var server = new grpc.Server(options);
|
||||||
|
this._server = server;
|
||||||
|
var started = false;
|
||||||
|
/**
|
||||||
|
* Start the server and begin handling requests
|
||||||
|
* @this Server
|
||||||
|
*/
|
||||||
|
this.start = function() {
|
||||||
|
if (this.started) {
|
||||||
|
throw 'Server is already running';
|
||||||
|
}
|
||||||
|
server.start();
|
||||||
|
/**
|
||||||
|
* Handles the SERVER_RPC_NEW event. If there is a handler associated with
|
||||||
|
* the requested method, use that handler to respond to the request. Then
|
||||||
|
* wait for the next request
|
||||||
|
* @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW
|
||||||
|
*/
|
||||||
|
function handleNewCall(event) {
|
||||||
|
var call = event.call;
|
||||||
|
var data = event.data;
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.requestCall(handleNewCall);
|
||||||
|
var handler = undefined;
|
||||||
|
var deadline = data.absolute_deadline;
|
||||||
|
var cancelled = false;
|
||||||
|
if (handlers.hasOwnProperty(data.method)) {
|
||||||
|
handler = handlers[data.method];
|
||||||
|
}
|
||||||
|
call.serverAccept(function(event) {
|
||||||
|
if (event.data.code === grpc.status.CANCELLED) {
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
call.serverEndInitialMetadata(0);
|
||||||
|
var stream = new GrpcServerStream(call);
|
||||||
|
Object.defineProperty(stream, 'cancelled', {
|
||||||
|
get: function() { return cancelled;}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
handler(stream, data.metadata);
|
||||||
|
} catch (e) {
|
||||||
|
stream.emit('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.requestCall(handleNewCall);
|
||||||
|
};
|
||||||
|
/** Shuts down the server.
|
||||||
|
*/
|
||||||
|
this.shutdown = function() {
|
||||||
|
server.shutdown();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a handler to handle the named method. Fails if there already is
|
||||||
|
* a handler for the given method. Returns true on success
|
||||||
|
* @param {string} name The name of the method that the provided function should
|
||||||
|
* handle/respond to.
|
||||||
|
* @param {function} handler Function that takes a stream of request values and
|
||||||
|
* returns a stream of response values
|
||||||
|
* @return {boolean} True if the handler was set. False if a handler was already
|
||||||
|
* set for that name.
|
||||||
|
*/
|
||||||
|
Server.prototype.register = function(name, handler) {
|
||||||
|
if (this.handlers.hasOwnProperty(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.handlers[name] = handler;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the server to the given port, with SSL enabled if secure is specified
|
||||||
|
* @param {string} port The port that the server should bind on, in the format
|
||||||
|
* "address:port"
|
||||||
|
* @param {boolean=} secure Whether the server should open a secure port
|
||||||
|
*/
|
||||||
|
Server.prototype.bind = function(port, secure) {
|
||||||
|
if (secure) {
|
||||||
|
this._server.addSecureHttp2Port(port);
|
||||||
|
} else {
|
||||||
|
this._server.addHttp2Port(port);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for Server
|
||||||
|
*/
|
||||||
|
module.exports = Server;
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include <node.h>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
#include "grpc/support/log.h"
|
||||||
|
#include "server_credentials.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using ::node::Buffer;
|
||||||
|
using v8::Arguments;
|
||||||
|
using v8::Exception;
|
||||||
|
using v8::External;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Integer;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::ObjectTemplate;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
Persistent<Function> ServerCredentials::constructor;
|
||||||
|
Persistent<FunctionTemplate> ServerCredentials::fun_tpl;
|
||||||
|
|
||||||
|
ServerCredentials::ServerCredentials(grpc_server_credentials *credentials)
|
||||||
|
: wrapped_credentials(credentials) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerCredentials::~ServerCredentials() {
|
||||||
|
gpr_log(GPR_DEBUG, "Destroying server credentials object");
|
||||||
|
grpc_server_credentials_release(wrapped_credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerCredentials::Init(Handle<Object> exports) {
|
||||||
|
NanScope();
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(NanNew("ServerCredentials"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
|
NanAssignPersistent(constructor, tpl->GetFunction());
|
||||||
|
constructor->Set(NanNew("createSsl"),
|
||||||
|
FunctionTemplate::New(CreateSsl)->GetFunction());
|
||||||
|
constructor->Set(NanNew("createFake"),
|
||||||
|
FunctionTemplate::New(CreateFake)->GetFunction());
|
||||||
|
exports->Set(NanNew("ServerCredentials"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerCredentials::HasInstance(Handle<Value> val) {
|
||||||
|
NanScope();
|
||||||
|
return NanHasInstance(fun_tpl, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> ServerCredentials::WrapStruct(
|
||||||
|
grpc_server_credentials *credentials) {
|
||||||
|
NanEscapableScope();
|
||||||
|
if (credentials == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
const int argc = 1;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
External::New(reinterpret_cast<void*>(credentials)) };
|
||||||
|
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() {
|
||||||
|
return wrapped_credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(ServerCredentials::New) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
if (args.IsConstructCall()) {
|
||||||
|
if (!args[0]->IsExternal()) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"ServerCredentials can only be created with the provide functions");
|
||||||
|
}
|
||||||
|
grpc_server_credentials *creds_value =
|
||||||
|
reinterpret_cast<grpc_server_credentials*>(External::Unwrap(args[0]));
|
||||||
|
ServerCredentials *credentials = new ServerCredentials(creds_value);
|
||||||
|
credentials->Wrap(args.This());
|
||||||
|
NanReturnValue(args.This());
|
||||||
|
} else {
|
||||||
|
const int argc = 1;
|
||||||
|
Local<Value> argv[argc] = { args[0] };
|
||||||
|
NanReturnValue(constructor->NewInstance(argc, argv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(ServerCredentials::CreateSsl) {
|
||||||
|
NanScope();
|
||||||
|
char *root_certs = NULL;
|
||||||
|
char *private_key;
|
||||||
|
char *cert_chain;
|
||||||
|
int root_certs_length = 0, private_key_length, cert_chain_length;
|
||||||
|
if (Buffer::HasInstance(args[0])) {
|
||||||
|
root_certs = Buffer::Data(args[0]);
|
||||||
|
root_certs_length = Buffer::Length(args[0]);
|
||||||
|
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSSl's first argument must be a Buffer if provided");
|
||||||
|
}
|
||||||
|
if (!Buffer::HasInstance(args[1])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSsl's second argument must be a Buffer");
|
||||||
|
}
|
||||||
|
private_key = Buffer::Data(args[1]);
|
||||||
|
private_key_length = Buffer::Length(args[1]);
|
||||||
|
if (!Buffer::HasInstance(args[2])) {
|
||||||
|
return NanThrowTypeError(
|
||||||
|
"createSsl's third argument must be a Buffer");
|
||||||
|
}
|
||||||
|
cert_chain = Buffer::Data(args[2]);
|
||||||
|
cert_chain_length = Buffer::Length(args[2]);
|
||||||
|
NanReturnValue(WrapStruct(grpc_ssl_server_credentials_create(
|
||||||
|
reinterpret_cast<unsigned char*>(root_certs), root_certs_length,
|
||||||
|
reinterpret_cast<unsigned char*>(private_key), private_key_length,
|
||||||
|
reinterpret_cast<unsigned char*>(cert_chain), cert_chain_length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_METHOD(ServerCredentials::CreateFake) {
|
||||||
|
NanScope();
|
||||||
|
NanReturnValue(WrapStruct(
|
||||||
|
grpc_fake_transport_security_server_credentials_create()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,44 @@
|
||||||
|
#ifndef NET_GRPC_NODE_SERVER_CREDENTIALS_H_
|
||||||
|
#define NET_GRPC_NODE_SERVER_CREDENTIALS_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/grpc_security.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Wrapper class for grpc_server_credentials structs */
|
||||||
|
class ServerCredentials : public ::node::ObjectWrap {
|
||||||
|
public:
|
||||||
|
static void Init(v8::Handle<v8::Object> exports);
|
||||||
|
static bool HasInstance(v8::Handle<v8::Value> val);
|
||||||
|
/* Wrap a grpc_server_credentials struct in a javascript object */
|
||||||
|
static v8::Handle<v8::Value> WrapStruct(grpc_server_credentials *credentials);
|
||||||
|
|
||||||
|
/* Returns the grpc_server_credentials struct that this object wraps */
|
||||||
|
grpc_server_credentials *GetWrappedServerCredentials();
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit ServerCredentials(grpc_server_credentials *credentials);
|
||||||
|
~ServerCredentials();
|
||||||
|
|
||||||
|
// Prevent copying
|
||||||
|
ServerCredentials(const ServerCredentials&);
|
||||||
|
ServerCredentials& operator=(const ServerCredentials&);
|
||||||
|
|
||||||
|
static NAN_METHOD(New);
|
||||||
|
static NAN_METHOD(CreateSsl);
|
||||||
|
static NAN_METHOD(CreateFake);
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
// Used for typechecking instances of this javascript class
|
||||||
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
grpc_server_credentials *wrapped_credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_SERVER_CREDENTIALS_H_
|
|
@ -0,0 +1,306 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var client = require('./client.js');
|
||||||
|
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
|
var stream = require('stream');
|
||||||
|
|
||||||
|
var Readable = stream.Readable;
|
||||||
|
var Writable = stream.Writable;
|
||||||
|
var Duplex = stream.Duplex;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
function forwardEvent(fromEmitter, toEmitter, event) {
|
||||||
|
fromEmitter.on(event, function forward() {
|
||||||
|
_.partial(toEmitter.emit, event).apply(toEmitter, arguments);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(ClientReadableObjectStream, Readable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC server streaming call as a Node stream on the
|
||||||
|
* client side. Extends from stream.Readable.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(Buffer)} deserialize Function for deserializing binary data
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ClientReadableObjectStream(stream, deserialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Readable.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
var self = this;
|
||||||
|
forwardEvent(stream, this, 'status');
|
||||||
|
forwardEvent(stream, this, 'metadata');
|
||||||
|
this._stream.on('data', function forwardData(chunk) {
|
||||||
|
if (!self.push(deserialize(chunk))) {
|
||||||
|
self._stream.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._stream.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(ClientWritableObjectStream, Writable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC client streaming call as a Node stream on the
|
||||||
|
* client side. Extends from stream.Writable.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(*):Buffer} serialize Function for serializing objects
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ClientWritableObjectStream(stream, serialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Writable.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
this._serialize = serialize;
|
||||||
|
forwardEvent(stream, this, 'status');
|
||||||
|
forwardEvent(stream, this, 'metadata');
|
||||||
|
this.on('finish', function() {
|
||||||
|
this._stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
util.inherits(ClientBidiObjectStream, Duplex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC bidi streaming call as a Node stream on the
|
||||||
|
* client side. Extends from stream.Duplex.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(*):Buffer} serialize Function for serializing objects
|
||||||
|
* @param {function(Buffer)} deserialize Function for deserializing binary data
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ClientBidiObjectStream(stream, serialize, deserialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Duplex.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
this._serialize = serialize;
|
||||||
|
var self = this;
|
||||||
|
forwardEvent(stream, this, 'status');
|
||||||
|
forwardEvent(stream, this, 'metadata');
|
||||||
|
this._stream.on('data', function forwardData(chunk) {
|
||||||
|
if (!self.push(deserialize(chunk))) {
|
||||||
|
self._stream.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._stream.pause();
|
||||||
|
this.on('finish', function() {
|
||||||
|
this._stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _read implementation for both types of streams that allow reading.
|
||||||
|
* @this {ClientReadableObjectStream|ClientBidiObjectStream}
|
||||||
|
* @param {number} size Ignored
|
||||||
|
*/
|
||||||
|
function _read(size) {
|
||||||
|
this._stream.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for _read
|
||||||
|
*/
|
||||||
|
ClientReadableObjectStream.prototype._read = _read;
|
||||||
|
/**
|
||||||
|
* See docs for _read
|
||||||
|
*/
|
||||||
|
ClientBidiObjectStream.prototype._read = _read;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _write implementation for both types of streams that allow writing
|
||||||
|
* @this {ClientWritableObjectStream|ClientBidiObjectStream}
|
||||||
|
* @param {*} chunk The value to write to the stream
|
||||||
|
* @param {string} encoding Ignored
|
||||||
|
* @param {function(Error)} callback Callback to call when finished writing
|
||||||
|
*/
|
||||||
|
function _write(chunk, encoding, callback) {
|
||||||
|
this._stream.write(this._serialize(chunk), encoding, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for _write
|
||||||
|
*/
|
||||||
|
ClientWritableObjectStream.prototype._write = _write;
|
||||||
|
/**
|
||||||
|
* See docs for _write
|
||||||
|
*/
|
||||||
|
ClientBidiObjectStream.prototype._write = _write;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param {function(Buffer)} deserialize The deserialization function for
|
||||||
|
* outputs
|
||||||
|
* @return {Function} makeUnaryRequest
|
||||||
|
*/
|
||||||
|
function makeUnaryRequestFunction(method, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Make a unary request with this method on the given channel with the given
|
||||||
|
* argument, callback, etc.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {*} argument The argument to the call. Should be serializable with
|
||||||
|
* serialize
|
||||||
|
* @param {function(?Error, value=)} callback The callback to for when the
|
||||||
|
* 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
function makeUnaryRequest(channel, argument, callback, metadata, deadline) {
|
||||||
|
var stream = client.makeRequest(channel, method, metadata, deadline);
|
||||||
|
var emitter = new EventEmitter();
|
||||||
|
forwardEvent(stream, emitter, 'status');
|
||||||
|
forwardEvent(stream, emitter, 'metadata');
|
||||||
|
stream.write(serialize(argument));
|
||||||
|
stream.end();
|
||||||
|
stream.on('data', function forwardData(chunk) {
|
||||||
|
try {
|
||||||
|
callback(null, deserialize(chunk));
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
return makeUnaryRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that can make client stream requests to the specified method.
|
||||||
|
* @param {string} method The name of the method to request
|
||||||
|
* @param {function(*):Buffer} serialize The serialization function for inputs
|
||||||
|
* @param {function(Buffer)} deserialize The deserialization function for
|
||||||
|
* outputs
|
||||||
|
* @return {Function} makeClientStreamRequest
|
||||||
|
*/
|
||||||
|
function makeClientStreamRequestFunction(method, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Make a client stream request with this method on the given channel with the
|
||||||
|
* given callback, etc.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {function(?Error, value=)} callback The callback to for when the
|
||||||
|
* 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
function makeClientStreamRequest(channel, callback, metadata, deadline) {
|
||||||
|
var stream = client.makeRequest(channel, method, metadata, deadline);
|
||||||
|
var obj_stream = new ClientWritableObjectStream(stream, serialize, {});
|
||||||
|
stream.on('data', function forwardData(chunk) {
|
||||||
|
try {
|
||||||
|
callback(null, deserialize(chunk));
|
||||||
|
} catch (e) {
|
||||||
|
callback(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return obj_stream;
|
||||||
|
}
|
||||||
|
return makeClientStreamRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that can make server stream requests to the specified method.
|
||||||
|
* @param {string} method The name of the method to request
|
||||||
|
* @param {function(*):Buffer} serialize The serialization function for inputs
|
||||||
|
* @param {function(Buffer)} deserialize The deserialization function for
|
||||||
|
* outputs
|
||||||
|
* @return {Function} makeServerStreamRequest
|
||||||
|
*/
|
||||||
|
function makeServerStreamRequestFunction(method, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Make a server stream request with this method on the given channel with the
|
||||||
|
* given argument, etc.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {*} argument The argument to the call. Should be serializable with
|
||||||
|
* 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
function makeServerStreamRequest(channel, argument, metadata, deadline) {
|
||||||
|
var stream = client.makeRequest(channel, method, metadata, deadline);
|
||||||
|
var obj_stream = new ClientReadableObjectStream(stream, deserialize, {});
|
||||||
|
stream.write(serialize(argument));
|
||||||
|
stream.end();
|
||||||
|
return obj_stream;
|
||||||
|
}
|
||||||
|
return makeServerStreamRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that can make bidirectional stream requests to the specified
|
||||||
|
* method.
|
||||||
|
* @param {string} method The name of the method to request
|
||||||
|
* @param {function(*):Buffer} serialize The serialization function for inputs
|
||||||
|
* @param {function(Buffer)} deserialize The deserialization function for
|
||||||
|
* outputs
|
||||||
|
* @return {Function} makeBidiStreamRequest
|
||||||
|
*/
|
||||||
|
function makeBidiStreamRequestFunction(method, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Make a bidirectional stream request with this method on the given channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
function makeBidiStreamRequest(channel, metadata, deadline) {
|
||||||
|
var stream = client.makeRequest(channel, method, metadata, deadline);
|
||||||
|
var obj_stream = new ClientBidiObjectStream(stream,
|
||||||
|
serialize,
|
||||||
|
deserialize,
|
||||||
|
{});
|
||||||
|
return obj_stream;
|
||||||
|
}
|
||||||
|
return makeBidiStreamRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for makeUnaryRequestFunction
|
||||||
|
*/
|
||||||
|
exports.makeUnaryRequestFunction = makeUnaryRequestFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for makeClientStreamRequestFunction
|
||||||
|
*/
|
||||||
|
exports.makeClientStreamRequestFunction = makeClientStreamRequestFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for makeServerStreamRequestFunction
|
||||||
|
*/
|
||||||
|
exports.makeServerStreamRequestFunction = makeServerStreamRequestFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for makeBidiStreamRequestFunction
|
||||||
|
*/
|
||||||
|
exports.makeBidiStreamRequestFunction = makeBidiStreamRequestFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for client.Channel
|
||||||
|
*/
|
||||||
|
exports.Channel = client.Channel;
|
||||||
|
/**
|
||||||
|
* See docs for client.status
|
||||||
|
*/
|
||||||
|
exports.status = client.status;
|
||||||
|
/**
|
||||||
|
* See docs for client.callError
|
||||||
|
*/
|
||||||
|
exports.callError = client.callError;
|
|
@ -0,0 +1,325 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var Server = require('./server.js');
|
||||||
|
|
||||||
|
var stream = require('stream');
|
||||||
|
|
||||||
|
var Readable = stream.Readable;
|
||||||
|
var Writable = stream.Writable;
|
||||||
|
var Duplex = stream.Duplex;
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
util.inherits(ServerReadableObjectStream, Readable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC client streaming call as a Node stream on the
|
||||||
|
* server side. Extends from stream.Readable.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(Buffer)} deserialize Function for deserializing binary data
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ServerReadableObjectStream(stream, deserialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Readable.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
Object.defineProperty(this, 'cancelled', {
|
||||||
|
get: function() { return stream.cancelled; }
|
||||||
|
});
|
||||||
|
var self = this;
|
||||||
|
this._stream.on('data', function forwardData(chunk) {
|
||||||
|
if (!self.push(deserialize(chunk))) {
|
||||||
|
self._stream.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._stream.on('end', function forwardEnd() {
|
||||||
|
self.push(null);
|
||||||
|
});
|
||||||
|
this._stream.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(ServerWritableObjectStream, Writable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC server streaming call as a Node stream on the
|
||||||
|
* server side. Extends from stream.Writable.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(*):Buffer} serialize Function for serializing objects
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ServerWritableObjectStream(stream, serialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Writable.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
this._serialize = serialize;
|
||||||
|
this.on('finish', function() {
|
||||||
|
this._stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(ServerBidiObjectStream, Duplex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for representing a gRPC bidi streaming call as a Node stream on the
|
||||||
|
* server side. Extends from stream.Duplex.
|
||||||
|
* @constructor
|
||||||
|
* @param {stream} stream Underlying binary Duplex stream for the call
|
||||||
|
* @param {function(*):Buffer} serialize Function for serializing objects
|
||||||
|
* @param {function(Buffer)} deserialize Function for deserializing binary data
|
||||||
|
* @param {object} options Stream options
|
||||||
|
*/
|
||||||
|
function ServerBidiObjectStream(stream, serialize, deserialize, options) {
|
||||||
|
options = _.extend(options, {objectMode: true});
|
||||||
|
Duplex.call(this, options);
|
||||||
|
this._stream = stream;
|
||||||
|
this._serialize = serialize;
|
||||||
|
var self = this;
|
||||||
|
this._stream.on('data', function forwardData(chunk) {
|
||||||
|
if (!self.push(deserialize(chunk))) {
|
||||||
|
self._stream.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._stream.on('end', function forwardEnd() {
|
||||||
|
self.push(null);
|
||||||
|
});
|
||||||
|
this._stream.pause();
|
||||||
|
this.on('finish', function() {
|
||||||
|
this._stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _read implementation for both types of streams that allow reading.
|
||||||
|
* @this {ServerReadableObjectStream|ServerBidiObjectStream}
|
||||||
|
* @param {number} size Ignored
|
||||||
|
*/
|
||||||
|
function _read(size) {
|
||||||
|
this._stream.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for _read
|
||||||
|
*/
|
||||||
|
ServerReadableObjectStream.prototype._read = _read;
|
||||||
|
/**
|
||||||
|
* See docs for _read
|
||||||
|
*/
|
||||||
|
ServerBidiObjectStream.prototype._read = _read;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* _write implementation for both types of streams that allow writing
|
||||||
|
* @this {ServerWritableObjectStream|ServerBidiObjectStream}
|
||||||
|
* @param {*} chunk The value to write to the stream
|
||||||
|
* @param {string} encoding Ignored
|
||||||
|
* @param {function(Error)} callback Callback to call when finished writing
|
||||||
|
*/
|
||||||
|
function _write(chunk, encoding, callback) {
|
||||||
|
this._stream.write(this._serialize(chunk), encoding, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for _write
|
||||||
|
*/
|
||||||
|
ServerWritableObjectStream.prototype._write = _write;
|
||||||
|
/**
|
||||||
|
* See docs for _write
|
||||||
|
*/
|
||||||
|
ServerBidiObjectStream.prototype._write = _write;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a binary stream handler function from a unary handler function
|
||||||
|
* @param {function(Object, function(Error, *))} handler Unary call handler
|
||||||
|
* @param {function(*):Buffer} serialize Serialization function
|
||||||
|
* @param {function(Buffer):*} deserialize Deserialization function
|
||||||
|
* @return {function(stream)} Binary stream handler
|
||||||
|
*/
|
||||||
|
function makeUnaryHandler(handler, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Handles a stream by reading a single data value, passing it to the handler,
|
||||||
|
* and writing the response back to the stream.
|
||||||
|
* @param {stream} stream Binary data stream
|
||||||
|
*/
|
||||||
|
return function handleUnaryCall(stream) {
|
||||||
|
stream.on('data', function handleUnaryData(value) {
|
||||||
|
var call = {request: deserialize(value)};
|
||||||
|
Object.defineProperty(call, 'cancelled', {
|
||||||
|
get: function() { return stream.cancelled;}
|
||||||
|
});
|
||||||
|
handler(call, function sendUnaryData(err, value) {
|
||||||
|
if (err) {
|
||||||
|
stream.emit('error', err);
|
||||||
|
} else {
|
||||||
|
stream.write(serialize(value));
|
||||||
|
stream.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a binary stream handler function from a client stream handler
|
||||||
|
* function
|
||||||
|
* @param {function(Readable, function(Error, *))} handler Client stream call
|
||||||
|
* handler
|
||||||
|
* @param {function(*):Buffer} serialize Serialization function
|
||||||
|
* @param {function(Buffer):*} deserialize Deserialization function
|
||||||
|
* @return {function(stream)} Binary stream handler
|
||||||
|
*/
|
||||||
|
function makeClientStreamHandler(handler, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Handles a stream by passing a deserializing stream to the handler and
|
||||||
|
* writing the response back to the stream.
|
||||||
|
* @param {stream} stream Binary data stream
|
||||||
|
*/
|
||||||
|
return function handleClientStreamCall(stream) {
|
||||||
|
var object_stream = new ServerReadableObjectStream(stream, deserialize, {});
|
||||||
|
handler(object_stream, function sendClientStreamData(err, value) {
|
||||||
|
if (err) {
|
||||||
|
stream.emit('error', err);
|
||||||
|
} else {
|
||||||
|
stream.write(serialize(value));
|
||||||
|
stream.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a binary stream handler function from a server stream handler
|
||||||
|
* function
|
||||||
|
* @param {function(Writable)} handler Server stream call handler
|
||||||
|
* @param {function(*):Buffer} serialize Serialization function
|
||||||
|
* @param {function(Buffer):*} deserialize Deserialization function
|
||||||
|
* @return {function(stream)} Binary stream handler
|
||||||
|
*/
|
||||||
|
function makeServerStreamHandler(handler, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Handles a stream by attaching it to a serializing stream, and passing it to
|
||||||
|
* the handler.
|
||||||
|
* @param {stream} stream Binary data stream
|
||||||
|
*/
|
||||||
|
return function handleServerStreamCall(stream) {
|
||||||
|
stream.on('data', function handleClientData(value) {
|
||||||
|
var object_stream = new ServerWritableObjectStream(stream,
|
||||||
|
serialize,
|
||||||
|
{});
|
||||||
|
object_stream.request = deserialize(value);
|
||||||
|
handler(object_stream);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a binary stream handler function from a bidi stream handler function
|
||||||
|
* @param {function(Duplex)} handler Unary call handler
|
||||||
|
* @param {function(*):Buffer} serialize Serialization function
|
||||||
|
* @param {function(Buffer):*} deserialize Deserialization function
|
||||||
|
* @return {function(stream)} Binary stream handler
|
||||||
|
*/
|
||||||
|
function makeBidiStreamHandler(handler, serialize, deserialize) {
|
||||||
|
/**
|
||||||
|
* Handles a stream by wrapping it in a serializing and deserializing object
|
||||||
|
* stream, and passing it to the handler.
|
||||||
|
* @param {stream} stream Binary data stream
|
||||||
|
*/
|
||||||
|
return function handleBidiStreamCall(stream) {
|
||||||
|
var object_stream = new ServerBidiObjectStream(stream,
|
||||||
|
serialize,
|
||||||
|
deserialize,
|
||||||
|
{});
|
||||||
|
handler(object_stream);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map with short names for each of the handler maker functions. Used in
|
||||||
|
* makeServerConstructor
|
||||||
|
*/
|
||||||
|
var handler_makers = {
|
||||||
|
unary: makeUnaryHandler,
|
||||||
|
server_stream: makeServerStreamHandler,
|
||||||
|
client_stream: makeClientStreamHandler,
|
||||||
|
bidi: makeBidiStreamHandler
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
function makeServerConstructor(methods, prefix) {
|
||||||
|
/**
|
||||||
|
* Create a server with the given handlers for all of the methods.
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} handlers Map from method names to method handlers.
|
||||||
|
* @param {Object} options Options to pass to the underlying server
|
||||||
|
*/
|
||||||
|
function SurfaceServer(handlers, options) {
|
||||||
|
var server = new Server(options);
|
||||||
|
this.inner_server = server;
|
||||||
|
_.each(handlers, function(handler, name) {
|
||||||
|
var method = methods[name];
|
||||||
|
var method_type;
|
||||||
|
if (method.client_stream) {
|
||||||
|
if (method.server_stream) {
|
||||||
|
method_type = 'bidi';
|
||||||
|
} else {
|
||||||
|
method_type = 'client_stream';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (method.server_stream) {
|
||||||
|
method_type = 'server_stream';
|
||||||
|
} else {
|
||||||
|
method_type = 'unary';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var binary_handler = handler_makers[method_type](handler,
|
||||||
|
method.serialize,
|
||||||
|
method.deserialize);
|
||||||
|
server.register('' + prefix + name, binary_handler);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the server to the given port, with SSL enabled if secure is specified
|
||||||
|
* @param {string} port The port that the server should bind on, in the format
|
||||||
|
* "address:port"
|
||||||
|
* @param {boolean=} secure Whether the server should open a secure port
|
||||||
|
* @return {SurfaceServer} this
|
||||||
|
*/
|
||||||
|
SurfaceServer.prototype.bind = function(port, secure) {
|
||||||
|
this.inner_server.bind(port, secure);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the server listening on any bound ports
|
||||||
|
* @return {SurfaceServer} this
|
||||||
|
*/
|
||||||
|
SurfaceServer.prototype.listen = function() {
|
||||||
|
this.inner_server.start();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts the server down; tells it to stop listening for new requests and to
|
||||||
|
* kill old requests.
|
||||||
|
*/
|
||||||
|
SurfaceServer.prototype.shutdown = function() {
|
||||||
|
this.inner_server.shutdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
return SurfaceServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See documentation for makeServerConstructor
|
||||||
|
*/
|
||||||
|
exports.makeServerConstructor = makeServerConstructor;
|
|
@ -0,0 +1,71 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include "tag.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Persistent;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
struct tag {
|
||||||
|
tag(Persistent<Value> *tag, Persistent<Value> *call)
|
||||||
|
: persist_tag(tag), persist_call(call) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~tag() {
|
||||||
|
persist_tag->Dispose();
|
||||||
|
if (persist_call != NULL) {
|
||||||
|
persist_call->Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Persistent<Value> *persist_tag;
|
||||||
|
Persistent<Value> *persist_call;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *CreateTag(Handle<Value> tag, Handle<Value> call) {
|
||||||
|
NanScope();
|
||||||
|
Persistent<Value> *persist_tag = new Persistent<Value>();
|
||||||
|
NanAssignPersistent(*persist_tag, tag);
|
||||||
|
Persistent<Value> *persist_call;
|
||||||
|
if (call->IsNull() || call->IsUndefined()) {
|
||||||
|
persist_call = NULL;
|
||||||
|
} else {
|
||||||
|
persist_call = new Persistent<Value>();
|
||||||
|
NanAssignPersistent(*persist_call, call);
|
||||||
|
}
|
||||||
|
struct tag *tag_struct = new struct tag(persist_tag, persist_call);
|
||||||
|
return reinterpret_cast<void*>(tag_struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> GetTagHandle(void *tag) {
|
||||||
|
NanEscapableScope();
|
||||||
|
struct tag *tag_struct = reinterpret_cast<struct tag*>(tag);
|
||||||
|
Handle<Value> tag_value = NanNew<Value>(*tag_struct->persist_tag);
|
||||||
|
return NanEscapeScope(tag_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagHasCall(void *tag) {
|
||||||
|
struct tag *tag_struct = reinterpret_cast<struct tag*>(tag);
|
||||||
|
return tag_struct->persist_call != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> TagGetCall(void *tag) {
|
||||||
|
NanEscapableScope();
|
||||||
|
struct tag *tag_struct = reinterpret_cast<struct tag*>(tag);
|
||||||
|
if (tag_struct->persist_call == NULL) {
|
||||||
|
return NanEscapeScope(NanNull());
|
||||||
|
}
|
||||||
|
Handle<Value> call_value = NanNew<Value>(*tag_struct->persist_call);
|
||||||
|
return NanEscapeScope(call_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyTag(void *tag) {
|
||||||
|
delete reinterpret_cast<struct tag*>(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef NET_GRPC_NODE_TAG_H_
|
||||||
|
#define NET_GRPC_NODE_TAG_H_
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
/* Create a void* tag that can be passed to various grpc_call functions from
|
||||||
|
a javascript value and the javascript wrapper for the call. The call can be
|
||||||
|
null. */
|
||||||
|
void *CreateTag(v8::Handle<v8::Value> tag, v8::Handle<v8::Value> call);
|
||||||
|
/* Return the javascript value stored in the tag */
|
||||||
|
v8::Handle<v8::Value> GetTagHandle(void *tag);
|
||||||
|
/* Returns true if the call was set (non-null) when the tag was created */
|
||||||
|
bool TagHasCall(void *tag);
|
||||||
|
/* Returns the javascript wrapper for the call associated with this tag */
|
||||||
|
v8::Handle<v8::Value> TagGetCall(void *call);
|
||||||
|
/* Destroy the tag and all resources it is holding. It is illegal to call any
|
||||||
|
of these other functions on a tag after it has been destroyed. */
|
||||||
|
void DestroyTag(void *tag);
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_TAG_H_
|
|
@ -0,0 +1,35 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('..build/Release/grpc');
|
||||||
|
|
||||||
|
describe('byte buffer', function() {
|
||||||
|
describe('constructor', function() {
|
||||||
|
it('should reject bad constructor calls', function() {
|
||||||
|
it('should require at least one argument', function() {
|
||||||
|
assert.throws(new grpc.ByteBuffer(), TypeError);
|
||||||
|
});
|
||||||
|
it('should reject non-string arguments', function() {
|
||||||
|
assert.throws(new grpc.ByteBuffer(0), TypeError);
|
||||||
|
assert.throws(new grpc.ByteBuffer(1.5), TypeError);
|
||||||
|
assert.throws(new grpc.ByteBuffer(null), TypeError);
|
||||||
|
assert.throws(new grpc.ByteBuffer(Date.now()), TypeError);
|
||||||
|
});
|
||||||
|
it('should accept string arguments', function() {
|
||||||
|
assert.doesNotThrow(new grpc.ByteBuffer(''));
|
||||||
|
assert.doesNotThrow(new grpc.ByteBuffer('test'));
|
||||||
|
assert.doesNotThrow(new grpc.ByteBuffer('\0'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('bytes', function() {
|
||||||
|
it('should return the passed string', function() {
|
||||||
|
it('should preserve simple strings', function() {
|
||||||
|
var buffer = new grpc.ByteBuffer('test');
|
||||||
|
assert.strictEqual(buffer.bytes(), 'test');
|
||||||
|
});
|
||||||
|
it('should preserve null characters', function() {
|
||||||
|
var buffer = new grpc.ByteBuffer('test\0test');
|
||||||
|
assert.strictEqual(buffer.bytes(), 'test\0test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,169 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
var channel = new grpc.Channel('localhost:7070');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to return an absolute deadline given a relative timeout in
|
||||||
|
* seconds.
|
||||||
|
* @param {number} timeout_secs The number of seconds to wait before timing out
|
||||||
|
* @return {Date} A date timeout_secs in the future
|
||||||
|
*/
|
||||||
|
function getDeadline(timeout_secs) {
|
||||||
|
var deadline = new Date();
|
||||||
|
deadline.setSeconds(deadline.getSeconds() + timeout_secs);
|
||||||
|
return deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('call', function() {
|
||||||
|
describe('constructor', function() {
|
||||||
|
it('should reject anything less than 3 arguments', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call();
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call(channel);
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call(channel, 'method');
|
||||||
|
}, TypeError);
|
||||||
|
});
|
||||||
|
it('should succeed with a Channel, a string, and a date or number',
|
||||||
|
function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Call(channel, 'method', new Date());
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Call(channel, 'method', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fail with a closed channel', function() {
|
||||||
|
var local_channel = new grpc.Channel('hostname');
|
||||||
|
local_channel.close();
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call(channel, 'method');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fail with other types', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call({}, 'method', 0);
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call(channel, null, 0);
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Call(channel, 'method', 'now');
|
||||||
|
}, TypeError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('addMetadata', function() {
|
||||||
|
it('should succeed with objects containing keys and values', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
call.addMetadata();
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
call.addMetadata({'key' : 'key',
|
||||||
|
'value' : new Buffer('value')});
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
call.addMetadata({'key' : 'key1',
|
||||||
|
'value' : new Buffer('value1')},
|
||||||
|
{'key' : 'key2',
|
||||||
|
'value' : new Buffer('value2')});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fail with other parameter types', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.throws(function() {
|
||||||
|
call.addMetadata(null);
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.addMetadata('value');
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.addMetadata(5);
|
||||||
|
}, TypeError);
|
||||||
|
});
|
||||||
|
it('should fail if startInvoke was already called', function(done) {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
call.startInvoke(function() {},
|
||||||
|
function() {},
|
||||||
|
function() {done();},
|
||||||
|
0);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.addMetadata({'key' : 'key', 'value' : new Buffer('value') });
|
||||||
|
}, function(err) {
|
||||||
|
return err.code === grpc.callError.ALREADY_INVOKED;
|
||||||
|
});
|
||||||
|
// Cancel to speed up the test
|
||||||
|
call.cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('startInvoke', function() {
|
||||||
|
it('should fail with fewer than 4 arguments', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke();
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke(function() {});
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke(function() {},
|
||||||
|
function() {});
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke(function() {},
|
||||||
|
function() {},
|
||||||
|
function() {});
|
||||||
|
}, TypeError);
|
||||||
|
});
|
||||||
|
it('should work with 3 args and an int', function(done) {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
call.startInvoke(function() {},
|
||||||
|
function() {},
|
||||||
|
function() {done();},
|
||||||
|
0);
|
||||||
|
// Cancel to speed up the test
|
||||||
|
call.cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should reject incorrectly typed arguments', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke(0, 0, 0, 0);
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startInvoke(function() {},
|
||||||
|
function() {},
|
||||||
|
function() {}, 'test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('serverAccept', function() {
|
||||||
|
it('should fail with fewer than 1 argument1', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.throws(function() {
|
||||||
|
call.serverAccept();
|
||||||
|
}, TypeError);
|
||||||
|
});
|
||||||
|
it('should return an error when called on a client Call', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.throws(function() {
|
||||||
|
call.serverAccept(function() {});
|
||||||
|
}, function(err) {
|
||||||
|
return err.code === grpc.callError.NOT_ON_CLIENT;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('cancel', function() {
|
||||||
|
it('should succeed', function() {
|
||||||
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
call.cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('../build/Release/grpc');
|
||||||
|
|
||||||
|
describe('call', function() {
|
||||||
|
describe('constructor', function() {
|
||||||
|
it('should reject anything less than 4 arguments', function() {
|
|
@ -0,0 +1,55 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
describe('channel', function() {
|
||||||
|
describe('constructor', function() {
|
||||||
|
it('should require a string for the first argument', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Channel('hostname');
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Channel();
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Channel(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should accept an object for the second parameter', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Channel('hostname', {});
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Channel('hostname', 5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should only accept objects with string or int values', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Channel('hostname', {'key' : 'value'});
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.Channel('hostname', {'key' : 5});
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Channel('hostname', {'key' : null});
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
new grpc.Channel('hostname', {'key' : new Date()});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('close', function() {
|
||||||
|
it('should succeed silently', function() {
|
||||||
|
var channel = new grpc.Channel('hostname', {});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
channel.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should be idempotent', function() {
|
||||||
|
var channel = new grpc.Channel('hostname', {});
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
channel.close();
|
||||||
|
channel.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,150 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
var Server = require('../server');
|
||||||
|
var client = require('../client');
|
||||||
|
var port_picker = require('../port_picker');
|
||||||
|
var common = require('../common');
|
||||||
|
var _ = require('highland');
|
||||||
|
|
||||||
|
var ca_path = path.join(__dirname, 'data/ca.pem');
|
||||||
|
|
||||||
|
var key_path = path.join(__dirname, 'data/server1.key');
|
||||||
|
|
||||||
|
var pem_path = path.join(__dirname, 'data/server1.pem');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to return an absolute deadline given a relative timeout in
|
||||||
|
* seconds.
|
||||||
|
* @param {number} timeout_secs The number of seconds to wait before timing out
|
||||||
|
* @return {Date} A date timeout_secs in the future
|
||||||
|
*/
|
||||||
|
function getDeadline(timeout_secs) {
|
||||||
|
var deadline = new Date();
|
||||||
|
deadline.setSeconds(deadline.getSeconds() + timeout_secs);
|
||||||
|
return deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to every request with the same data as a response
|
||||||
|
* @param {Stream} stream
|
||||||
|
*/
|
||||||
|
function echoHandler(stream) {
|
||||||
|
stream.pipe(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to every request with an error status
|
||||||
|
* @param {Stream} stream
|
||||||
|
*/
|
||||||
|
function errorHandler(stream) {
|
||||||
|
throw {
|
||||||
|
'code' : grpc.status.UNIMPLEMENTED,
|
||||||
|
'details' : 'error details'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('echo client', function() {
|
||||||
|
it('should receive echo responses', function(done) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var server = new Server();
|
||||||
|
server.bind(port);
|
||||||
|
server.register('echo', echoHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var stream = client.makeRequest(
|
||||||
|
channel,
|
||||||
|
'echo');
|
||||||
|
_(messages).map(function(val) {
|
||||||
|
return new Buffer(val);
|
||||||
|
}).pipe(stream);
|
||||||
|
var index = 0;
|
||||||
|
stream.on('data', function(chunk) {
|
||||||
|
assert.equal(messages[index], chunk.toString());
|
||||||
|
index += 1;
|
||||||
|
});
|
||||||
|
stream.on('end', function() {
|
||||||
|
server.shutdown();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should get an error status that the server throws', function(done) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var server = new Server();
|
||||||
|
server.bind(port);
|
||||||
|
server.register('error', errorHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var stream = client.makeRequest(
|
||||||
|
channel,
|
||||||
|
'error',
|
||||||
|
null,
|
||||||
|
getDeadline(1));
|
||||||
|
|
||||||
|
stream.on('data', function() {});
|
||||||
|
stream.write(new Buffer('test'));
|
||||||
|
stream.end();
|
||||||
|
stream.on('status', function(status) {
|
||||||
|
assert.equal(status.code, grpc.status.UNIMPLEMENTED);
|
||||||
|
assert.equal(status.details, 'error details');
|
||||||
|
server.shutdown();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/* TODO(mlumish): explore options for reducing duplication between this test
|
||||||
|
* and the insecure echo client test */
|
||||||
|
describe('secure echo client', function() {
|
||||||
|
it('should recieve echo responses', function(done) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
fs.readFile(ca_path, function(err, ca_data) {
|
||||||
|
assert.ifError(err);
|
||||||
|
fs.readFile(key_path, function(err, key_data) {
|
||||||
|
assert.ifError(err);
|
||||||
|
fs.readFile(pem_path, function(err, pem_data) {
|
||||||
|
assert.ifError(err);
|
||||||
|
var creds = grpc.Credentials.createSsl(ca_data);
|
||||||
|
var server_creds = grpc.ServerCredentials.createSsl(null,
|
||||||
|
key_data,
|
||||||
|
pem_data);
|
||||||
|
|
||||||
|
var server = new Server({'credentials' : server_creds});
|
||||||
|
server.bind(port, true);
|
||||||
|
server.register('echo', echoHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
|
||||||
|
var channel = new grpc.Channel(port, {
|
||||||
|
'grpc.ssl_target_name_override' : 'foo.test.google.com',
|
||||||
|
'credentials' : creds
|
||||||
|
});
|
||||||
|
var stream = client.makeRequest(
|
||||||
|
channel,
|
||||||
|
'echo');
|
||||||
|
|
||||||
|
_(messages).map(function(val) {
|
||||||
|
return new Buffer(val);
|
||||||
|
}).pipe(stream);
|
||||||
|
var index = 0;
|
||||||
|
stream.on('data', function(chunk) {
|
||||||
|
assert.equal(messages[index], chunk.toString());
|
||||||
|
index += 1;
|
||||||
|
});
|
||||||
|
stream.on('end', function() {
|
||||||
|
server.shutdown();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('../build/Debug/grpc');
|
||||||
|
var Server = require('../server');
|
||||||
|
var client = require('../client');
|
||||||
|
var port_picker = require('../port_picker');
|
||||||
|
var iterators = require('async-iterators');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General function to process an event by checking that there was no error and
|
||||||
|
* calling the callback passed as a tag.
|
||||||
|
* @param {*} err Truthy values indicate an error (in this case, that there was
|
||||||
|
* no event available).
|
||||||
|
* @param {grpc.Event} event The event to process.
|
||||||
|
*/
|
||||||
|
function processEvent(err, event) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
event.getTag()(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to every request with the same data as a response
|
||||||
|
* @param {{next:function(function(*, Buffer))}} arg_iter The async iterator of
|
||||||
|
* arguments.
|
||||||
|
* @return {{next:function(function(*, Buffer))}} The async iterator of results
|
||||||
|
*/
|
||||||
|
function echoHandler(arg_iter) {
|
||||||
|
return {
|
||||||
|
'next' : function(write) {
|
||||||
|
arg_iter.next(function(err, value) {
|
||||||
|
if (value == undefined) {
|
||||||
|
write({
|
||||||
|
'code' : grpc.status.OK,
|
||||||
|
'details' : 'OK'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
write(err, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('echo client server', function() {
|
||||||
|
it('should recieve echo responses', function(done) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var server = new Server(port);
|
||||||
|
server.register('echo', echoHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
var messages = ['echo1', 'echo2', 'echo3'];
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var responses = client.makeRequest(channel,
|
||||||
|
'echo',
|
||||||
|
iterators.fromArray(messages));
|
||||||
|
assert.equal(messages, iterators.toArray(responses));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('../build/Release/grpc');
|
||||||
|
|
||||||
|
describe('completion queue', function() {
|
||||||
|
describe('constructor', function() {
|
||||||
|
it('should succeed with now arguments', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
new grpc.CompletionQueue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('next', function() {
|
||||||
|
it('should require a date parameter', function() {
|
||||||
|
var queue = new grpc.CompletionQueue();
|
||||||
|
assert.throws(function() {
|
||||||
|
queue->next();
|
||||||
|
}, TypeError);
|
||||||
|
assert.throws(function() {
|
||||||
|
queue->next('test');
|
||||||
|
}, TypeError);
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
queue->next(Date.now());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return null from a new queue', function() {
|
||||||
|
var queue = new grpc.CompletionQueue();
|
||||||
|
assert.strictEqual(queue->next(Date.now()), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,97 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all status names
|
||||||
|
* @const
|
||||||
|
* @type {Array.<string>}
|
||||||
|
*/
|
||||||
|
var statusNames = [
|
||||||
|
'OK',
|
||||||
|
'CANCELLED',
|
||||||
|
'UNKNOWN',
|
||||||
|
'INVALID_ARGUMENT',
|
||||||
|
'DEADLINE_EXCEEDED',
|
||||||
|
'NOT_FOUND',
|
||||||
|
'ALREADY_EXISTS',
|
||||||
|
'PERMISSION_DENIED',
|
||||||
|
'UNAUTHENTICATED',
|
||||||
|
'RESOURCE_EXHAUSTED',
|
||||||
|
'FAILED_PRECONDITION',
|
||||||
|
'ABORTED',
|
||||||
|
'OUT_OF_RANGE',
|
||||||
|
'UNIMPLEMENTED',
|
||||||
|
'INTERNAL',
|
||||||
|
'UNAVAILABLE',
|
||||||
|
'DATA_LOSS'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all call error names
|
||||||
|
* @const
|
||||||
|
* @type {Array.<string>}
|
||||||
|
*/
|
||||||
|
var callErrorNames = [
|
||||||
|
'OK',
|
||||||
|
'ERROR',
|
||||||
|
'NOT_ON_SERVER',
|
||||||
|
'NOT_ON_CLIENT',
|
||||||
|
'ALREADY_INVOKED',
|
||||||
|
'NOT_INVOKED',
|
||||||
|
'ALREADY_FINISHED',
|
||||||
|
'TOO_MANY_OPERATIONS',
|
||||||
|
'INVALID_FLAGS'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all op error names
|
||||||
|
* @const
|
||||||
|
* @type {Array.<string>}
|
||||||
|
*/
|
||||||
|
var opErrorNames = [
|
||||||
|
'OK',
|
||||||
|
'ERROR'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all completion type names
|
||||||
|
* @const
|
||||||
|
* @type {Array.<string>}
|
||||||
|
*/
|
||||||
|
var completionTypeNames = [
|
||||||
|
'QUEUE_SHUTDOWN',
|
||||||
|
'READ',
|
||||||
|
'INVOKE_ACCEPTED',
|
||||||
|
'WRITE_ACCEPTED',
|
||||||
|
'FINISH_ACCEPTED',
|
||||||
|
'CLIENT_METADATA_READ',
|
||||||
|
'FINISHED',
|
||||||
|
'SERVER_RPC_NEW'
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('constants', function() {
|
||||||
|
it('should have all of the status constants', function() {
|
||||||
|
for (var i = 0; i < statusNames.length; i++) {
|
||||||
|
assert(grpc.status.hasOwnProperty(statusNames[i]),
|
||||||
|
'status missing: ' + statusNames[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should have all of the call errors', function() {
|
||||||
|
for (var i = 0; i < callErrorNames.length; i++) {
|
||||||
|
assert(grpc.callError.hasOwnProperty(callErrorNames[i]),
|
||||||
|
'call error missing: ' + callErrorNames[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should have all of the op errors', function() {
|
||||||
|
for (var i = 0; i < opErrorNames.length; i++) {
|
||||||
|
assert(grpc.opError.hasOwnProperty(opErrorNames[i]),
|
||||||
|
'op error missing: ' + opErrorNames[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should have all of the completion types', function() {
|
||||||
|
for (var i = 0; i < completionTypeNames.length; i++) {
|
||||||
|
assert(grpc.completionType.hasOwnProperty(completionTypeNames[i]),
|
||||||
|
'completion type missing: ' + completionTypeNames[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
var assert = require("assert");
|
||||||
|
var grpc = require("../build/Release");
|
||||||
|
|
||||||
|
var status_names = [
|
||||||
|
"OK",
|
||||||
|
"CANCELLED",
|
||||||
|
"UNKNOWN",
|
||||||
|
"INVALID_ARGUMENT",
|
||||||
|
"DEADLINE_EXCEEDED",
|
||||||
|
"NOT_FOUND",
|
||||||
|
"ALREADY_EXISTS",
|
||||||
|
"PERMISSION_DENIED",
|
||||||
|
"UNAUTHENTICATED",
|
||||||
|
"RESOURCE_EXHAUSTED",
|
||||||
|
"FAILED_PRECONDITION",
|
||||||
|
"ABORTED",
|
||||||
|
"OUT_OF_RANGE",
|
||||||
|
"UNIMPLEMENTED",
|
||||||
|
"INTERNAL",
|
||||||
|
"UNAVAILABLE",
|
||||||
|
"DATA_LOSS"
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("constants", function() {
|
||||||
|
it("should have all of the status constants", function() {
|
|
@ -0,0 +1 @@
|
||||||
|
CONFIRMEDTESTKEY
|
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
|
||||||
|
Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
|
||||||
|
YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
|
||||||
|
BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
|
||||||
|
+L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
|
||||||
|
g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
|
||||||
|
Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
|
||||||
|
HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
|
||||||
|
sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
|
||||||
|
oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
|
||||||
|
Dfcog5wrJytaQ6UA0wE=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
|
||||||
|
M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
|
||||||
|
3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
|
||||||
|
AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
|
||||||
|
V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
|
||||||
|
tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
|
||||||
|
dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
|
||||||
|
K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
|
||||||
|
81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
|
||||||
|
DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
|
||||||
|
aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
|
||||||
|
ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
|
||||||
|
XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
|
||||||
|
F98XJ7tIFfJq
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
|
||||||
|
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||||
|
dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
|
||||||
|
MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||||
|
BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
|
||||||
|
c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
|
||||||
|
JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
|
||||||
|
RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
|
||||||
|
3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
|
||||||
|
BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
|
||||||
|
b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
|
||||||
|
KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
|
||||||
|
wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
|
||||||
|
aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,168 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
var port_picker = require('../port_picker');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('end-to-end', function() {
|
||||||
|
it('should start and end a request without error', function(complete) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var server = new grpc.Server();
|
||||||
|
var done = multiDone(function() {
|
||||||
|
complete();
|
||||||
|
server.shutdown();
|
||||||
|
}, 2);
|
||||||
|
server.addHttp2Port(port);
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var deadline = new Date();
|
||||||
|
deadline.setSeconds(deadline.getSeconds() + 3);
|
||||||
|
var status_text = 'xyz';
|
||||||
|
var call = new grpc.Call(channel,
|
||||||
|
'dummy_method',
|
||||||
|
deadline);
|
||||||
|
call.startInvoke(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.INVOKE_ACCEPTED);
|
||||||
|
|
||||||
|
call.writesDone(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
});
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.CLIENT_METADATA_READ);
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
||||||
|
var status = event.data;
|
||||||
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.strictEqual(status.details, status_text);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.requestCall(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
|
||||||
|
var server_call = event.call;
|
||||||
|
assert.notEqual(server_call, null);
|
||||||
|
server_call.serverAccept(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
||||||
|
}, 0);
|
||||||
|
server_call.serverEndInitialMetadata(0);
|
||||||
|
server_call.startWriteStatus(
|
||||||
|
grpc.status.OK,
|
||||||
|
status_text,
|
||||||
|
function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send and receive data without error', function(complete) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var req_text = 'client_request';
|
||||||
|
var reply_text = 'server_response';
|
||||||
|
var server = new grpc.Server();
|
||||||
|
var done = multiDone(function() {
|
||||||
|
complete();
|
||||||
|
server.shutdown();
|
||||||
|
}, 6);
|
||||||
|
server.addHttp2Port(port);
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var deadline = new Date();
|
||||||
|
deadline.setSeconds(deadline.getSeconds() + 3);
|
||||||
|
var status_text = 'success';
|
||||||
|
var call = new grpc.Call(channel,
|
||||||
|
'dummy_method',
|
||||||
|
deadline);
|
||||||
|
call.startInvoke(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.INVOKE_ACCEPTED);
|
||||||
|
call.startWrite(
|
||||||
|
new Buffer(req_text),
|
||||||
|
function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.WRITE_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
call.writesDone(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
call.startRead(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.READ);
|
||||||
|
assert.strictEqual(event.data.toString(), reply_text);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.CLIENT_METADATA_READ);
|
||||||
|
done();
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
||||||
|
var status = event.data;
|
||||||
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.strictEqual(status.details, status_text);
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.requestCall(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
|
||||||
|
var server_call = event.call;
|
||||||
|
assert.notEqual(server_call, null);
|
||||||
|
server_call.serverAccept(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
server_call.serverEndInitialMetadata(0);
|
||||||
|
server_call.startRead(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.READ);
|
||||||
|
assert.strictEqual(event.data.toString(), req_text);
|
||||||
|
server_call.startWrite(
|
||||||
|
new Buffer(reply_text),
|
||||||
|
function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.WRITE_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data,
|
||||||
|
grpc.opError.OK);
|
||||||
|
server_call.startWriteStatus(
|
||||||
|
grpc.status.OK,
|
||||||
|
status_text,
|
||||||
|
function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,72 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('../build/Release/grpc');
|
||||||
|
|
||||||
|
describe('end-to-end', function() {
|
||||||
|
it('should start and end a request without error', function() {
|
||||||
|
var event;
|
||||||
|
var client_queue = new grpc.CompletionQueue();
|
||||||
|
var server_queue = new grpc.CompletionQueue();
|
||||||
|
var server = new grpc.Server(server_queue);
|
||||||
|
server.addHttp2Port('localhost:9000');
|
||||||
|
var channel = new grpc.Channel('localhost:9000');
|
||||||
|
var deadline = Infinity;
|
||||||
|
var status_text = 'xyz';
|
||||||
|
var call = new grpc.Call(channel, 'dummy_method', deadline);
|
||||||
|
var tag = 1;
|
||||||
|
assert.strictEqual(call.startInvoke(client_queue, tag, tag, tag),
|
||||||
|
grpc.callError.OK);
|
||||||
|
var server_tag = 2;
|
||||||
|
|
||||||
|
// the client invocation was accepted
|
||||||
|
event = client_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event->getType(), grpc.completionType.INVOKE_ACCEPTED);
|
||||||
|
|
||||||
|
assert.strictEqual(call.writesDone(tag), grpc.callError.CALL_OK);
|
||||||
|
event = client_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(), grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.getData(), grpc.opError.OK);
|
||||||
|
|
||||||
|
// check that a server rpc new was recieved
|
||||||
|
assert(server.start());
|
||||||
|
assert.strictEqual(server.requestCall(server_tag, server_tag),
|
||||||
|
grpc.callError.OK);
|
||||||
|
event = server_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(), grpc.completionType.SERVER_RPC_NEW);
|
||||||
|
var server_call = event.getCall();
|
||||||
|
assert.notEqual(server_call, null);
|
||||||
|
assert.strictEqual(server_call.accept(server_queue, server_tag),
|
||||||
|
grpc.callError.OK);
|
||||||
|
|
||||||
|
// the server sends the status
|
||||||
|
assert.strictEqual(server_call.start_write_status(grpc.status.OK,
|
||||||
|
status_text,
|
||||||
|
server_tag),
|
||||||
|
grpc.callError.OK);
|
||||||
|
event = server_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(), grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.getData(), grpc.opError.OK);
|
||||||
|
|
||||||
|
// the client gets CLIENT_METADATA_READ
|
||||||
|
event = client_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(),
|
||||||
|
grpc.completionType.CLIENT_METADATA_READ);
|
||||||
|
|
||||||
|
// the client gets FINISHED
|
||||||
|
event = client_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(), grpc.completionType.FINISHED);
|
||||||
|
var status = event.getData();
|
||||||
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.strictEqual(status.details, status_text);
|
||||||
|
|
||||||
|
// the server gets FINISHED
|
||||||
|
event = client_queue.next(deadline);
|
||||||
|
assert.notEqual(event, null);
|
||||||
|
assert.strictEqual(event.getType(), grpc.completionType.FINISHED);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,176 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var client = require('../surface_client.js');
|
||||||
|
var ProtoBuf = require('protobufjs');
|
||||||
|
var port_picker = require('../port_picker');
|
||||||
|
|
||||||
|
var builder = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
|
||||||
|
var math = builder.build('math');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that deserializes a specific type of protobuf.
|
||||||
|
* @param {function()} cls The constructor of the message type to deserialize
|
||||||
|
* @return {function(Buffer):cls} The deserialization function
|
||||||
|
*/
|
||||||
|
function deserializeCls(cls) {
|
||||||
|
/**
|
||||||
|
* Deserialize a buffer to a message object
|
||||||
|
* @param {Buffer} arg_buf The buffer to deserialize
|
||||||
|
* @return {cls} The resulting object
|
||||||
|
*/
|
||||||
|
return function deserialize(arg_buf) {
|
||||||
|
return cls.decode(arg_buf);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an object to a buffer
|
||||||
|
* @param {*} arg The object to serialize
|
||||||
|
* @return {Buffer} The serialized object
|
||||||
|
*/
|
||||||
|
function serialize(arg) {
|
||||||
|
return new Buffer(arg.encode().toBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Div request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {DivArg} argument The argument to the call. Should be serializable
|
||||||
|
* with serialize
|
||||||
|
* @param {function(?Error, value=)} The callback to for when the response is
|
||||||
|
* received
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var div = client.makeUnaryRequestFunction(
|
||||||
|
'/Math/Div',
|
||||||
|
serialize,
|
||||||
|
deserializeCls(math.DivReply));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Fib request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {*} argument The argument to the call. Should be serializable with
|
||||||
|
* serialize
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var fib = client.makeServerStreamRequestFunction(
|
||||||
|
'/Math/Fib',
|
||||||
|
serialize,
|
||||||
|
deserializeCls(math.Num));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Sum request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {function(?Error, value=)} The callback to for when the response is
|
||||||
|
* received
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var sum = client.makeClientStreamRequestFunction(
|
||||||
|
'/Math/Sum',
|
||||||
|
serialize,
|
||||||
|
deserializeCls(math.Num));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DivMany request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var divMany = client.makeBidiStreamRequestFunction(
|
||||||
|
'/Math/DivMany',
|
||||||
|
serialize,
|
||||||
|
deserializeCls(math.DivReply));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel to use to make requests to a running server.
|
||||||
|
*/
|
||||||
|
var channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server to test against
|
||||||
|
*/
|
||||||
|
var server = require('../examples/math_server.js');
|
||||||
|
|
||||||
|
|
||||||
|
describe('Math client', function() {
|
||||||
|
before(function(done) {
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
server.bind(port).listen();
|
||||||
|
channel = new client.Channel(port);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
server.shutdown();
|
||||||
|
});
|
||||||
|
it('should handle a single request', function(done) {
|
||||||
|
var arg = new math.DivArgs({dividend: 7, divisor: 4});
|
||||||
|
var call = div(channel, arg, function handleDivResult(err, value) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(value.get('quotient'), 1);
|
||||||
|
assert.equal(value.get('remainder'), 3);
|
||||||
|
});
|
||||||
|
call.on('status', function checkStatus(status) {
|
||||||
|
assert.strictEqual(status.code, client.status.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should handle a server streaming request', function(done) {
|
||||||
|
var arg = new math.FibArgs({limit: 7});
|
||||||
|
var call = fib(channel, arg);
|
||||||
|
var expected_results = [1, 1, 2, 3, 5, 8, 13];
|
||||||
|
var next_expected = 0;
|
||||||
|
call.on('data', function checkResponse(value) {
|
||||||
|
assert.equal(value.get('num'), expected_results[next_expected]);
|
||||||
|
next_expected += 1;
|
||||||
|
});
|
||||||
|
call.on('status', function checkStatus(status) {
|
||||||
|
assert.strictEqual(status.code, client.status.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should handle a client streaming request', function(done) {
|
||||||
|
var call = sum(channel, function handleSumResult(err, value) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(value.get('num'), 21);
|
||||||
|
});
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
call.write(new math.Num({'num': i}));
|
||||||
|
}
|
||||||
|
call.end();
|
||||||
|
call.on('status', function checkStatus(status) {
|
||||||
|
assert.strictEqual(status.code, client.status.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should handle a bidirectional streaming request', function(done) {
|
||||||
|
function checkResponse(index, value) {
|
||||||
|
assert.equal(value.get('quotient'), index);
|
||||||
|
assert.equal(value.get('remainder'), 1);
|
||||||
|
}
|
||||||
|
var call = divMany(channel);
|
||||||
|
var response_index = 0;
|
||||||
|
call.on('data', function(value) {
|
||||||
|
checkResponse(response_index, value);
|
||||||
|
response_index += 1;
|
||||||
|
});
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
call.write(new math.DivArgs({dividend: 2 * i + 1, divisor: 2}));
|
||||||
|
}
|
||||||
|
call.end();
|
||||||
|
call.on('status', function checkStatus(status) {
|
||||||
|
assert.strictEqual(status.code, client.status.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,87 @@
|
||||||
|
var client = require('../surface_client.js');
|
||||||
|
|
||||||
|
var builder = ProtoBuf.loadProtoFile(__dirname + '/../examples/math.proto');
|
||||||
|
var math = builder.build('math');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a function that deserializes a specific type of protobuf.
|
||||||
|
* @param {function()} cls The constructor of the message type to deserialize
|
||||||
|
* @return {function(Buffer):cls} The deserialization function
|
||||||
|
*/
|
||||||
|
function deserializeCls(cls) {
|
||||||
|
/**
|
||||||
|
* Deserialize a buffer to a message object
|
||||||
|
* @param {Buffer} arg_buf The buffer to deserialize
|
||||||
|
* @return {cls} The resulting object
|
||||||
|
*/
|
||||||
|
return function deserialize(arg_buf) {
|
||||||
|
return cls.decode(arg_buf);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an object to a buffer
|
||||||
|
* @param {*} arg The object to serialize
|
||||||
|
* @return {Buffer} The serialized object
|
||||||
|
*/
|
||||||
|
function serialize(arg) {
|
||||||
|
return new Buffer(arg.encode.toBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Div request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {*} argument The argument to the call. Should be serializable with
|
||||||
|
* serialize
|
||||||
|
* @param {function(?Error, value=)} The callback to for when the response is
|
||||||
|
* received
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var div = client.makeUnaryRequestFunction('/Math/Div',
|
||||||
|
serialize,
|
||||||
|
deserialize(math.DivReply));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Fib request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {*} argument The argument to the call. Should be serializable with
|
||||||
|
* serialize
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var fib = client.makeServerStreamRequestFunction('/Math/Fib',
|
||||||
|
serialize,
|
||||||
|
deserialize(math.Num));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a Sum request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {function(?Error, value=)} The callback to for when the response is
|
||||||
|
* received
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var sum = client.makeClientStreamRequestFunction('/Math/Sum',
|
||||||
|
serialize,
|
||||||
|
deserialize(math.Num));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a DivMany request on the channel.
|
||||||
|
* @param {client.Channel} channel The channel on which to make the request
|
||||||
|
* @param {array=} 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
|
||||||
|
* @return {EventEmitter} An event emitter for stream related events
|
||||||
|
*/
|
||||||
|
var divMany = client.makeBidiStreamRequestFunction('/Math/DivMany',
|
||||||
|
serialize,
|
||||||
|
deserialize(math.DivReply));
|
||||||
|
|
||||||
|
var channel = new client.Channel('localhost:7070');
|
|
@ -0,0 +1,88 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
var Server = require('../server');
|
||||||
|
var port_picker = require('../port_picker');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to every request with the same data as a response
|
||||||
|
* @param {Stream} stream
|
||||||
|
*/
|
||||||
|
function echoHandler(stream) {
|
||||||
|
stream.pipe(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('echo server', function() {
|
||||||
|
it('should echo inputs as responses', function(done) {
|
||||||
|
done = multiDone(done, 4);
|
||||||
|
port_picker.nextAvailablePort(function(port) {
|
||||||
|
var server = new Server();
|
||||||
|
server.bind(port);
|
||||||
|
server.register('echo', echoHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
var req_text = 'echo test string';
|
||||||
|
var status_text = 'OK';
|
||||||
|
|
||||||
|
var channel = new grpc.Channel(port);
|
||||||
|
var deadline = new Date();
|
||||||
|
deadline.setSeconds(deadline.getSeconds() + 3);
|
||||||
|
var call = new grpc.Call(channel,
|
||||||
|
'echo',
|
||||||
|
deadline);
|
||||||
|
call.startInvoke(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.INVOKE_ACCEPTED);
|
||||||
|
call.startWrite(
|
||||||
|
new Buffer(req_text),
|
||||||
|
function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.WRITE_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
call.writesDone(function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.FINISH_ACCEPTED);
|
||||||
|
assert.strictEqual(event.data, grpc.opError.OK);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
call.startRead(function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.READ);
|
||||||
|
assert.strictEqual(event.data.toString(), req_text);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type,
|
||||||
|
grpc.completionType.CLIENT_METADATA_READ);
|
||||||
|
done();
|
||||||
|
},function(event) {
|
||||||
|
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
||||||
|
var status = event.data;
|
||||||
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.strictEqual(status.details, status_text);
|
||||||
|
server.shutdown();
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
var assert = require('assert');
|
||||||
|
var grpc = require('./build/Debug/grpc');
|
||||||
|
var Server = require('server');
|
||||||
|
|
||||||
|
function echoHandler(arg_iter) {
|
||||||
|
return {
|
||||||
|
'next' : function(write) {
|
||||||
|
arg_iter.next(function(err, value) {
|
||||||
|
write(err, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('echo server', function() {
|
||||||
|
it('should echo inputs as responses', function(done) {
|
||||||
|
var server = new Server('localhost:5000');
|
||||||
|
server.register('echo', echoHandler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/time.h"
|
||||||
|
#include "timeval.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
gpr_timespec MillisecondsToTimespec(double millis) {
|
||||||
|
if (millis == std::numeric_limits<double>::infinity()) {
|
||||||
|
return gpr_inf_future;
|
||||||
|
} else if (millis == -std::numeric_limits<double>::infinity()) {
|
||||||
|
return gpr_inf_past;
|
||||||
|
} else {
|
||||||
|
return gpr_time_from_micros(static_cast<int64_t>(millis*1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double TimespecToMilliseconds(gpr_timespec timespec) {
|
||||||
|
if (gpr_time_cmp(timespec, gpr_inf_future) == 0) {
|
||||||
|
return std::numeric_limits<double>::infinity();
|
||||||
|
} else if (gpr_time_cmp(timespec, gpr_inf_past) == 0) {
|
||||||
|
return -std::numeric_limits<double>::infinity();
|
||||||
|
} else {
|
||||||
|
struct timeval time = gpr_timeval_from_timespec(timespec);
|
||||||
|
return (static_cast<double>(time.tv_sec) * 1000 +
|
||||||
|
static_cast<double>(time.tv_usec) / 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef NET_GRPC_NODE_TIMEVAL_H_
|
||||||
|
#define NET_GRPC_NODE_TIMEVAL_H_
|
||||||
|
|
||||||
|
#include "grpc/support/time.h"
|
||||||
|
|
||||||
|
namespace grpc {
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
double TimespecToMilliseconds(gpr_timespec time);
|
||||||
|
gpr_timespec MillisecondsToTimespec(double millis);
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
} // namespace grpc
|
||||||
|
|
||||||
|
#endif // NET_GRPC_NODE_TIMEVAL_H_
|
Loading…
Reference in New Issue