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