Resolved merge conflicts with master

This commit is contained in:
murgatroid99 2015-03-18 11:07:51 -07:00
commit aeff44dba7
61 changed files with 4970 additions and 2497 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build
node_modules

28
.jshintrc Normal file
View File

@ -0,0 +1,28 @@
{
"bitwise": true,
"curly": true,
"eqeqeq": true,
"esnext": true,
"freeze": true,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"maxlen": 80,
"newcap": true,
"node": true,
"noarg": true,
"quotmark": "single",
"strict": true,
"trailing": true,
"undef": true,
"unused": "vars",
"globals": {
/* Mocha-provided globals */
"describe": false,
"it": false,
"before": false,
"beforeEach": false,
"after": false,
"afterEach": false
}
}

28
LICENSE Normal file
View File

@ -0,0 +1,28 @@
Copyright 2015, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,12 +1,78 @@
# Node.js GRPC extension
# Node.js gRPC Library
The package is built with
## Status
node-gyp configure
node-gyp build
Alpha : Ready for early adopters
or, for brevity
## Prerequisites
node-gyp configure build
This requires `node` to be installed. If you instead have the `nodejs` executable on Debian, you should install the [`nodejs-legacy`](https://packages.debian.org/sid/nodejs-legacy) package.
The tests can be run with `npm test` on a dev install.
## Installation
1. Clone [the grpc repository](https://github.com/grpc/grpc).
2. Follow the instructions in the `INSTALL` file in the root of that repository to install the C core library that this package depends on.
3. Run `npm install`.
## Tests
To run the test suite, simply run `npm test` in the install location.
## API
This library internally uses [ProtoBuf.js](https://github.com/dcodeIO/ProtoBuf.js), and some structures it exports match those exported by that library
If you require this module, you will get an object with the following members
```javascript
function load(filename)
```
Takes a filename of a [Protocol Buffer](https://developers.google.com/protocol-buffers/) file, and returns an object representing the structure of the protocol buffer in the following way:
- Namespaces become maps from the names of their direct members to those member objects
- Service definitions become client constructors for clients for that service. They also have a `service` member that can be used for constructing servers.
- Message definitions become Message constructors like those that ProtoBuf.js would create
- Enum definitions become Enum objects like those that ProtoBuf.js would create
- Anything else becomes the relevant reflection object that ProtoBuf.js would create
```javascript
function loadObject(reflectionObject)
```
Returns the same structure that `load` returns, but takes a reflection object from `ProtoBuf.js` instead of a file name.
```javascript
function buildServer(serviceArray)
```
Takes an array of service objects and returns a constructor for a server that handles requests to all of those services.
```javascript
status
```
An object mapping status names to status code numbers.
```javascript
callError
```
An object mapping call error names to codes. This is primarily useful for tracking down certain kinds of internal errors.
```javascript
Credentials
```
An object with factory methods for creating credential objects for clients.
```javascript
ServerCredentials
```
An object with factory methods fro creating credential objects for servers.

View File

@ -1,21 +1,17 @@
{
"variables" : {
'no_install': "<!(echo $GRPC_NO_INSTALL)",
'grpc_root': "<!(echo $GRPC_ROOT)",
'grpc_lib_subdir': "<!(echo $GRPC_LIB_SUBDIR)"
},
"targets" : [
{
'include_dirs': [
"<!(nodejs -e \"require('nan')\")"
"<!(node -e \"require('nan')\")"
],
'cxxflags': [
'cflags': [
'-std=c++0x',
'-Wall',
'-pthread',
'-pedantic',
'-g',
'-zdefs'
'-Werror',
'-Werror'
],
'ldflags': [
'-g'
@ -23,7 +19,9 @@
'link_settings': {
'libraries': [
'-lrt',
'-lpthread'
'-lpthread',
'-lgrpc',
'-lgpr'
],
},
"target_name": "grpc",
@ -33,33 +31,10 @@
"ext/channel.cc",
"ext/completion_queue_async_worker.cc",
"ext/credentials.cc",
"ext/event.cc",
"ext/node_grpc.cc",
"ext/server.cc",
"ext/server_credentials.cc",
"ext/tag.cc",
"ext/timeval.cc"
],
'conditions' : [
['no_install=="yes"', {
'include_dirs': [
"<(grpc_root)/include"
],
'link_settings': {
'libraries': [
'<(grpc_root)/<(grpc_lib_subdir)/libgrpc.a',
'<(grpc_root)/<(grpc_lib_subdir)/libgpr.a'
]
}
}],
['no_install!="yes"', {
'link_settings': {
'libraries': [
'-lgrpc',
'-lgpr'
]
}
}]
]
}
]

View File

@ -1,3 +1,33 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package math;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,9 +31,8 @@
*
*/
var _ = require('underscore');
var ProtoBuf = require('protobufjs');
var fs = require('fs');
'use strict';
var util = require('util');
var Transform = require('stream').Transform;
@ -128,7 +127,8 @@ var server = new Server({
});
if (require.main === module) {
server.bind('localhost:7070').listen();
server.bind('0.0.0.0:7070');
server.listen();
}
/**

118
examples/perf_test.js Normal file
View File

@ -0,0 +1,118 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
var grpc = require('..');
var testProto = grpc.load(__dirname + '/../interop/test.proto').grpc.testing;
var _ = require('underscore');
var interop_server = require('../interop/interop_server.js');
function runTest(iterations, callback) {
var testServer = interop_server.getServer(0, false);
testServer.server.listen();
var client = new testProto.TestService('localhost:' + testServer.port);
function runIterations(finish) {
var start = process.hrtime();
var intervals = [];
function next(i) {
if (i >= iterations) {
testServer.server.shutdown();
var totalDiff = process.hrtime(start);
finish({
total: totalDiff[0] * 1000000 + totalDiff[1] / 1000,
intervals: intervals
});
} else{
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 3);
var startTime = process.hrtime();
client.emptyCall({}, function(err, resp) {
var timeDiff = process.hrtime(startTime);
intervals[i] = timeDiff[0] * 1000000 + timeDiff[1] / 1000;
next(i+1);
}, {}, deadline);
}
}
next(0);
}
function warmUp(num) {
var pending = num;
function startCall() {
client.emptyCall({}, function(err, resp) {
pending--;
if (pending === 0) {
runIterations(callback);
}
});
}
for (var i = 0; i < num; i++) {
startCall();
}
}
warmUp(100);
}
function percentile(arr, pct) {
if (pct > 99) {
pct = 99;
}
if (pct < 0) {
pct = 0;
}
var index = Math.floor(arr.length * pct / 100);
return arr[index];
}
if (require.main === module) {
var count;
if (process.argv.length >= 3) {
count = process.argv[2];
} else {
count = 100;
}
runTest(count, function(results) {
var sorted_intervals = _.sortBy(results.intervals, _.identity);
console.log('count:', count);
console.log('total time:', results.total, 'us');
console.log('median:', percentile(sorted_intervals, 50), 'us');
console.log('90th percentile:', percentile(sorted_intervals, 90), 'us');
console.log('95th percentile:', percentile(sorted_intervals, 95), 'us');
console.log('99th percentile:', percentile(sorted_intervals, 99), 'us');
console.log('QPS:', (count / results.total) * 1000000);
});
}
module.exports = runTest;

View File

@ -0,0 +1,44 @@
// This file will be moved to a new location.
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto2";
package proto2;
// An empty message that you can re-use to avoid defining duplicated empty
// messages in your project. A typical example is to use it as argument or the
// return value of a service API. For instance:
//
// service Foo {
// rpc Bar (proto2.Empty) returns (proto2.Empty) { };
// };
//
message Empty {}

View File

@ -0,0 +1,79 @@
// This file will be moved to a new location.
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Labels provide a way to associate user-defined metadata with various
// objects. Labels may be used to organize objects into non-hierarchical
// groups; think metadata tags attached to mp3s.
syntax = "proto2";
package tech.label;
// A key-value pair applied to a given object.
message Label {
// The key of a label is a syntactically valid URL (as per RFC 1738) with
// the "scheme" and initial slashes omitted and with the additional
// restrictions noted below. Each key should be globally unique. The
// "host" portion is called the "namespace" and is not necessarily
// resolvable to a network endpoint. Instead, the namespace indicates what
// system or entity defines the semantics of the label. Namespaces do not
// restrict the set of objects to which a label may be associated.
//
// Keys are defined by the following grammar:
//
// key = hostname "/" kpath
// kpath = ksegment *[ "/" ksegment ]
// ksegment = alphadigit | *[ alphadigit | "-" | "_" | "." ]
//
// where "hostname" and "alphadigit" are defined as in RFC 1738.
//
// Example key:
// spanner.google.com/universe
required string key = 1;
// The value of the label.
oneof value {
// A string value.
string str_value = 2;
// An integer value.
int64 num_value = 3;
}
}
// A collection of labels, such as the set of all labels attached to an
// object. Each label in the set must have a different key.
//
// Users should prefer to embed "repeated Label" directly when possible.
// This message should only be used in cases where that isn't possible (e.g.
// with oneof).
message Labels {
repeated Label label = 1;
}

View File

@ -0,0 +1,734 @@
// This file will be moved to a new location.
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Specification of the Pubsub API.
syntax = "proto2";
import "empty.proto";
import "label.proto";
package tech.pubsub;
// -----------------------------------------------------------------------------
// Overview of the Pubsub API
// -----------------------------------------------------------------------------
// This file describes an API for a Pubsub system. This system provides a
// reliable many-to-many communication mechanism between independently written
// publishers and subscribers where the publisher publishes messages to "topics"
// and each subscriber creates a "subscription" and consumes messages from it.
//
// (a) The pubsub system maintains bindings between topics and subscriptions.
// (b) A publisher publishes messages into a topic.
// (c) The pubsub system delivers messages from topics into relevant
// subscriptions.
// (d) A subscriber receives pending messages from its subscription and
// acknowledges or nacks each one to the pubsub system.
// (e) The pubsub system removes acknowledged messages from that subscription.
// -----------------------------------------------------------------------------
// Data Model
// -----------------------------------------------------------------------------
// The data model consists of the following:
//
// * Topic: A topic is a resource to which messages are published by publishers.
// Topics are named, and the name of the topic is unique within the pubsub
// system.
//
// * Subscription: A subscription records the subscriber's interest in a topic.
// It can optionally include a query to select a subset of interesting
// messages. The pubsub system maintains a logical cursor tracking the
// matching messages which still need to be delivered and acked so that
// they can retried as needed. The set of messages that have not been
// acknowledged is called the subscription backlog.
//
// * Message: A message is a unit of data that flows in the system. It contains
// opaque data from the publisher along with its labels.
//
// * Message Labels (optional): A set of opaque key, value pairs assigned
// by the publisher which the subscriber can use for filtering out messages
// in the topic. For example, a label with key "foo.com/device_type" and
// value "mobile" may be added for messages that are only relevant for a
// mobile subscriber; a subscriber on a phone may decide to create a
// subscription only for messages that have this label.
// -----------------------------------------------------------------------------
// Publisher Flow
// -----------------------------------------------------------------------------
// A publisher publishes messages to the topic using the Publish request:
//
// PubsubMessage message;
// message.set_data("....");
// Label label;
// label.set_key("foo.com/key1");
// label.set_str_value("value1");
// message.add_label(label);
// PublishRequest request;
// request.set_topic("topicName");
// request.set_message(message);
// PublisherService.Publish(request);
// -----------------------------------------------------------------------------
// Subscriber Flow
// -----------------------------------------------------------------------------
// The subscriber part of the API is richer than the publisher part and has a
// number of concepts w.r.t. subscription creation and monitoring:
//
// (1) A subscriber creates a subscription using the CreateSubscription call.
// It may specify an optional "query" to indicate that it wants to receive
// only messages with a certain set of labels using the label query syntax.
// It may also specify an optional truncation policy to indicate when old
// messages from the subcription can be removed.
//
// (2) A subscriber receives messages in one of two ways: via push or pull.
//
// (a) To receive messages via push, the PushConfig field must be specified in
// the Subscription parameter when creating a subscription. The PushConfig
// specifies an endpoint at which the subscriber must expose the
// PushEndpointService. Messages are received via the HandlePubsubEvent
// method. The push subscriber responds to the HandlePubsubEvent method
// with a result code that indicates one of three things: Ack (the message
// has been successfully processed and the Pubsub system may delete it),
// Nack (the message has been rejected, the Pubsub system should resend it
// at a later time), or Push-Back (this is a Nack with the additional
// semantics that the subscriber is overloaded and the pubsub system should
// back off on the rate at which it is invoking HandlePubsubEvent). The
// endpoint may be a load balancer for better scalability.
//
// (b) To receive messages via pull a subscriber calls the Pull method on the
// SubscriberService to get messages from the subscription. For each
// individual message, the subscriber may use the ack_id received in the
// PullResponse to Ack the message, Nack the message, or modify the ack
// deadline with ModifyAckDeadline. See the
// Subscription.ack_deadline_seconds field documentation for details on the
// ack deadline behavior.
//
// Note: Messages may be consumed in parallel by multiple subscribers making
// Pull calls to the same subscription; this will result in the set of
// messages from the subscription being shared and each subscriber
// receiving a subset of the messages.
//
// (4) The subscriber can explicitly truncate the current subscription.
//
// (5) "Truncated" events are delivered when a subscription is
// truncated, whether due to the subscription's truncation policy
// or an explicit request from the subscriber.
//
// Subscription creation:
//
// Subscription subscription;
// subscription.set_topic("topicName");
// subscription.set_name("subscriptionName");
// subscription.push_config().set_push_endpoint("machinename:8888");
// SubscriberService.CreateSubscription(subscription);
//
// Consuming messages via push:
//
// TODO(eschapira): Add HTTP push example.
//
// The port 'machinename:8888' must be bound to a stubby server that implements
// the PushEndpointService with the following method:
//
// int HandlePubsubEvent(PubsubEvent event) {
// if (event.subscription().equals("subscriptionName")) {
// if (event.has_message()) {
// Process(event.message().data());
// } else if (event.truncated()) {
// ProcessTruncatedEvent();
// }
// }
// return OK; // This return code implies an acknowledgment
// }
//
// Consuming messages via pull:
//
// The subscription must be created without setting the push_config field.
//
// PullRequest pull_request;
// pull_request.set_subscription("subscriptionName");
// pull_request.set_return_immediately(false);
// while (true) {
// PullResponse pull_response;
// if (SubscriberService.Pull(pull_request, pull_response) == OK) {
// PubsubEvent event = pull_response.pubsub_event();
// if (event.has_message()) {
// Process(event.message().data());
// } else if (event.truncated()) {
// ProcessTruncatedEvent();
// }
// AcknowledgeRequest ack_request;
// ackRequest.set_subscription("subscriptionName");
// ackRequest.set_ack_id(pull_response.ack_id());
// SubscriberService.Acknowledge(ack_request);
// }
// }
// -----------------------------------------------------------------------------
// Reliability Semantics
// -----------------------------------------------------------------------------
// When a subscriber successfully creates a subscription using
// Subscriber.CreateSubscription, it establishes a "subscription point" with
// respect to that subscription - the subscriber is guaranteed to receive any
// message published after this subscription point that matches the
// subscription's query. Note that messages published before the Subscription
// point may or may not be delivered.
//
// If the system truncates the subscription according to the specified
// truncation policy, the system delivers a subscription status event with the
// "truncated" field set to true. We refer to such events as "truncation
// events". A truncation event:
//
// * Informs the subscriber that part of the subscription messages have been
// discarded. The subscriber may want to recover from the message loss, e.g.,
// by resyncing its state with its backend.
// * Establishes a new subscription point, i.e., the subscriber is guaranteed to
// receive all changes published after the trunction event is received (or
// until another truncation event is received).
//
// Note that messages are not delivered in any particular order by the pubsub
// system. Furthermore, the system guarantees at-least-once delivery
// of each message or truncation events until acked.
// -----------------------------------------------------------------------------
// Deletion
// -----------------------------------------------------------------------------
// Both topics and subscriptions may be deleted. Deletion of a topic implies
// deletion of all attached subscriptions.
//
// When a subscription is deleted directly by calling DeleteSubscription, all
// messages are immediately dropped. If it is a pull subscriber, future pull
// requests will return NOT_FOUND.
//
// When a topic is deleted all corresponding subscriptions are immediately
// deleted, and subscribers experience the same behavior as directly deleting
// the subscription.
// -----------------------------------------------------------------------------
// The Publisher service and its protos.
// -----------------------------------------------------------------------------
// The service that an application uses to manipulate topics, and to send
// messages to a topic.
service PublisherService {
// Creates the given topic with the given name.
rpc CreateTopic(Topic) returns (Topic) {
}
// Adds a message to the topic. Returns NOT_FOUND if the topic does not
// exist.
// (-- For different error code values returned via Stubby, see
// util/task/codes.proto. --)
rpc Publish(PublishRequest) returns (proto2.Empty) {
}
// Adds one or more messages to the topic. Returns NOT_FOUND if the topic does
// not exist.
rpc PublishBatch(PublishBatchRequest) returns (PublishBatchResponse) {
}
// Gets the configuration of a topic. Since the topic only has the name
// attribute, this method is only useful to check the existence of a topic.
// If other attributes are added in the future, they will be returned here.
rpc GetTopic(GetTopicRequest) returns (Topic) {
}
// Lists matching topics.
rpc ListTopics(ListTopicsRequest) returns (ListTopicsResponse) {
}
// Deletes the topic with the given name. All subscriptions to this topic
// are also deleted. Returns NOT_FOUND if the topic does not exist.
// After a topic is deleted, a new topic may be created with the same name.
rpc DeleteTopic(DeleteTopicRequest) returns (proto2.Empty) {
}
}
// A topic resource.
message Topic {
// Name of the topic.
optional string name = 1;
}
// A message data and its labels.
message PubsubMessage {
// The message payload.
optional bytes data = 1;
// Optional list of labels for this message. Keys in this collection must
// be unique.
//(-- TODO(eschapira): Define how key namespace may be scoped to the topic.--)
repeated tech.label.Label label = 2;
// ID of this message assigned by the server at publication time. Guaranteed
// to be unique within the topic. This value may be read by a subscriber
// that receives a PubsubMessage via a Pull call or a push delivery. It must
// not be populated by a publisher in a Publish call.
optional string message_id = 3;
}
// Request for the GetTopic method.
message GetTopicRequest {
// The name of the topic to get.
optional string topic = 1;
}
// Request for the Publish method.
message PublishRequest {
// The message in the request will be published on this topic.
optional string topic = 1;
// The message to publish.
optional PubsubMessage message = 2;
}
// Request for the PublishBatch method.
message PublishBatchRequest {
// The messages in the request will be published on this topic.
optional string topic = 1;
// The messages to publish.
repeated PubsubMessage messages = 2;
}
// Response for the PublishBatch method.
message PublishBatchResponse {
// The server-assigned ID of each published message, in the same order as
// the messages in the request. IDs are guaranteed to be unique within
// the topic.
repeated string message_ids = 1;
}
// Request for the ListTopics method.
message ListTopicsRequest {
// A valid label query expression.
//
optional string query = 1;
// Maximum number of topics to return.
// (-- If not specified or <= 0, the implementation will select a reasonable
// value. --)
optional int32 max_results = 2;
// The value obtained in the last <code>ListTopicsResponse</code>
// for continuation.
optional string page_token = 3;
}
// Response for the ListTopics method.
message ListTopicsResponse {
// The resulting topics.
repeated Topic topic = 1;
// If not empty, indicates that there are more topics that match the request,
// and this value should be passed to the next <code>ListTopicsRequest</code>
// to continue.
optional string next_page_token = 2;
}
// Request for the Delete method.
message DeleteTopicRequest {
// Name of the topic to delete.
optional string topic = 1;
}
// -----------------------------------------------------------------------------
// The Subscriber service and its protos.
// -----------------------------------------------------------------------------
// The service that an application uses to manipulate subscriptions and to
// consume messages from a subscription via the pull method.
service SubscriberService {
// Creates a subscription on a given topic for a given subscriber.
// If the subscription already exists, returns ALREADY_EXISTS.
// If the corresponding topic doesn't exist, returns NOT_FOUND.
//
// If the name is not provided in the request, the server will assign a random
// name for this subscription on the same project as the topic.
rpc CreateSubscription(Subscription) returns (Subscription) {
}
// Gets the configuration details of a subscription.
rpc GetSubscription(GetSubscriptionRequest) returns (Subscription) {
}
// Lists matching subscriptions.
rpc ListSubscriptions(ListSubscriptionsRequest)
returns (ListSubscriptionsResponse) {
}
// Deletes an existing subscription. All pending messages in the subscription
// are immediately dropped. Calls to Pull after deletion will return
// NOT_FOUND.
rpc DeleteSubscription(DeleteSubscriptionRequest) returns (proto2.Empty) {
}
// Removes all the pending messages in the subscription and releases the
// storage associated with them. Results in a truncation event to be sent to
// the subscriber. Messages added after this call returns are stored in the
// subscription as before.
rpc TruncateSubscription(TruncateSubscriptionRequest) returns (proto2.Empty) {
}
//
// Push subscriber calls.
//
// Modifies the <code>PushConfig</code> for a specified subscription.
// This method can be used to suspend the flow of messages to an endpoint
// by clearing the <code>PushConfig</code> field in the request. Messages
// will be accumulated for delivery even if no push configuration is
// defined or while the configuration is modified.
rpc ModifyPushConfig(ModifyPushConfigRequest) returns (proto2.Empty) {
}
//
// Pull Subscriber calls
//
// Pulls a single message from the server.
// If return_immediately is true, and no messages are available in the
// subscription, this method returns FAILED_PRECONDITION. The system is free
// to return an UNAVAILABLE error if no messages are available in a
// reasonable amount of time (to reduce system load).
rpc Pull(PullRequest) returns (PullResponse) {
}
// Pulls messages from the server. Returns an empty list if there are no
// messages available in the backlog. The system is free to return UNAVAILABLE
// if there are too many pull requests outstanding for the given subscription.
rpc PullBatch(PullBatchRequest) returns (PullBatchResponse) {
}
// Modifies the Ack deadline for a message received from a pull request.
rpc ModifyAckDeadline(ModifyAckDeadlineRequest) returns (proto2.Empty) {
}
// Acknowledges a particular received message: the Pub/Sub system can remove
// the given message from the subscription. Acknowledging a message whose
// Ack deadline has expired may succeed, but the message could have been
// already redelivered. Acknowledging a message more than once will not
// result in an error. This is only used for messages received via pull.
rpc Acknowledge(AcknowledgeRequest) returns (proto2.Empty) {
}
// Refuses processing a particular received message. The system will
// redeliver this message to some consumer of the subscription at some
// future time. This is only used for messages received via pull.
rpc Nack(NackRequest) returns (proto2.Empty) {
}
}
// A subscription resource.
message Subscription {
// Name of the subscription.
optional string name = 1;
// The name of the topic from which this subscription is receiving messages.
optional string topic = 2;
// If <code>query</code> is non-empty, only messages on the subscriber's
// topic whose labels match the query will be returned. Otherwise all
// messages on the topic will be returned.
//
optional string query = 3;
// The subscriber may specify requirements for truncating unacknowledged
// subscription entries. The system will honor the
// <code>CreateSubscription</code> request only if it can meet these
// requirements. If this field is not specified, messages are never truncated
// by the system.
optional TruncationPolicy truncation_policy = 4;
// Specifies which messages can be truncated by the system.
message TruncationPolicy {
oneof policy {
// If <code>max_bytes</code> is specified, the system is allowed to drop
// old messages to keep the combined size of stored messages under
// <code>max_bytes</code>. This is a hint; the system may keep more than
// this many bytes, but will make a best effort to keep the size from
// growing much beyond this parameter.
int64 max_bytes = 1;
// If <code>max_age_seconds</code> is specified, the system is allowed to
// drop messages that have been stored for at least this many seconds.
// This is a hint; the system may keep these messages, but will make a
// best effort to remove them when their maximum age is reached.
int64 max_age_seconds = 2;
}
}
// If push delivery is used with this subscription, this field is
// used to configure it.
optional PushConfig push_config = 5;
// For either push or pull delivery, the value is the maximum time after a
// subscriber receives a message before the subscriber should acknowledge or
// Nack the message. If the Ack deadline for a message passes without an
// Ack or a Nack, the Pub/Sub system will eventually redeliver the message.
// If a subscriber acknowledges after the deadline, the Pub/Sub system may
// accept the Ack, but it is possible that the message has been already
// delivered again. Multiple Acks to the message are allowed and will
// succeed.
//
// For push delivery, this value is used to set the request timeout for
// the call to the push endpoint.
//
// For pull delivery, this value is used as the initial value for the Ack
// deadline. It may be overridden for a specific pull request (message) with
// <code>ModifyAckDeadline</code>.
// While a message is outstanding (i.e. it has been delivered to a pull
// subscriber and the subscriber has not yet Acked or Nacked), the Pub/Sub
// system will not deliver that message to another pull subscriber
// (on a best-effort basis).
optional int32 ack_deadline_seconds = 6;
// If this parameter is set to n, the system is allowed to (but not required
// to) delete the subscription when at least n seconds have elapsed since the
// client presence was detected. (Presence is detected through any
// interaction using the subscription ID, including Pull(), Get(), or
// acknowledging a message.)
//
// If this parameter is not set, the subscription will stay live until
// explicitly deleted.
//
// Clients can detect such garbage collection when a Get call or a Pull call
// (for pull subscribers only) returns NOT_FOUND.
optional int64 garbage_collect_seconds = 7;
}
// Configuration for a push delivery endpoint.
message PushConfig {
// A URL locating the endpoint to which messages should be pushed.
// For example, a Webhook endpoint might use "https://example.com/push".
// (-- An Android application might use "gcm:<REGID>", where <REGID> is a
// GCM registration id allocated for pushing messages to the application. --)
optional string push_endpoint = 1;
}
// An event indicating a received message or truncation event.
message PubsubEvent {
// The subscription that received the event.
optional string subscription = 1;
oneof type {
// A received message.
PubsubMessage message = 2;
// Indicates that this subscription has been truncated.
bool truncated = 3;
// Indicates that this subscription has been deleted. (Note that pull
// subscribers will always receive NOT_FOUND in response in their pull
// request on the subscription, rather than seeing this boolean.)
bool deleted = 4;
}
}
// Request for the GetSubscription method.
message GetSubscriptionRequest {
// The name of the subscription to get.
optional string subscription = 1;
}
// Request for the ListSubscriptions method.
message ListSubscriptionsRequest {
// A valid label query expression.
// (-- Which labels are required or supported is implementation-specific.
// TODO(eschapira): This method must support to query by topic. We must
// define the key URI for the "topic" label. --)
optional string query = 1;
// Maximum number of subscriptions to return.
// (-- If not specified or <= 0, the implementation will select a reasonable
// value. --)
optional int32 max_results = 3;
// The value obtained in the last <code>ListSubscriptionsResponse</code>
// for continuation.
optional string page_token = 4;
}
// Response for the ListSubscriptions method.
message ListSubscriptionsResponse {
// The subscriptions that match the request.
repeated Subscription subscription = 1;
// If not empty, indicates that there are more subscriptions that match the
// request and this value should be passed to the next
// <code>ListSubscriptionsRequest</code> to continue.
optional string next_page_token = 2;
}
// Request for the TruncateSubscription method.
message TruncateSubscriptionRequest {
// The subscription that is being truncated.
optional string subscription = 1;
}
// Request for the DeleteSubscription method.
message DeleteSubscriptionRequest {
// The subscription to delete.
optional string subscription = 1;
}
// Request for the ModifyPushConfig method.
message ModifyPushConfigRequest {
// The name of the subscription.
optional string subscription = 1;
// An empty <code>push_config</code> indicates that the Pub/Sub system should
// pause pushing messages from the given subscription.
optional PushConfig push_config = 2;
}
// -----------------------------------------------------------------------------
// The protos used by a pull subscriber.
// -----------------------------------------------------------------------------
// Request for the Pull method.
message PullRequest {
// The subscription from which a message should be pulled.
optional string subscription = 1;
// If this is specified as true the system will respond immediately even if
// it is not able to return a message in the Pull response. Otherwise the
// system is allowed to wait until at least one message is available rather
// than returning FAILED_PRECONDITION. The client may cancel the request if
// it does not wish to wait any longer for the response.
optional bool return_immediately = 2;
}
// Either a <code>PubsubMessage</code> or a truncation event. One of these two
// must be populated.
message PullResponse {
// This ID must be used to acknowledge the received event or message.
optional string ack_id = 1;
// A pubsub message or truncation event.
optional PubsubEvent pubsub_event = 2;
}
// Request for the PullBatch method.
message PullBatchRequest {
// The subscription from which messages should be pulled.
optional string subscription = 1;
// If this is specified as true the system will respond immediately even if
// it is not able to return a message in the Pull response. Otherwise the
// system is allowed to wait until at least one message is available rather
// than returning no messages. The client may cancel the request if it does
// not wish to wait any longer for the response.
optional bool return_immediately = 2;
// The maximum number of PubsubEvents returned for this request. The Pub/Sub
// system may return fewer than the number of events specified.
optional int32 max_events = 3;
}
// Response for the PullBatch method.
message PullBatchResponse {
// Received Pub/Sub messages or status events. The Pub/Sub system will return
// zero messages if there are no more messages available in the backlog. The
// Pub/Sub system may return fewer than the max_events requested even if
// there are more messages available in the backlog.
repeated PullResponse pull_responses = 2;
}
// Request for the ModifyAckDeadline method.
message ModifyAckDeadlineRequest {
// The name of the subscription from which messages are being pulled.
optional string subscription = 1;
// The acknowledgment ID.
optional string ack_id = 2;
// The new Ack deadline. Must be >= 0.
optional int32 ack_deadline_seconds = 3;
}
// Request for the Acknowledge method.
message AcknowledgeRequest {
// The subscription whose message is being acknowledged.
optional string subscription = 1;
// The acknowledgment ID for the message being acknowledged. This was
// returned by the Pub/Sub system in the Pull response.
repeated string ack_id = 2;
}
// Request for the Nack method.
message NackRequest {
// The subscription whose message is being Nacked.
optional string subscription = 1;
// The acknowledgment ID for the message being refused. This was returned by
// the Pub/Sub system in the Pull response.
repeated string ack_id = 2;
}
// -----------------------------------------------------------------------------
// The service and protos used by a push subscriber.
// -----------------------------------------------------------------------------
// The service that a subscriber uses to handle messages sent via push
// delivery.
// This service is not currently exported for HTTP clients.
// TODO(eschapira): Explain HTTP subscribers.
service PushEndpointService {
// Sends a <code>PubsubMessage</code> or a subscription status event to a
// push endpoint.
// The push endpoint responds with an empty message and a code from
// util/task/codes.proto. The following codes have a particular meaning to the
// Pub/Sub system:
// OK - This is interpreted by Pub/Sub as Ack.
// ABORTED - This is intepreted by Pub/Sub as a Nack, without implying
// pushback for congestion control. The Pub/Sub system will
// retry this message at a later time.
// UNAVAILABLE - This is intepreted by Pub/Sub as a Nack, with the additional
// semantics of push-back. The Pub/Sub system will use an AIMD
// congestion control algorithm to backoff the rate of sending
// messages from this subscription.
// Any other code, or a failure to respond, will be interpreted in the same
// way as ABORTED; i.e. the system will retry the message at a later time to
// ensure reliable delivery.
rpc HandlePubsubEvent(PubsubEvent) returns (proto2.Empty);
}

View File

@ -0,0 +1,285 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
var async = require('async');
var fs = require('fs');
var GoogleAuth = require('google-auth-library');
var parseArgs = require('minimist');
var strftime = require('strftime');
var _ = require('underscore');
var grpc = require('../..');
var PROTO_PATH = __dirname + '/pubsub.proto';
var pubsub = grpc.load(PROTO_PATH).tech.pubsub;
function PubsubRunner(pub, sub, args) {
this.pub = pub;
this.sub = sub;
this.args = args;
}
PubsubRunner.prototype.getTestTopicName = function() {
var base_name = '/topics/' + this.args.project_id + '/';
if (this.args.topic_name) {
return base_name + this.args.topic_name;
}
var now_text = strftime('%Y%m%d%H%M%S%L');
return base_name + process.env.USER + '-' + now_text;
};
PubsubRunner.prototype.getTestSubName = function() {
var base_name = '/subscriptions/' + this.args.project_id + '/';
if (this.args.sub_name) {
return base_name + this.args.sub_name;
}
var now_text = strftime('%Y%m%d%H%M%S%L');
return base_name + process.env.USER + '-' + now_text;
};
PubsubRunner.prototype.listProjectTopics = function(callback) {
var q = ('cloud.googleapis.com/project in (/projects/' +
this.args.project_id + ')');
this.pub.listTopics({query: q}, callback);
};
PubsubRunner.prototype.topicExists = function(name, callback) {
this.listProjectTopics(function(err, response) {
if (err) {
callback(err);
} else {
callback(null, _.some(response.topic, function(t) {
return t.name === name;
}));
}
});
};
PubsubRunner.prototype.createTopicIfNeeded = function(name, callback) {
var self = this;
this.topicExists(name, function(err, exists) {
if (err) {
callback(err);
} else{
if (exists) {
callback(null);
} else {
self.pub.createTopic({name: name}, callback);
}
}
});
};
PubsubRunner.prototype.removeTopic = function(callback) {
var name = this.getTestTopicName();
console.log('... removing Topic', name);
this.pub.deleteTopic({topic: name}, function(err, value) {
if (err) {
console.log('Could not delete a topic: rpc failed with', err);
callback(err);
} else {
console.log('removed Topic', name, 'OK');
callback(null);
}
});
};
PubsubRunner.prototype.createTopic = function(callback) {
var name = this.getTestTopicName();
console.log('... creating Topic', name);
this.pub.createTopic({name: name}, function(err, value) {
if (err) {
console.log('Could not create a topic: rpc failed with', err);
callback(err);
} else {
console.log('created Topic', name, 'OK');
callback(null);
}
});
};
PubsubRunner.prototype.listSomeTopics = function(callback) {
console.log('Listing topics');
console.log('-------------_');
this.listProjectTopics(function(err, response) {
if (err) {
console.log('Could not list topic: rpc failed with', err);
callback(err);
} else {
_.each(response.topic, function(t) {
console.log(t.name);
});
callback(null);
}
});
};
PubsubRunner.prototype.checkExists = function(callback) {
var name = this.getTestTopicName();
console.log('... checking for topic', name);
this.topicExists(name, function(err, exists) {
if (err) {
console.log('Could not check for a topics: rpc failed with', err);
callback(err);
} else {
if (exists) {
console.log(name, 'is a topic');
} else {
console.log(name, 'is not a topic');
}
callback(null);
}
});
};
PubsubRunner.prototype.randomPubSub = function(callback) {
var self = this;
var topic_name = this.getTestTopicName();
var sub_name = this.getTestSubName();
var subscription = {name: sub_name, topic: topic_name};
async.waterfall([
_.bind(this.createTopicIfNeeded, this, topic_name),
_.bind(this.sub.createSubscription, this.sub, subscription),
function(resp, cb) {
var msg_count = _.random(10, 30);
// Set up msg_count messages to publish
var message_senders = _.times(msg_count, function(n) {
return _.bind(self.pub.publish, self.pub, {
topic: topic_name,
message: {data: new Buffer('message ' + n)}
});
});
async.parallel(message_senders, function(err, result) {
cb(err, result, msg_count);
});
},
function(result, msg_count, cb) {
console.log('Sent', msg_count, 'messages to', topic_name + ',',
'checking for them now.');
var batch_request = {
subscription: sub_name,
max_events: msg_count
};
self.sub.pullBatch(batch_request, cb);
},
function(batch, cb) {
var ack_id = _.pluck(batch.pull_responses, 'ack_id');
console.log('Got', ack_id.length, 'messages, acknowledging them...');
var ack_request = {
subscription: sub_name,
ack_id: ack_id
};
self.sub.acknowledge(ack_request, cb);
},
function(result, cb) {
console.log(
'Test messages were acknowledged OK, deleting the subscription');
self.sub.deleteSubscription({subscription: sub_name}, cb);
}
], function (err, result) {
if (err) {
console.log('Could not do random pub sub: rpc failed with', err);
}
callback(err, result);
});
};
function main(callback) {
var argv = parseArgs(process.argv, {
string: [
'host',
'oauth_scope',
'port',
'action',
'project_id',
'topic_name',
'sub_name'
],
default: {
host: 'pubsub-staging.googleapis.com',
oauth_scope: 'https://www.googleapis.com/auth/pubsub',
port: 443,
action: 'listSomeTopics',
project_id: 'stoked-keyword-656'
}
});
var valid_actions = [
'createTopic',
'removeTopic',
'listSomeTopics',
'checkExists',
'randomPubSub'
];
if (_.some(valid_actions, function(action) {
return action === argv.action;
})) {
callback(new Error('Action was not valid'));
}
var address = argv.host + ':' + argv.port;
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
if (err) {
callback(err);
return;
}
if (credential.createScopedRequired()) {
credential = credential.createScoped(argv.oauth_scope);
}
var updateMetadata = grpc.getGoogleAuthDelegate(credential);
var ca_path = process.env.SSL_CERT_FILE;
fs.readFile(ca_path, function(err, ca_data) {
if (err) {
callback(err);
return;
}
var ssl_creds = grpc.Credentials.createSsl(ca_data);
var options = {
credentials: ssl_creds,
'grpc.ssl_target_name_override': argv.host
};
var pub = new pubsub.PublisherService(address, options, updateMetadata);
var sub = new pubsub.SubscriberService(address, options, updateMetadata);
var runner = new PubsubRunner(pub, sub, argv);
runner[argv.action](callback);
});
});
}
if (require.main === module) {
main(function(err) {
if (err) {
throw err;
}
});
}
module.exports = PubsubRunner;

120
examples/route_guide.proto Normal file
View File

@ -0,0 +1,120 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
option java_package = "io.grpc.examples";
package examples;
// Interface exported by the server.
service RouteGuide {
// A simple RPC.
//
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
// A server-to-client streaming RPC.
//
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
// A client-to-server streaming RPC.
//
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
// A Bidirectional streaming RPC.
//
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
optional int32 latitude = 1;
optional int32 longitude = 2;
}
// A latitude-longitude rectangle, represented as two diagonally opposite
// points "lo" and "hi".
message Rectangle {
// One corner of the rectangle.
optional Point lo = 1;
// The other corner of the rectangle.
optional Point hi = 2;
}
// A feature names something at a given point.
//
// If a feature could not be named, the name is empty.
message Feature {
// The name of the feature.
optional string name = 1;
// The point where the feature is detected.
optional Point location = 2;
}
// A RouteNote is a message sent while at a given point.
message RouteNote {
// The location from which the message is sent.
optional Point location = 1;
// The message to be sent.
optional string message = 2;
}
// A RouteSummary is received in response to a RecordRoute rpc.
//
// It contains the number of individual points received, the number of
// detected features, and the total distance covered as the cumulative sum of
// the distance between each point.
message RouteSummary {
// The number of points received.
optional int32 point_count = 1;
// The number of known features passed while traversing the route.
optional int32 feature_count = 2;
// The distance covered in metres.
optional int32 distance = 3;
// The duration of the traversal in seconds.
optional int32 elapsed_time = 4;
}

View File

@ -0,0 +1,239 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
var async = require('async');
var fs = require('fs');
var parseArgs = require('minimist');
var path = require('path');
var _ = require('underscore');
var grpc = require('..');
var examples = grpc.load(__dirname + '/route_guide.proto').examples;
var client = new examples.RouteGuide('localhost:50051');
var COORD_FACTOR = 1e7;
/**
* Run the getFeature demo. Calls getFeature with a point known to have a
* feature and a point known not to have a feature.
* @param {function} callback Called when this demo is complete
*/
function runGetFeature(callback) {
var next = _.after(2, callback);
function featureCallback(error, feature) {
if (error) {
callback(error);
}
if (feature.name === '') {
console.log('Found no feature at ' +
feature.location.latitude/COORD_FACTOR + ', ' +
feature.location.longitude/COORD_FACTOR);
} else {
console.log('Found feature called "' + feature.name + '" at ' +
feature.location.latitude/COORD_FACTOR + ', ' +
feature.location.longitude/COORD_FACTOR);
}
next();
}
var point1 = {
latitude: 409146138,
longitude: -746188906
};
var point2 = {
latitude: 0,
longitude: 0
};
client.getFeature(point1, featureCallback);
client.getFeature(point2, featureCallback);
}
/**
* Run the listFeatures demo. Calls listFeatures with a rectangle containing all
* of the features in the pre-generated database. Prints each response as it
* comes in.
* @param {function} callback Called when this demo is complete
*/
function runListFeatures(callback) {
var rectangle = {
lo: {
latitude: 400000000,
longitude: -750000000
},
hi: {
latitude: 420000000,
longitude: -730000000
}
};
console.log('Looking for features between 40, -75 and 42, -73');
var call = client.listFeatures(rectangle);
call.on('data', function(feature) {
console.log('Found feature called "' + feature.name + '" at ' +
feature.location.latitude/COORD_FACTOR + ', ' +
feature.location.longitude/COORD_FACTOR);
});
call.on('end', callback);
}
/**
* Run the recordRoute demo. Sends several randomly chosen points from the
* pre-generated feature database with a variable delay in between. Prints the
* statistics when they are sent from the server.
* @param {function} callback Called when this demo is complete
*/
function runRecordRoute(callback) {
var argv = parseArgs(process.argv, {
string: 'db_path'
});
fs.readFile(path.resolve(argv.db_path), function(err, data) {
if (err) {
callback(err);
}
var feature_list = JSON.parse(data);
var num_points = 10;
var call = client.recordRoute(function(error, stats) {
if (error) {
callback(error);
}
console.log('Finished trip with', stats.point_count, 'points');
console.log('Passed', stats.feature_count, 'features');
console.log('Travelled', stats.distance, 'meters');
console.log('It took', stats.elapsed_time, 'seconds');
callback();
});
/**
* Constructs a function that asynchronously sends the given point and then
* delays sending its callback
* @param {number} lat The latitude to send
* @param {number} lng The longitude to send
* @return {function(function)} The function that sends the point
*/
function pointSender(lat, lng) {
/**
* Sends the point, then calls the callback after a delay
* @param {function} callback Called when complete
*/
return function(callback) {
console.log('Visiting point ' + lat/COORD_FACTOR + ', ' +
lng/COORD_FACTOR);
call.write({
latitude: lat,
longitude: lng
});
_.delay(callback, _.random(500, 1500));
};
}
var point_senders = [];
for (var i = 0; i < num_points; i++) {
var rand_point = feature_list[_.random(0, feature_list.length - 1)];
point_senders[i] = pointSender(rand_point.location.latitude,
rand_point.location.longitude);
}
async.series(point_senders, function() {
call.end();
});
});
}
/**
* Run the routeChat demo. Send some chat messages, and print any chat messages
* that are sent from the server.
* @param {function} callback Called when the demo is complete
*/
function runRouteChat(callback) {
var call = client.routeChat();
call.on('data', function(note) {
console.log('Got message "' + note.message + '" at ' +
note.location.latitude + ', ' + note.location.longitude);
});
call.on('end', callback);
var notes = [{
location: {
latitude: 0,
longitude: 0
},
message: 'First message'
}, {
location: {
latitude: 0,
longitude: 1
},
message: 'Second message'
}, {
location: {
latitude: 1,
longitude: 0
},
message: 'Third message'
}, {
location: {
latitude: 0,
longitude: 0
},
message: 'Fourth message'
}];
for (var i = 0; i < notes.length; i++) {
var note = notes[i];
console.log('Sending message "' + note.message + '" at ' +
note.location.latitude + ', ' + note.location.longitude);
call.write(note);
}
call.end();
}
/**
* Run all of the demos in order
*/
function main() {
async.series([
runGetFeature,
runListFeatures,
runRecordRoute,
runRouteChat
]);
}
if (require.main === module) {
main();
}
exports.runGetFeature = runGetFeature;
exports.runListFeatures = runListFeatures;
exports.runRecordRoute = runRecordRoute;
exports.runRouteChat = runRouteChat;

View File

@ -0,0 +1,601 @@
[{
"location": {
"latitude": 407838351,
"longitude": -746143763
},
"name": "Patriots Path, Mendham, NJ 07945, USA"
}, {
"location": {
"latitude": 408122808,
"longitude": -743999179
},
"name": "101 New Jersey 10, Whippany, NJ 07981, USA"
}, {
"location": {
"latitude": 413628156,
"longitude": -749015468
},
"name": "U.S. 6, Shohola, PA 18458, USA"
}, {
"location": {
"latitude": 419999544,
"longitude": -740371136
},
"name": "5 Conners Road, Kingston, NY 12401, USA"
}, {
"location": {
"latitude": 414008389,
"longitude": -743951297
},
"name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
}, {
"location": {
"latitude": 419611318,
"longitude": -746524769
},
"name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
}, {
"location": {
"latitude": 406109563,
"longitude": -742186778
},
"name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
}, {
"location": {
"latitude": 416802456,
"longitude": -742370183
},
"name": "352 South Mountain Road, Wallkill, NY 12589, USA"
}, {
"location": {
"latitude": 412950425,
"longitude": -741077389
},
"name": "Bailey Turn Road, Harriman, NY 10926, USA"
}, {
"location": {
"latitude": 412144655,
"longitude": -743949739
},
"name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
}, {
"location": {
"latitude": 415736605,
"longitude": -742847522
},
"name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
}, {
"location": {
"latitude": 413843930,
"longitude": -740501726
},
"name": "162 Merrill Road, Highland Mills, NY 10930, USA"
}, {
"location": {
"latitude": 410873075,
"longitude": -744459023
},
"name": "Clinton Road, West Milford, NJ 07480, USA"
}, {
"location": {
"latitude": 412346009,
"longitude": -744026814
},
"name": "16 Old Brook Lane, Warwick, NY 10990, USA"
}, {
"location": {
"latitude": 402948455,
"longitude": -747903913
},
"name": "3 Drake Lane, Pennington, NJ 08534, USA"
}, {
"location": {
"latitude": 406337092,
"longitude": -740122226
},
"name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
}, {
"location": {
"latitude": 406421967,
"longitude": -747727624
},
"name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
}, {
"location": {
"latitude": 416318082,
"longitude": -749677716
},
"name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
}, {
"location": {
"latitude": 415301720,
"longitude": -748416257
},
"name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
}, {
"location": {
"latitude": 402647019,
"longitude": -747071791
},
"name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
}, {
"location": {
"latitude": 412567807,
"longitude": -741058078
},
"name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
}, {
"location": {
"latitude": 416855156,
"longitude": -744420597
},
"name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
}, {
"location": {
"latitude": 404663628,
"longitude": -744820157
},
"name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
}, {
"location": {
"latitude": 407113723,
"longitude": -749746483
},
"name": ""
}, {
"location": {
"latitude": 402133926,
"longitude": -743613249
},
"name": ""
}, {
"location": {
"latitude": 400273442,
"longitude": -741220915
},
"name": ""
}, {
"location": {
"latitude": 411236786,
"longitude": -744070769
},
"name": ""
}, {
"location": {
"latitude": 411633782,
"longitude": -746784970
},
"name": "211-225 Plains Road, Augusta, NJ 07822, USA"
}, {
"location": {
"latitude": 415830701,
"longitude": -742952812
},
"name": ""
}, {
"location": {
"latitude": 413447164,
"longitude": -748712898
},
"name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
}, {
"location": {
"latitude": 405047245,
"longitude": -749800722
},
"name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
}, {
"location": {
"latitude": 418858923,
"longitude": -746156790
},
"name": ""
}, {
"location": {
"latitude": 417951888,
"longitude": -748484944
},
"name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
}, {
"location": {
"latitude": 407033786,
"longitude": -743977337
},
"name": "26 East 3rd Street, New Providence, NJ 07974, USA"
}, {
"location": {
"latitude": 417548014,
"longitude": -740075041
},
"name": ""
}, {
"location": {
"latitude": 410395868,
"longitude": -744972325
},
"name": ""
}, {
"location": {
"latitude": 404615353,
"longitude": -745129803
},
"name": ""
}, {
"location": {
"latitude": 406589790,
"longitude": -743560121
},
"name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
}, {
"location": {
"latitude": 414653148,
"longitude": -740477477
},
"name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
}, {
"location": {
"latitude": 405957808,
"longitude": -743255336
},
"name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
}, {
"location": {
"latitude": 411733589,
"longitude": -741648093
},
"name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
}, {
"location": {
"latitude": 412676291,
"longitude": -742606606
},
"name": "1270 Lakes Road, Monroe, NY 10950, USA"
}, {
"location": {
"latitude": 409224445,
"longitude": -748286738
},
"name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
}, {
"location": {
"latitude": 406523420,
"longitude": -742135517
},
"name": "652 Garden Street, Elizabeth, NJ 07202, USA"
}, {
"location": {
"latitude": 401827388,
"longitude": -740294537
},
"name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
}, {
"location": {
"latitude": 410564152,
"longitude": -743685054
},
"name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
}, {
"location": {
"latitude": 408472324,
"longitude": -740726046
},
"name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
}, {
"location": {
"latitude": 412452168,
"longitude": -740214052
},
"name": "5 White Oak Lane, Stony Point, NY 10980, USA"
}, {
"location": {
"latitude": 409146138,
"longitude": -746188906
},
"name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
}, {
"location": {
"latitude": 404701380,
"longitude": -744781745
},
"name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
}, {
"location": {
"latitude": 409642566,
"longitude": -746017679
},
"name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
}, {
"location": {
"latitude": 408031728,
"longitude": -748645385
},
"name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
}, {
"location": {
"latitude": 413700272,
"longitude": -742135189
},
"name": "367 Prospect Road, Chester, NY 10918, USA"
}, {
"location": {
"latitude": 404310607,
"longitude": -740282632
},
"name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
}, {
"location": {
"latitude": 409319800,
"longitude": -746201391
},
"name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
}, {
"location": {
"latitude": 406685311,
"longitude": -742108603
},
"name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
}, {
"location": {
"latitude": 419018117,
"longitude": -749142781
},
"name": "43 Dreher Road, Roscoe, NY 12776, USA"
}, {
"location": {
"latitude": 412856162,
"longitude": -745148837
},
"name": "Swan Street, Pine Island, NY 10969, USA"
}, {
"location": {
"latitude": 416560744,
"longitude": -746721964
},
"name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
}, {
"location": {
"latitude": 405314270,
"longitude": -749836354
},
"name": ""
}, {
"location": {
"latitude": 414219548,
"longitude": -743327440
},
"name": ""
}, {
"location": {
"latitude": 415534177,
"longitude": -742900616
},
"name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
}, {
"location": {
"latitude": 406898530,
"longitude": -749127080
},
"name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
}, {
"location": {
"latitude": 407586880,
"longitude": -741670168
},
"name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
}, {
"location": {
"latitude": 400106455,
"longitude": -742870190
},
"name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
}, {
"location": {
"latitude": 400066188,
"longitude": -746793294
},
"name": ""
}, {
"location": {
"latitude": 418803880,
"longitude": -744102673
},
"name": "40 Mountain Road, Napanoch, NY 12458, USA"
}, {
"location": {
"latitude": 414204288,
"longitude": -747895140
},
"name": ""
}, {
"location": {
"latitude": 414777405,
"longitude": -740615601
},
"name": ""
}, {
"location": {
"latitude": 415464475,
"longitude": -747175374
},
"name": "48 North Road, Forestburgh, NY 12777, USA"
}, {
"location": {
"latitude": 404062378,
"longitude": -746376177
},
"name": ""
}, {
"location": {
"latitude": 405688272,
"longitude": -749285130
},
"name": ""
}, {
"location": {
"latitude": 400342070,
"longitude": -748788996
},
"name": ""
}, {
"location": {
"latitude": 401809022,
"longitude": -744157964
},
"name": ""
}, {
"location": {
"latitude": 404226644,
"longitude": -740517141
},
"name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
}, {
"location": {
"latitude": 410322033,
"longitude": -747871659
},
"name": ""
}, {
"location": {
"latitude": 407100674,
"longitude": -747742727
},
"name": ""
}, {
"location": {
"latitude": 418811433,
"longitude": -741718005
},
"name": "213 Bush Road, Stone Ridge, NY 12484, USA"
}, {
"location": {
"latitude": 415034302,
"longitude": -743850945
},
"name": ""
}, {
"location": {
"latitude": 411349992,
"longitude": -743694161
},
"name": ""
}, {
"location": {
"latitude": 404839914,
"longitude": -744759616
},
"name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
}, {
"location": {
"latitude": 414638017,
"longitude": -745957854
},
"name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
}, {
"location": {
"latitude": 412127800,
"longitude": -740173578
},
"name": ""
}, {
"location": {
"latitude": 401263460,
"longitude": -747964303
},
"name": ""
}, {
"location": {
"latitude": 412843391,
"longitude": -749086026
},
"name": ""
}, {
"location": {
"latitude": 418512773,
"longitude": -743067823
},
"name": ""
}, {
"location": {
"latitude": 404318328,
"longitude": -740835638
},
"name": "42-102 Main Street, Belford, NJ 07718, USA"
}, {
"location": {
"latitude": 419020746,
"longitude": -741172328
},
"name": ""
}, {
"location": {
"latitude": 404080723,
"longitude": -746119569
},
"name": ""
}, {
"location": {
"latitude": 401012643,
"longitude": -744035134
},
"name": ""
}, {
"location": {
"latitude": 404306372,
"longitude": -741079661
},
"name": ""
}, {
"location": {
"latitude": 403966326,
"longitude": -748519297
},
"name": ""
}, {
"location": {
"latitude": 405002031,
"longitude": -748407866
},
"name": ""
}, {
"location": {
"latitude": 409532885,
"longitude": -742200683
},
"name": ""
}, {
"location": {
"latitude": 416851321,
"longitude": -742674555
},
"name": ""
}, {
"location": {
"latitude": 406411633,
"longitude": -741722051
},
"name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
}, {
"location": {
"latitude": 413069058,
"longitude": -744597778
},
"name": "261 Van Sickle Road, Goshen, NY 10924, USA"
}, {
"location": {
"latitude": 418465462,
"longitude": -746859398
},
"name": ""
}, {
"location": {
"latitude": 411733222,
"longitude": -744228360
},
"name": ""
}, {
"location": {
"latitude": 410248224,
"longitude": -747127767
},
"name": "3 Hasta Way, Newton, NJ 07860, USA"
}]

View File

@ -0,0 +1,257 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
'use strict';
var fs = require('fs');
var parseArgs = require('minimist');
var path = require('path');
var _ = require('underscore');
var grpc = require('..');
var examples = grpc.load(__dirname + '/route_guide.proto').examples;
var Server = grpc.buildServer([examples.RouteGuide.service]);
var COORD_FACTOR = 1e7;
/**
* For simplicity, a point is a record type that looks like
* {latitude: number, longitude: number}, and a feature is a record type that
* looks like {name: string, location: point}. feature objects with name===''
* are points with no feature.
*/
/**
* List of feature objects at points that have been requested so far.
*/
var feature_list = [];
/**
* Get a feature object at the given point.
* @param {point} point The point to check
* @return {feature} The feature object at the point. Note that an empty name
* indicates no feature
*/
function checkFeature(point) {
var feature;
// Check if there is already a feature object for the given point
for (var i = 0; i < feature_list.length; i++) {
feature = feature_list[i];
if (feature.location.latitude === point.latitude &&
feature.location.longitude === point.longitude) {
return feature;
}
}
var name = '';
feature = {
name: name,
location: point
};
return feature;
}
/**
* getFeature request handler. Gets a request with a point, and responds with a
* feature object indicating whether there is a feature at that point.
* @param {EventEmitter} call Call object for the handler to process
* @param {function(Error, feature)} callback Response callback
*/
function getFeature(call, callback) {
callback(null, checkFeature(call.request));
}
/**
* listFeatures request handler. Gets a request with two points, and responds
* with a stream of all features in the bounding box defined by those points.
* @param {Writable} call Writable stream for responses with an additional
* request property for the request value.
*/
function listFeatures(call) {
var lo = call.request.lo;
var hi = call.request.hi;
var left = _.min([lo.longitude, hi.longitude]);
var right = _.max([lo.longitude, hi.longitude]);
var top = _.max([lo.latitude, hi.latitude]);
var bottom = _.min([lo.latitude, hi.latitude]);
// For each feature, check if it is in the given bounding box
_.each(feature_list, function(feature) {
if (feature.name === '') {
return;
}
if (feature.location.longitude >= left &&
feature.location.longitude <= right &&
feature.location.latitude >= bottom &&
feature.location.latitude <= top) {
call.write(feature);
}
});
call.end();
}
/**
* Calculate the distance between two points using the "haversine" formula.
* This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
* @param start The starting point
* @param end The end point
* @return The distance between the points in meters
*/
function getDistance(start, end) {
function toRadians(num) {
return num * Math.PI / 180;
}
var lat1 = start.latitude / COORD_FACTOR;
var lat2 = end.latitude / COORD_FACTOR;
var lon1 = start.longitude / COORD_FACTOR;
var lon2 = end.longitude / COORD_FACTOR;
var R = 6371000; // metres
var φ1 = toRadians(lat1);
var φ2 = toRadians(lat2);
var Δφ = toRadians(lat2-lat1);
var Δλ = toRadians(lon2-lon1);
var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
/**
* recordRoute handler. Gets a stream of points, and responds with statistics
* about the "trip": number of points, number of known features visited, total
* distance traveled, and total time spent.
* @param {Readable} call The request point stream.
* @param {function(Error, routeSummary)} callback The callback to pass the
* response to
*/
function recordRoute(call, callback) {
var point_count = 0;
var feature_count = 0;
var distance = 0;
var previous = null;
// Start a timer
var start_time = process.hrtime();
call.on('data', function(point) {
point_count += 1;
if (checkFeature(point).name !== '') {
feature_count += 1;
}
/* For each point after the first, add the incremental distance from the
* previous point to the total distance value */
if (previous !== null) {
distance += getDistance(previous, point);
}
previous = point;
});
call.on('end', function() {
callback(null, {
point_count: point_count,
feature_count: feature_count,
// Cast the distance to an integer
distance: Math.floor(distance),
// End the timer
elapsed_time: process.hrtime(start_time)[0]
});
});
}
var route_notes = {};
/**
* Turn the point into a dictionary key.
* @param {point} point The point to use
* @return {string} The key for an object
*/
function pointKey(point) {
return point.latitude + ' ' + point.longitude;
}
/**
* routeChat handler. Receives a stream of message/location pairs, and responds
* with a stream of all previous messages at each of those locations.
* @param {Duplex} call The stream for incoming and outgoing messages
*/
function routeChat(call) {
call.on('data', function(note) {
var key = pointKey(note.location);
/* For each note sent, respond with all previous notes that correspond to
* the same point */
if (route_notes.hasOwnProperty(key)) {
_.each(route_notes[key], function(note) {
call.write(note);
});
} else {
route_notes[key] = [];
}
// Then add the new note to the list
route_notes[key].push(JSON.parse(JSON.stringify(note)));
});
call.on('end', function() {
call.end();
});
}
/**
* Get a new server with the handler functions in this file bound to the methods
* it serves.
* @return {Server} The new server object
*/
function getServer() {
return new Server({
'examples.RouteGuide' : {
getFeature: getFeature,
listFeatures: listFeatures,
recordRoute: recordRoute,
routeChat: routeChat
}
});
}
if (require.main === module) {
// If this is run as a script, start a server on an unused port
var routeServer = getServer();
routeServer.bind('0.0.0.0:50051');
var argv = parseArgs(process.argv, {
string: 'db_path'
});
fs.readFile(path.resolve(argv.db_path), function(err, data) {
if (err) {
throw err;
}
feature_list = JSON.parse(data);
routeServer.listen();
});
}
exports.getServer = getServer;

View File

@ -35,28 +35,28 @@ package examples;
message StockRequest {
optional string symbol = 1;
optional int32 num_trades_to_watch = 2 [default=0];
};
}
message StockReply {
optional float price = 1;
optional string symbol = 2;
};
}
// Interface exported by the server
service Stock {
// Simple blocking RPC
rpc GetLastTradePrice(StockRequest) returns (StockReply) {
};
}
// Bidirectional streaming RPC
rpc GetLastTradePriceMultiple(stream StockRequest) returns
(stream StockReply) {
};
}
// Unidirectional server-to-client streaming RPC
rpc WatchFutureTrades(StockRequest) returns (stream StockReply) {
};
}
// Unidirectional client-to-server streaming RPC
rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) {
};
}
};
}

View File

@ -39,5 +39,8 @@ var examples = grpc.load(__dirname + '/stock.proto').examples;
*
* var StockClient = require('stock_client.js');
* var stockClient = new StockClient(server_address);
* stockClient.getLastTradePrice({symbol: 'GOOG'}, function(error, response) {
* console.log(error || response);
* });
*/
module.exports = examples.Stock;

View File

@ -31,14 +31,16 @@
*
*/
'use strict';
var _ = require('underscore');
var grpc = require('..');
var examples = grpc.load(__dirname + '/stock.proto').examples;
var StockServer = grpc.makeServerConstructor([examples.Stock.service]);
var StockServer = grpc.buildServer([examples.Stock.service]);
function getLastTradePrice(call, callback) {
callback(null, {price: 88});
callback(null, {symbol: call.request.symbol, price: 88});
}
function watchFutureTrades(call) {
@ -80,4 +82,9 @@ var stockServer = new StockServer({
}
});
exports.module = stockServer;
if (require.main === module) {
stockServer.bind('0.0.0.0:8080');
stockServer.listen();
}
module.exports = stockServer;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -44,7 +44,6 @@
namespace grpc {
namespace node {
using ::node::Buffer;
using v8::Context;
using v8::Function;
using v8::Handle;
@ -54,8 +53,8 @@ using v8::Value;
grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
NanScope();
int length = Buffer::Length(buffer);
char *data = Buffer::Data(buffer);
int length = ::node::Buffer::Length(buffer);
char *data = ::node::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));
@ -66,7 +65,7 @@ grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
NanEscapableScope();
if (buffer == NULL) {
NanReturnNull();
return NanEscapeScope(NanNull());
}
size_t length = grpc_byte_buffer_length(buffer);
char *result = reinterpret_cast<char *>(calloc(length, sizeof(char)));
@ -82,12 +81,14 @@ Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
Handle<Value> MakeFastBuffer(Handle<Value> slowBuffer) {
NanEscapableScope();
Handle<Object> globalObj = Context::GetCurrent()->Global();
Handle<Object> globalObj = NanGetCurrentContext()->Global();
Handle<Function> bufferConstructor = Handle<Function>::Cast(
globalObj->Get(NanNew("Buffer")));
Handle<Value> consArgs[3] = { slowBuffer,
NanNew<Number>(Buffer::Length(slowBuffer)),
NanNew<Number>(0) };
Handle<Value> consArgs[3] = {
slowBuffer,
NanNew<Number>(::node::Buffer::Length(slowBuffer)),
NanNew<Number>(0)
};
Handle<Object> fastBuffer = bufferConstructor->NewInstance(3, consArgs);
return NanEscapeScope(fastBuffer);
}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,24 +31,31 @@
*
*/
#include <memory>
#include <vector>
#include <map>
#include <node.h>
#include "grpc/support/log.h"
#include "grpc/grpc.h"
#include "grpc/support/alloc.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"
using std::unique_ptr;
using std::shared_ptr;
using std::vector;
namespace grpc {
namespace node {
using ::node::Buffer;
using v8::Arguments;
using v8::Array;
using v8::Boolean;
using v8::Exception;
using v8::External;
using v8::Function;
@ -65,47 +72,383 @@ using v8::Uint32;
using v8::String;
using v8::Value;
Persistent<Function> Call::constructor;
NanCallback *Call::constructor;
Persistent<FunctionTemplate> Call::fun_tpl;
Call::Call(grpc_call *call) : wrapped_call(call) {}
Call::~Call() { grpc_call_destroy(wrapped_call); }
bool CreateMetadataArray(Handle<Object> metadata, grpc_metadata_array *array,
shared_ptr<Resources> resources) {
NanScope();
grpc_metadata_array_init(array);
Handle<Array> keys(metadata->GetOwnPropertyNames());
for (unsigned int i = 0; i < keys->Length(); i++) {
Handle<String> current_key(keys->Get(i)->ToString());
if (!metadata->Get(current_key)->IsArray()) {
return false;
}
array->capacity += Local<Array>::Cast(metadata->Get(current_key))->Length();
}
array->metadata = reinterpret_cast<grpc_metadata*>(
gpr_malloc(array->capacity * sizeof(grpc_metadata)));
for (unsigned int i = 0; i < keys->Length(); i++) {
Handle<String> current_key(keys->Get(i)->ToString());
NanUtf8String *utf8_key = new NanUtf8String(current_key);
resources->strings.push_back(unique_ptr<NanUtf8String>(utf8_key));
Handle<Array> values = Local<Array>::Cast(metadata->Get(current_key));
for (unsigned int j = 0; j < values->Length(); j++) {
Handle<Value> value = values->Get(j);
grpc_metadata *current = &array->metadata[array->count];
current->key = **utf8_key;
if (::node::Buffer::HasInstance(value)) {
current->value = ::node::Buffer::Data(value);
current->value_length = ::node::Buffer::Length(value);
Persistent<Value> *handle = new Persistent<Value>();
NanAssignPersistent(*handle, value);
resources->handles.push_back(unique_ptr<PersistentHolder>(
new PersistentHolder(handle)));
} else if (value->IsString()) {
Handle<String> string_value = value->ToString();
NanUtf8String *utf8_value = new NanUtf8String(string_value);
resources->strings.push_back(unique_ptr<NanUtf8String>(utf8_value));
current->value = **utf8_value;
current->value_length = string_value->Length();
} else {
return false;
}
array->count += 1;
}
}
return true;
}
Handle<Value> ParseMetadata(const grpc_metadata_array *metadata_array) {
NanEscapableScope();
grpc_metadata *metadata_elements = metadata_array->metadata;
size_t length = metadata_array->count;
std::map<const char*, size_t> size_map;
std::map<const char*, size_t> index_map;
for (unsigned int i = 0; i < length; i++) {
const char *key = metadata_elements[i].key;
if (size_map.count(key)) {
size_map[key] += 1;
}
index_map[key] = 0;
}
Handle<Object> metadata_object = NanNew<Object>();
for (unsigned int i = 0; i < length; i++) {
grpc_metadata* elem = &metadata_elements[i];
Handle<String> key_string = NanNew(elem->key);
Handle<Array> array;
if (metadata_object->Has(key_string)) {
array = Handle<Array>::Cast(metadata_object->Get(key_string));
} else {
array = NanNew<Array>(size_map[elem->key]);
metadata_object->Set(key_string, array);
}
array->Set(index_map[elem->key],
MakeFastBuffer(
NanNewBufferHandle(elem->value, elem->value_length)));
index_map[elem->key] += 1;
}
return NanEscapeScope(metadata_object);
}
Handle<Value> Op::GetOpType() const {
NanEscapableScope();
return NanEscapeScope(NanNew<String>(GetTypeString()));
}
class SendMetadataOp : public Op {
public:
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(NanTrue());
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
if (!value->IsObject()) {
return false;
}
grpc_metadata_array array;
if (!CreateMetadataArray(value->ToObject(), &array, resources)) {
return false;
}
out->data.send_initial_metadata.count = array.count;
out->data.send_initial_metadata.metadata = array.metadata;
return true;
}
protected:
std::string GetTypeString() const {
return "send metadata";
}
};
class SendMessageOp : public Op {
public:
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(NanTrue());
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
if (!::node::Buffer::HasInstance(value)) {
return false;
}
out->data.send_message = BufferToByteBuffer(value);
Persistent<Value> *handle = new Persistent<Value>();
NanAssignPersistent(*handle, value);
resources->handles.push_back(unique_ptr<PersistentHolder>(
new PersistentHolder(handle)));
return true;
}
protected:
std::string GetTypeString() const {
return "send message";
}
};
class SendClientCloseOp : public Op {
public:
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(NanTrue());
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
return true;
}
protected:
std::string GetTypeString() const {
return "client close";
}
};
class SendServerStatusOp : public Op {
public:
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(NanTrue());
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
if (!value->IsObject()) {
return false;
}
Handle<Object> server_status = value->ToObject();
if (!server_status->Get(NanNew("metadata"))->IsObject()) {
return false;
}
if (!server_status->Get(NanNew("code"))->IsUint32()) {
return false;
}
if (!server_status->Get(NanNew("details"))->IsString()) {
return false;
}
grpc_metadata_array array;
if (!CreateMetadataArray(server_status->Get(NanNew("metadata"))->
ToObject(),
&array, resources)) {
return false;
}
out->data.send_status_from_server.trailing_metadata_count = array.count;
out->data.send_status_from_server.trailing_metadata = array.metadata;
out->data.send_status_from_server.status =
static_cast<grpc_status_code>(
server_status->Get(NanNew("code"))->Uint32Value());
NanUtf8String *str = new NanUtf8String(
server_status->Get(NanNew("details")));
resources->strings.push_back(unique_ptr<NanUtf8String>(str));
out->data.send_status_from_server.status_details = **str;
return true;
}
protected:
std::string GetTypeString() const {
return "send status";
}
};
class GetMetadataOp : public Op {
public:
GetMetadataOp() {
grpc_metadata_array_init(&recv_metadata);
}
~GetMetadataOp() {
grpc_metadata_array_destroy(&recv_metadata);
}
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(ParseMetadata(&recv_metadata));
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
out->data.recv_initial_metadata = &recv_metadata;
return true;
}
protected:
std::string GetTypeString() const {
return "metadata";
}
private:
grpc_metadata_array recv_metadata;
};
class ReadMessageOp : public Op {
public:
ReadMessageOp() {
recv_message = NULL;
}
~ReadMessageOp() {
if (recv_message != NULL) {
gpr_free(recv_message);
}
}
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(ByteBufferToBuffer(recv_message));
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
out->data.recv_message = &recv_message;
return true;
}
protected:
std::string GetTypeString() const {
return "read";
}
private:
grpc_byte_buffer *recv_message;
};
class ClientStatusOp : public Op {
public:
ClientStatusOp() {
grpc_metadata_array_init(&metadata_array);
status_details = NULL;
details_capacity = 0;
}
~ClientStatusOp() {
grpc_metadata_array_destroy(&metadata_array);
gpr_free(status_details);
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
out->data.recv_status_on_client.trailing_metadata = &metadata_array;
out->data.recv_status_on_client.status = &status;
out->data.recv_status_on_client.status_details = &status_details;
out->data.recv_status_on_client.status_details_capacity = &details_capacity;
return true;
}
Handle<Value> GetNodeValue() const {
NanEscapableScope();
Handle<Object> status_obj = NanNew<Object>();
status_obj->Set(NanNew("code"), NanNew<Number>(status));
if (status_details != NULL) {
status_obj->Set(NanNew("details"), NanNew(status_details));
}
status_obj->Set(NanNew("metadata"), ParseMetadata(&metadata_array));
return NanEscapeScope(status_obj);
}
protected:
std::string GetTypeString() const {
return "status";
}
private:
grpc_metadata_array metadata_array;
grpc_status_code status;
char *status_details;
size_t details_capacity;
};
class ServerCloseResponseOp : public Op {
public:
Handle<Value> GetNodeValue() const {
NanEscapableScope();
return NanEscapeScope(NanNew<Boolean>(cancelled));
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
out->data.recv_close_on_server.cancelled = &cancelled;
return true;
}
protected:
std::string GetTypeString() const {
return "cancelled";
}
private:
int cancelled;
};
tag::tag(NanCallback *callback, OpVec *ops,
shared_ptr<Resources> resources) :
callback(callback), ops(ops), resources(resources){
}
tag::~tag() {
delete callback;
delete ops;
}
Handle<Value> GetTagNodeValue(void *tag) {
NanEscapableScope();
struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
Handle<Object> tag_obj = NanNew<Object>();
for (vector<unique_ptr<Op> >::iterator it = tag_struct->ops->begin();
it != tag_struct->ops->end(); ++it) {
Op *op_ptr = it->get();
tag_obj->Set(op_ptr->GetOpType(), op_ptr->GetNodeValue());
}
return NanEscapeScope(tag_obj);
}
NanCallback *GetTagCallback(void *tag) {
struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
return tag_struct->callback;
}
void DestroyTag(void *tag) {
struct tag *tag_struct = reinterpret_cast<struct tag *>(tag);
delete tag_struct;
}
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);
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("Call"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
NanSetPrototypeTemplate(tpl, "addMetadata",
FunctionTemplate::New(AddMetadata)->GetFunction());
NanSetPrototypeTemplate(tpl, "invoke",
FunctionTemplate::New(Invoke)->GetFunction());
NanSetPrototypeTemplate(tpl, "serverAccept",
FunctionTemplate::New(ServerAccept)->GetFunction());
NanSetPrototypeTemplate(
tpl, "serverEndInitialMetadata",
FunctionTemplate::New(ServerEndInitialMetadata)->GetFunction());
NanSetPrototypeTemplate(tpl, "startBatch",
NanNew<FunctionTemplate>(StartBatch)->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());
NanNew<FunctionTemplate>(Cancel)->GetFunction());
NanAssignPersistent(fun_tpl, tpl);
NanAssignPersistent(constructor, tpl->GetFunction());
constructor->Set(NanNew("WRITE_BUFFER_HINT"),
Handle<Function> ctr = tpl->GetFunction();
ctr->Set(NanNew("WRITE_BUFFER_HINT"),
NanNew<Uint32, uint32_t>(GRPC_WRITE_BUFFER_HINT));
constructor->Set(NanNew("WRITE_NO_COMPRESS"),
ctr->Set(NanNew("WRITE_NO_COMPRESS"),
NanNew<Uint32, uint32_t>(GRPC_WRITE_NO_COMPRESS));
exports->Set(String::NewSymbol("Call"), constructor);
exports->Set(NanNew("Call"), ctr);
constructor = new NanCallback(ctr);
}
bool Call::HasInstance(Handle<Value> val) {
@ -119,8 +462,8 @@ Handle<Value> Call::WrapStruct(grpc_call *call) {
return NanEscapeScope(NanNull());
}
const int argc = 1;
Handle<Value> argv[argc] = {External::New(reinterpret_cast<void *>(call))};
return NanEscapeScope(constructor->NewInstance(argc, argv));
Handle<Value> argv[argc] = {NanNew<External>(reinterpret_cast<void *>(call))};
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
}
NAN_METHOD(Call::New) {
@ -129,9 +472,10 @@ NAN_METHOD(Call::New) {
if (args.IsConstructCall()) {
Call *call;
if (args[0]->IsExternal()) {
Handle<External> ext = args[0].As<External>();
// This option is used for wrapping an existing call
grpc_call *call_value =
reinterpret_cast<grpc_call *>(External::Unwrap(args[0]));
reinterpret_cast<grpc_call *>(ext->Value());
call = new Call(call_value);
} else {
if (!Channel::HasInstance(args[0])) {
@ -152,135 +496,89 @@ NAN_METHOD(Call::New) {
NanUtf8String method(args[1]);
double deadline = args[2]->NumberValue();
grpc_channel *wrapped_channel = channel->GetWrappedChannel();
grpc_call *wrapped_call =
grpc_channel_create_call(wrapped_channel, *method, channel->GetHost(),
MillisecondsToTimespec(deadline));
grpc_call *wrapped_call = grpc_channel_create_call(
wrapped_channel, CompletionQueueAsyncWorker::GetQueue(), *method,
channel->GetHost(), MillisecondsToTimespec(deadline));
call = new Call(wrapped_call);
args.This()->SetHiddenValue(String::NewSymbol("channel_"),
channel_object);
args.This()->SetHiddenValue(NanNew("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));
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
}
}
NAN_METHOD(Call::AddMetadata) {
NAN_METHOD(Call::StartBatch) {
NanScope();
if (!HasInstance(args.This())) {
return NanThrowTypeError("addMetadata can only be called on Call objects");
return NanThrowTypeError("startBatch can only be called on Call objects");
}
Call *call = ObjectWrap::Unwrap<Call>(args.This());
if (!args[0]->IsObject()) {
return NanThrowTypeError("addMetadata's first argument must be an object");
}
Handle<Object> metadata = args[0]->ToObject();
Handle<Array> keys(metadata->GetOwnPropertyNames());
for (unsigned int i = 0; i < keys->Length(); i++) {
Handle<String> current_key(keys->Get(i)->ToString());
if (!metadata->Get(current_key)->IsArray()) {
return NanThrowTypeError(
"addMetadata's first argument's values must be arrays");
}
NanUtf8String utf8_key(current_key);
Handle<Array> values = Local<Array>::Cast(metadata->Get(current_key));
for (unsigned int j = 0; j < values->Length(); j++) {
Handle<Value> value = values->Get(j);
grpc_metadata metadata;
grpc_call_error error;
metadata.key = *utf8_key;
if (Buffer::HasInstance(value)) {
metadata.value = Buffer::Data(value);
metadata.value_length = Buffer::Length(value);
error = grpc_call_add_metadata(call->wrapped_call, &metadata, 0);
} else if (value->IsString()) {
Handle<String> string_value = value->ToString();
NanUtf8String utf8_value(string_value);
metadata.value = *utf8_value;
metadata.value_length = string_value->Length();
gpr_log(GPR_DEBUG, "adding metadata: %s, %s, %d", metadata.key,
metadata.value, metadata.value_length);
error = grpc_call_add_metadata(call->wrapped_call, &metadata, 0);
} else {
return NanThrowTypeError(
"addMetadata values must be strings or buffers");
}
if (error != GRPC_CALL_OK) {
return NanThrowError("addMetadata failed", error);
}
}
}
NanReturnUndefined();
}
NAN_METHOD(Call::Invoke) {
NanScope();
if (!HasInstance(args.This())) {
return NanThrowTypeError("invoke can only be called on Call objects");
}
if (!args[0]->IsFunction()) {
return NanThrowTypeError("invoke's first argument must be a function");
return NanThrowError("startBatch's first argument must be an object");
}
if (!args[1]->IsFunction()) {
return NanThrowTypeError("invoke's second argument must be a function");
}
if (!args[2]->IsUint32()) {
return NanThrowTypeError("invoke's third argument must be integer flags");
return NanThrowError("startBatch's second argument must be a callback");
}
Handle<Function> callback_func = args[1].As<Function>();
Call *call = ObjectWrap::Unwrap<Call>(args.This());
unsigned int flags = args[3]->Uint32Value();
grpc_call_error error = grpc_call_invoke(
call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
CreateTag(args[0], args.This()), CreateTag(args[1], args.This()), flags);
if (error == GRPC_CALL_OK) {
CompletionQueueAsyncWorker::Next();
CompletionQueueAsyncWorker::Next();
} else {
return NanThrowError("invoke failed", error);
shared_ptr<Resources> resources(new Resources);
Handle<Object> obj = args[0]->ToObject();
Handle<Array> keys = obj->GetOwnPropertyNames();
size_t nops = keys->Length();
vector<grpc_op> ops(nops);
unique_ptr<OpVec> op_vector(new OpVec());
for (unsigned int i = 0; i < nops; i++) {
unique_ptr<Op> op;
if (!keys->Get(i)->IsUint32()) {
return NanThrowError(
"startBatch's first argument's keys must be integers");
}
NanReturnUndefined();
}
NAN_METHOD(Call::ServerAccept) {
NanScope();
if (!HasInstance(args.This())) {
return NanThrowTypeError("accept can only be called on Call objects");
uint32_t type = keys->Get(i)->Uint32Value();
ops[i].op = static_cast<grpc_op_type>(type);
switch (type) {
case GRPC_OP_SEND_INITIAL_METADATA:
op.reset(new SendMetadataOp());
break;
case GRPC_OP_SEND_MESSAGE:
op.reset(new SendMessageOp());
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
op.reset(new SendClientCloseOp());
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
op.reset(new SendServerStatusOp());
break;
case GRPC_OP_RECV_INITIAL_METADATA:
op.reset(new GetMetadataOp());
break;
case GRPC_OP_RECV_MESSAGE:
op.reset(new ReadMessageOp());
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
op.reset(new ClientStatusOp());
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
op.reset(new ServerCloseResponseOp());
break;
default:
return NanThrowError("Argument object had an unrecognized key");
}
if (!args[0]->IsFunction()) {
return NanThrowTypeError("accept's first argument must be a function");
if (!op->ParseOp(obj->Get(type), &ops[i], resources)) {
return NanThrowTypeError("Incorrectly typed arguments to startBatch");
}
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);
op_vector->push_back(std::move(op));
}
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);
NanCallback *callback = new NanCallback(callback_func);
grpc_call_error error = grpc_call_start_batch(
call->wrapped_call, &ops[0], nops, new struct tag(
callback, op_vector.release(), resources));
if (error != GRPC_CALL_OK) {
return NanThrowError("serverEndInitialMetadata failed", error);
return NanThrowError("startBatch failed", error);
}
CompletionQueueAsyncWorker::Next();
NanReturnUndefined();
}
@ -297,102 +595,5 @@ NAN_METHOD(Call::Cancel) {
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

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -34,15 +34,73 @@
#ifndef NET_GRPC_NODE_CALL_H_
#define NET_GRPC_NODE_CALL_H_
#include <memory>
#include <vector>
#include <node.h>
#include <nan.h>
#include "grpc/grpc.h"
#include "grpc/support/log.h"
#include "channel.h"
namespace grpc {
namespace node {
using std::unique_ptr;
using std::shared_ptr;
v8::Handle<v8::Value> ParseMetadata(const grpc_metadata_array *metadata_array);
class PersistentHolder {
public:
explicit PersistentHolder(v8::Persistent<v8::Value> *persist) :
persist(persist) {
}
~PersistentHolder() {
NanDisposePersistent(*persist);
delete persist;
}
private:
v8::Persistent<v8::Value> *persist;
};
struct Resources {
std::vector<unique_ptr<NanUtf8String> > strings;
std::vector<unique_ptr<PersistentHolder> > handles;
};
class Op {
public:
virtual v8::Handle<v8::Value> GetNodeValue() const = 0;
virtual bool ParseOp(v8::Handle<v8::Value> value, grpc_op *out,
shared_ptr<Resources> resources) = 0;
v8::Handle<v8::Value> GetOpType() const;
protected:
virtual std::string GetTypeString() const = 0;
};
typedef std::vector<unique_ptr<Op>> OpVec;
struct tag {
tag(NanCallback *callback, OpVec *ops,
shared_ptr<Resources> resources);
~tag();
NanCallback *callback;
OpVec *ops;
shared_ptr<Resources> resources;
};
v8::Handle<v8::Value> GetTagNodeValue(void *tag);
NanCallback *GetTagCallback(void *tag);
void DestroyTag(void *tag);
/* Wrapper class for grpc_call structs. */
class Call : public ::node::ObjectWrap {
public:
@ -60,16 +118,9 @@ class Call : public ::node::ObjectWrap {
Call &operator=(const Call &);
static NAN_METHOD(New);
static NAN_METHOD(AddMetadata);
static NAN_METHOD(Invoke);
static NAN_METHOD(ServerAccept);
static NAN_METHOD(ServerEndInitialMetadata);
static NAN_METHOD(StartBatch);
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;
static NanCallback *constructor;
// Used for typechecking instances of this javascript class
static v8::Persistent<v8::FunctionTemplate> fun_tpl;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -45,7 +45,6 @@
namespace grpc {
namespace node {
using v8::Arguments;
using v8::Array;
using v8::Exception;
using v8::Function;
@ -59,7 +58,7 @@ using v8::Persistent;
using v8::String;
using v8::Value;
Persistent<Function> Channel::constructor;
NanCallback *Channel::constructor;
Persistent<FunctionTemplate> Channel::fun_tpl;
Channel::Channel(grpc_channel *channel, NanUtf8String *host)
@ -74,14 +73,15 @@ Channel::~Channel() {
void Channel::Init(Handle<Object> exports) {
NanScope();
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("Channel"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
NanSetPrototypeTemplate(tpl, "close",
FunctionTemplate::New(Close)->GetFunction());
NanNew<FunctionTemplate>(Close)->GetFunction());
NanAssignPersistent(fun_tpl, tpl);
NanAssignPersistent(constructor, tpl->GetFunction());
exports->Set(NanNew("Channel"), constructor);
Handle<Function> ctr = tpl->GetFunction();
constructor = new NanCallback(ctr);
exports->Set(NanNew("Channel"), ctr);
}
bool Channel::HasInstance(Handle<Value> val) {
@ -103,11 +103,15 @@ NAN_METHOD(Channel::New) {
grpc_channel *wrapped_channel;
// Owned by the Channel object
NanUtf8String *host = new NanUtf8String(args[0]);
NanUtf8String *host_override = NULL;
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(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG))) {
host_override = new NanUtf8String(args_hash->Get(NanNew(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG)));
}
if (args_hash->HasOwnProperty(NanNew("credentials"))) {
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
if (!Credentials::HasInstance(creds_value)) {
@ -155,13 +159,18 @@ NAN_METHOD(Channel::New) {
} else {
return NanThrowTypeError("Channel expects a string and an object");
}
Channel *channel = new Channel(wrapped_channel, host);
Channel *channel;
if (host_override == NULL) {
channel = new Channel(wrapped_channel, host);
} else {
channel = new Channel(wrapped_channel, host_override);
}
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));
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
}
}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -66,7 +66,7 @@ class Channel : public ::node::ObjectWrap {
static NAN_METHOD(New);
static NAN_METHOD(Close);
static v8::Persistent<v8::Function> constructor;
static NanCallback *constructor;
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
grpc_channel *wrapped_channel;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -35,10 +35,10 @@
#include <nan.h>
#include "grpc/grpc.h"
#include "grpc/support/log.h"
#include "grpc/support/time.h"
#include "completion_queue_async_worker.h"
#include "event.h"
#include "tag.h"
#include "call.h"
namespace grpc {
namespace node {
@ -63,6 +63,9 @@ CompletionQueueAsyncWorker::~CompletionQueueAsyncWorker() {}
void CompletionQueueAsyncWorker::Execute() {
result = grpc_completion_queue_next(queue, gpr_inf_future);
if (result->data.op_complete != GRPC_OP_OK) {
SetErrorMessage("The batch encountered an error");
}
}
grpc_completion_queue *CompletionQueueAsyncWorker::GetQueue() { return queue; }
@ -93,14 +96,25 @@ void CompletionQueueAsyncWorker::HandleOKCallback() {
} else {
current_threads -= 1;
}
NanCallback event_callback(GetTagHandle(result->tag).As<Function>());
Handle<Value> argv[] = {CreateEventObject(result)};
NanCallback *callback = GetTagCallback(result->tag);
Handle<Value> argv[] = {NanNull(), GetTagNodeValue(result->tag)};
callback->Call(2, argv);
DestroyTag(result->tag);
grpc_event_finish(result);
result = NULL;
}
event_callback.Call(1, argv);
void CompletionQueueAsyncWorker::HandleErrorCallback() {
NanScope();
NanCallback *callback = GetTagCallback(result->tag);
Handle<Value> argv[] = {NanError(ErrorMessage())};
callback->Call(1, argv);
DestroyTag(result->tag);
grpc_event_finish(result);
result = NULL;
}
} // namespace node

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -67,6 +67,8 @@ class CompletionQueueAsyncWorker : public NanAsyncWorker {
completion_queue_next */
void HandleOKCallback();
void HandleErrorCallback();
private:
grpc_event *result;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -41,8 +41,6 @@
namespace grpc {
namespace node {
using ::node::Buffer;
using v8::Arguments;
using v8::Exception;
using v8::External;
using v8::Function;
@ -56,37 +54,37 @@ using v8::ObjectTemplate;
using v8::Persistent;
using v8::Value;
Persistent<Function> Credentials::constructor;
NanCallback *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);
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(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);
Handle<Function> ctr = tpl->GetFunction();
ctr->Set(NanNew("createDefault"),
NanNew<FunctionTemplate>(CreateDefault)->GetFunction());
ctr->Set(NanNew("createSsl"),
NanNew<FunctionTemplate>(CreateSsl)->GetFunction());
ctr->Set(NanNew("createComposite"),
NanNew<FunctionTemplate>(CreateComposite)->GetFunction());
ctr->Set(NanNew("createGce"),
NanNew<FunctionTemplate>(CreateGce)->GetFunction());
ctr->Set(NanNew("createFake"),
NanNew<FunctionTemplate>(CreateFake)->GetFunction());
ctr->Set(NanNew("createIam"),
NanNew<FunctionTemplate>(CreateIam)->GetFunction());
constructor = new NanCallback(ctr);
exports->Set(NanNew("Credentials"), ctr);
}
bool Credentials::HasInstance(Handle<Value> val) {
@ -101,8 +99,8 @@ Handle<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
}
const int argc = 1;
Handle<Value> argv[argc] = {
External::New(reinterpret_cast<void *>(credentials))};
return NanEscapeScope(constructor->NewInstance(argc, argv));
NanNew<External>(reinterpret_cast<void *>(credentials))};
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
}
grpc_credentials *Credentials::GetWrappedCredentials() {
@ -117,40 +115,41 @@ NAN_METHOD(Credentials::New) {
return NanThrowTypeError(
"Credentials can only be created with the provided functions");
}
Handle<External> ext = args[0].As<External>();
grpc_credentials *creds_value =
reinterpret_cast<grpc_credentials *>(External::Unwrap(args[0]));
reinterpret_cast<grpc_credentials *>(ext->Value());
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));
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
}
}
NAN_METHOD(Credentials::CreateDefault) {
NanScope();
NanReturnValue(WrapStruct(grpc_default_credentials_create()));
NanReturnValue(WrapStruct(grpc_google_default_credentials_create()));
}
NAN_METHOD(Credentials::CreateSsl) {
NanScope();
char *root_certs = NULL;
grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL};
if (Buffer::HasInstance(args[0])) {
root_certs = Buffer::Data(args[0]);
if (::node::Buffer::HasInstance(args[0])) {
root_certs = ::node::Buffer::Data(args[0]);
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
return NanThrowTypeError("createSsl's first argument must be a Buffer");
}
if (Buffer::HasInstance(args[1])) {
key_cert_pair.private_key = Buffer::Data(args[1]);
if (::node::Buffer::HasInstance(args[1])) {
key_cert_pair.private_key = ::node::Buffer::Data(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])) {
key_cert_pair.cert_chain = Buffer::Data(args[2]);
if (::node::Buffer::HasInstance(args[2])) {
key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]);
} else if (!(args[2]->IsNull() || args[2]->IsUndefined())) {
return NanThrowTypeError(
"createSSl's third argument must be a Buffer if provided");

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -68,7 +68,7 @@ class Credentials : public ::node::ObjectWrap {
static NAN_METHOD(CreateGce);
static NAN_METHOD(CreateFake);
static NAN_METHOD(CreateIam);
static v8::Persistent<v8::Function> constructor;
static NanCallback *constructor;
// Used for typechecking instances of this javascript class
static v8::Persistent<v8::FunctionTemplate> fun_tpl;

View File

@ -1,176 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <map>
#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 ::node::Buffer;
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> ParseMetadata(grpc_metadata *metadata_elements, size_t length) {
NanEscapableScope();
std::map<char*, size_t> size_map;
std::map<char*, size_t> index_map;
for (unsigned int i = 0; i < length; i++) {
char *key = metadata_elements[i].key;
if (size_map.count(key)) {
size_map[key] += 1;
}
index_map[key] = 0;
}
Handle<Object> metadata_object = NanNew<Object>();
for (unsigned int i = 0; i < length; i++) {
grpc_metadata* elem = &metadata_elements[i];
Handle<String> key_string = String::New(elem->key);
Handle<Array> array;
if (metadata_object->Has(key_string)) {
array = Handle<Array>::Cast(metadata_object->Get(key_string));
} else {
array = NanNew<Array>(size_map[elem->key]);
metadata_object->Set(key_string, array);
}
array->Set(index_map[elem->key],
MakeFastBuffer(
NanNewBufferHandle(elem->value, elem->value_length)));
index_map[elem->key] += 1;
}
return NanEscapeScope(metadata_object);
}
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;
return NanEscapeScope(ParseMetadata(items, count));
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;
status->Set(NanNew("metadata"), ParseMetadata(items, count));
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("metadata"), ParseMetadata(items, count));
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

View File

@ -1,48 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#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_

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -38,7 +38,6 @@
#include "call.h"
#include "channel.h"
#include "event.h"
#include "server.h"
#include "completion_queue_async_worker.h"
#include "credentials.h"
@ -52,7 +51,7 @@ using v8::String;
void InitStatusConstants(Handle<Object> exports) {
NanScope();
Handle<Object> status = Object::New();
Handle<Object> status = NanNew<Object>();
exports->Set(NanNew("status"), status);
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_STATUS_OK));
status->Set(NanNew("OK"), OK);
@ -101,7 +100,7 @@ void InitStatusConstants(Handle<Object> exports) {
void InitCallErrorConstants(Handle<Object> exports) {
NanScope();
Handle<Object> call_error = Object::New();
Handle<Object> call_error = NanNew<Object>();
exports->Set(NanNew("callError"), call_error);
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_CALL_OK));
call_error->Set(NanNew("OK"), OK);
@ -130,35 +129,34 @@ void InitCallErrorConstants(Handle<Object> exports) {
call_error->Set(NanNew("INVALID_FLAGS"), INVALID_FLAGS);
}
void InitOpErrorConstants(Handle<Object> exports) {
void InitOpTypeConstants(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> 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);
Handle<Object> op_type = NanNew<Object>();
exports->Set(NanNew("opType"), op_type);
Handle<Value> SEND_INITIAL_METADATA(
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_INITIAL_METADATA));
op_type->Set(NanNew("SEND_INITIAL_METADATA"), SEND_INITIAL_METADATA);
Handle<Value> SEND_MESSAGE(
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_MESSAGE));
op_type->Set(NanNew("SEND_MESSAGE"), SEND_MESSAGE);
Handle<Value> SEND_CLOSE_FROM_CLIENT(
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_CLOSE_FROM_CLIENT));
op_type->Set(NanNew("SEND_CLOSE_FROM_CLIENT"), SEND_CLOSE_FROM_CLIENT);
Handle<Value> SEND_STATUS_FROM_SERVER(
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_STATUS_FROM_SERVER));
op_type->Set(NanNew("SEND_STATUS_FROM_SERVER"), SEND_STATUS_FROM_SERVER);
Handle<Value> RECV_INITIAL_METADATA(
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_INITIAL_METADATA));
op_type->Set(NanNew("RECV_INITIAL_METADATA"), RECV_INITIAL_METADATA);
Handle<Value> RECV_MESSAGE(
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_MESSAGE));
op_type->Set(NanNew("RECV_MESSAGE"), RECV_MESSAGE);
Handle<Value> RECV_STATUS_ON_CLIENT(
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_STATUS_ON_CLIENT));
op_type->Set(NanNew("RECV_STATUS_ON_CLIENT"), RECV_STATUS_ON_CLIENT);
Handle<Value> RECV_CLOSE_ON_SERVER(
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_CLOSE_ON_SERVER));
op_type->Set(NanNew("RECV_CLOSE_ON_SERVER"), RECV_CLOSE_ON_SERVER);
}
void init(Handle<Object> exports) {
@ -166,8 +164,7 @@ void init(Handle<Object> exports) {
grpc_init();
InitStatusConstants(exports);
InitCallErrorConstants(exports);
InitOpErrorConstants(exports);
InitCompletionTypeConstants(exports);
InitOpTypeConstants(exports);
grpc::node::Call::Init(exports);
grpc::node::Channel::Init(exports);

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
#include <memory>
#include "server.h"
#include <node.h>
@ -41,17 +43,19 @@
#include <vector>
#include "grpc/grpc.h"
#include "grpc/grpc_security.h"
#include "grpc/support/log.h"
#include "call.h"
#include "completion_queue_async_worker.h"
#include "tag.h"
#include "server_credentials.h"
#include "timeval.h"
namespace grpc {
namespace node {
using v8::Arguments;
using std::unique_ptr;
using v8::Array;
using v8::Boolean;
using v8::Date;
using v8::Exception;
using v8::Function;
using v8::FunctionTemplate;
@ -64,37 +68,82 @@ using v8::Persistent;
using v8::String;
using v8::Value;
Persistent<Function> Server::constructor;
NanCallback *Server::constructor;
Persistent<FunctionTemplate> Server::fun_tpl;
class NewCallOp : public Op {
public:
NewCallOp() {
call = NULL;
grpc_call_details_init(&details);
grpc_metadata_array_init(&request_metadata);
}
~NewCallOp() {
grpc_call_details_destroy(&details);
grpc_metadata_array_destroy(&request_metadata);
}
Handle<Value> GetNodeValue() const {
NanEscapableScope();
if (call == NULL) {
return NanEscapeScope(NanNull());
}
Handle<Object> obj = NanNew<Object>();
obj->Set(NanNew("call"), Call::WrapStruct(call));
obj->Set(NanNew("method"), NanNew(details.method));
obj->Set(NanNew("host"), NanNew(details.host));
obj->Set(NanNew("deadline"),
NanNew<Date>(TimespecToMilliseconds(details.deadline)));
obj->Set(NanNew("metadata"), ParseMetadata(&request_metadata));
return NanEscapeScope(obj);
}
bool ParseOp(Handle<Value> value, grpc_op *out,
shared_ptr<Resources> resources) {
return true;
}
grpc_call *call;
grpc_call_details details;
grpc_metadata_array request_metadata;
protected:
std::string GetTypeString() const {
return "new call";
}
};
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"));
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew("Server"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
NanSetPrototypeTemplate(tpl, "requestCall",
FunctionTemplate::New(RequestCall)->GetFunction());
NanNew<FunctionTemplate>(RequestCall)->GetFunction());
NanSetPrototypeTemplate(tpl, "addHttp2Port",
FunctionTemplate::New(AddHttp2Port)->GetFunction());
NanSetPrototypeTemplate(
tpl, "addHttp2Port",
NanNew<FunctionTemplate>(AddHttp2Port)->GetFunction());
NanSetPrototypeTemplate(
tpl, "addSecureHttp2Port",
FunctionTemplate::New(AddSecureHttp2Port)->GetFunction());
NanNew<FunctionTemplate>(AddSecureHttp2Port)->GetFunction());
NanSetPrototypeTemplate(tpl, "start",
FunctionTemplate::New(Start)->GetFunction());
NanNew<FunctionTemplate>(Start)->GetFunction());
NanSetPrototypeTemplate(tpl, "shutdown",
FunctionTemplate::New(Shutdown)->GetFunction());
NanNew<FunctionTemplate>(Shutdown)->GetFunction());
NanAssignPersistent(fun_tpl, tpl);
NanAssignPersistent(constructor, tpl->GetFunction());
exports->Set(String::NewSymbol("Server"), constructor);
Handle<Function> ctr = tpl->GetFunction();
constructor = new NanCallback(ctr);
exports->Set(NanNew("Server"), ctr);
}
bool Server::HasInstance(Handle<Value> val) {
@ -109,26 +158,14 @@ NAN_METHOD(Server::New) {
if (!args.IsConstructCall()) {
const int argc = 1;
Local<Value> argv[argc] = {args[0]};
NanReturnValue(constructor->NewInstance(argc, argv));
NanReturnValue(constructor->GetFunction()->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<Object> args_hash(args[0]->ToObject());
Handle<Array> keys(args_hash->GetOwnPropertyNames());
grpc_channel_args channel_args;
channel_args.num_args = keys->Length();
@ -155,11 +192,7 @@ NAN_METHOD(Server::New) {
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");
@ -175,13 +208,18 @@ NAN_METHOD(Server::RequestCall) {
return NanThrowTypeError("requestCall can only be called on a Server");
}
Server *server = ObjectWrap::Unwrap<Server>(args.This());
NewCallOp *op = new NewCallOp();
unique_ptr<OpVec> ops(new OpVec());
ops->push_back(unique_ptr<Op>(op));
grpc_call_error error = grpc_server_request_call(
server->wrapped_server, CreateTag(args[0], NanNull()));
if (error == GRPC_CALL_OK) {
CompletionQueueAsyncWorker::Next();
} else {
server->wrapped_server, &op->call, &op->details, &op->request_metadata,
CompletionQueueAsyncWorker::GetQueue(),
new struct tag(new NanCallback(args[0].As<Function>()), ops.release(),
shared_ptr<Resources>(nullptr)));
if (error != GRPC_CALL_OK) {
return NanThrowError("requestCall failed", error);
}
CompletionQueueAsyncWorker::Next();
NanReturnUndefined();
}
@ -205,11 +243,19 @@ NAN_METHOD(Server::AddSecureHttp2Port) {
"addSecureHttp2Port can only be called on a Server");
}
if (!args[0]->IsString()) {
return NanThrowTypeError("addSecureHttp2Port's argument must be a String");
return NanThrowTypeError(
"addSecureHttp2Port's first argument must be a String");
}
if (!ServerCredentials::HasInstance(args[1])) {
return NanThrowTypeError(
"addSecureHttp2Port's second argument must be ServerCredentials");
}
Server *server = ObjectWrap::Unwrap<Server>(args.This());
ServerCredentials *creds = ObjectWrap::Unwrap<ServerCredentials>(
args[1]->ToObject());
NanReturnValue(NanNew<Number>(grpc_server_add_secure_http2_port(
server->wrapped_server, *NanUtf8String(args[0]))));
server->wrapped_server, *NanUtf8String(args[0]),
creds->GetWrappedServerCredentials())));
}
NAN_METHOD(Server::Start) {

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -67,7 +67,7 @@ class Server : public ::node::ObjectWrap {
static NAN_METHOD(AddSecureHttp2Port);
static NAN_METHOD(Start);
static NAN_METHOD(Shutdown);
static v8::Persistent<v8::Function> constructor;
static NanCallback *constructor;
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
grpc_server *wrapped_server;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -41,8 +41,6 @@
namespace grpc {
namespace node {
using ::node::Buffer;
using v8::Arguments;
using v8::Exception;
using v8::External;
using v8::Function;
@ -56,29 +54,29 @@ using v8::ObjectTemplate;
using v8::Persistent;
using v8::Value;
Persistent<Function> ServerCredentials::constructor;
NanCallback *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);
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(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);
Handle<Function> ctr = tpl->GetFunction();
ctr->Set(NanNew("createSsl"),
NanNew<FunctionTemplate>(CreateSsl)->GetFunction());
ctr->Set(NanNew("createFake"),
NanNew<FunctionTemplate>(CreateFake)->GetFunction());
constructor = new NanCallback(ctr);
exports->Set(NanNew("ServerCredentials"), ctr);
}
bool ServerCredentials::HasInstance(Handle<Value> val) {
@ -94,8 +92,8 @@ Handle<Value> ServerCredentials::WrapStruct(
}
const int argc = 1;
Handle<Value> argv[argc] = {
External::New(reinterpret_cast<void *>(credentials))};
return NanEscapeScope(constructor->NewInstance(argc, argv));
NanNew<External>(reinterpret_cast<void *>(credentials))};
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
}
grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() {
@ -110,15 +108,16 @@ NAN_METHOD(ServerCredentials::New) {
return NanThrowTypeError(
"ServerCredentials can only be created with the provide functions");
}
Handle<External> ext = args[0].As<External>();
grpc_server_credentials *creds_value =
reinterpret_cast<grpc_server_credentials *>(External::Unwrap(args[0]));
reinterpret_cast<grpc_server_credentials *>(ext->Value());
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));
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
}
}
@ -127,20 +126,20 @@ NAN_METHOD(ServerCredentials::CreateSsl) {
NanScope();
char *root_certs = NULL;
grpc_ssl_pem_key_cert_pair key_cert_pair;
if (Buffer::HasInstance(args[0])) {
root_certs = Buffer::Data(args[0]);
if (::node::Buffer::HasInstance(args[0])) {
root_certs = ::node::Buffer::Data(args[0]);
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
return NanThrowTypeError(
"createSSl's first argument must be a Buffer if provided");
}
if (!Buffer::HasInstance(args[1])) {
if (!::node::Buffer::HasInstance(args[1])) {
return NanThrowTypeError("createSsl's second argument must be a Buffer");
}
key_cert_pair.private_key = Buffer::Data(args[1]);
if (!Buffer::HasInstance(args[2])) {
key_cert_pair.private_key = ::node::Buffer::Data(args[1]);
if (!::node::Buffer::HasInstance(args[2])) {
return NanThrowTypeError("createSsl's third argument must be a Buffer");
}
key_cert_pair.cert_chain = Buffer::Data(args[2]);
key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]);
NanReturnValue(WrapStruct(
grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1)));
}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -64,7 +64,7 @@ class ServerCredentials : public ::node::ObjectWrap {
static NAN_METHOD(New);
static NAN_METHOD(CreateSsl);
static NAN_METHOD(CreateFake);
static v8::Persistent<v8::Function> constructor;
static NanCallback *constructor;
// Used for typechecking instances of this javascript class
static v8::Persistent<v8::FunctionTemplate> fun_tpl;

View File

@ -1,101 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#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

View File

@ -1,59 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#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_

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -56,9 +56,8 @@ double TimespecToMilliseconds(gpr_timespec timespec) {
} 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);
return (static_cast<double>(timespec.tv_sec) * 1000 +
static_cast<double>(timespec.tv_nsec) / 1000000);
}
}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,13 +31,15 @@
*
*/
'use strict';
var _ = require('underscore');
var ProtoBuf = require('protobufjs');
var surface_client = require('./src/surface_client.js');
var client = require('./src/client.js');
var surface_server = require('./src/surface_server.js');
var server = require('./src/server.js');
var grpc = require('bindings')('grpc');
@ -54,7 +56,7 @@ function loadObject(value) {
});
return result;
} else if (value.className === 'Service') {
return surface_client.makeClientConstructor(value);
return client.makeClientConstructor(value);
} else if (value.className === 'Message' || value.className === 'Enum') {
return value.build();
} else {
@ -73,6 +75,37 @@ function load(filename) {
return loadObject(builder.ns);
}
/**
* Get a function that a client can use to update metadata with authentication
* information from a Google Auth credential object, which comes from the
* google-auth-library.
* @param {Object} credential The credential object to use
* @return {function(Object, callback)} Metadata updater function
*/
function getGoogleAuthDelegate(credential) {
/**
* Update a metadata object with authentication information.
* @param {Object} metadata Metadata object
* @param {function(Error, Object)} callback
*/
return function updateMetadata(metadata, callback) {
metadata = _.clone(metadata);
if (metadata.Authorization) {
metadata.Authorization = _.clone(metadata.Authorization);
} else {
metadata.Authorization = [];
}
credential.getAccessToken(function(err, token) {
if (err) {
callback(err);
return;
}
metadata.Authorization.push('Bearer ' + token);
callback(null, metadata);
});
};
}
/**
* See docs for loadObject
*/
@ -84,9 +117,9 @@ exports.loadObject = loadObject;
exports.load = load;
/**
* See docs for surface_server.makeServerConstructor
* See docs for server.makeServerConstructor
*/
exports.buildServer = surface_server.makeServerConstructor;
exports.buildServer = server.makeServerConstructor;
/**
* Status name to code number mapping
@ -106,3 +139,5 @@ exports.Credentials = grpc.Credentials;
* ServerCredentials factories
*/
exports.ServerCredentials = grpc.ServerCredentials;
exports.getGoogleAuthDelegate = getGoogleAuthDelegate;

View File

@ -1,3 +1,33 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto2";
package grpc.testing;
@ -10,10 +40,4 @@ package grpc.testing;
// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
// };
//
// MOE:begin_strip
// The difference between this one and net/rpc/empty-message.proto is that
// 1) The generated message here is in proto2 C++ API.
// 2) The proto2.Empty has minimum dependencies
// (no message_set or net/rpc dependencies)
// MOE:end_strip
message Empty {}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,13 +31,21 @@
*
*/
'use strict';
var fs = require('fs');
var path = require('path');
var grpc = require('..');
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
var GoogleAuth = require('google-auth-library');
var assert = require('assert');
var AUTH_SCOPE = 'https://www.googleapis.com/auth/xapi.zoo';
var AUTH_SCOPE_RESPONSE = 'xapi.zoo';
var AUTH_USER = ('155450119199-3psnrh1sdr3d8cpj1v46naggf81mhdnk' +
'@developer.gserviceaccount.com');
/**
* Create a buffer filled with size zeroes
* @param {number} size The length of the buffer
@ -145,8 +153,8 @@ function serverStreaming(client, done) {
resp_index += 1;
});
call.on('status', function(status) {
assert.strictEqual(resp_index, 4);
assert.strictEqual(status.code, grpc.status.OK);
assert.strictEqual(resp_index, 4);
if (done) {
done();
}
@ -255,6 +263,45 @@ function cancelAfterFirstResponse(client, done) {
});
}
/**
* Run one of the authentication tests.
* @param {Client} client The client to test against
* @param {function} done Callback to call when the test is completed. Included
* primarily for use with mocha
*/
function authTest(client, done) {
(new GoogleAuth()).getApplicationDefault(function(err, credential) {
assert.ifError(err);
if (credential.createScopedRequired()) {
credential = credential.createScoped(AUTH_SCOPE);
}
client.updateMetadata = grpc.getGoogleAuthDelegate(credential);
var arg = {
response_type: testProto.PayloadType.COMPRESSABLE,
response_size: 314159,
payload: {
body: zeroBuffer(271828)
},
fill_username: true,
fill_oauth_scope: true
};
var call = client.unaryCall(arg, function(err, resp) {
assert.ifError(err);
assert.strictEqual(resp.payload.type, testProto.PayloadType.COMPRESSABLE);
assert.strictEqual(resp.payload.body.limit - resp.payload.body.offset,
314159);
assert.strictEqual(resp.username, AUTH_USER);
assert.strictEqual(resp.oauth_scope, AUTH_SCOPE_RESPONSE);
});
call.on('status', function(status) {
assert.strictEqual(status.code, grpc.status.OK);
if (done) {
done();
}
});
});
}
/**
* Map from test case names to test functions
*/
@ -266,13 +313,15 @@ var test_cases = {
ping_pong: pingPong,
empty_stream: emptyStream,
cancel_after_begin: cancelAfterBegin,
cancel_after_first_response: cancelAfterFirstResponse
cancel_after_first_response: cancelAfterFirstResponse,
compute_engine_creds: authTest,
service_account_creds: authTest
};
/**
* Execute a single test case.
* @param {string} address The address of the server to connect to, in the
* format "hostname:port"
* format 'hostname:port'
* @param {string} host_overrirde The hostname of the server to use as an SSL
* override
* @param {string} test_case The name of the test case to run
@ -280,11 +329,16 @@ var test_cases = {
* @param {function} done Callback to call when the test is completed. Included
* primarily for use with mocha
*/
function runTest(address, host_override, test_case, tls, done) {
function runTest(address, host_override, test_case, tls, test_ca, done) {
// TODO(mlumish): enable TLS functionality
var options = {};
if (tls) {
var ca_path = path.join(__dirname, '../test/data/ca.pem');
var ca_path;
if (test_ca) {
ca_path = path.join(__dirname, '../test/data/ca.pem');
} else {
ca_path = process.env.SSL_CERT_FILE;
}
var ca_data = fs.readFileSync(ca_path);
var creds = grpc.Credentials.createSsl(ca_data);
options.credentials = creds;
@ -304,7 +358,10 @@ if (require.main === module) {
'use_tls', 'use_test_ca']
});
runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
argv.test_case, argv.use_tls === 'true');
argv.test_case, argv.use_tls === 'true', argv.use_test_ca === 'true',
function () {
console.log('OK:', argv.test_case);
});
}
/**

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
@ -163,16 +165,16 @@ function handleHalfDuplex(call) {
function getServer(port, tls) {
// TODO(mlumish): enable TLS functionality
var options = {};
var server_creds = null;
if (tls) {
var key_path = path.join(__dirname, '../test/data/server1.key');
var pem_path = path.join(__dirname, '../test/data/server1.pem');
var key_data = fs.readFileSync(key_path);
var pem_data = fs.readFileSync(pem_path);
var server_creds = grpc.ServerCredentials.createSsl(null,
server_creds = grpc.ServerCredentials.createSsl(null,
key_data,
pem_data);
options.credentials = server_creds;
}
var server = new Server({
'grpc.testing.TestService' : {
@ -184,7 +186,7 @@ function getServer(port, tls) {
halfDuplexCall: handleHalfDuplex
}
}, null, options);
var port_num = server.bind('0.0.0.0:' + port, tls);
var port_num = server.bind('0.0.0.0:' + port, server_creds);
return {server: server, port: port_num};
}

View File

@ -1,3 +1,33 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Message definitions to be used by integration test service definitions.
syntax = "proto2";
@ -19,7 +49,7 @@ enum PayloadType {
// A block of data, to simply increase gRPC message size.
message Payload {
// The type of data in body.
optional PayloadType type = 1;
optional PayloadType type = 1 [default = COMPRESSABLE];
// Primary contents of payload.
optional bytes body = 2;
}
@ -28,7 +58,7 @@ message Payload {
message SimpleRequest {
// Desired payload type in the response from the server.
// If response_type is RANDOM, server randomly chooses one from other formats.
optional PayloadType response_type = 1;
optional PayloadType response_type = 1 [default = COMPRESSABLE];
// Desired payload size in the response from the server.
// If response_type is COMPRESSABLE, this denotes the size before compression.
@ -36,6 +66,12 @@ message SimpleRequest {
// Optional input payload sent along with the request.
optional Payload payload = 3;
// Whether SimpleResponse should include username.
optional bool fill_username = 4;
// Whether SimpleResponse should include OAuth scope.
optional bool fill_oauth_scope = 5;
}
// Unary response, as configured by the request.
@ -44,7 +80,9 @@ message SimpleResponse {
optional Payload payload = 1;
// The user the request came from, for verifying authentication was
// successful when the client expected it.
optional int64 effective_gaia_user_id = 2;
optional string username = 2;
// OAuth scope.
optional string oauth_scope = 3;
}
// Client-streaming request.
@ -78,7 +116,7 @@ message StreamingOutputCallRequest {
// If response_type is RANDOM, the payload from each response in the stream
// might be of different types. This is to simulate a mixed type of payload
// stream.
optional PayloadType response_type = 1;
optional PayloadType response_type = 1 [default = COMPRESSABLE];
// Configuration for each expected response message.
repeated ResponseParameters response_parameters = 2;

View File

@ -1,3 +1,33 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// An integration test service that covers all the method signature permutations
// of unary/streaming requests/responses.
syntax = "proto2";
@ -14,7 +44,7 @@ service TestService {
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
// One request followed by one response.
// The server returns the client payload as-is.
// TODO(Issue 527): Describe required server behavior.
rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
// One request followed by a sequence of responses (streamed download).

View File

@ -1,20 +1,57 @@
{
"name": "grpc",
"version": "0.1.0",
"version": "0.5.5",
"author": "Google Inc.",
"description": "gRPC Library for Node",
"homepage": "http://www.grpc.io/",
"repository": {
"type": "git",
"url": "https://github.com/grpc/grpc.git"
},
"bugs": "https://github.com/grpc/grpc/issues",
"contributors": [
{
"name": "Michael Lumish",
"email": "mlumish@google.com"
}
],
"directories": {
"lib": "src",
"example": "examples"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha"
"lint": "node ./node_modules/jshint/bin/jshint src test examples interop index.js",
"test": "node ./node_modules/mocha/bin/mocha && npm run-script lint"
},
"dependencies": {
"bindings": "^1.2.1",
"nan": "~1.3.0",
"protobufjs": "murgatroid99/ProtoBuf.js",
"underscore": "^1.7.0",
"bindings": "^1.2.0",
"nan": "^1.5.0",
"protobufjs": "^4.0.0-b2",
"underscore": "^1.6.0",
"underscore.string": "^3.0.0"
},
"devDependencies": {
"async": "^0.9.0",
"google-auth-library": "^0.9.2",
"jshint": "^2.5.0",
"minimist": "^1.1.0",
"mocha": "~1.21.0",
"minimist": "^1.1.0"
"strftime": "^0.8.2"
},
"main": "index.js"
"engines": {
"node": ">=0.10.13"
},
"files": [
"LICENSE",
"README.md",
"index.js",
"binding.gyp",
"examples",
"ext",
"interop",
"src",
"test"
],
"main": "index.js",
"license": "BSD-3-Clause"
}

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,185 +31,500 @@
*
*/
'use strict';
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node');
var common = require('./common');
var common = require('./common.js');
var Duplex = require('stream').Duplex;
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');
util.inherits(GrpcClientStream, Duplex);
util.inherits(ClientWritableStream, Writable);
/**
* Class for representing a gRPC client side stream as a Node stream. Extends
* from stream.Duplex.
* A stream that the client can write to. Used for calls that are streaming from
* the client side.
* @constructor
* @param {grpc.Call} call Call object to proxy
* @param {function(*):Buffer=} serialize Serialization function for requests
* @param {function(Buffer):*=} deserialize Deserialization function for
* responses
* @param {grpc.Call} call The call object to send data with
* @param {function(*):Buffer=} serialize Serialization function for writes.
*/
function GrpcClientStream(call, serialize, deserialize) {
Duplex.call(this, {objectMode: true});
if (!serialize) {
serialize = function(value) {
return value;
};
}
if (!deserialize) {
deserialize = function(value) {
return value;
};
}
var self = this;
var finished = false;
// Indicates that a read is currently pending
var reading = false;
// Indicates that a write is currently pending
var writing = false;
this._call = call;
/**
* Serialize a request value to a buffer. Always maps null to null. Otherwise
* uses the provided serialize function
* @param {*} value The value to serialize
* @return {Buffer} The serialized value
*/
this.serialize = function(value) {
if (value === null || value === undefined) {
return null;
}
return serialize(value);
};
/**
* Deserialize a response buffer to a value. Always maps null to null.
* Otherwise uses the provided deserialize function.
* @param {Buffer} buffer The buffer to deserialize
* @return {*} The deserialized value
*/
this.deserialize = function(buffer) {
if (buffer === null) {
return null;
}
return deserialize(buffer);
};
/**
* 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(self.deserialize(data)) && data != null) {
self._call.startRead(readCallback);
} else {
reading = false;
}
}
call.invoke(function(event) {
self.emit('metadata', event.data);
}, function(event) {
finished = true;
self.emit('status', event.data);
}, 0);
function ClientWritableStream(call, serialize) {
Writable.call(this, {objectMode: true});
this.call = call;
this.serialize = common.wrapIgnoreNull(serialize);
this.on('finish', function() {
call.writesDone(function() {});
var batch = {};
batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(batch, function() {});
});
/**
* 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. This is an implementation of a method needed for implementing
* stream.Readable.
* @param {number} size Ignored
*/
GrpcClientStream.prototype._read = function(size) {
this.startReading();
};
/**
* 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
* @param {function(Error=)} callback Called when the write is complete
*/
GrpcClientStream.prototype._write = function(chunk, encoding, callback) {
var self = this;
self._call.startWrite(self.serialize(chunk), function(event) {
function _write(chunk, encoding, callback) {
/* jshint validthis: true */
var batch = {};
batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk);
this.call.startBatch(batch, function(err, event) {
if (err) {
throw err;
}
callback();
}, 0);
};
});
}
ClientWritableStream.prototype._write = _write;
util.inherits(ClientReadableStream, Readable);
/**
* Cancel the ongoing call. If the call has not already finished, it will finish
* with status CANCELLED.
* A stream that the client can read from. Used for calls that are streaming
* from the server side.
* @constructor
* @param {grpc.Call} call The call object to read data with
* @param {function(Buffer):*=} deserialize Deserialization function for reads
*/
GrpcClientStream.prototype.cancel = function() {
this._call.cancel();
};
/**
* 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 {function(*):Buffer} serialize Serialization function for requests
* @param {function(Buffer):*} deserialize Deserialization function for
* responses
* @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,
serialize,
deserialize,
metadata,
deadline) {
if (deadline === undefined) {
deadline = Infinity;
}
var call = new grpc.Call(channel, method, deadline);
if (metadata) {
call.addMetadata(metadata);
}
return new GrpcClientStream(call, serialize, deserialize);
function ClientReadableStream(call, deserialize) {
Readable.call(this, {objectMode: true});
this.call = call;
this.finished = false;
this.reading = false;
this.deserialize = common.wrapIgnoreNull(deserialize);
}
/**
* See documentation for makeRequest above
* Read the next object from the stream.
* @param {*} size Ignored because we use objectMode=true
*/
exports.makeRequest = makeRequest;
function _read(size) {
/* jshint validthis: true */
var self = this;
/**
* 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(err, event) {
if (err) {
throw err;
}
if (self.finished) {
self.push(null);
return;
}
var data = event.read;
if (self.push(self.deserialize(data)) && data !== null) {
var read_batch = {};
read_batch[grpc.opType.RECV_MESSAGE] = true;
self.call.startBatch(read_batch, readCallback);
} else {
self.reading = false;
}
}
if (self.finished) {
self.push(null);
} else {
if (!self.reading) {
self.reading = true;
var read_batch = {};
read_batch[grpc.opType.RECV_MESSAGE] = true;
self.call.startBatch(read_batch, readCallback);
}
}
}
ClientReadableStream.prototype._read = _read;
util.inherits(ClientDuplexStream, Duplex);
/**
* Represents a client side gRPC channel associated with a single host.
* A stream that the client can read from or write to. Used for calls with
* duplex streaming.
* @constructor
* @param {grpc.Call} call Call object to proxy
* @param {function(*):Buffer=} serialize Serialization function for requests
* @param {function(Buffer):*=} deserialize Deserialization function for
* responses
*/
exports.Channel = grpc.Channel;
function ClientDuplexStream(call, serialize, deserialize) {
Duplex.call(this, {objectMode: true});
this.serialize = common.wrapIgnoreNull(serialize);
this.deserialize = common.wrapIgnoreNull(deserialize);
this.call = call;
this.on('finish', function() {
var batch = {};
batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(batch, function() {});
});
}
ClientDuplexStream.prototype._read = _read;
ClientDuplexStream.prototype._write = _write;
/**
* Status name to code number mapping
* Cancel the ongoing call
*/
function cancel() {
/* jshint validthis: true */
this.call.cancel();
}
ClientReadableStream.prototype.cancel = cancel;
ClientWritableStream.prototype.cancel = cancel;
ClientDuplexStream.prototype.cancel = cancel;
/**
* 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.
* @this {Client} Client object. Must have a channel member.
* @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(argument, callback, metadata, deadline) {
/* jshint validthis: true */
if (deadline === undefined) {
deadline = Infinity;
}
var emitter = new EventEmitter();
var call = new grpc.Call(this.channel, method, deadline);
if (metadata === null || metadata === undefined) {
metadata = {};
}
emitter.cancel = function cancel() {
call.cancel();
};
this.updateMetadata(metadata, function(error, metadata) {
if (error) {
call.cancel();
callback(error);
return;
}
var client_batch = {};
client_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
client_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
client_batch[grpc.opType.RECV_MESSAGE] = true;
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
if (err) {
callback(err);
return;
}
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
callback(error);
return;
}
emitter.emit('status', response.status);
emitter.emit('metadata', response.metadata);
callback(null, deserialize(response.read));
});
});
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.
* @this {Client} Client object. Must have a channel member.
* @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(callback, metadata, deadline) {
/* jshint validthis: true */
if (deadline === undefined) {
deadline = Infinity;
}
var call = new grpc.Call(this.channel, method, deadline);
if (metadata === null || metadata === undefined) {
metadata = {};
}
var stream = new ClientWritableStream(call, serialize);
this.updateMetadata(metadata, function(error, metadata) {
if (error) {
call.cancel();
callback(error);
return;
}
var metadata_batch = {};
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
metadata_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(metadata_batch, function(err, response) {
if (err) {
callback(err);
return;
}
stream.emit('metadata', response.metadata);
});
var client_batch = {};
client_batch[grpc.opType.RECV_MESSAGE] = true;
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
if (err) {
callback(err);
return;
}
if (response.status.code !== grpc.status.OK) {
var error = new Error(response.status.details);
error.code = response.status.code;
callback(error);
return;
}
stream.emit('status', response.status);
callback(null, deserialize(response.read));
});
});
return 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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @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(argument, metadata, deadline) {
/* jshint validthis: true */
if (deadline === undefined) {
deadline = Infinity;
}
var call = new grpc.Call(this.channel, method, deadline);
if (metadata === null || metadata === undefined) {
metadata = {};
}
var stream = new ClientReadableStream(call, deserialize);
this.updateMetadata(metadata, function(error, metadata) {
if (error) {
call.cancel();
stream.emit('error', error);
return;
}
var start_batch = {};
start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
start_batch[grpc.opType.SEND_MESSAGE] = serialize(argument);
start_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
call.startBatch(start_batch, function(err, response) {
if (err) {
throw err;
}
stream.emit('metadata', response.metadata);
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
if (err) {
throw err;
}
stream.emit('status', response.status);
});
});
return 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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
* @param {(number|Date)=} deadline The deadline for processing this request.
* Defaults to infinite future
* @return {EventEmitter} An event emitter for stream related events
*/
function makeBidiStreamRequest(metadata, deadline) {
/* jshint validthis: true */
if (deadline === undefined) {
deadline = Infinity;
}
var call = new grpc.Call(this.channel, method, deadline);
if (metadata === null || metadata === undefined) {
metadata = {};
}
var stream = new ClientDuplexStream(call, serialize, deserialize);
this.updateMetadata(metadata, function(error, metadata) {
if (error) {
call.cancel();
stream.emit('error', error);
return;
}
var start_batch = {};
start_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
start_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(start_batch, function(err, response) {
if (err) {
throw err;
}
stream.emit('metadata', response.metadata);
});
var status_batch = {};
status_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(status_batch, function(err, response) {
if (err) {
throw err;
}
stream.emit('status', response.status);
});
});
return stream;
}
return makeBidiStreamRequest;
}
/**
* Map with short names for each of the requester maker functions. Used in
* makeClientConstructor
*/
var requester_makers = {
unary: makeUnaryRequestFunction,
server_stream: makeServerStreamRequestFunction,
client_stream: makeClientStreamRequestFunction,
bidi: makeBidiStreamRequestFunction
};
/**
* Creates a constructor for clients for the given service
* @param {ProtoBuf.Reflect.Service} service The service to generate a client
* for
* @return {function(string, Object)} New client constructor
*/
function makeClientConstructor(service) {
var prefix = '/' + common.fullyQualifiedName(service) + '/';
/**
* Create a client with the given methods
* @constructor
* @param {string} address The address of the server to connect to
* @param {Object} options Options to pass to the underlying channel
* @param {function(Object, function)=} updateMetadata function to update the
* metadata for each request
*/
function Client(address, options, updateMetadata) {
if (updateMetadata) {
this.updateMetadata = updateMetadata;
} else {
this.updateMetadata = function(metadata, callback) {
callback(null, metadata);
};
}
this.channel = new grpc.Channel(address, options);
}
_.each(service.children, function(method) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
var serialize = common.serializeCls(method.resolvedRequestType.build());
var deserialize = common.deserializeCls(
method.resolvedResponseType.build());
Client.prototype[decapitalize(method.name)] = requester_makers[method_type](
prefix + capitalize(method.name), serialize, deserialize);
Client.prototype[decapitalize(method.name)].serialize = serialize;
Client.prototype[decapitalize(method.name)].deserialize = deserialize;
});
Client.service = service;
return Client;
}
exports.makeClientConstructor = makeClientConstructor;
/**
* See docs for client.status
*/
exports.status = grpc.status;
/**
* Call error name to code number mapping
* See docs for client.callError
*/
exports.callError = grpc.callError;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,10 @@
*
*/
'use strict';
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
/**
@ -87,6 +91,24 @@ function fullyQualifiedName(value) {
return name;
}
/**
* Wrap a function to pass null-like values through without calling it. If no
* function is given, just uses the identity;
* @param {?function} func The function to wrap
* @return {function} The wrapped function
*/
function wrapIgnoreNull(func) {
if (!func) {
return _.identity;
}
return function(arg) {
if (arg === null || arg === undefined) {
return null;
}
return func(arg);
};
}
/**
* See docs for deserializeCls
*/
@ -101,3 +123,8 @@ exports.serializeCls = serializeCls;
* See docs for fullyQualifiedName
*/
exports.fullyQualifiedName = fullyQualifiedName;
/**
* See docs for wrapIgnoreNull
*/
exports.wrapIgnoreNull = wrapIgnoreNull;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,82 +31,112 @@
*
*/
'use strict';
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var grpc = require('bindings')('grpc.node');
var common = require('./common');
var Duplex = require('stream').Duplex;
var stream = require('stream');
var Readable = stream.Readable;
var Writable = stream.Writable;
var Duplex = stream.Duplex;
var util = require('util');
util.inherits(GrpcServerStream, Duplex);
var EventEmitter = require('events').EventEmitter;
var common = require('./common.js');
/**
* 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 {function(*):Buffer=} serialize Serialization function for responses
* @param {function(Buffer):*=} deserialize Deserialization function for
* requests
* Handle an error on a call by sending it as a status
* @param {grpc.Call} call The call to send the error on
* @param {Object} error The error object
*/
function GrpcServerStream(call, serialize, deserialize) {
Duplex.call(this, {objectMode: true});
if (!serialize) {
serialize = function(value) {
return value;
};
}
if (!deserialize) {
deserialize = function(value) {
return value;
};
}
this._call = call;
// Indicate that a status has been sent
var finished = false;
var self = this;
function handleError(call, error) {
var status = {
'code' : grpc.status.OK,
'details' : 'OK'
code: grpc.status.INTERNAL,
details: 'Unknown Error',
metadata: {}
};
/**
* Serialize a response value to a buffer. Always maps null to null. Otherwise
* uses the provided serialize function
* @param {*} value The value to serialize
* @return {Buffer} The serialized value
*/
this.serialize = function(value) {
if (value === null || value === undefined) {
return null;
if (error.hasOwnProperty('message')) {
status.details = error.message;
}
return serialize(value);
};
/**
* Deserialize a request buffer to a value. Always maps null to null.
* Otherwise uses the provided deserialize function.
* @param {Buffer} buffer The buffer to deserialize
* @return {*} The deserialized value
*/
this.deserialize = function(buffer) {
if (buffer === null) {
return null;
if (error.hasOwnProperty('code')) {
status.code = error.code;
if (error.hasOwnProperty('details')) {
status.details = error.details;
}
return deserialize(buffer);
};
}
var error_batch = {};
error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
call.startBatch(error_batch, function(){});
}
/**
* Send the pending status
/**
* Wait for the client to close, then emit a cancelled event if the client
* cancelled.
* @param {grpc.Call} call The call object to wait on
* @param {EventEmitter} emitter The event emitter to emit the cancelled event
* on
*/
function sendStatus() {
call.startWriteStatus(status.code, status.details, function() {
function waitForCancel(call, emitter) {
var cancel_batch = {};
cancel_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
call.startBatch(cancel_batch, function(err, result) {
if (err) {
emitter.emit('error', err);
}
if (result.cancelled) {
emitter.cancelled = true;
emitter.emit('cancelled');
}
});
finished = true;
}
/**
* Send a response to a unary or client streaming call.
* @param {grpc.Call} call The call to respond on
* @param {*} value The value to respond with
* @param {function(*):Buffer=} serialize Serialization function for the
* response
*/
function sendUnaryResponse(call, value, serialize) {
var end_batch = {};
end_batch[grpc.opType.SEND_MESSAGE] = serialize(value);
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
code: grpc.status.OK,
details: 'OK',
metadata: {}
};
call.startBatch(end_batch, function (){});
}
/**
* Initialize a writable stream. This is used for both the writable and duplex
* stream constructors.
* @param {Writable} stream The stream to set up
* @param {function(*):Buffer=} Serialization function for responses
*/
function setUpWritable(stream, serialize) {
stream.finished = false;
stream.status = {
code : grpc.status.OK,
details : 'OK',
metadata : {}
};
stream.serialize = common.wrapIgnoreNull(serialize);
function sendStatus() {
var batch = {};
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = stream.status;
stream.call.startBatch(batch, function(){});
}
this.on('finish', sendStatus);
stream.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
@ -116,14 +146,16 @@ function GrpcServerStream(call, serialize, deserialize) {
function setStatus(err) {
var code = grpc.status.INTERNAL;
var details = 'Unknown Error';
if (err.hasOwnProperty('message')) {
details = err.message;
}
if (err.hasOwnProperty('code')) {
code = err.code;
if (err.hasOwnProperty('details')) {
details = err.details;
}
}
status = {'code': code, 'details': details};
stream.status = {code: code, details: details, metadata: {}};
}
/**
* Terminate the call. This includes indicating that reads are done, draining
@ -133,55 +165,50 @@ function GrpcServerStream(call, serialize, deserialize) {
*/
function terminateCall(err) {
// Drain readable data
this.on('data', function() {});
setStatus(err);
this.end();
stream.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(self.deserialize(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);
}
}
};
stream.on('error', terminateCall);
}
/**
* Start reading from the gRPC data source. This is an implementation of a
* method required for implementing stream.Readable
* @param {number} size Ignored
* Initialize a readable stream. This is used for both the readable and duplex
* stream constructors.
* @param {Readable} stream The stream to initialize
* @param {function(Buffer):*=} deserialize Deserialization function for
* incoming data.
*/
GrpcServerStream.prototype._read = function(size) {
this.startReading();
};
function setUpReadable(stream, deserialize) {
stream.deserialize = common.wrapIgnoreNull(deserialize);
stream.finished = false;
stream.reading = false;
stream.terminate = function() {
stream.finished = true;
stream.on('data', function() {});
};
stream.on('cancelled', function() {
stream.terminate();
});
}
util.inherits(ServerWritableStream, Writable);
/**
* A stream that the server can write to. Used for calls that are streaming from
* the server side.
* @constructor
* @param {grpc.Call} call The call object to send data with
* @param {function(*):Buffer=} serialize Serialization function for writes
*/
function ServerWritableStream(call, serialize) {
Writable.call(this, {objectMode: true});
this.call = call;
this.finished = false;
setUpWritable(this, serialize);
}
/**
* Start writing a chunk of data. This is an implementation of a method required
@ -191,11 +218,199 @@ GrpcServerStream.prototype._read = function(size) {
* @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(self.serialize(chunk), function(event) {
function _write(chunk, encoding, callback) {
/* jshint validthis: true */
var batch = {};
batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk);
this.call.startBatch(batch, function(err, value) {
if (err) {
this.emit('error', err);
return;
}
callback();
}, 0);
});
}
ServerWritableStream.prototype._write = _write;
util.inherits(ServerReadableStream, Readable);
/**
* A stream that the server can read from. Used for calls that are streaming
* from the client side.
* @constructor
* @param {grpc.Call} call The call object to read data with
* @param {function(Buffer):*=} deserialize Deserialization function for reads
*/
function ServerReadableStream(call, deserialize) {
Readable.call(this, {objectMode: true});
this.call = call;
setUpReadable(this, deserialize);
}
/**
* Start reading from the gRPC data source. This is an implementation of a
* method required for implementing stream.Readable
* @param {number} size Ignored
*/
function _read(size) {
/* jshint validthis: true */
var self = this;
/**
* 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(err, event) {
if (err) {
self.terminate();
return;
}
if (self.finished) {
self.push(null);
return;
}
var data = event.read;
if (self.push(self.deserialize(data)) && data !== null) {
var read_batch = {};
read_batch[grpc.opType.RECV_MESSAGE] = true;
self.call.startBatch(read_batch, readCallback);
} else {
self.reading = false;
}
}
if (self.finished) {
self.push(null);
} else {
if (!self.reading) {
self.reading = true;
var batch = {};
batch[grpc.opType.RECV_MESSAGE] = true;
self.call.startBatch(batch, readCallback);
}
}
}
ServerReadableStream.prototype._read = _read;
util.inherits(ServerDuplexStream, Duplex);
/**
* A stream that the server can read from or write to. Used for calls with
* duplex streaming.
* @constructor
* @param {grpc.Call} call Call object to proxy
* @param {function(*):Buffer=} serialize Serialization function for requests
* @param {function(Buffer):*=} deserialize Deserialization function for
* responses
*/
function ServerDuplexStream(call, serialize, deserialize) {
Duplex.call(this, {objectMode: true});
this.call = call;
setUpWritable(this, serialize);
setUpReadable(this, deserialize);
}
ServerDuplexStream.prototype._read = _read;
ServerDuplexStream.prototype._write = _write;
/**
* Fully handle a unary call
* @param {grpc.Call} call The call to handle
* @param {Object} handler Request handler object for the method that was called
* @param {Object} metadata Metadata from the client
*/
function handleUnary(call, handler, metadata) {
var emitter = new EventEmitter();
emitter.on('error', function(error) {
handleError(call, error);
});
waitForCancel(call, emitter);
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
batch[grpc.opType.RECV_MESSAGE] = true;
call.startBatch(batch, function(err, result) {
if (err) {
handleError(call, err);
return;
}
emitter.request = handler.deserialize(result.read);
if (emitter.cancelled) {
return;
}
handler.func(emitter, function sendUnaryData(err, value) {
if (err) {
handleError(call, err);
}
sendUnaryResponse(call, value, handler.serialize);
});
});
}
/**
* Fully handle a server streaming call
* @param {grpc.Call} call The call to handle
* @param {Object} handler Request handler object for the method that was called
* @param {Object} metadata Metadata from the client
*/
function handleServerStreaming(call, handler, metadata) {
var stream = new ServerWritableStream(call, handler.serialize);
waitForCancel(call, stream);
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
batch[grpc.opType.RECV_MESSAGE] = true;
call.startBatch(batch, function(err, result) {
if (err) {
stream.emit('error', err);
return;
}
stream.request = handler.deserialize(result.read);
handler.func(stream);
});
}
/**
* Fully handle a client streaming call
* @param {grpc.Call} call The call to handle
* @param {Object} handler Request handler object for the method that was called
* @param {Object} metadata Metadata from the client
*/
function handleClientStreaming(call, handler, metadata) {
var stream = new ServerReadableStream(call, handler.deserialize);
waitForCancel(call, stream);
var metadata_batch = {};
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
call.startBatch(metadata_batch, function() {});
handler.func(stream, function(err, value) {
stream.terminate();
if (err) {
handleError(call, err);
}
sendUnaryResponse(call, value, handler.serialize);
});
}
/**
* Fully handle a bidirectional streaming call
* @param {grpc.Call} call The call to handle
* @param {Object} handler Request handler object for the method that was called
* @param {Object} metadata Metadata from the client
*/
function handleBidiStreaming(call, handler, metadata) {
var stream = new ServerDuplexStream(call, handler.serialize,
handler.deserialize);
waitForCancel(call, stream);
var metadata_batch = {};
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata;
call.startBatch(metadata_batch, function() {});
handler.func(stream);
}
var streamHandlers = {
unary: handleUnary,
server_stream: handleServerStreaming,
client_stream: handleClientStreaming,
bidi: handleBidiStreaming
};
/**
@ -213,12 +428,11 @@ function Server(getMetadata, options) {
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() {
this.listen = function() {
console.log('Server starting');
_.each(handlers, function(handler, handler_name) {
console.log('Serving', handler_name);
@ -233,48 +447,38 @@ function Server(getMetadata, options) {
* 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) {
function handleNewCall(err, event) {
if (err) {
return;
}
var details = event['new call'];
var call = details.call;
var method = details.method;
var metadata = details.metadata;
if (method === null) {
return;
}
server.requestCall(handleNewCall);
var handler = undefined;
var deadline = data.absolute_deadline;
var cancelled = false;
call.serverAccept(function(event) {
if (event.data.code === grpc.status.CANCELLED) {
cancelled = true;
if (stream) {
stream.emit('cancelled');
}
}
}, 0);
if (handlers.hasOwnProperty(data.method)) {
handler = handlers[data.method];
var handler;
if (handlers.hasOwnProperty(method)) {
handler = handlers[method];
} else {
call.serverEndInitialMetadata(0);
call.startWriteStatus(
grpc.status.UNIMPLEMENTED,
"This method is not available on this server.",
function() {});
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = {};
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
code: grpc.status.UNIMPLEMENTED,
details: 'This method is not available on this server.',
metadata: {}
};
batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
call.startBatch(batch, function() {});
return;
}
var response_metadata = {};
if (getMetadata) {
call.addMetadata(getMetadata(data.method, data.metadata));
}
call.serverEndInitialMetadata(0);
var stream = new GrpcServerStream(call, handler.serialize,
handler.deserialize);
Object.defineProperty(stream, 'cancelled', {
get: function() { return cancelled;}
});
try {
handler.func(stream, data.metadata);
} catch (e) {
stream.emit('error', e);
response_metadata = getMetadata(method, metadata);
}
streamHandlers[handler.type](call, handler, response_metadata);
}
server.requestCall(handleNewCall);
};
@ -294,36 +498,144 @@ function Server(getMetadata, options) {
* returns a stream of response values
* @param {function(*):Buffer} serialize Serialization function for responses
* @param {function(Buffer):*} deserialize Deserialization function for requests
* @param {string} type The streaming type of method that this handles
* @return {boolean} True if the handler was set. False if a handler was already
* set for that name.
*/
Server.prototype.register = function(name, handler, serialize, deserialize) {
Server.prototype.register = function(name, handler, serialize, deserialize,
type) {
if (this.handlers.hasOwnProperty(name)) {
return false;
}
this.handlers[name] = {
func: handler,
serialize: serialize,
deserialize: deserialize
deserialize: deserialize,
type: type
};
return true;
};
/**
* Binds the server to the given port, with SSL enabled if secure is specified
* Binds the server to the given port, with SSL enabled if creds is given
* @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
* @param {boolean=} creds Server credential object to be used for SSL. Pass
* nothing for an insecure port
*/
Server.prototype.bind = function(port, secure) {
if (secure) {
return this._server.addSecureHttp2Port(port);
Server.prototype.bind = function(port, creds) {
if (creds) {
return this._server.addSecureHttp2Port(port, creds);
} else {
return this._server.addHttp2Port(port);
}
};
/**
* See documentation for Server
* 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
*/
module.exports = Server;
function makeServerConstructor(services) {
var qual_names = [];
_.each(services, function(service) {
_.each(service.children, function(method) {
var name = common.fullyQualifiedName(method);
if (_.indexOf(qual_names, name) !== -1) {
throw new Error('Method ' + name + ' exposed by more than one service');
}
qual_names.push(name);
});
});
/**
* Create a server with the given handlers for all of the methods.
* @constructor
* @param {Object} service_handlers Map from service names to map from method
* names to handlers
* @param {function(string, Object<string, Array<Buffer>>):
Object<string, Array<Buffer|string>>=} getMetadata Callback that
* gets metatada for a given method
* @param {Object=} options Options to pass to the underlying server
*/
function SurfaceServer(service_handlers, getMetadata, options) {
var server = new Server(getMetadata, options);
this.inner_server = server;
_.each(services, function(service) {
var service_name = common.fullyQualifiedName(service);
if (service_handlers[service_name] === undefined) {
throw new Error('Handlers for service ' +
service_name + ' not provided.');
}
var prefix = '/' + common.fullyQualifiedName(service) + '/';
_.each(service.children, function(method) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
if (service_handlers[service_name][decapitalize(method.name)] ===
undefined) {
throw new Error('Method handler for ' +
common.fullyQualifiedName(method) + ' not provided.');
}
var serialize = common.serializeCls(
method.resolvedResponseType.build());
var deserialize = common.deserializeCls(
method.resolvedRequestType.build());
server.register(
prefix + capitalize(method.name),
service_handlers[service_name][decapitalize(method.name)],
serialize, deserialize, method_type);
});
}, this);
}
/**
* Binds the server to the given port, with SSL enabled if creds is supplied
* @param {string} port The port that the server should bind on, in the format
* "address:port"
* @param {boolean=} creds Credentials to use for SSL
* @return {SurfaceServer} this
*/
SurfaceServer.prototype.bind = function(port, creds) {
return this.inner_server.bind(port, creds);
};
/**
* Starts the server listening on any bound ports
* @return {SurfaceServer} this
*/
SurfaceServer.prototype.listen = function() {
this.inner_server.listen();
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;

View File

@ -1,357 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
var client = require('./client.js');
var common = require('./common.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
*/
function ClientReadableObjectStream(stream) {
var 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(chunk)) {
self._stream.pause();
}
});
this._stream.pause();
}
/**
* _read implementation for both types of streams that allow reading.
* @this {ClientReadableObjectStream}
* @param {number} size Ignored
*/
function _read(size) {
this._stream.resume();
}
/**
* See docs for _read
*/
ClientReadableObjectStream.prototype._read = _read;
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
*/
function ClientWritableObjectStream(stream) {
var options = {objectMode: true};
Writable.call(this, options);
this._stream = stream;
forwardEvent(stream, this, 'status');
forwardEvent(stream, this, 'metadata');
this.on('finish', function() {
this._stream.end();
});
}
/**
* _write implementation for both types of streams that allow writing
* @this {ClientWritableObjectStream}
* @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(chunk, encoding, callback);
}
/**
* See docs for _write
*/
ClientWritableObjectStream.prototype._write = _write;
/**
* Cancel the underlying call
*/
function cancel() {
this._stream.cancel();
}
ClientReadableObjectStream.prototype.cancel = cancel;
ClientWritableObjectStream.prototype.cancel = cancel;
/**
* 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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @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(argument, callback, metadata, deadline) {
var stream = client.makeRequest(this.channel, method, serialize,
deserialize, metadata, deadline);
var emitter = new EventEmitter();
emitter.cancel = function cancel() {
stream.cancel();
};
forwardEvent(stream, emitter, 'status');
forwardEvent(stream, emitter, 'metadata');
stream.write(argument);
stream.end();
stream.on('data', function forwardData(chunk) {
try {
callback(null, chunk);
} catch (e) {
callback(e);
}
});
stream.on('status', function forwardStatus(status) {
if (status.code !== client.status.OK) {
callback(status);
}
});
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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @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(callback, metadata, deadline) {
var stream = client.makeRequest(this.channel, method, serialize,
deserialize, metadata, deadline);
var obj_stream = new ClientWritableObjectStream(stream);
stream.on('data', function forwardData(chunk) {
try {
callback(null, chunk);
} catch (e) {
callback(e);
}
});
stream.on('status', function forwardStatus(status) {
if (status.code !== client.status.OK) {
callback(status);
}
});
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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @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(argument, metadata, deadline) {
var stream = client.makeRequest(this.channel, method, serialize,
deserialize, metadata, deadline);
var obj_stream = new ClientReadableObjectStream(stream);
stream.write(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.
* @this {SurfaceClient} Client object. Must have a channel member.
* @param {array=} metadata Array of metadata key/value pairs to add to the
* call
* @param {(number|Date)=} deadline The deadline for processing this request.
* Defaults to infinite future
* @return {EventEmitter} An event emitter for stream related events
*/
function makeBidiStreamRequest(metadata, deadline) {
return client.makeRequest(this.channel, method, serialize,
deserialize, metadata, deadline);
}
return makeBidiStreamRequest;
}
/**
* Map with short names for each of the requester maker functions. Used in
* makeClientConstructor
*/
var requester_makers = {
unary: makeUnaryRequestFunction,
server_stream: makeServerStreamRequestFunction,
client_stream: makeClientStreamRequestFunction,
bidi: makeBidiStreamRequestFunction
}
/**
* Creates a constructor for clients for the given service
* @param {ProtoBuf.Reflect.Service} service The service to generate a client
* for
* @return {function(string, Object)} New client constructor
*/
function makeClientConstructor(service) {
var prefix = '/' + common.fullyQualifiedName(service) + '/';
/**
* Create a client with the given methods
* @constructor
* @param {string} address The address of the server to connect to
* @param {Object} options Options to pass to the underlying channel
*/
function SurfaceClient(address, options) {
this.channel = new client.Channel(address, options);
}
_.each(service.children, function(method) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
SurfaceClient.prototype[decapitalize(method.name)] =
requester_makers[method_type](
prefix + capitalize(method.name),
common.serializeCls(method.resolvedRequestType.build()),
common.deserializeCls(method.resolvedResponseType.build()));
});
SurfaceClient.service = service;
return SurfaceClient;
}
exports.makeClientConstructor = makeClientConstructor;
/**
* See docs for client.status
*/
exports.status = client.status;
/**
* See docs for client.callError
*/
exports.callError = client.callError;

View File

@ -1,340 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
var _ = require('underscore');
var capitalize = require('underscore.string/capitalize');
var decapitalize = require('underscore.string/decapitalize');
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');
var common = require('./common.js');
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
*/
function ServerReadableObjectStream(stream) {
var 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('cancelled', function() {
self.emit('cancelled');
});
this._stream.on('data', function forwardData(chunk) {
if (!self.push(chunk)) {
self._stream.pause();
}
});
this._stream.on('end', function forwardEnd() {
self.push(null);
});
this._stream.pause();
}
/**
* _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;
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
*/
function ServerWritableObjectStream(stream) {
var options = {objectMode: true};
Writable.call(this, options);
this._stream = stream;
this._stream.on('cancelled', function() {
self.emit('cancelled');
});
this.on('finish', function() {
this._stream.end();
});
}
/**
* _write implementation for both types of streams that allow writing
* @this {ServerWritableObjectStream}
* @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(chunk, encoding, callback);
}
/**
* See docs for _write
*/
ServerWritableObjectStream.prototype._write = _write;
/**
* Creates a binary stream handler function from a unary handler function
* @param {function(Object, function(Error, *), metadata=)} handler Unary call
* handler
* @return {function(stream, metadata=)} Binary stream handler
*/
function makeUnaryHandler(handler) {
/**
* 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
* @param {metadata=} metadata Incoming metadata array
*/
return function handleUnaryCall(stream, metadata) {
stream.on('data', function handleUnaryData(value) {
var call = {request: value};
Object.defineProperty(call, 'cancelled', {
get: function() { return stream.cancelled;}
});
stream.on('cancelled', function() {
call.emit('cancelled');
});
handler(call, function sendUnaryData(err, value) {
if (err) {
stream.emit('error', err);
} else {
stream.write(value);
stream.end();
}
}, metadata);
});
};
}
/**
* Creates a binary stream handler function from a client stream handler
* function
* @param {function(Readable, function(Error, *), metadata=)} handler Client
* stream call handler
* @return {function(stream, metadata=)} Binary stream handler
*/
function makeClientStreamHandler(handler) {
/**
* 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
* @param {metadata=} metadata Incoming metadata array
*/
return function handleClientStreamCall(stream, metadata) {
var object_stream = new ServerReadableObjectStream(stream);
handler(object_stream, function sendClientStreamData(err, value) {
if (err) {
stream.emit('error', err);
} else {
stream.write(value);
stream.end();
}
}, metadata);
};
}
/**
* Creates a binary stream handler function from a server stream handler
* function
* @param {function(Writable, metadata=)} handler Server stream call handler
* @return {function(stream, metadata=)} Binary stream handler
*/
function makeServerStreamHandler(handler) {
/**
* Handles a stream by attaching it to a serializing stream, and passing it to
* the handler.
* @param {stream} stream Binary data stream
* @param {metadata=} metadata Incoming metadata array
*/
return function handleServerStreamCall(stream, metadata) {
stream.on('data', function handleClientData(value) {
var object_stream = new ServerWritableObjectStream(stream);
object_stream.request = value;
handler(object_stream, metadata);
});
};
}
/**
* Creates a binary stream handler function from a bidi stream handler function
* @param {function(Duplex, metadata=)} handler Unary call handler
* @return {function(stream, metadata=)} Binary stream handler
*/
function makeBidiStreamHandler(handler) {
return handler;
}
/**
* 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(services) {
var qual_names = [];
_.each(services, function(service) {
_.each(service.children, function(method) {
var name = common.fullyQualifiedName(method);
if (_.indexOf(qual_names, name) !== -1) {
throw new Error('Method ' + name + ' exposed by more than one service');
}
qual_names.push(name);
});
});
/**
* Create a server with the given handlers for all of the methods.
* @constructor
* @param {Object} service_handlers Map from service names to map from method
* names to handlers
* @param {function(string, Object<string, Array<Buffer>>):
Object<string, Array<Buffer|string>>=} getMetadata Callback that
* gets metatada for a given method
* @param {Object=} options Options to pass to the underlying server
*/
function SurfaceServer(service_handlers, getMetadata, options) {
var server = new Server(getMetadata, options);
this.inner_server = server;
_.each(services, function(service) {
var service_name = common.fullyQualifiedName(service);
if (service_handlers[service_name] === undefined) {
throw new Error('Handlers for service ' +
service_name + ' not provided.');
}
var prefix = '/' + common.fullyQualifiedName(service) + '/';
_.each(service.children, function(method) {
var method_type;
if (method.requestStream) {
if (method.responseStream) {
method_type = 'bidi';
} else {
method_type = 'client_stream';
}
} else {
if (method.responseStream) {
method_type = 'server_stream';
} else {
method_type = 'unary';
}
}
if (service_handlers[service_name][decapitalize(method.name)] ===
undefined) {
throw new Error('Method handler for ' +
common.fullyQualifiedName(method) + ' not provided.');
}
var binary_handler = handler_makers[method_type](
service_handlers[service_name][decapitalize(method.name)]);
var serialize = common.serializeCls(
method.resolvedResponseType.build());
var deserialize = common.deserializeCls(
method.resolvedRequestType.build());
server.register(prefix + capitalize(method.name), binary_handler,
serialize, deserialize);
});
}, 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) {
return this.inner_server.bind(port, secure);
};
/**
* 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;

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('bindings')('grpc.node');
@ -98,105 +100,81 @@ describe('call', function() {
}, TypeError);
});
});
describe('addMetadata', function() {
it('should succeed with a map from strings to string arrays', function() {
describe('startBatch', function() {
it('should fail without an object and a function', function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.throws(function() {
call.startBatch();
});
assert.throws(function() {
call.startBatch({});
});
assert.throws(function() {
call.startBatch(null, function(){});
});
});
it('should succeed with an empty object', function(done) {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.doesNotThrow(function() {
call.addMetadata({'key': ['value']});
});
assert.doesNotThrow(function() {
call.addMetadata({'key1': ['value1'], 'key2': ['value2']});
call.startBatch({}, function(err) {
assert.ifError(err);
done();
});
});
it('should succeed with a map from strings to buffer arrays', function() {
});
});
describe('startBatch with metadata', function() {
it('should succeed with a map of strings to string arrays', function(done) {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.doesNotThrow(function() {
call.addMetadata({'key': [new Buffer('value')]});
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = {'key1': ['value1'],
'key2': ['value2']};
call.startBatch(batch, function(err, resp) {
assert.ifError(err);
assert.deepEqual(resp, {'send metadata': true});
done();
});
});
});
it('should succeed with a map of strings to buffer arrays', function(done) {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.doesNotThrow(function() {
call.addMetadata({'key1': [new Buffer('value1')],
'key2': [new Buffer('value2')]});
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = {
'key1': [new Buffer('value1')],
'key2': [new Buffer('value2')]
};
call.startBatch(batch, function(err, resp) {
assert.ifError(err);
assert.deepEqual(resp, {'send metadata': true});
done();
});
});
});
it('should fail with other parameter types', function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.throws(function() {
call.addMetadata();
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = undefined;
call.startBatch(batch, function(){});
});
assert.throws(function() {
call.addMetadata(null);
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = null;
call.startBatch(batch, function(){});
}, TypeError);
assert.throws(function() {
call.addMetadata('value');
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = 'value';
call.startBatch(batch, function(){});
}, TypeError);
assert.throws(function() {
call.addMetadata(5);
var batch = {};
batch[grpc.opType.SEND_INITIAL_METADATA] = 5;
call.startBatch(batch, function(){});
}, TypeError);
});
it('should fail if invoke was already called', function(done) {
var call = new grpc.Call(channel, 'method', getDeadline(1));
call.invoke(function() {},
function() {done();},
0);
assert.throws(function() {
call.addMetadata({'key': ['value']});
}, function(err) {
return err.code === grpc.callError.ALREADY_INVOKED;
});
// Cancel to speed up the test
call.cancel();
});
});
describe('invoke', function() {
it('should fail with fewer than 3 arguments', function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
assert.throws(function() {
call.invoke();
}, TypeError);
assert.throws(function() {
call.invoke(function() {});
}, TypeError);
assert.throws(function() {
call.invoke(function() {},
function() {});
}, TypeError);
});
it('should work with 2 args and an int', function(done) {
assert.doesNotThrow(function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
call.invoke(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.invoke(0, 0, 0);
}, TypeError);
assert.throws(function() {
call.invoke(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() {

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('bindings')('grpc.node');

View File

@ -1,255 +0,0 @@
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var grpc = require('bindings')('grpc.node');
var Server = require('../src/server');
var client = require('../src/client');
var common = require('../src/common');
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'
};
}
/**
* Wait for a cancellation instead of responding
* @param {Stream} stream
*/
function cancelHandler(stream) {
// do nothing
}
function metadataHandler(stream, metadata) {
stream.end();
}
/**
* Serialize a string to a Buffer
* @param {string} value The string to serialize
* @return {Buffer} The serialized value
*/
function stringSerialize(value) {
return new Buffer(value);
}
/**
* Deserialize a Buffer to a string
* @param {Buffer} buffer The buffer to deserialize
* @return {string} The string value of the buffer
*/
function stringDeserialize(buffer) {
return buffer.toString();
}
describe('echo client', function() {
var server;
var channel;
before(function() {
server = new Server(function getMetadata(method, metadata) {
return {method: [method]};
});
var port_num = server.bind('0.0.0.0:0');
server.register('echo', echoHandler);
server.register('error', errorHandler);
server.register('cancellation', cancelHandler);
server.register('metadata', metadataHandler);
server.start();
channel = new grpc.Channel('localhost:' + port_num);
});
after(function() {
server.shutdown();
});
it('should receive echo responses', function(done) {
var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
var stream = client.makeRequest(
channel,
'echo',
stringSerialize,
stringDeserialize);
for (var i = 0; i < messages.length; i++) {
stream.write(messages[i]);
}
stream.end();
var index = 0;
stream.on('data', function(chunk) {
assert.equal(messages[index], chunk);
index += 1;
});
stream.on('status', function(status) {
assert.equal(status.code, client.status.OK);
});
stream.on('end', function() {
assert.equal(index, messages.length);
done();
});
});
it('should recieve metadata set by the server', function(done) {
var stream = client.makeRequest(channel, 'metadata');
stream.on('metadata', function(metadata) {
assert.strictEqual(metadata.method[0].toString(), 'metadata');
});
stream.on('status', function(status) {
assert.equal(status.code, client.status.OK);
done();
});
stream.end();
});
it('should get an error status that the server throws', function(done) {
var stream = client.makeRequest(channel, 'error');
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');
done();
});
});
it('should be able to cancel a call', function(done) {
var stream = client.makeRequest(
channel,
'cancellation',
null,
getDeadline(1));
stream.cancel();
stream.on('status', function(status) {
assert.equal(status.code, grpc.status.CANCELLED);
done();
});
});
it('should get correct status for unimplemented method', function(done) {
var stream = client.makeRequest(channel, 'unimplemented_method');
stream.end();
stream.on('status', function(status) {
assert.equal(status.code, grpc.status.UNIMPLEMENTED);
done();
});
});
});
/* TODO(mlumish): explore options for reducing duplication between this test
* and the insecure echo client test */
describe('secure echo client', function() {
var server;
var channel;
before(function(done) {
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);
server = new Server(null, {'credentials' : server_creds});
var port_num = server.bind('0.0.0.0:0', true);
server.register('echo', echoHandler);
server.start();
channel = new grpc.Channel('localhost:' + port_num, {
'grpc.ssl_target_name_override' : 'foo.test.google.com',
'credentials' : creds
});
done();
});
});
});
});
after(function() {
server.shutdown();
});
it('should recieve echo responses', function(done) {
var messages = ['echo1', 'echo2', 'echo3', 'echo4'];
var stream = client.makeRequest(
channel,
'echo',
stringSerialize,
stringDeserialize);
for (var i = 0; i < messages.length; i++) {
stream.write(messages[i]);
}
stream.end();
var index = 0;
stream.on('data', function(chunk) {
assert.equal(messages[index], chunk);
index += 1;
});
stream.on('status', function(status) {
assert.equal(status.code, client.status.OK);
});
stream.on('end', function() {
assert.equal(index, messages.length);
done();
});
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('bindings')('grpc.node');
@ -76,31 +78,6 @@ var callErrorNames = [
'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',
'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++) {
@ -114,16 +91,4 @@ describe('constants', function() {
'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]);
}
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('bindings')('grpc.node');
@ -74,41 +76,50 @@ describe('end-to-end', function() {
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
deadline);
call.invoke(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);
Infinity);
var client_batch = {};
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
assert.ifError(err);
assert.deepEqual(response, {
'send metadata': true,
'client close': true,
'metadata': {},
'status': {
'code': grpc.status.OK,
'details': status_text,
'metadata': {}
}
});
done();
}, 0);
});
server.requestCall(function(event) {
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
var server_call = event.call;
server.requestCall(function(err, call_details) {
var new_call = call_details['new call'];
assert.notEqual(new_call, null);
var server_call = new_call.call;
assert.notEqual(server_call, null);
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);
var server_batch = {};
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
'metadata': {},
'code': grpc.status.OK,
'details': status_text
};
server_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
server_call.startBatch(server_batch, function(err, response) {
assert.ifError(err);
assert.deepEqual(response, {
'send metadata': true,
'send status': true,
'cancelled': false
});
done();
});
});
call.writesDone(function(event) {
assert.strictEqual(event.type,
grpc.completionType.FINISH_ACCEPTED);
assert.strictEqual(event.data, grpc.opError.OK);
});
});
it('should successfully send and receive metadata', function(complete) {
var done = multiDone(complete, 2);
@ -117,115 +128,179 @@ describe('end-to-end', function() {
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
deadline);
call.addMetadata({'client_key': ['client_value']});
call.invoke(function(event) {
assert.strictEqual(event.type,
grpc.completionType.CLIENT_METADATA_READ);
assert.strictEqual(event.data.server_key[0].toString(), 'server_value');
},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);
Infinity);
var client_batch = {};
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {
'client_key': ['client_value']
};
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
assert.ifError(err);
assert(response['send metadata']);
assert(response['client close']);
assert(response.hasOwnProperty('metadata'));
assert.strictEqual(response.metadata.server_key[0].toString(),
'server_value');
assert.deepEqual(response.status, {'code': grpc.status.OK,
'details': status_text,
'metadata': {}});
done();
}, 0);
});
server.requestCall(function(event) {
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
assert.strictEqual(event.data.metadata.client_key[0].toString(),
server.requestCall(function(err, call_details) {
var new_call = call_details['new call'];
assert.notEqual(new_call, null);
assert.strictEqual(new_call.metadata.client_key[0].toString(),
'client_value');
var server_call = event.call;
var server_call = new_call.call;
assert.notEqual(server_call, null);
server_call.serverAccept(function(event) {
assert.strictEqual(event.type, grpc.completionType.FINISHED);
}, 0);
server_call.addMetadata({'server_key': ['server_value']});
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);
var server_batch = {};
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {
'server_key': ['server_value']
};
server_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
'metadata': {},
'code': grpc.status.OK,
'details': status_text
};
server_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
server_call.startBatch(server_batch, function(err, response) {
assert.ifError(err);
assert.deepEqual(response, {
'send metadata': true,
'send status': true,
'cancelled': false
});
done();
});
});
call.writesDone(function(event) {
assert.strictEqual(event.type,
grpc.completionType.FINISH_ACCEPTED);
assert.strictEqual(event.data, grpc.opError.OK);
});
});
it('should send and receive data without error', function(complete) {
var req_text = 'client_request';
var reply_text = 'server_response';
var done = multiDone(complete, 6);
var done = multiDone(complete, 2);
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'success';
var call = new grpc.Call(channel,
'dummy_method',
deadline);
call.invoke(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);
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);
Infinity);
var client_batch = {};
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(req_text);
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
client_batch[grpc.opType.RECV_MESSAGE] = true;
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(client_batch, function(err, response) {
assert.ifError(err);
assert(response['send metadata']);
assert(response['client close']);
assert.deepEqual(response.metadata, {});
assert(response['send message']);
assert.strictEqual(response.read.toString(), reply_text);
assert.deepEqual(response.status, {'code': grpc.status.OK,
'details': status_text,
'metadata': {}});
done();
});
}, 0);
call.startRead(function(event) {
assert.strictEqual(event.type, grpc.completionType.READ);
assert.strictEqual(event.data.toString(), reply_text);
done();
});
server.requestCall(function(event) {
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
var server_call = event.call;
server.requestCall(function(err, call_details) {
var new_call = call_details['new call'];
assert.notEqual(new_call, null);
var server_call = new_call.call;
assert.notEqual(server_call, null);
server_call.serverAccept(function(event) {
assert.strictEqual(event.type, grpc.completionType.FINISHED);
var server_batch = {};
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
server_batch[grpc.opType.RECV_MESSAGE] = true;
server_call.startBatch(server_batch, function(err, response) {
assert.ifError(err);
assert(response['send metadata']);
assert.strictEqual(response.read.toString(), req_text);
var response_batch = {};
response_batch[grpc.opType.SEND_MESSAGE] = new Buffer(reply_text);
response_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
'metadata': {},
'code': grpc.status.OK,
'details': status_text
};
response_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
server_call.startBatch(response_batch, function(err, response) {
assert(response['send status']);
assert(!response.cancelled);
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);
});
});
});
it('should send multiple messages', function(complete) {
var done = multiDone(complete, 2);
var requests = ['req1', 'req2'];
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 3);
var status_text = 'xyz';
var call = new grpc.Call(channel,
'dummy_method',
Infinity);
var client_batch = {};
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[0]);
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
call.startBatch(client_batch, function(err, response) {
assert.ifError(err);
assert.deepEqual(response, {
'send metadata': true,
'send message': true,
'metadata': {}
});
var req2_batch = {};
req2_batch[grpc.opType.SEND_MESSAGE] = new Buffer(requests[1]);
req2_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
req2_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
call.startBatch(req2_batch, function(err, resp) {
assert.ifError(err);
assert.deepEqual(resp, {
'send message': true,
'client close': true,
'status': {
'code': grpc.status.OK,
'details': status_text,
'metadata': {}
}
});
done();
});
});
server.requestCall(function(err, call_details) {
var new_call = call_details['new call'];
assert.notEqual(new_call, null);
var server_call = new_call.call;
assert.notEqual(server_call, null);
var server_batch = {};
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
server_batch[grpc.opType.RECV_MESSAGE] = true;
server_call.startBatch(server_batch, function(err, response) {
assert.ifError(err);
assert(response['send metadata']);
assert.strictEqual(response.read.toString(), requests[0]);
var end_batch = {};
end_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
'metadata': {},
'code': grpc.status.OK,
'details': status_text
};
end_batch[grpc.opType.RECV_MESSAGE] = true;
server_call.startBatch(end_batch, function(err, response) {
assert.ifError(err);
assert(response['send status']);
assert(!response.cancelled);
assert.strictEqual(response.read.toString(), requests[1]);
done();
});
}, 0);
});
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var interop_server = require('../interop/interop_server.js');
var interop_client = require('../interop/interop_client.js');
@ -38,7 +40,7 @@ var server;
var port;
var name_override = 'foo.test.google.com';
var name_override = 'foo.test.google.fr';
describe('Interop tests', function() {
before(function(done) {
@ -53,30 +55,35 @@ describe('Interop tests', function() {
});
// This depends on not using a binary stream
it('should pass empty_unary', function(done) {
interop_client.runTest(port, name_override, 'empty_unary', true, done);
interop_client.runTest(port, name_override, 'empty_unary', true, true,
done);
});
// This fails due to an unknown bug
it.skip('should pass large_unary', function(done) {
interop_client.runTest(port, name_override, 'large_unary', true, done);
it('should pass large_unary', function(done) {
interop_client.runTest(port, name_override, 'large_unary', true, true,
done);
});
it('should pass client_streaming', function(done) {
interop_client.runTest(port, name_override, 'client_streaming', true, done);
interop_client.runTest(port, name_override, 'client_streaming', true, true,
done);
});
it('should pass server_streaming', function(done) {
interop_client.runTest(port, name_override, 'server_streaming', true, done);
interop_client.runTest(port, name_override, 'server_streaming', true, true,
done);
});
it('should pass ping_pong', function(done) {
interop_client.runTest(port, name_override, 'ping_pong', true, done);
interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
});
it('should pass empty_stream', function(done) {
interop_client.runTest(port, name_override, 'empty_stream', true, done);
interop_client.runTest(port, name_override, 'empty_stream', true, true,
done);
});
it('should pass cancel_after_begin', function(done) {
interop_client.runTest(port, name_override, 'cancel_after_begin', true,
done);
true, done);
});
it('should pass cancel_after_first_response', function(done) {
interop_client.runTest(port, name_override, 'cancel_after_first_response',
true, done);
true, true, done);
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,6 +31,8 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('..');
@ -59,13 +61,10 @@ describe('Math client', function() {
});
it('should handle a single request', function(done) {
var arg = {dividend: 7, divisor: 4};
var call = math_client.div(arg, function handleDivResult(err, value) {
math_client.div(arg, function handleDivResult(err, value) {
assert.ifError(err);
assert.equal(value.quotient, 1);
assert.equal(value.remainder, 3);
});
call.on('status', function checkStatus(status) {
assert.strictEqual(status.code, grpc.status.OK);
done();
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,92 +31,64 @@
*
*/
'use strict';
var assert = require('assert');
var grpc = require('bindings')('grpc.node');
var Server = require('../src/server');
/**
* 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() {
describe('server', function() {
describe('constructor', function() {
it('should work with no arguments', function() {
assert.doesNotThrow(function() {
new grpc.Server();
});
});
it('should work with an empty list argument', function() {
assert.doesNotThrow(function() {
new grpc.Server([]);
});
});
});
describe('addHttp2Port', function() {
var server;
var channel;
before(function() {
server = new Server();
var port_num = server.bind('[::]:0');
server.register('echo', echoHandler);
server.start();
channel = new grpc.Channel('localhost:' + port_num);
server = new grpc.Server();
});
it('should bind to an unused port', function() {
var port;
assert.doesNotThrow(function() {
port = server.addHttp2Port('0.0.0.0:0');
});
assert(port > 0);
});
});
describe('addSecureHttp2Port', function() {
var server;
before(function() {
server = new grpc.Server();
});
it('should bind to an unused port with fake credentials', function() {
var port;
var creds = grpc.ServerCredentials.createFake();
assert.doesNotThrow(function() {
port = server.addSecureHttp2Port('0.0.0.0:0', creds);
});
assert(port > 0);
});
});
describe('listen', function() {
var server;
before(function() {
server = new grpc.Server();
server.addHttp2Port('0.0.0.0:0');
});
after(function() {
server.shutdown();
});
it('should echo inputs as responses', function(done) {
done = multiDone(done, 4);
var req_text = 'echo test string';
var status_text = 'OK';
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 3);
var call = new grpc.Call(channel,
'echo',
deadline);
call.invoke(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);
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();
it('should listen without error', function() {
assert.doesNotThrow(function() {
server.start();
});
}, 0);
call.startRead(function(event) {
assert.strictEqual(event.type, grpc.completionType.READ);
assert.strictEqual(event.data.toString(), req_text);
done();
});
});
});

View File

@ -1,6 +1,6 @@
/*
*
* Copyright 2014, Google Inc.
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -31,11 +31,11 @@
*
*/
'use strict';
var assert = require('assert');
var surface_server = require('../src/surface_server.js');
var surface_client = require('../src/surface_client.js');
var surface_client = require('../src/client.js');
var ProtoBuf = require('protobufjs');