mirror of https://github.com/grpc/grpc-node.git
Resolved merge conflicts with master
This commit is contained in:
commit
aeff44dba7
|
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
80
README.md
80
README.md
|
|
@ -1,12 +1,78 @@
|
||||||
# Node.js GRPC extension
|
# Node.js gRPC Library
|
||||||
|
|
||||||
The package is built with
|
## Status
|
||||||
|
|
||||||
node-gyp configure
|
Alpha : Ready for early adopters
|
||||||
node-gyp build
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
41
binding.gyp
41
binding.gyp
|
|
@ -1,29 +1,27 @@
|
||||||
{
|
{
|
||||||
"variables" : {
|
|
||||||
'no_install': "<!(echo $GRPC_NO_INSTALL)",
|
|
||||||
'grpc_root': "<!(echo $GRPC_ROOT)",
|
|
||||||
'grpc_lib_subdir': "<!(echo $GRPC_LIB_SUBDIR)"
|
|
||||||
},
|
|
||||||
"targets" : [
|
"targets" : [
|
||||||
{
|
{
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
"<!(nodejs -e \"require('nan')\")"
|
"<!(node -e \"require('nan')\")"
|
||||||
],
|
],
|
||||||
'cxxflags': [
|
'cflags': [
|
||||||
|
'-std=c++0x',
|
||||||
'-Wall',
|
'-Wall',
|
||||||
'-pthread',
|
'-pthread',
|
||||||
'-pedantic',
|
'-pedantic',
|
||||||
'-g',
|
'-g',
|
||||||
'-zdefs'
|
'-zdefs'
|
||||||
'-Werror',
|
'-Werror'
|
||||||
],
|
],
|
||||||
'ldflags': [
|
'ldflags': [
|
||||||
'-g'
|
'-g'
|
||||||
],
|
],
|
||||||
'link_settings': {
|
'link_settings': {
|
||||||
'libraries': [
|
'libraries': [
|
||||||
'-lrt',
|
'-lrt',
|
||||||
'-lpthread'
|
'-lpthread',
|
||||||
|
'-lgrpc',
|
||||||
|
'-lgpr'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"target_name": "grpc",
|
"target_name": "grpc",
|
||||||
|
|
@ -33,33 +31,10 @@
|
||||||
"ext/channel.cc",
|
"ext/channel.cc",
|
||||||
"ext/completion_queue_async_worker.cc",
|
"ext/completion_queue_async_worker.cc",
|
||||||
"ext/credentials.cc",
|
"ext/credentials.cc",
|
||||||
"ext/event.cc",
|
|
||||||
"ext/node_grpc.cc",
|
"ext/node_grpc.cc",
|
||||||
"ext/server.cc",
|
"ext/server.cc",
|
||||||
"ext/server_credentials.cc",
|
"ext/server_credentials.cc",
|
||||||
"ext/tag.cc",
|
|
||||||
"ext/timeval.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'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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";
|
syntax = "proto3";
|
||||||
|
|
||||||
package math;
|
package math;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,9 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var _ = require('underscore');
|
'use strict';
|
||||||
var ProtoBuf = require('protobufjs');
|
|
||||||
var fs = require('fs');
|
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
var Transform = require('stream').Transform;
|
var Transform = require('stream').Transform;
|
||||||
|
|
@ -128,7 +127,8 @@ var server = new Server({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
server.bind('localhost:7070').listen();
|
server.bind('0.0.0.0:7070');
|
||||||
|
server.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 {}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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"
|
||||||
|
}]
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -35,28 +35,28 @@ package examples;
|
||||||
message StockRequest {
|
message StockRequest {
|
||||||
optional string symbol = 1;
|
optional string symbol = 1;
|
||||||
optional int32 num_trades_to_watch = 2 [default=0];
|
optional int32 num_trades_to_watch = 2 [default=0];
|
||||||
};
|
}
|
||||||
|
|
||||||
message StockReply {
|
message StockReply {
|
||||||
optional float price = 1;
|
optional float price = 1;
|
||||||
optional string symbol = 2;
|
optional string symbol = 2;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
// Interface exported by the server
|
// Interface exported by the server
|
||||||
service Stock {
|
service Stock {
|
||||||
// Simple blocking RPC
|
// Simple blocking RPC
|
||||||
rpc GetLastTradePrice(StockRequest) returns (StockReply) {
|
rpc GetLastTradePrice(StockRequest) returns (StockReply) {
|
||||||
};
|
}
|
||||||
// Bidirectional streaming RPC
|
// Bidirectional streaming RPC
|
||||||
rpc GetLastTradePriceMultiple(stream StockRequest) returns
|
rpc GetLastTradePriceMultiple(stream StockRequest) returns
|
||||||
(stream StockReply) {
|
(stream StockReply) {
|
||||||
};
|
}
|
||||||
// Unidirectional server-to-client streaming RPC
|
// Unidirectional server-to-client streaming RPC
|
||||||
rpc WatchFutureTrades(StockRequest) returns (stream StockReply) {
|
rpc WatchFutureTrades(StockRequest) returns (stream StockReply) {
|
||||||
};
|
}
|
||||||
// Unidirectional client-to-server streaming RPC
|
// Unidirectional client-to-server streaming RPC
|
||||||
rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) {
|
rpc GetHighestTradePrice(stream StockRequest) returns (StockReply) {
|
||||||
};
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,5 +39,8 @@ var examples = grpc.load(__dirname + '/stock.proto').examples;
|
||||||
*
|
*
|
||||||
* var StockClient = require('stock_client.js');
|
* var StockClient = require('stock_client.js');
|
||||||
* var stockClient = new StockClient(server_address);
|
* var stockClient = new StockClient(server_address);
|
||||||
|
* stockClient.getLastTradePrice({symbol: 'GOOG'}, function(error, response) {
|
||||||
|
* console.log(error || response);
|
||||||
|
* });
|
||||||
*/
|
*/
|
||||||
module.exports = examples.Stock;
|
module.exports = examples.Stock;
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,16 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var grpc = require('..');
|
var grpc = require('..');
|
||||||
var examples = grpc.load(__dirname + '/stock.proto').examples;
|
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) {
|
function getLastTradePrice(call, callback) {
|
||||||
callback(null, {price: 88});
|
callback(null, {symbol: call.request.symbol, price: 88});
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchFutureTrades(call) {
|
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;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -44,7 +44,6 @@
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using ::node::Buffer;
|
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::Handle;
|
using v8::Handle;
|
||||||
|
|
@ -54,8 +53,8 @@ using v8::Value;
|
||||||
|
|
||||||
grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
|
grpc_byte_buffer *BufferToByteBuffer(Handle<Value> buffer) {
|
||||||
NanScope();
|
NanScope();
|
||||||
int length = Buffer::Length(buffer);
|
int length = ::node::Buffer::Length(buffer);
|
||||||
char *data = Buffer::Data(buffer);
|
char *data = ::node::Buffer::Data(buffer);
|
||||||
gpr_slice slice = gpr_slice_malloc(length);
|
gpr_slice slice = gpr_slice_malloc(length);
|
||||||
memcpy(GPR_SLICE_START_PTR(slice), data, length);
|
memcpy(GPR_SLICE_START_PTR(slice), data, length);
|
||||||
grpc_byte_buffer *byte_buffer(grpc_byte_buffer_create(&slice, 1));
|
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) {
|
Handle<Value> ByteBufferToBuffer(grpc_byte_buffer *buffer) {
|
||||||
NanEscapableScope();
|
NanEscapableScope();
|
||||||
if (buffer == NULL) {
|
if (buffer == NULL) {
|
||||||
NanReturnNull();
|
return NanEscapeScope(NanNull());
|
||||||
}
|
}
|
||||||
size_t length = grpc_byte_buffer_length(buffer);
|
size_t length = grpc_byte_buffer_length(buffer);
|
||||||
char *result = reinterpret_cast<char *>(calloc(length, sizeof(char)));
|
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) {
|
Handle<Value> MakeFastBuffer(Handle<Value> slowBuffer) {
|
||||||
NanEscapableScope();
|
NanEscapableScope();
|
||||||
Handle<Object> globalObj = Context::GetCurrent()->Global();
|
Handle<Object> globalObj = NanGetCurrentContext()->Global();
|
||||||
Handle<Function> bufferConstructor = Handle<Function>::Cast(
|
Handle<Function> bufferConstructor = Handle<Function>::Cast(
|
||||||
globalObj->Get(NanNew("Buffer")));
|
globalObj->Get(NanNew("Buffer")));
|
||||||
Handle<Value> consArgs[3] = { slowBuffer,
|
Handle<Value> consArgs[3] = {
|
||||||
NanNew<Number>(Buffer::Length(slowBuffer)),
|
slowBuffer,
|
||||||
NanNew<Number>(0) };
|
NanNew<Number>(::node::Buffer::Length(slowBuffer)),
|
||||||
|
NanNew<Number>(0)
|
||||||
|
};
|
||||||
Handle<Object> fastBuffer = bufferConstructor->NewInstance(3, consArgs);
|
Handle<Object> fastBuffer = bufferConstructor->NewInstance(3, consArgs);
|
||||||
return NanEscapeScope(fastBuffer);
|
return NanEscapeScope(fastBuffer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
687
ext/call.cc
687
ext/call.cc
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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 <node.h>
|
||||||
|
|
||||||
#include "grpc/support/log.h"
|
#include "grpc/support/log.h"
|
||||||
#include "grpc/grpc.h"
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/alloc.h"
|
||||||
#include "grpc/support/time.h"
|
#include "grpc/support/time.h"
|
||||||
#include "byte_buffer.h"
|
#include "byte_buffer.h"
|
||||||
#include "call.h"
|
#include "call.h"
|
||||||
#include "channel.h"
|
#include "channel.h"
|
||||||
#include "completion_queue_async_worker.h"
|
#include "completion_queue_async_worker.h"
|
||||||
#include "timeval.h"
|
#include "timeval.h"
|
||||||
#include "tag.h"
|
|
||||||
|
using std::unique_ptr;
|
||||||
|
using std::shared_ptr;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using ::node::Buffer;
|
|
||||||
using v8::Arguments;
|
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
|
using v8::Boolean;
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::External;
|
using v8::External;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
|
|
@ -65,47 +72,383 @@ using v8::Uint32;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
Persistent<Function> Call::constructor;
|
NanCallback *Call::constructor;
|
||||||
Persistent<FunctionTemplate> Call::fun_tpl;
|
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) {
|
void Call::Init(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(NanNew("Call"));
|
tpl->SetClassName(NanNew("Call"));
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
NanSetPrototypeTemplate(tpl, "addMetadata",
|
NanSetPrototypeTemplate(tpl, "startBatch",
|
||||||
FunctionTemplate::New(AddMetadata)->GetFunction());
|
NanNew<FunctionTemplate>(StartBatch)->GetFunction());
|
||||||
NanSetPrototypeTemplate(tpl, "invoke",
|
|
||||||
FunctionTemplate::New(Invoke)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(tpl, "serverAccept",
|
|
||||||
FunctionTemplate::New(ServerAccept)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(
|
|
||||||
tpl, "serverEndInitialMetadata",
|
|
||||||
FunctionTemplate::New(ServerEndInitialMetadata)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(tpl, "cancel",
|
NanSetPrototypeTemplate(tpl, "cancel",
|
||||||
FunctionTemplate::New(Cancel)->GetFunction());
|
NanNew<FunctionTemplate>(Cancel)->GetFunction());
|
||||||
NanSetPrototypeTemplate(tpl, "startWrite",
|
|
||||||
FunctionTemplate::New(StartWrite)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(
|
|
||||||
tpl, "startWriteStatus",
|
|
||||||
FunctionTemplate::New(StartWriteStatus)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(tpl, "writesDone",
|
|
||||||
FunctionTemplate::New(WritesDone)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(tpl, "startReadMetadata",
|
|
||||||
FunctionTemplate::New(WritesDone)->GetFunction());
|
|
||||||
NanSetPrototypeTemplate(tpl, "startRead",
|
|
||||||
FunctionTemplate::New(StartRead)->GetFunction());
|
|
||||||
NanAssignPersistent(fun_tpl, tpl);
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
NanAssignPersistent(constructor, tpl->GetFunction());
|
Handle<Function> ctr = tpl->GetFunction();
|
||||||
constructor->Set(NanNew("WRITE_BUFFER_HINT"),
|
ctr->Set(NanNew("WRITE_BUFFER_HINT"),
|
||||||
NanNew<Uint32, uint32_t>(GRPC_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));
|
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) {
|
bool Call::HasInstance(Handle<Value> val) {
|
||||||
|
|
@ -119,8 +462,8 @@ Handle<Value> Call::WrapStruct(grpc_call *call) {
|
||||||
return NanEscapeScope(NanNull());
|
return NanEscapeScope(NanNull());
|
||||||
}
|
}
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Handle<Value> argv[argc] = {External::New(reinterpret_cast<void *>(call))};
|
Handle<Value> argv[argc] = {NanNew<External>(reinterpret_cast<void *>(call))};
|
||||||
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
|
|
||||||
NAN_METHOD(Call::New) {
|
NAN_METHOD(Call::New) {
|
||||||
|
|
@ -129,9 +472,10 @@ NAN_METHOD(Call::New) {
|
||||||
if (args.IsConstructCall()) {
|
if (args.IsConstructCall()) {
|
||||||
Call *call;
|
Call *call;
|
||||||
if (args[0]->IsExternal()) {
|
if (args[0]->IsExternal()) {
|
||||||
|
Handle<External> ext = args[0].As<External>();
|
||||||
// This option is used for wrapping an existing call
|
// This option is used for wrapping an existing call
|
||||||
grpc_call *call_value =
|
grpc_call *call_value =
|
||||||
reinterpret_cast<grpc_call *>(External::Unwrap(args[0]));
|
reinterpret_cast<grpc_call *>(ext->Value());
|
||||||
call = new Call(call_value);
|
call = new Call(call_value);
|
||||||
} else {
|
} else {
|
||||||
if (!Channel::HasInstance(args[0])) {
|
if (!Channel::HasInstance(args[0])) {
|
||||||
|
|
@ -152,135 +496,89 @@ NAN_METHOD(Call::New) {
|
||||||
NanUtf8String method(args[1]);
|
NanUtf8String method(args[1]);
|
||||||
double deadline = args[2]->NumberValue();
|
double deadline = args[2]->NumberValue();
|
||||||
grpc_channel *wrapped_channel = channel->GetWrappedChannel();
|
grpc_channel *wrapped_channel = channel->GetWrappedChannel();
|
||||||
grpc_call *wrapped_call =
|
grpc_call *wrapped_call = grpc_channel_create_call(
|
||||||
grpc_channel_create_call(wrapped_channel, *method, channel->GetHost(),
|
wrapped_channel, CompletionQueueAsyncWorker::GetQueue(), *method,
|
||||||
MillisecondsToTimespec(deadline));
|
channel->GetHost(), MillisecondsToTimespec(deadline));
|
||||||
call = new Call(wrapped_call);
|
call = new Call(wrapped_call);
|
||||||
args.This()->SetHiddenValue(String::NewSymbol("channel_"),
|
args.This()->SetHiddenValue(NanNew("channel_"), channel_object);
|
||||||
channel_object);
|
|
||||||
}
|
}
|
||||||
call->Wrap(args.This());
|
call->Wrap(args.This());
|
||||||
NanReturnValue(args.This());
|
NanReturnValue(args.This());
|
||||||
} else {
|
} else {
|
||||||
const int argc = 4;
|
const int argc = 4;
|
||||||
Local<Value> argv[argc] = {args[0], args[1], args[2], args[3]};
|
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();
|
NanScope();
|
||||||
if (!HasInstance(args.This())) {
|
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()) {
|
if (!args[0]->IsObject()) {
|
||||||
return NanThrowTypeError("addMetadata's first argument must be an object");
|
return NanThrowError("startBatch'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");
|
|
||||||
}
|
}
|
||||||
if (!args[1]->IsFunction()) {
|
if (!args[1]->IsFunction()) {
|
||||||
return NanThrowTypeError("invoke's second argument must be a function");
|
return NanThrowError("startBatch's second argument must be a callback");
|
||||||
}
|
|
||||||
if (!args[2]->IsUint32()) {
|
|
||||||
return NanThrowTypeError("invoke's third argument must be integer flags");
|
|
||||||
}
|
}
|
||||||
|
Handle<Function> callback_func = args[1].As<Function>();
|
||||||
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
||||||
unsigned int flags = args[3]->Uint32Value();
|
shared_ptr<Resources> resources(new Resources);
|
||||||
grpc_call_error error = grpc_call_invoke(
|
Handle<Object> obj = args[0]->ToObject();
|
||||||
call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
|
Handle<Array> keys = obj->GetOwnPropertyNames();
|
||||||
CreateTag(args[0], args.This()), CreateTag(args[1], args.This()), flags);
|
size_t nops = keys->Length();
|
||||||
if (error == GRPC_CALL_OK) {
|
vector<grpc_op> ops(nops);
|
||||||
CompletionQueueAsyncWorker::Next();
|
unique_ptr<OpVec> op_vector(new OpVec());
|
||||||
CompletionQueueAsyncWorker::Next();
|
for (unsigned int i = 0; i < nops; i++) {
|
||||||
} else {
|
unique_ptr<Op> op;
|
||||||
return NanThrowError("invoke failed", error);
|
if (!keys->Get(i)->IsUint32()) {
|
||||||
|
return NanThrowError(
|
||||||
|
"startBatch's first argument's keys must be integers");
|
||||||
|
}
|
||||||
|
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 (!op->ParseOp(obj->Get(type), &ops[i], resources)) {
|
||||||
|
return NanThrowTypeError("Incorrectly typed arguments to startBatch");
|
||||||
|
}
|
||||||
|
op_vector->push_back(std::move(op));
|
||||||
}
|
}
|
||||||
NanReturnUndefined();
|
NanCallback *callback = new NanCallback(callback_func);
|
||||||
}
|
grpc_call_error error = grpc_call_start_batch(
|
||||||
|
call->wrapped_call, &ops[0], nops, new struct tag(
|
||||||
NAN_METHOD(Call::ServerAccept) {
|
callback, op_vector.release(), resources));
|
||||||
NanScope();
|
|
||||||
if (!HasInstance(args.This())) {
|
|
||||||
return NanThrowTypeError("accept can only be called on Call objects");
|
|
||||||
}
|
|
||||||
if (!args[0]->IsFunction()) {
|
|
||||||
return NanThrowTypeError("accept's first argument must be a function");
|
|
||||||
}
|
|
||||||
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
|
||||||
grpc_call_error error = grpc_call_server_accept(
|
|
||||||
call->wrapped_call, CompletionQueueAsyncWorker::GetQueue(),
|
|
||||||
CreateTag(args[0], args.This()));
|
|
||||||
if (error == GRPC_CALL_OK) {
|
|
||||||
CompletionQueueAsyncWorker::Next();
|
|
||||||
} else {
|
|
||||||
return NanThrowError("serverAccept failed", error);
|
|
||||||
}
|
|
||||||
NanReturnUndefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
NAN_METHOD(Call::ServerEndInitialMetadata) {
|
|
||||||
NanScope();
|
|
||||||
if (!HasInstance(args.This())) {
|
|
||||||
return NanThrowTypeError(
|
|
||||||
"serverEndInitialMetadata can only be called on Call objects");
|
|
||||||
}
|
|
||||||
if (!args[0]->IsUint32()) {
|
|
||||||
return NanThrowTypeError(
|
|
||||||
"serverEndInitialMetadata's second argument must be integer flags");
|
|
||||||
}
|
|
||||||
Call *call = ObjectWrap::Unwrap<Call>(args.This());
|
|
||||||
unsigned int flags = args[1]->Uint32Value();
|
|
||||||
grpc_call_error error =
|
|
||||||
grpc_call_server_end_initial_metadata(call->wrapped_call, flags);
|
|
||||||
if (error != GRPC_CALL_OK) {
|
if (error != GRPC_CALL_OK) {
|
||||||
return NanThrowError("serverEndInitialMetadata failed", error);
|
return NanThrowError("startBatch failed", error);
|
||||||
}
|
}
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
NanReturnUndefined();
|
NanReturnUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,102 +595,5 @@ NAN_METHOD(Call::Cancel) {
|
||||||
NanReturnUndefined();
|
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 node
|
||||||
} // namespace grpc
|
} // namespace grpc
|
||||||
|
|
|
||||||
71
ext/call.h
71
ext/call.h
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -34,15 +34,73 @@
|
||||||
#ifndef NET_GRPC_NODE_CALL_H_
|
#ifndef NET_GRPC_NODE_CALL_H_
|
||||||
#define NET_GRPC_NODE_CALL_H_
|
#define NET_GRPC_NODE_CALL_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <nan.h>
|
#include <nan.h>
|
||||||
#include "grpc/grpc.h"
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/log.h"
|
||||||
|
|
||||||
#include "channel.h"
|
#include "channel.h"
|
||||||
|
|
||||||
|
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
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. */
|
/* Wrapper class for grpc_call structs. */
|
||||||
class Call : public ::node::ObjectWrap {
|
class Call : public ::node::ObjectWrap {
|
||||||
public:
|
public:
|
||||||
|
|
@ -60,16 +118,9 @@ class Call : public ::node::ObjectWrap {
|
||||||
Call &operator=(const Call &);
|
Call &operator=(const Call &);
|
||||||
|
|
||||||
static NAN_METHOD(New);
|
static NAN_METHOD(New);
|
||||||
static NAN_METHOD(AddMetadata);
|
static NAN_METHOD(StartBatch);
|
||||||
static NAN_METHOD(Invoke);
|
|
||||||
static NAN_METHOD(ServerAccept);
|
|
||||||
static NAN_METHOD(ServerEndInitialMetadata);
|
|
||||||
static NAN_METHOD(Cancel);
|
static NAN_METHOD(Cancel);
|
||||||
static NAN_METHOD(StartWrite);
|
static NanCallback *constructor;
|
||||||
static NAN_METHOD(StartWriteStatus);
|
|
||||||
static NAN_METHOD(WritesDone);
|
|
||||||
static NAN_METHOD(StartRead);
|
|
||||||
static v8::Persistent<v8::Function> constructor;
|
|
||||||
// Used for typechecking instances of this javascript class
|
// Used for typechecking instances of this javascript class
|
||||||
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -45,7 +45,6 @@
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using v8::Arguments;
|
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
|
|
@ -59,7 +58,7 @@ using v8::Persistent;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
Persistent<Function> Channel::constructor;
|
NanCallback *Channel::constructor;
|
||||||
Persistent<FunctionTemplate> Channel::fun_tpl;
|
Persistent<FunctionTemplate> Channel::fun_tpl;
|
||||||
|
|
||||||
Channel::Channel(grpc_channel *channel, NanUtf8String *host)
|
Channel::Channel(grpc_channel *channel, NanUtf8String *host)
|
||||||
|
|
@ -74,14 +73,15 @@ Channel::~Channel() {
|
||||||
|
|
||||||
void Channel::Init(Handle<Object> exports) {
|
void Channel::Init(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(NanNew("Channel"));
|
tpl->SetClassName(NanNew("Channel"));
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
NanSetPrototypeTemplate(tpl, "close",
|
NanSetPrototypeTemplate(tpl, "close",
|
||||||
FunctionTemplate::New(Close)->GetFunction());
|
NanNew<FunctionTemplate>(Close)->GetFunction());
|
||||||
NanAssignPersistent(fun_tpl, tpl);
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
NanAssignPersistent(constructor, tpl->GetFunction());
|
Handle<Function> ctr = tpl->GetFunction();
|
||||||
exports->Set(NanNew("Channel"), constructor);
|
constructor = new NanCallback(ctr);
|
||||||
|
exports->Set(NanNew("Channel"), ctr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Channel::HasInstance(Handle<Value> val) {
|
bool Channel::HasInstance(Handle<Value> val) {
|
||||||
|
|
@ -103,11 +103,15 @@ NAN_METHOD(Channel::New) {
|
||||||
grpc_channel *wrapped_channel;
|
grpc_channel *wrapped_channel;
|
||||||
// Owned by the Channel object
|
// Owned by the Channel object
|
||||||
NanUtf8String *host = new NanUtf8String(args[0]);
|
NanUtf8String *host = new NanUtf8String(args[0]);
|
||||||
|
NanUtf8String *host_override = NULL;
|
||||||
if (args[1]->IsUndefined()) {
|
if (args[1]->IsUndefined()) {
|
||||||
wrapped_channel = grpc_channel_create(**host, NULL);
|
wrapped_channel = grpc_channel_create(**host, NULL);
|
||||||
} else if (args[1]->IsObject()) {
|
} else if (args[1]->IsObject()) {
|
||||||
grpc_credentials *creds = NULL;
|
grpc_credentials *creds = NULL;
|
||||||
Handle<Object> args_hash(args[1]->ToObject()->Clone());
|
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"))) {
|
if (args_hash->HasOwnProperty(NanNew("credentials"))) {
|
||||||
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
|
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
|
||||||
if (!Credentials::HasInstance(creds_value)) {
|
if (!Credentials::HasInstance(creds_value)) {
|
||||||
|
|
@ -155,13 +159,18 @@ NAN_METHOD(Channel::New) {
|
||||||
} else {
|
} else {
|
||||||
return NanThrowTypeError("Channel expects a string and an object");
|
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());
|
channel->Wrap(args.This());
|
||||||
NanReturnValue(args.This());
|
NanReturnValue(args.This());
|
||||||
} else {
|
} else {
|
||||||
const int argc = 2;
|
const int argc = 2;
|
||||||
Local<Value> argv[argc] = {args[0], args[1]};
|
Local<Value> argv[argc] = {args[0], args[1]};
|
||||||
NanReturnValue(constructor->NewInstance(argc, argv));
|
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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(New);
|
||||||
static NAN_METHOD(Close);
|
static NAN_METHOD(Close);
|
||||||
static v8::Persistent<v8::Function> constructor;
|
static NanCallback *constructor;
|
||||||
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
grpc_channel *wrapped_channel;
|
grpc_channel *wrapped_channel;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -35,10 +35,10 @@
|
||||||
#include <nan.h>
|
#include <nan.h>
|
||||||
|
|
||||||
#include "grpc/grpc.h"
|
#include "grpc/grpc.h"
|
||||||
|
#include "grpc/support/log.h"
|
||||||
#include "grpc/support/time.h"
|
#include "grpc/support/time.h"
|
||||||
#include "completion_queue_async_worker.h"
|
#include "completion_queue_async_worker.h"
|
||||||
#include "event.h"
|
#include "call.h"
|
||||||
#include "tag.h"
|
|
||||||
|
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
@ -63,6 +63,9 @@ CompletionQueueAsyncWorker::~CompletionQueueAsyncWorker() {}
|
||||||
|
|
||||||
void CompletionQueueAsyncWorker::Execute() {
|
void CompletionQueueAsyncWorker::Execute() {
|
||||||
result = grpc_completion_queue_next(queue, gpr_inf_future);
|
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; }
|
grpc_completion_queue *CompletionQueueAsyncWorker::GetQueue() { return queue; }
|
||||||
|
|
@ -93,14 +96,25 @@ void CompletionQueueAsyncWorker::HandleOKCallback() {
|
||||||
} else {
|
} else {
|
||||||
current_threads -= 1;
|
current_threads -= 1;
|
||||||
}
|
}
|
||||||
NanCallback event_callback(GetTagHandle(result->tag).As<Function>());
|
NanCallback *callback = GetTagCallback(result->tag);
|
||||||
Handle<Value> argv[] = {CreateEventObject(result)};
|
Handle<Value> argv[] = {NanNull(), GetTagNodeValue(result->tag)};
|
||||||
|
callback->Call(2, argv);
|
||||||
|
|
||||||
DestroyTag(result->tag);
|
DestroyTag(result->tag);
|
||||||
grpc_event_finish(result);
|
grpc_event_finish(result);
|
||||||
result = NULL;
|
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
|
} // namespace node
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -67,6 +67,8 @@ class CompletionQueueAsyncWorker : public NanAsyncWorker {
|
||||||
completion_queue_next */
|
completion_queue_next */
|
||||||
void HandleOKCallback();
|
void HandleOKCallback();
|
||||||
|
|
||||||
|
void HandleErrorCallback();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
grpc_event *result;
|
grpc_event *result;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -41,8 +41,6 @@
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using ::node::Buffer;
|
|
||||||
using v8::Arguments;
|
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::External;
|
using v8::External;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
|
|
@ -56,37 +54,37 @@ using v8::ObjectTemplate;
|
||||||
using v8::Persistent;
|
using v8::Persistent;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
Persistent<Function> Credentials::constructor;
|
NanCallback *Credentials::constructor;
|
||||||
Persistent<FunctionTemplate> Credentials::fun_tpl;
|
Persistent<FunctionTemplate> Credentials::fun_tpl;
|
||||||
|
|
||||||
Credentials::Credentials(grpc_credentials *credentials)
|
Credentials::Credentials(grpc_credentials *credentials)
|
||||||
: wrapped_credentials(credentials) {}
|
: wrapped_credentials(credentials) {}
|
||||||
|
|
||||||
Credentials::~Credentials() {
|
Credentials::~Credentials() {
|
||||||
gpr_log(GPR_DEBUG, "Destroying credentials object");
|
|
||||||
grpc_credentials_release(wrapped_credentials);
|
grpc_credentials_release(wrapped_credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Credentials::Init(Handle<Object> exports) {
|
void Credentials::Init(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(NanNew("Credentials"));
|
tpl->SetClassName(NanNew("Credentials"));
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
NanAssignPersistent(fun_tpl, tpl);
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
NanAssignPersistent(constructor, tpl->GetFunction());
|
Handle<Function> ctr = tpl->GetFunction();
|
||||||
constructor->Set(NanNew("createDefault"),
|
ctr->Set(NanNew("createDefault"),
|
||||||
FunctionTemplate::New(CreateDefault)->GetFunction());
|
NanNew<FunctionTemplate>(CreateDefault)->GetFunction());
|
||||||
constructor->Set(NanNew("createSsl"),
|
ctr->Set(NanNew("createSsl"),
|
||||||
FunctionTemplate::New(CreateSsl)->GetFunction());
|
NanNew<FunctionTemplate>(CreateSsl)->GetFunction());
|
||||||
constructor->Set(NanNew("createComposite"),
|
ctr->Set(NanNew("createComposite"),
|
||||||
FunctionTemplate::New(CreateComposite)->GetFunction());
|
NanNew<FunctionTemplate>(CreateComposite)->GetFunction());
|
||||||
constructor->Set(NanNew("createGce"),
|
ctr->Set(NanNew("createGce"),
|
||||||
FunctionTemplate::New(CreateGce)->GetFunction());
|
NanNew<FunctionTemplate>(CreateGce)->GetFunction());
|
||||||
constructor->Set(NanNew("createFake"),
|
ctr->Set(NanNew("createFake"),
|
||||||
FunctionTemplate::New(CreateFake)->GetFunction());
|
NanNew<FunctionTemplate>(CreateFake)->GetFunction());
|
||||||
constructor->Set(NanNew("createIam"),
|
ctr->Set(NanNew("createIam"),
|
||||||
FunctionTemplate::New(CreateIam)->GetFunction());
|
NanNew<FunctionTemplate>(CreateIam)->GetFunction());
|
||||||
exports->Set(NanNew("Credentials"), constructor);
|
constructor = new NanCallback(ctr);
|
||||||
|
exports->Set(NanNew("Credentials"), ctr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Credentials::HasInstance(Handle<Value> val) {
|
bool Credentials::HasInstance(Handle<Value> val) {
|
||||||
|
|
@ -101,8 +99,8 @@ Handle<Value> Credentials::WrapStruct(grpc_credentials *credentials) {
|
||||||
}
|
}
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Handle<Value> argv[argc] = {
|
Handle<Value> argv[argc] = {
|
||||||
External::New(reinterpret_cast<void *>(credentials))};
|
NanNew<External>(reinterpret_cast<void *>(credentials))};
|
||||||
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
|
|
||||||
grpc_credentials *Credentials::GetWrappedCredentials() {
|
grpc_credentials *Credentials::GetWrappedCredentials() {
|
||||||
|
|
@ -117,40 +115,41 @@ NAN_METHOD(Credentials::New) {
|
||||||
return NanThrowTypeError(
|
return NanThrowTypeError(
|
||||||
"Credentials can only be created with the provided functions");
|
"Credentials can only be created with the provided functions");
|
||||||
}
|
}
|
||||||
|
Handle<External> ext = args[0].As<External>();
|
||||||
grpc_credentials *creds_value =
|
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 *credentials = new Credentials(creds_value);
|
||||||
credentials->Wrap(args.This());
|
credentials->Wrap(args.This());
|
||||||
NanReturnValue(args.This());
|
NanReturnValue(args.This());
|
||||||
} else {
|
} else {
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Local<Value> argv[argc] = {args[0]};
|
Local<Value> argv[argc] = {args[0]};
|
||||||
NanReturnValue(constructor->NewInstance(argc, argv));
|
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NAN_METHOD(Credentials::CreateDefault) {
|
NAN_METHOD(Credentials::CreateDefault) {
|
||||||
NanScope();
|
NanScope();
|
||||||
NanReturnValue(WrapStruct(grpc_default_credentials_create()));
|
NanReturnValue(WrapStruct(grpc_google_default_credentials_create()));
|
||||||
}
|
}
|
||||||
|
|
||||||
NAN_METHOD(Credentials::CreateSsl) {
|
NAN_METHOD(Credentials::CreateSsl) {
|
||||||
NanScope();
|
NanScope();
|
||||||
char *root_certs = NULL;
|
char *root_certs = NULL;
|
||||||
grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL};
|
grpc_ssl_pem_key_cert_pair key_cert_pair = {NULL, NULL};
|
||||||
if (Buffer::HasInstance(args[0])) {
|
if (::node::Buffer::HasInstance(args[0])) {
|
||||||
root_certs = Buffer::Data(args[0]);
|
root_certs = ::node::Buffer::Data(args[0]);
|
||||||
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
|
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
|
||||||
return NanThrowTypeError("createSsl's first argument must be a Buffer");
|
return NanThrowTypeError("createSsl's first argument must be a Buffer");
|
||||||
}
|
}
|
||||||
if (Buffer::HasInstance(args[1])) {
|
if (::node::Buffer::HasInstance(args[1])) {
|
||||||
key_cert_pair.private_key = Buffer::Data(args[1]);
|
key_cert_pair.private_key = ::node::Buffer::Data(args[1]);
|
||||||
} else if (!(args[1]->IsNull() || args[1]->IsUndefined())) {
|
} else if (!(args[1]->IsNull() || args[1]->IsUndefined())) {
|
||||||
return NanThrowTypeError(
|
return NanThrowTypeError(
|
||||||
"createSSl's second argument must be a Buffer if provided");
|
"createSSl's second argument must be a Buffer if provided");
|
||||||
}
|
}
|
||||||
if (Buffer::HasInstance(args[2])) {
|
if (::node::Buffer::HasInstance(args[2])) {
|
||||||
key_cert_pair.cert_chain = Buffer::Data(args[2]);
|
key_cert_pair.cert_chain = ::node::Buffer::Data(args[2]);
|
||||||
} else if (!(args[2]->IsNull() || args[2]->IsUndefined())) {
|
} else if (!(args[2]->IsNull() || args[2]->IsUndefined())) {
|
||||||
return NanThrowTypeError(
|
return NanThrowTypeError(
|
||||||
"createSSl's third argument must be a Buffer if provided");
|
"createSSl's third argument must be a Buffer if provided");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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(CreateGce);
|
||||||
static NAN_METHOD(CreateFake);
|
static NAN_METHOD(CreateFake);
|
||||||
static NAN_METHOD(CreateIam);
|
static NAN_METHOD(CreateIam);
|
||||||
static v8::Persistent<v8::Function> constructor;
|
static NanCallback *constructor;
|
||||||
// Used for typechecking instances of this javascript class
|
// Used for typechecking instances of this javascript class
|
||||||
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
|
|
||||||
176
ext/event.cc
176
ext/event.cc
|
|
@ -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
|
|
||||||
48
ext/event.h
48
ext/event.h
|
|
@ -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_
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
|
|
||||||
#include "call.h"
|
#include "call.h"
|
||||||
#include "channel.h"
|
#include "channel.h"
|
||||||
#include "event.h"
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "completion_queue_async_worker.h"
|
#include "completion_queue_async_worker.h"
|
||||||
#include "credentials.h"
|
#include "credentials.h"
|
||||||
|
|
@ -52,7 +51,7 @@ using v8::String;
|
||||||
|
|
||||||
void InitStatusConstants(Handle<Object> exports) {
|
void InitStatusConstants(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Handle<Object> status = Object::New();
|
Handle<Object> status = NanNew<Object>();
|
||||||
exports->Set(NanNew("status"), status);
|
exports->Set(NanNew("status"), status);
|
||||||
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_STATUS_OK));
|
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_STATUS_OK));
|
||||||
status->Set(NanNew("OK"), OK);
|
status->Set(NanNew("OK"), OK);
|
||||||
|
|
@ -101,7 +100,7 @@ void InitStatusConstants(Handle<Object> exports) {
|
||||||
|
|
||||||
void InitCallErrorConstants(Handle<Object> exports) {
|
void InitCallErrorConstants(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Handle<Object> call_error = Object::New();
|
Handle<Object> call_error = NanNew<Object>();
|
||||||
exports->Set(NanNew("callError"), call_error);
|
exports->Set(NanNew("callError"), call_error);
|
||||||
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_CALL_OK));
|
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_CALL_OK));
|
||||||
call_error->Set(NanNew("OK"), OK);
|
call_error->Set(NanNew("OK"), OK);
|
||||||
|
|
@ -130,35 +129,34 @@ void InitCallErrorConstants(Handle<Object> exports) {
|
||||||
call_error->Set(NanNew("INVALID_FLAGS"), INVALID_FLAGS);
|
call_error->Set(NanNew("INVALID_FLAGS"), INVALID_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitOpErrorConstants(Handle<Object> exports) {
|
void InitOpTypeConstants(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Handle<Object> op_error = Object::New();
|
Handle<Object> op_type = NanNew<Object>();
|
||||||
exports->Set(NanNew("opError"), op_error);
|
exports->Set(NanNew("opType"), op_type);
|
||||||
Handle<Value> OK(NanNew<Uint32, uint32_t>(GRPC_OP_OK));
|
Handle<Value> SEND_INITIAL_METADATA(
|
||||||
op_error->Set(NanNew("OK"), OK);
|
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_INITIAL_METADATA));
|
||||||
Handle<Value> ERROR(NanNew<Uint32, uint32_t>(GRPC_OP_ERROR));
|
op_type->Set(NanNew("SEND_INITIAL_METADATA"), SEND_INITIAL_METADATA);
|
||||||
op_error->Set(NanNew("ERROR"), ERROR);
|
Handle<Value> SEND_MESSAGE(
|
||||||
}
|
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_MESSAGE));
|
||||||
|
op_type->Set(NanNew("SEND_MESSAGE"), SEND_MESSAGE);
|
||||||
void InitCompletionTypeConstants(Handle<Object> exports) {
|
Handle<Value> SEND_CLOSE_FROM_CLIENT(
|
||||||
NanScope();
|
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_CLOSE_FROM_CLIENT));
|
||||||
Handle<Object> completion_type = Object::New();
|
op_type->Set(NanNew("SEND_CLOSE_FROM_CLIENT"), SEND_CLOSE_FROM_CLIENT);
|
||||||
exports->Set(NanNew("completionType"), completion_type);
|
Handle<Value> SEND_STATUS_FROM_SERVER(
|
||||||
Handle<Value> QUEUE_SHUTDOWN(NanNew<Uint32, uint32_t>(GRPC_QUEUE_SHUTDOWN));
|
NanNew<Uint32, uint32_t>(GRPC_OP_SEND_STATUS_FROM_SERVER));
|
||||||
completion_type->Set(NanNew("QUEUE_SHUTDOWN"), QUEUE_SHUTDOWN);
|
op_type->Set(NanNew("SEND_STATUS_FROM_SERVER"), SEND_STATUS_FROM_SERVER);
|
||||||
Handle<Value> READ(NanNew<Uint32, uint32_t>(GRPC_READ));
|
Handle<Value> RECV_INITIAL_METADATA(
|
||||||
completion_type->Set(NanNew("READ"), READ);
|
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_INITIAL_METADATA));
|
||||||
Handle<Value> WRITE_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_WRITE_ACCEPTED));
|
op_type->Set(NanNew("RECV_INITIAL_METADATA"), RECV_INITIAL_METADATA);
|
||||||
completion_type->Set(NanNew("WRITE_ACCEPTED"), WRITE_ACCEPTED);
|
Handle<Value> RECV_MESSAGE(
|
||||||
Handle<Value> FINISH_ACCEPTED(NanNew<Uint32, uint32_t>(GRPC_FINISH_ACCEPTED));
|
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_MESSAGE));
|
||||||
completion_type->Set(NanNew("FINISH_ACCEPTED"), FINISH_ACCEPTED);
|
op_type->Set(NanNew("RECV_MESSAGE"), RECV_MESSAGE);
|
||||||
Handle<Value> CLIENT_METADATA_READ(
|
Handle<Value> RECV_STATUS_ON_CLIENT(
|
||||||
NanNew<Uint32, uint32_t>(GRPC_CLIENT_METADATA_READ));
|
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_STATUS_ON_CLIENT));
|
||||||
completion_type->Set(NanNew("CLIENT_METADATA_READ"), CLIENT_METADATA_READ);
|
op_type->Set(NanNew("RECV_STATUS_ON_CLIENT"), RECV_STATUS_ON_CLIENT);
|
||||||
Handle<Value> FINISHED(NanNew<Uint32, uint32_t>(GRPC_FINISHED));
|
Handle<Value> RECV_CLOSE_ON_SERVER(
|
||||||
completion_type->Set(NanNew("FINISHED"), FINISHED);
|
NanNew<Uint32, uint32_t>(GRPC_OP_RECV_CLOSE_ON_SERVER));
|
||||||
Handle<Value> SERVER_RPC_NEW(NanNew<Uint32, uint32_t>(GRPC_SERVER_RPC_NEW));
|
op_type->Set(NanNew("RECV_CLOSE_ON_SERVER"), RECV_CLOSE_ON_SERVER);
|
||||||
completion_type->Set(NanNew("SERVER_RPC_NEW"), SERVER_RPC_NEW);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(Handle<Object> exports) {
|
void init(Handle<Object> exports) {
|
||||||
|
|
@ -166,8 +164,7 @@ void init(Handle<Object> exports) {
|
||||||
grpc_init();
|
grpc_init();
|
||||||
InitStatusConstants(exports);
|
InitStatusConstants(exports);
|
||||||
InitCallErrorConstants(exports);
|
InitCallErrorConstants(exports);
|
||||||
InitOpErrorConstants(exports);
|
InitOpTypeConstants(exports);
|
||||||
InitCompletionTypeConstants(exports);
|
|
||||||
|
|
||||||
grpc::node::Call::Init(exports);
|
grpc::node::Call::Init(exports);
|
||||||
grpc::node::Channel::Init(exports);
|
grpc::node::Channel::Init(exports);
|
||||||
|
|
|
||||||
124
ext/server.cc
124
ext/server.cc
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
#include <node.h>
|
#include <node.h>
|
||||||
|
|
@ -41,17 +43,19 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "grpc/grpc.h"
|
#include "grpc/grpc.h"
|
||||||
#include "grpc/grpc_security.h"
|
#include "grpc/grpc_security.h"
|
||||||
|
#include "grpc/support/log.h"
|
||||||
#include "call.h"
|
#include "call.h"
|
||||||
#include "completion_queue_async_worker.h"
|
#include "completion_queue_async_worker.h"
|
||||||
#include "tag.h"
|
|
||||||
#include "server_credentials.h"
|
#include "server_credentials.h"
|
||||||
|
#include "timeval.h"
|
||||||
|
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using v8::Arguments;
|
using std::unique_ptr;
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Boolean;
|
using v8::Boolean;
|
||||||
|
using v8::Date;
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::FunctionTemplate;
|
using v8::FunctionTemplate;
|
||||||
|
|
@ -64,37 +68,82 @@ using v8::Persistent;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
Persistent<Function> Server::constructor;
|
NanCallback *Server::constructor;
|
||||||
Persistent<FunctionTemplate> Server::fun_tpl;
|
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 *server) : wrapped_server(server) {}
|
||||||
|
|
||||||
Server::~Server() { grpc_server_destroy(wrapped_server); }
|
Server::~Server() { grpc_server_destroy(wrapped_server); }
|
||||||
|
|
||||||
void Server::Init(Handle<Object> exports) {
|
void Server::Init(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(String::NewSymbol("Server"));
|
tpl->SetClassName(NanNew("Server"));
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
NanSetPrototypeTemplate(tpl, "requestCall",
|
NanSetPrototypeTemplate(tpl, "requestCall",
|
||||||
FunctionTemplate::New(RequestCall)->GetFunction());
|
NanNew<FunctionTemplate>(RequestCall)->GetFunction());
|
||||||
|
|
||||||
NanSetPrototypeTemplate(tpl, "addHttp2Port",
|
NanSetPrototypeTemplate(
|
||||||
FunctionTemplate::New(AddHttp2Port)->GetFunction());
|
tpl, "addHttp2Port",
|
||||||
|
NanNew<FunctionTemplate>(AddHttp2Port)->GetFunction());
|
||||||
|
|
||||||
NanSetPrototypeTemplate(
|
NanSetPrototypeTemplate(
|
||||||
tpl, "addSecureHttp2Port",
|
tpl, "addSecureHttp2Port",
|
||||||
FunctionTemplate::New(AddSecureHttp2Port)->GetFunction());
|
NanNew<FunctionTemplate>(AddSecureHttp2Port)->GetFunction());
|
||||||
|
|
||||||
NanSetPrototypeTemplate(tpl, "start",
|
NanSetPrototypeTemplate(tpl, "start",
|
||||||
FunctionTemplate::New(Start)->GetFunction());
|
NanNew<FunctionTemplate>(Start)->GetFunction());
|
||||||
|
|
||||||
NanSetPrototypeTemplate(tpl, "shutdown",
|
NanSetPrototypeTemplate(tpl, "shutdown",
|
||||||
FunctionTemplate::New(Shutdown)->GetFunction());
|
NanNew<FunctionTemplate>(Shutdown)->GetFunction());
|
||||||
|
|
||||||
NanAssignPersistent(fun_tpl, tpl);
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
NanAssignPersistent(constructor, tpl->GetFunction());
|
Handle<Function> ctr = tpl->GetFunction();
|
||||||
exports->Set(String::NewSymbol("Server"), constructor);
|
constructor = new NanCallback(ctr);
|
||||||
|
exports->Set(NanNew("Server"), ctr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::HasInstance(Handle<Value> val) {
|
bool Server::HasInstance(Handle<Value> val) {
|
||||||
|
|
@ -109,26 +158,14 @@ NAN_METHOD(Server::New) {
|
||||||
if (!args.IsConstructCall()) {
|
if (!args.IsConstructCall()) {
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Local<Value> argv[argc] = {args[0]};
|
Local<Value> argv[argc] = {args[0]};
|
||||||
NanReturnValue(constructor->NewInstance(argc, argv));
|
NanReturnValue(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
grpc_server *wrapped_server;
|
grpc_server *wrapped_server;
|
||||||
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
|
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
|
||||||
if (args[0]->IsUndefined()) {
|
if (args[0]->IsUndefined()) {
|
||||||
wrapped_server = grpc_server_create(queue, NULL);
|
wrapped_server = grpc_server_create(queue, NULL);
|
||||||
} else if (args[0]->IsObject()) {
|
} else if (args[0]->IsObject()) {
|
||||||
grpc_server_credentials *creds = NULL;
|
Handle<Object> args_hash(args[0]->ToObject());
|
||||||
Handle<Object> args_hash(args[0]->ToObject()->Clone());
|
|
||||||
if (args_hash->HasOwnProperty(NanNew("credentials"))) {
|
|
||||||
Handle<Value> creds_value = args_hash->Get(NanNew("credentials"));
|
|
||||||
if (!ServerCredentials::HasInstance(creds_value)) {
|
|
||||||
return NanThrowTypeError(
|
|
||||||
"credentials arg must be a ServerCredentials object");
|
|
||||||
}
|
|
||||||
ServerCredentials *creds_object =
|
|
||||||
ObjectWrap::Unwrap<ServerCredentials>(creds_value->ToObject());
|
|
||||||
creds = creds_object->GetWrappedServerCredentials();
|
|
||||||
args_hash->Delete(NanNew("credentials"));
|
|
||||||
}
|
|
||||||
Handle<Array> keys(args_hash->GetOwnPropertyNames());
|
Handle<Array> keys(args_hash->GetOwnPropertyNames());
|
||||||
grpc_channel_args channel_args;
|
grpc_channel_args channel_args;
|
||||||
channel_args.num_args = keys->Length();
|
channel_args.num_args = keys->Length();
|
||||||
|
|
@ -155,11 +192,7 @@ NAN_METHOD(Server::New) {
|
||||||
return NanThrowTypeError("Arg values must be strings");
|
return NanThrowTypeError("Arg values must be strings");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (creds == NULL) {
|
wrapped_server = grpc_server_create(queue, &channel_args);
|
||||||
wrapped_server = grpc_server_create(queue, &channel_args);
|
|
||||||
} else {
|
|
||||||
wrapped_server = grpc_secure_server_create(creds, queue, &channel_args);
|
|
||||||
}
|
|
||||||
free(channel_args.args);
|
free(channel_args.args);
|
||||||
} else {
|
} else {
|
||||||
return NanThrowTypeError("Server expects an object");
|
return NanThrowTypeError("Server expects an object");
|
||||||
|
|
@ -175,13 +208,18 @@ NAN_METHOD(Server::RequestCall) {
|
||||||
return NanThrowTypeError("requestCall can only be called on a Server");
|
return NanThrowTypeError("requestCall can only be called on a Server");
|
||||||
}
|
}
|
||||||
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
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(
|
grpc_call_error error = grpc_server_request_call(
|
||||||
server->wrapped_server, CreateTag(args[0], NanNull()));
|
server->wrapped_server, &op->call, &op->details, &op->request_metadata,
|
||||||
if (error == GRPC_CALL_OK) {
|
CompletionQueueAsyncWorker::GetQueue(),
|
||||||
CompletionQueueAsyncWorker::Next();
|
new struct tag(new NanCallback(args[0].As<Function>()), ops.release(),
|
||||||
} else {
|
shared_ptr<Resources>(nullptr)));
|
||||||
|
if (error != GRPC_CALL_OK) {
|
||||||
return NanThrowError("requestCall failed", error);
|
return NanThrowError("requestCall failed", error);
|
||||||
}
|
}
|
||||||
|
CompletionQueueAsyncWorker::Next();
|
||||||
NanReturnUndefined();
|
NanReturnUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,11 +243,19 @@ NAN_METHOD(Server::AddSecureHttp2Port) {
|
||||||
"addSecureHttp2Port can only be called on a Server");
|
"addSecureHttp2Port can only be called on a Server");
|
||||||
}
|
}
|
||||||
if (!args[0]->IsString()) {
|
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());
|
Server *server = ObjectWrap::Unwrap<Server>(args.This());
|
||||||
|
ServerCredentials *creds = ObjectWrap::Unwrap<ServerCredentials>(
|
||||||
|
args[1]->ToObject());
|
||||||
NanReturnValue(NanNew<Number>(grpc_server_add_secure_http2_port(
|
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) {
|
NAN_METHOD(Server::Start) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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(AddSecureHttp2Port);
|
||||||
static NAN_METHOD(Start);
|
static NAN_METHOD(Start);
|
||||||
static NAN_METHOD(Shutdown);
|
static NAN_METHOD(Shutdown);
|
||||||
static v8::Persistent<v8::Function> constructor;
|
static NanCallback *constructor;
|
||||||
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
grpc_server *wrapped_server;
|
grpc_server *wrapped_server;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -41,8 +41,6 @@
|
||||||
namespace grpc {
|
namespace grpc {
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
using ::node::Buffer;
|
|
||||||
using v8::Arguments;
|
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::External;
|
using v8::External;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
|
|
@ -56,29 +54,29 @@ using v8::ObjectTemplate;
|
||||||
using v8::Persistent;
|
using v8::Persistent;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
Persistent<Function> ServerCredentials::constructor;
|
NanCallback *ServerCredentials::constructor;
|
||||||
Persistent<FunctionTemplate> ServerCredentials::fun_tpl;
|
Persistent<FunctionTemplate> ServerCredentials::fun_tpl;
|
||||||
|
|
||||||
ServerCredentials::ServerCredentials(grpc_server_credentials *credentials)
|
ServerCredentials::ServerCredentials(grpc_server_credentials *credentials)
|
||||||
: wrapped_credentials(credentials) {}
|
: wrapped_credentials(credentials) {}
|
||||||
|
|
||||||
ServerCredentials::~ServerCredentials() {
|
ServerCredentials::~ServerCredentials() {
|
||||||
gpr_log(GPR_DEBUG, "Destroying server credentials object");
|
|
||||||
grpc_server_credentials_release(wrapped_credentials);
|
grpc_server_credentials_release(wrapped_credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerCredentials::Init(Handle<Object> exports) {
|
void ServerCredentials::Init(Handle<Object> exports) {
|
||||||
NanScope();
|
NanScope();
|
||||||
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(NanNew("ServerCredentials"));
|
tpl->SetClassName(NanNew("ServerCredentials"));
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
NanAssignPersistent(fun_tpl, tpl);
|
NanAssignPersistent(fun_tpl, tpl);
|
||||||
NanAssignPersistent(constructor, tpl->GetFunction());
|
Handle<Function> ctr = tpl->GetFunction();
|
||||||
constructor->Set(NanNew("createSsl"),
|
ctr->Set(NanNew("createSsl"),
|
||||||
FunctionTemplate::New(CreateSsl)->GetFunction());
|
NanNew<FunctionTemplate>(CreateSsl)->GetFunction());
|
||||||
constructor->Set(NanNew("createFake"),
|
ctr->Set(NanNew("createFake"),
|
||||||
FunctionTemplate::New(CreateFake)->GetFunction());
|
NanNew<FunctionTemplate>(CreateFake)->GetFunction());
|
||||||
exports->Set(NanNew("ServerCredentials"), constructor);
|
constructor = new NanCallback(ctr);
|
||||||
|
exports->Set(NanNew("ServerCredentials"), ctr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerCredentials::HasInstance(Handle<Value> val) {
|
bool ServerCredentials::HasInstance(Handle<Value> val) {
|
||||||
|
|
@ -94,8 +92,8 @@ Handle<Value> ServerCredentials::WrapStruct(
|
||||||
}
|
}
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Handle<Value> argv[argc] = {
|
Handle<Value> argv[argc] = {
|
||||||
External::New(reinterpret_cast<void *>(credentials))};
|
NanNew<External>(reinterpret_cast<void *>(credentials))};
|
||||||
return NanEscapeScope(constructor->NewInstance(argc, argv));
|
return NanEscapeScope(constructor->GetFunction()->NewInstance(argc, argv));
|
||||||
}
|
}
|
||||||
|
|
||||||
grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() {
|
grpc_server_credentials *ServerCredentials::GetWrappedServerCredentials() {
|
||||||
|
|
@ -110,15 +108,16 @@ NAN_METHOD(ServerCredentials::New) {
|
||||||
return NanThrowTypeError(
|
return NanThrowTypeError(
|
||||||
"ServerCredentials can only be created with the provide functions");
|
"ServerCredentials can only be created with the provide functions");
|
||||||
}
|
}
|
||||||
|
Handle<External> ext = args[0].As<External>();
|
||||||
grpc_server_credentials *creds_value =
|
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);
|
ServerCredentials *credentials = new ServerCredentials(creds_value);
|
||||||
credentials->Wrap(args.This());
|
credentials->Wrap(args.This());
|
||||||
NanReturnValue(args.This());
|
NanReturnValue(args.This());
|
||||||
} else {
|
} else {
|
||||||
const int argc = 1;
|
const int argc = 1;
|
||||||
Local<Value> argv[argc] = {args[0]};
|
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();
|
NanScope();
|
||||||
char *root_certs = NULL;
|
char *root_certs = NULL;
|
||||||
grpc_ssl_pem_key_cert_pair key_cert_pair;
|
grpc_ssl_pem_key_cert_pair key_cert_pair;
|
||||||
if (Buffer::HasInstance(args[0])) {
|
if (::node::Buffer::HasInstance(args[0])) {
|
||||||
root_certs = Buffer::Data(args[0]);
|
root_certs = ::node::Buffer::Data(args[0]);
|
||||||
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
|
} else if (!(args[0]->IsNull() || args[0]->IsUndefined())) {
|
||||||
return NanThrowTypeError(
|
return NanThrowTypeError(
|
||||||
"createSSl's first argument must be a Buffer if provided");
|
"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");
|
return NanThrowTypeError("createSsl's second argument must be a Buffer");
|
||||||
}
|
}
|
||||||
key_cert_pair.private_key = Buffer::Data(args[1]);
|
key_cert_pair.private_key = ::node::Buffer::Data(args[1]);
|
||||||
if (!Buffer::HasInstance(args[2])) {
|
if (!::node::Buffer::HasInstance(args[2])) {
|
||||||
return NanThrowTypeError("createSsl's third argument must be a Buffer");
|
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(
|
NanReturnValue(WrapStruct(
|
||||||
grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1)));
|
grpc_ssl_server_credentials_create(root_certs, &key_cert_pair, 1)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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(New);
|
||||||
static NAN_METHOD(CreateSsl);
|
static NAN_METHOD(CreateSsl);
|
||||||
static NAN_METHOD(CreateFake);
|
static NAN_METHOD(CreateFake);
|
||||||
static v8::Persistent<v8::Function> constructor;
|
static NanCallback *constructor;
|
||||||
// Used for typechecking instances of this javascript class
|
// Used for typechecking instances of this javascript class
|
||||||
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
static v8::Persistent<v8::FunctionTemplate> fun_tpl;
|
||||||
|
|
||||||
|
|
|
||||||
101
ext/tag.cc
101
ext/tag.cc
|
|
@ -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
|
|
||||||
59
ext/tag.h
59
ext/tag.h
|
|
@ -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_
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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) {
|
} else if (gpr_time_cmp(timespec, gpr_inf_past) == 0) {
|
||||||
return -std::numeric_limits<double>::infinity();
|
return -std::numeric_limits<double>::infinity();
|
||||||
} else {
|
} else {
|
||||||
struct timeval time = gpr_timeval_from_timespec(timespec);
|
return (static_cast<double>(timespec.tv_sec) * 1000 +
|
||||||
return (static_cast<double>(time.tv_sec) * 1000 +
|
static_cast<double>(timespec.tv_nsec) / 1000000);
|
||||||
static_cast<double>(time.tv_usec) / 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
47
index.js
47
index.js
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,13 +31,15 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
var ProtoBuf = require('protobufjs');
|
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');
|
var grpc = require('bindings')('grpc');
|
||||||
|
|
||||||
|
|
@ -54,7 +56,7 @@ function loadObject(value) {
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
} else if (value.className === 'Service') {
|
} else if (value.className === 'Service') {
|
||||||
return surface_client.makeClientConstructor(value);
|
return client.makeClientConstructor(value);
|
||||||
} else if (value.className === 'Message' || value.className === 'Enum') {
|
} else if (value.className === 'Message' || value.className === 'Enum') {
|
||||||
return value.build();
|
return value.build();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -73,6 +75,37 @@ function load(filename) {
|
||||||
return loadObject(builder.ns);
|
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
|
* See docs for loadObject
|
||||||
*/
|
*/
|
||||||
|
|
@ -84,9 +117,9 @@ exports.loadObject = loadObject;
|
||||||
exports.load = load;
|
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
|
* Status name to code number mapping
|
||||||
|
|
@ -106,3 +139,5 @@ exports.Credentials = grpc.Credentials;
|
||||||
* ServerCredentials factories
|
* ServerCredentials factories
|
||||||
*/
|
*/
|
||||||
exports.ServerCredentials = grpc.ServerCredentials;
|
exports.ServerCredentials = grpc.ServerCredentials;
|
||||||
|
|
||||||
|
exports.getGoogleAuthDelegate = getGoogleAuthDelegate;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
syntax = "proto2";
|
||||||
|
|
||||||
package grpc.testing;
|
package grpc.testing;
|
||||||
|
|
@ -10,10 +40,4 @@ package grpc.testing;
|
||||||
// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
|
// 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 {}
|
message Empty {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,13 +31,21 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var grpc = require('..');
|
var grpc = require('..');
|
||||||
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
|
var testProto = grpc.load(__dirname + '/test.proto').grpc.testing;
|
||||||
|
var GoogleAuth = require('google-auth-library');
|
||||||
|
|
||||||
var assert = require('assert');
|
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
|
* Create a buffer filled with size zeroes
|
||||||
* @param {number} size The length of the buffer
|
* @param {number} size The length of the buffer
|
||||||
|
|
@ -145,8 +153,8 @@ function serverStreaming(client, done) {
|
||||||
resp_index += 1;
|
resp_index += 1;
|
||||||
});
|
});
|
||||||
call.on('status', function(status) {
|
call.on('status', function(status) {
|
||||||
assert.strictEqual(resp_index, 4);
|
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.strictEqual(resp_index, 4);
|
||||||
if (done) {
|
if (done) {
|
||||||
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
|
* Map from test case names to test functions
|
||||||
*/
|
*/
|
||||||
|
|
@ -266,13 +313,15 @@ var test_cases = {
|
||||||
ping_pong: pingPong,
|
ping_pong: pingPong,
|
||||||
empty_stream: emptyStream,
|
empty_stream: emptyStream,
|
||||||
cancel_after_begin: cancelAfterBegin,
|
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.
|
* Execute a single test case.
|
||||||
* @param {string} address The address of the server to connect to, in the
|
* @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
|
* @param {string} host_overrirde The hostname of the server to use as an SSL
|
||||||
* override
|
* override
|
||||||
* @param {string} test_case The name of the test case to run
|
* @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
|
* @param {function} done Callback to call when the test is completed. Included
|
||||||
* primarily for use with mocha
|
* 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
|
// TODO(mlumish): enable TLS functionality
|
||||||
var options = {};
|
var options = {};
|
||||||
if (tls) {
|
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 ca_data = fs.readFileSync(ca_path);
|
||||||
var creds = grpc.Credentials.createSsl(ca_data);
|
var creds = grpc.Credentials.createSsl(ca_data);
|
||||||
options.credentials = creds;
|
options.credentials = creds;
|
||||||
|
|
@ -304,7 +358,10 @@ if (require.main === module) {
|
||||||
'use_tls', 'use_test_ca']
|
'use_tls', 'use_test_ca']
|
||||||
});
|
});
|
||||||
runTest(argv.server_host + ':' + argv.server_port, argv.server_host_override,
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
@ -163,16 +165,16 @@ function handleHalfDuplex(call) {
|
||||||
function getServer(port, tls) {
|
function getServer(port, tls) {
|
||||||
// TODO(mlumish): enable TLS functionality
|
// TODO(mlumish): enable TLS functionality
|
||||||
var options = {};
|
var options = {};
|
||||||
|
var server_creds = null;
|
||||||
if (tls) {
|
if (tls) {
|
||||||
var key_path = path.join(__dirname, '../test/data/server1.key');
|
var key_path = path.join(__dirname, '../test/data/server1.key');
|
||||||
var pem_path = path.join(__dirname, '../test/data/server1.pem');
|
var pem_path = path.join(__dirname, '../test/data/server1.pem');
|
||||||
|
|
||||||
var key_data = fs.readFileSync(key_path);
|
var key_data = fs.readFileSync(key_path);
|
||||||
var pem_data = fs.readFileSync(pem_path);
|
var pem_data = fs.readFileSync(pem_path);
|
||||||
var server_creds = grpc.ServerCredentials.createSsl(null,
|
server_creds = grpc.ServerCredentials.createSsl(null,
|
||||||
key_data,
|
key_data,
|
||||||
pem_data);
|
pem_data);
|
||||||
options.credentials = server_creds;
|
|
||||||
}
|
}
|
||||||
var server = new Server({
|
var server = new Server({
|
||||||
'grpc.testing.TestService' : {
|
'grpc.testing.TestService' : {
|
||||||
|
|
@ -184,7 +186,7 @@ function getServer(port, tls) {
|
||||||
halfDuplexCall: handleHalfDuplex
|
halfDuplexCall: handleHalfDuplex
|
||||||
}
|
}
|
||||||
}, null, options);
|
}, 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};
|
return {server: server, port: port_num};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Message definitions to be used by integration test service definitions.
|
||||||
|
|
||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
|
|
@ -19,7 +49,7 @@ enum PayloadType {
|
||||||
// A block of data, to simply increase gRPC message size.
|
// A block of data, to simply increase gRPC message size.
|
||||||
message Payload {
|
message Payload {
|
||||||
// The type of data in body.
|
// The type of data in body.
|
||||||
optional PayloadType type = 1;
|
optional PayloadType type = 1 [default = COMPRESSABLE];
|
||||||
// Primary contents of payload.
|
// Primary contents of payload.
|
||||||
optional bytes body = 2;
|
optional bytes body = 2;
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +58,7 @@ message Payload {
|
||||||
message SimpleRequest {
|
message SimpleRequest {
|
||||||
// Desired payload type in the response from the server.
|
// Desired payload type in the response from the server.
|
||||||
// If response_type is RANDOM, server randomly chooses one from other formats.
|
// 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.
|
// Desired payload size in the response from the server.
|
||||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
// 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 input payload sent along with the request.
|
||||||
optional Payload payload = 3;
|
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.
|
// Unary response, as configured by the request.
|
||||||
|
|
@ -44,7 +80,9 @@ message SimpleResponse {
|
||||||
optional Payload payload = 1;
|
optional Payload payload = 1;
|
||||||
// The user the request came from, for verifying authentication was
|
// The user the request came from, for verifying authentication was
|
||||||
// successful when the client expected it.
|
// 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.
|
// Client-streaming request.
|
||||||
|
|
@ -78,7 +116,7 @@ message StreamingOutputCallRequest {
|
||||||
// If response_type is RANDOM, the payload from each response in the stream
|
// 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
|
// might be of different types. This is to simulate a mixed type of payload
|
||||||
// stream.
|
// stream.
|
||||||
optional PayloadType response_type = 1;
|
optional PayloadType response_type = 1 [default = COMPRESSABLE];
|
||||||
|
|
||||||
// Configuration for each expected response message.
|
// Configuration for each expected response message.
|
||||||
repeated ResponseParameters response_parameters = 2;
|
repeated ResponseParameters response_parameters = 2;
|
||||||
|
|
|
||||||
|
|
@ -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
|
// An integration test service that covers all the method signature permutations
|
||||||
// of unary/streaming requests/responses.
|
// of unary/streaming requests/responses.
|
||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
|
|
@ -14,7 +44,7 @@ service TestService {
|
||||||
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||||
|
|
||||||
// One request followed by one response.
|
// 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);
|
rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
|
||||||
|
|
||||||
// One request followed by a sequence of responses (streamed download).
|
// One request followed by a sequence of responses (streamed download).
|
||||||
|
|
|
||||||
53
package.json
53
package.json
|
|
@ -1,20 +1,57 @@
|
||||||
{
|
{
|
||||||
"name": "grpc",
|
"name": "grpc",
|
||||||
"version": "0.1.0",
|
"version": "0.5.5",
|
||||||
|
"author": "Google Inc.",
|
||||||
"description": "gRPC Library for Node",
|
"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": {
|
"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": {
|
"dependencies": {
|
||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.0",
|
||||||
"nan": "~1.3.0",
|
"nan": "^1.5.0",
|
||||||
"protobufjs": "murgatroid99/ProtoBuf.js",
|
"protobufjs": "^4.0.0-b2",
|
||||||
"underscore": "^1.7.0",
|
"underscore": "^1.6.0",
|
||||||
"underscore.string": "^3.0.0"
|
"underscore.string": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"async": "^0.9.0",
|
||||||
|
"google-auth-library": "^0.9.2",
|
||||||
|
"jshint": "^2.5.0",
|
||||||
|
"minimist": "^1.1.0",
|
||||||
"mocha": "~1.21.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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
609
src/client.js
609
src/client.js
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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 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');
|
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
|
* A stream that the client can write to. Used for calls that are streaming from
|
||||||
* from stream.Duplex.
|
* the client side.
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {grpc.Call} call Call object to proxy
|
* @param {grpc.Call} call The call object to send data with
|
||||||
* @param {function(*):Buffer=} serialize Serialization function for requests
|
* @param {function(*):Buffer=} serialize Serialization function for writes.
|
||||||
* @param {function(Buffer):*=} deserialize Deserialization function for
|
|
||||||
* responses
|
|
||||||
*/
|
*/
|
||||||
function GrpcClientStream(call, serialize, deserialize) {
|
function ClientWritableStream(call, serialize) {
|
||||||
Duplex.call(this, {objectMode: true});
|
Writable.call(this, {objectMode: true});
|
||||||
if (!serialize) {
|
this.call = call;
|
||||||
serialize = function(value) {
|
this.serialize = common.wrapIgnoreNull(serialize);
|
||||||
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);
|
|
||||||
this.on('finish', function() {
|
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
|
* Attempt to write the given chunk. Calls the callback when done. This is an
|
||||||
* implementation of a method needed for implementing stream.Writable.
|
* implementation of a method needed for implementing stream.Writable.
|
||||||
* @param {Buffer} chunk The chunk to write
|
* @param {Buffer} chunk The chunk to write
|
||||||
* @param {string} encoding Ignored
|
* @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) {
|
function _write(chunk, encoding, callback) {
|
||||||
var self = this;
|
/* jshint validthis: true */
|
||||||
self._call.startWrite(self.serialize(chunk), function(event) {
|
var batch = {};
|
||||||
|
batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk);
|
||||||
|
this.call.startBatch(batch, function(err, event) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
callback();
|
callback();
|
||||||
}, 0);
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
ClientWritableStream.prototype._write = _write;
|
||||||
|
|
||||||
|
util.inherits(ClientReadableStream, Readable);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the ongoing call. If the call has not already finished, it will finish
|
* A stream that the client can read from. Used for calls that are streaming
|
||||||
* with status CANCELLED.
|
* 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() {
|
function ClientReadableStream(call, deserialize) {
|
||||||
this._call.cancel();
|
Readable.call(this, {objectMode: true});
|
||||||
};
|
this.call = call;
|
||||||
|
this.finished = false;
|
||||||
/**
|
this.reading = false;
|
||||||
* Make a request on the channel to the given method with the given arguments
|
this.deserialize = common.wrapIgnoreNull(deserialize);
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
exports.status = grpc.status;
|
||||||
/**
|
/**
|
||||||
* Call error name to code number mapping
|
* See docs for client.callError
|
||||||
*/
|
*/
|
||||||
exports.callError = grpc.callError;
|
exports.callError = grpc.callError;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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');
|
var capitalize = require('underscore.string/capitalize');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,6 +91,24 @@ function fullyQualifiedName(value) {
|
||||||
return name;
|
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
|
* See docs for deserializeCls
|
||||||
*/
|
*/
|
||||||
|
|
@ -101,3 +123,8 @@ exports.serializeCls = serializeCls;
|
||||||
* See docs for fullyQualifiedName
|
* See docs for fullyQualifiedName
|
||||||
*/
|
*/
|
||||||
exports.fullyQualifiedName = fullyQualifiedName;
|
exports.fullyQualifiedName = fullyQualifiedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See docs for wrapIgnoreNull
|
||||||
|
*/
|
||||||
|
exports.wrapIgnoreNull = wrapIgnoreNull;
|
||||||
|
|
|
||||||
626
src/server.js
626
src/server.js
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,82 +31,112 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
var capitalize = require('underscore.string/capitalize');
|
||||||
|
var decapitalize = require('underscore.string/decapitalize');
|
||||||
|
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
var common = require('./common');
|
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');
|
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
|
* Handle an error on a call by sending it as a status
|
||||||
* from stream.Duplex.
|
* @param {grpc.Call} call The call to send the error on
|
||||||
* @constructor
|
* @param {Object} error The error object
|
||||||
* @param {grpc.Call} call Call object to proxy
|
|
||||||
* @param {function(*):Buffer=} serialize Serialization function for responses
|
|
||||||
* @param {function(Buffer):*=} deserialize Deserialization function for
|
|
||||||
* requests
|
|
||||||
*/
|
*/
|
||||||
function GrpcServerStream(call, serialize, deserialize) {
|
function handleError(call, error) {
|
||||||
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;
|
|
||||||
var status = {
|
var status = {
|
||||||
'code' : grpc.status.OK,
|
code: grpc.status.INTERNAL,
|
||||||
'details' : 'OK'
|
details: 'Unknown Error',
|
||||||
|
metadata: {}
|
||||||
};
|
};
|
||||||
|
if (error.hasOwnProperty('message')) {
|
||||||
/**
|
status.details = error.message;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return deserialize(buffer);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the pending status
|
|
||||||
*/
|
|
||||||
function sendStatus() {
|
|
||||||
call.startWriteStatus(status.code, status.details, function() {
|
|
||||||
});
|
|
||||||
finished = true;
|
|
||||||
}
|
}
|
||||||
this.on('finish', sendStatus);
|
if (error.hasOwnProperty('code')) {
|
||||||
|
status.code = error.code;
|
||||||
|
if (error.hasOwnProperty('details')) {
|
||||||
|
status.details = error.details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var error_batch = {};
|
||||||
|
error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = status;
|
||||||
|
call.startBatch(error_batch, function(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(){});
|
||||||
|
}
|
||||||
|
stream.on('finish', sendStatus);
|
||||||
/**
|
/**
|
||||||
* Set the pending status to a given error status. If the error does not have
|
* 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
|
* 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) {
|
function setStatus(err) {
|
||||||
var code = grpc.status.INTERNAL;
|
var code = grpc.status.INTERNAL;
|
||||||
var details = 'Unknown Error';
|
var details = 'Unknown Error';
|
||||||
|
if (err.hasOwnProperty('message')) {
|
||||||
|
details = err.message;
|
||||||
|
}
|
||||||
if (err.hasOwnProperty('code')) {
|
if (err.hasOwnProperty('code')) {
|
||||||
code = err.code;
|
code = err.code;
|
||||||
if (err.hasOwnProperty('details')) {
|
if (err.hasOwnProperty('details')) {
|
||||||
details = err.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
|
* Terminate the call. This includes indicating that reads are done, draining
|
||||||
|
|
@ -133,55 +165,50 @@ function GrpcServerStream(call, serialize, deserialize) {
|
||||||
*/
|
*/
|
||||||
function terminateCall(err) {
|
function terminateCall(err) {
|
||||||
// Drain readable data
|
// Drain readable data
|
||||||
this.on('data', function() {});
|
|
||||||
setStatus(err);
|
setStatus(err);
|
||||||
this.end();
|
stream.end();
|
||||||
}
|
}
|
||||||
this.on('error', terminateCall);
|
stream.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start reading from the gRPC data source. This is an implementation of a
|
* Initialize a readable stream. This is used for both the readable and duplex
|
||||||
* method required for implementing stream.Readable
|
* stream constructors.
|
||||||
* @param {number} size Ignored
|
* @param {Readable} stream The stream to initialize
|
||||||
|
* @param {function(Buffer):*=} deserialize Deserialization function for
|
||||||
|
* incoming data.
|
||||||
*/
|
*/
|
||||||
GrpcServerStream.prototype._read = function(size) {
|
function setUpReadable(stream, deserialize) {
|
||||||
this.startReading();
|
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
|
* 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
|
* @param {function(Error=)} callback Callback to indicate that the write is
|
||||||
* complete
|
* complete
|
||||||
*/
|
*/
|
||||||
GrpcServerStream.prototype._write = function(chunk, encoding, callback) {
|
function _write(chunk, encoding, callback) {
|
||||||
var self = this;
|
/* jshint validthis: true */
|
||||||
self._call.startWrite(self.serialize(chunk), function(event) {
|
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();
|
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 handlers = this.handlers;
|
||||||
var server = new grpc.Server(options);
|
var server = new grpc.Server(options);
|
||||||
this._server = server;
|
this._server = server;
|
||||||
var started = false;
|
|
||||||
/**
|
/**
|
||||||
* Start the server and begin handling requests
|
* Start the server and begin handling requests
|
||||||
* @this Server
|
* @this Server
|
||||||
*/
|
*/
|
||||||
this.start = function() {
|
this.listen = function() {
|
||||||
console.log('Server starting');
|
console.log('Server starting');
|
||||||
_.each(handlers, function(handler, handler_name) {
|
_.each(handlers, function(handler, handler_name) {
|
||||||
console.log('Serving', handler_name);
|
console.log('Serving', handler_name);
|
||||||
|
|
@ -233,48 +447,38 @@ function Server(getMetadata, options) {
|
||||||
* wait for the next request
|
* wait for the next request
|
||||||
* @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW
|
* @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW
|
||||||
*/
|
*/
|
||||||
function handleNewCall(event) {
|
function handleNewCall(err, event) {
|
||||||
var call = event.call;
|
if (err) {
|
||||||
var data = event.data;
|
return;
|
||||||
if (data === null) {
|
}
|
||||||
|
var details = event['new call'];
|
||||||
|
var call = details.call;
|
||||||
|
var method = details.method;
|
||||||
|
var metadata = details.metadata;
|
||||||
|
if (method === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
server.requestCall(handleNewCall);
|
server.requestCall(handleNewCall);
|
||||||
var handler = undefined;
|
var handler;
|
||||||
var deadline = data.absolute_deadline;
|
if (handlers.hasOwnProperty(method)) {
|
||||||
var cancelled = false;
|
handler = handlers[method];
|
||||||
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];
|
|
||||||
} else {
|
} else {
|
||||||
call.serverEndInitialMetadata(0);
|
var batch = {};
|
||||||
call.startWriteStatus(
|
batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||||
grpc.status.UNIMPLEMENTED,
|
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||||
"This method is not available on this server.",
|
code: grpc.status.UNIMPLEMENTED,
|
||||||
function() {});
|
details: 'This method is not available on this server.',
|
||||||
|
metadata: {}
|
||||||
|
};
|
||||||
|
batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
|
||||||
|
call.startBatch(batch, function() {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var response_metadata = {};
|
||||||
if (getMetadata) {
|
if (getMetadata) {
|
||||||
call.addMetadata(getMetadata(data.method, data.metadata));
|
response_metadata = getMetadata(method, 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);
|
|
||||||
}
|
}
|
||||||
|
streamHandlers[handler.type](call, handler, response_metadata);
|
||||||
}
|
}
|
||||||
server.requestCall(handleNewCall);
|
server.requestCall(handleNewCall);
|
||||||
};
|
};
|
||||||
|
|
@ -294,36 +498,144 @@ function Server(getMetadata, options) {
|
||||||
* returns a stream of response values
|
* returns a stream of response values
|
||||||
* @param {function(*):Buffer} serialize Serialization function for responses
|
* @param {function(*):Buffer} serialize Serialization function for responses
|
||||||
* @param {function(Buffer):*} deserialize Deserialization function for requests
|
* @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
|
* @return {boolean} True if the handler was set. False if a handler was already
|
||||||
* set for that name.
|
* 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)) {
|
if (this.handlers.hasOwnProperty(name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.handlers[name] = {
|
this.handlers[name] = {
|
||||||
func: handler,
|
func: handler,
|
||||||
serialize: serialize,
|
serialize: serialize,
|
||||||
deserialize: deserialize
|
deserialize: deserialize,
|
||||||
|
type: type
|
||||||
};
|
};
|
||||||
return true;
|
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
|
* @param {string} port The port that the server should bind on, in the format
|
||||||
* "address:port"
|
* "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) {
|
Server.prototype.bind = function(port, creds) {
|
||||||
if (secure) {
|
if (creds) {
|
||||||
return this._server.addSecureHttp2Port(port);
|
return this._server.addSecureHttp2Port(port, creds);
|
||||||
} else {
|
} else {
|
||||||
return this._server.addHttp2Port(port);
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
|
@ -98,105 +100,81 @@ describe('call', function() {
|
||||||
}, TypeError);
|
}, TypeError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('addMetadata', function() {
|
describe('startBatch', function() {
|
||||||
it('should succeed with a map from strings to string arrays', function() {
|
it('should fail without an object and a function', function() {
|
||||||
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
assert.doesNotThrow(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata({'key': ['value']});
|
call.startBatch();
|
||||||
});
|
});
|
||||||
assert.doesNotThrow(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata({'key1': ['value1'], 'key2': ['value2']});
|
call.startBatch({});
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
call.startBatch(null, function(){});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should succeed with a map from strings to buffer arrays', function() {
|
it('should succeed with an empty object', function(done) {
|
||||||
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
assert.doesNotThrow(function() {
|
assert.doesNotThrow(function() {
|
||||||
call.addMetadata({'key': [new Buffer('value')]});
|
call.startBatch({}, function(err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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() {
|
assert.doesNotThrow(function() {
|
||||||
call.addMetadata({'key1': [new Buffer('value1')],
|
var batch = {};
|
||||||
'key2': [new Buffer('value2')]});
|
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() {
|
||||||
|
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() {
|
it('should fail with other parameter types', function() {
|
||||||
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
var call = new grpc.Call(channel, 'method', getDeadline(1));
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata();
|
var batch = {};
|
||||||
|
batch[grpc.opType.SEND_INITIAL_METADATA] = undefined;
|
||||||
|
call.startBatch(batch, function(){});
|
||||||
});
|
});
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata(null);
|
var batch = {};
|
||||||
|
batch[grpc.opType.SEND_INITIAL_METADATA] = null;
|
||||||
|
call.startBatch(batch, function(){});
|
||||||
}, TypeError);
|
}, TypeError);
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata('value');
|
var batch = {};
|
||||||
|
batch[grpc.opType.SEND_INITIAL_METADATA] = 'value';
|
||||||
|
call.startBatch(batch, function(){});
|
||||||
}, TypeError);
|
}, TypeError);
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
call.addMetadata(5);
|
var batch = {};
|
||||||
|
batch[grpc.opType.SEND_INITIAL_METADATA] = 5;
|
||||||
|
call.startBatch(batch, function(){});
|
||||||
}, TypeError);
|
}, 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() {
|
describe('cancel', function() {
|
||||||
it('should succeed', function() {
|
it('should succeed', function() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
|
@ -76,31 +78,6 @@ var callErrorNames = [
|
||||||
'INVALID_FLAGS'
|
'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() {
|
describe('constants', function() {
|
||||||
it('should have all of the status constants', function() {
|
it('should have all of the status constants', function() {
|
||||||
for (var i = 0; i < statusNames.length; i++) {
|
for (var i = 0; i < statusNames.length; i++) {
|
||||||
|
|
@ -114,16 +91,4 @@ describe('constants', function() {
|
||||||
'call error missing: ' + callErrorNames[i]);
|
'call error missing: ' + callErrorNames[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should have all of the op errors', function() {
|
|
||||||
for (var i = 0; i < opErrorNames.length; i++) {
|
|
||||||
assert(grpc.opError.hasOwnProperty(opErrorNames[i]),
|
|
||||||
'op error missing: ' + opErrorNames[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
it('should have all of the completion types', function() {
|
|
||||||
for (var i = 0; i < completionTypeNames.length; i++) {
|
|
||||||
assert(grpc.completionType.hasOwnProperty(completionTypeNames[i]),
|
|
||||||
'completion type missing: ' + completionTypeNames[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
|
|
||||||
|
|
@ -74,40 +76,49 @@ describe('end-to-end', function() {
|
||||||
var status_text = 'xyz';
|
var status_text = 'xyz';
|
||||||
var call = new grpc.Call(channel,
|
var call = new grpc.Call(channel,
|
||||||
'dummy_method',
|
'dummy_method',
|
||||||
deadline);
|
Infinity);
|
||||||
call.invoke(function(event) {
|
var client_batch = {};
|
||||||
assert.strictEqual(event.type,
|
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||||
grpc.completionType.CLIENT_METADATA_READ);
|
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||||
},function(event) {
|
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
|
||||||
var status = event.data;
|
call.startBatch(client_batch, function(err, response) {
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
assert.ifError(err);
|
||||||
assert.strictEqual(status.details, status_text);
|
assert.deepEqual(response, {
|
||||||
|
'send metadata': true,
|
||||||
|
'client close': true,
|
||||||
|
'metadata': {},
|
||||||
|
'status': {
|
||||||
|
'code': grpc.status.OK,
|
||||||
|
'details': status_text,
|
||||||
|
'metadata': {}
|
||||||
|
}
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
}, 0);
|
|
||||||
|
|
||||||
server.requestCall(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
|
|
||||||
var server_call = event.call;
|
|
||||||
assert.notEqual(server_call, null);
|
|
||||||
server_call.serverAccept(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
|
||||||
}, 0);
|
|
||||||
server_call.serverEndInitialMetadata(0);
|
|
||||||
server_call.startWriteStatus(
|
|
||||||
grpc.status.OK,
|
|
||||||
status_text,
|
|
||||||
function(event) {
|
|
||||||
assert.strictEqual(event.type,
|
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
call.writesDone(function(event) {
|
|
||||||
assert.strictEqual(event.type,
|
server.requestCall(function(err, call_details) {
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
var new_call = call_details['new call'];
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
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.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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should successfully send and receive metadata', function(complete) {
|
it('should successfully send and receive metadata', function(complete) {
|
||||||
|
|
@ -117,115 +128,179 @@ describe('end-to-end', function() {
|
||||||
var status_text = 'xyz';
|
var status_text = 'xyz';
|
||||||
var call = new grpc.Call(channel,
|
var call = new grpc.Call(channel,
|
||||||
'dummy_method',
|
'dummy_method',
|
||||||
deadline);
|
Infinity);
|
||||||
call.addMetadata({'client_key': ['client_value']});
|
var client_batch = {};
|
||||||
call.invoke(function(event) {
|
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {
|
||||||
assert.strictEqual(event.type,
|
'client_key': ['client_value']
|
||||||
grpc.completionType.CLIENT_METADATA_READ);
|
};
|
||||||
assert.strictEqual(event.data.server_key[0].toString(), 'server_value');
|
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||||
},function(event) {
|
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
|
||||||
var status = event.data;
|
call.startBatch(client_batch, function(err, response) {
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
assert.ifError(err);
|
||||||
assert.strictEqual(status.details, status_text);
|
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();
|
done();
|
||||||
}, 0);
|
|
||||||
|
|
||||||
server.requestCall(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
|
|
||||||
assert.strictEqual(event.data.metadata.client_key[0].toString(),
|
|
||||||
'client_value');
|
|
||||||
var server_call = event.call;
|
|
||||||
assert.notEqual(server_call, null);
|
|
||||||
server_call.serverAccept(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
|
||||||
}, 0);
|
|
||||||
server_call.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);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
call.writesDone(function(event) {
|
|
||||||
assert.strictEqual(event.type,
|
server.requestCall(function(err, call_details) {
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
var new_call = call_details['new call'];
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
assert.notEqual(new_call, null);
|
||||||
|
assert.strictEqual(new_call.metadata.client_key[0].toString(),
|
||||||
|
'client_value');
|
||||||
|
var server_call = new_call.call;
|
||||||
|
assert.notEqual(server_call, null);
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should send and receive data without error', function(complete) {
|
it('should send and receive data without error', function(complete) {
|
||||||
var req_text = 'client_request';
|
var req_text = 'client_request';
|
||||||
var reply_text = 'server_response';
|
var reply_text = 'server_response';
|
||||||
var done = multiDone(complete, 6);
|
var done = multiDone(complete, 2);
|
||||||
var deadline = new Date();
|
var deadline = new Date();
|
||||||
deadline.setSeconds(deadline.getSeconds() + 3);
|
deadline.setSeconds(deadline.getSeconds() + 3);
|
||||||
var status_text = 'success';
|
var status_text = 'success';
|
||||||
var call = new grpc.Call(channel,
|
var call = new grpc.Call(channel,
|
||||||
'dummy_method',
|
'dummy_method',
|
||||||
deadline);
|
Infinity);
|
||||||
call.invoke(function(event) {
|
var client_batch = {};
|
||||||
assert.strictEqual(event.type,
|
client_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||||
grpc.completionType.CLIENT_METADATA_READ);
|
client_batch[grpc.opType.SEND_MESSAGE] = new Buffer(req_text);
|
||||||
done();
|
client_batch[grpc.opType.SEND_CLOSE_FROM_CLIENT] = true;
|
||||||
},function(event) {
|
client_batch[grpc.opType.RECV_INITIAL_METADATA] = true;
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
client_batch[grpc.opType.RECV_MESSAGE] = true;
|
||||||
var status = event.data;
|
client_batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
call.startBatch(client_batch, function(err, response) {
|
||||||
assert.strictEqual(status.details, status_text);
|
assert.ifError(err);
|
||||||
done();
|
assert(response['send metadata']);
|
||||||
}, 0);
|
assert(response['client close']);
|
||||||
call.startWrite(
|
assert.deepEqual(response.metadata, {});
|
||||||
new Buffer(req_text),
|
assert(response['send message']);
|
||||||
function(event) {
|
assert.strictEqual(response.read.toString(), reply_text);
|
||||||
assert.strictEqual(event.type,
|
assert.deepEqual(response.status, {'code': grpc.status.OK,
|
||||||
grpc.completionType.WRITE_ACCEPTED);
|
'details': status_text,
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
'metadata': {}});
|
||||||
call.writesDone(function(event) {
|
|
||||||
assert.strictEqual(event.type,
|
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}, 0);
|
|
||||||
call.startRead(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.READ);
|
|
||||||
assert.strictEqual(event.data.toString(), reply_text);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
server.requestCall(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.SERVER_RPC_NEW);
|
server.requestCall(function(err, call_details) {
|
||||||
var server_call = event.call;
|
var new_call = call_details['new call'];
|
||||||
|
assert.notEqual(new_call, null);
|
||||||
|
var server_call = new_call.call;
|
||||||
assert.notEqual(server_call, null);
|
assert.notEqual(server_call, null);
|
||||||
server_call.serverAccept(function(event) {
|
var server_batch = {};
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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();
|
done();
|
||||||
});
|
});
|
||||||
server_call.serverEndInitialMetadata(0);
|
});
|
||||||
server_call.startRead(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.READ);
|
server.requestCall(function(err, call_details) {
|
||||||
assert.strictEqual(event.data.toString(), req_text);
|
var new_call = call_details['new call'];
|
||||||
server_call.startWrite(
|
assert.notEqual(new_call, null);
|
||||||
new Buffer(reply_text),
|
var server_call = new_call.call;
|
||||||
function(event) {
|
assert.notEqual(server_call, null);
|
||||||
assert.strictEqual(event.type,
|
var server_batch = {};
|
||||||
grpc.completionType.WRITE_ACCEPTED);
|
server_batch[grpc.opType.SEND_INITIAL_METADATA] = {};
|
||||||
assert.strictEqual(event.data,
|
server_batch[grpc.opType.RECV_MESSAGE] = true;
|
||||||
grpc.opError.OK);
|
server_call.startBatch(server_batch, function(err, response) {
|
||||||
server_call.startWriteStatus(
|
assert.ifError(err);
|
||||||
grpc.status.OK,
|
assert(response['send metadata']);
|
||||||
status_text,
|
assert.strictEqual(response.read.toString(), requests[0]);
|
||||||
function(event) {
|
var end_batch = {};
|
||||||
assert.strictEqual(event.type,
|
end_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true;
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = {
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
'metadata': {},
|
||||||
done();
|
'code': grpc.status.OK,
|
||||||
});
|
'details': status_text
|
||||||
}, 0);
|
};
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* 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_server = require('../interop/interop_server.js');
|
||||||
var interop_client = require('../interop/interop_client.js');
|
var interop_client = require('../interop/interop_client.js');
|
||||||
|
|
||||||
|
|
@ -38,7 +40,7 @@ var server;
|
||||||
|
|
||||||
var port;
|
var port;
|
||||||
|
|
||||||
var name_override = 'foo.test.google.com';
|
var name_override = 'foo.test.google.fr';
|
||||||
|
|
||||||
describe('Interop tests', function() {
|
describe('Interop tests', function() {
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
|
|
@ -53,30 +55,35 @@ describe('Interop tests', function() {
|
||||||
});
|
});
|
||||||
// This depends on not using a binary stream
|
// This depends on not using a binary stream
|
||||||
it('should pass empty_unary', function(done) {
|
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
|
// This fails due to an unknown bug
|
||||||
it.skip('should pass large_unary', function(done) {
|
it('should pass large_unary', function(done) {
|
||||||
interop_client.runTest(port, name_override, 'large_unary', true, done);
|
interop_client.runTest(port, name_override, 'large_unary', true, true,
|
||||||
|
done);
|
||||||
});
|
});
|
||||||
it('should pass client_streaming', function(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) {
|
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) {
|
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) {
|
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) {
|
it('should pass cancel_after_begin', function(done) {
|
||||||
interop_client.runTest(port, name_override, 'cancel_after_begin', true,
|
interop_client.runTest(port, name_override, 'cancel_after_begin', true,
|
||||||
done);
|
true, done);
|
||||||
});
|
});
|
||||||
it('should pass cancel_after_first_response', function(done) {
|
it('should pass cancel_after_first_response', function(done) {
|
||||||
interop_client.runTest(port, name_override, 'cancel_after_first_response',
|
interop_client.runTest(port, name_override, 'cancel_after_first_response',
|
||||||
true, done);
|
true, true, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
var grpc = require('..');
|
var grpc = require('..');
|
||||||
|
|
@ -59,13 +61,10 @@ describe('Math client', function() {
|
||||||
});
|
});
|
||||||
it('should handle a single request', function(done) {
|
it('should handle a single request', function(done) {
|
||||||
var arg = {dividend: 7, divisor: 4};
|
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.ifError(err);
|
||||||
assert.equal(value.quotient, 1);
|
assert.equal(value.quotient, 1);
|
||||||
assert.equal(value.remainder, 3);
|
assert.equal(value.remainder, 3);
|
||||||
});
|
|
||||||
call.on('status', function checkStatus(status) {
|
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,92 +31,64 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var grpc = require('bindings')('grpc.node');
|
var grpc = require('bindings')('grpc.node');
|
||||||
var Server = require('../src/server');
|
|
||||||
|
|
||||||
/**
|
describe('server', function() {
|
||||||
* This is used for testing functions with multiple asynchronous calls that
|
describe('constructor', function() {
|
||||||
* can happen in different orders. This should be passed the number of async
|
it('should work with no arguments', function() {
|
||||||
* function invocations that can occur last, and each of those should call this
|
assert.doesNotThrow(function() {
|
||||||
* function's return value
|
new grpc.Server();
|
||||||
* @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
|
it('should work with an empty list argument', function() {
|
||||||
* test passes.
|
assert.doesNotThrow(function() {
|
||||||
* @return {function()} The function that should be called at the end of each
|
new grpc.Server([]);
|
||||||
* 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() {
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
after(function() {
|
describe('addHttp2Port', function() {
|
||||||
server.shutdown();
|
var server;
|
||||||
|
before(function() {
|
||||||
|
server = new grpc.Server();
|
||||||
|
});
|
||||||
|
it('should bind to an unused port', function() {
|
||||||
|
var port;
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
port = server.addHttp2Port('0.0.0.0:0');
|
||||||
|
});
|
||||||
|
assert(port > 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should echo inputs as responses', function(done) {
|
describe('addSecureHttp2Port', function() {
|
||||||
done = multiDone(done, 4);
|
var server;
|
||||||
|
before(function() {
|
||||||
var req_text = 'echo test string';
|
server = new grpc.Server();
|
||||||
var status_text = 'OK';
|
});
|
||||||
|
it('should bind to an unused port with fake credentials', function() {
|
||||||
var deadline = new Date();
|
var port;
|
||||||
deadline.setSeconds(deadline.getSeconds() + 3);
|
var creds = grpc.ServerCredentials.createFake();
|
||||||
var call = new grpc.Call(channel,
|
assert.doesNotThrow(function() {
|
||||||
'echo',
|
port = server.addSecureHttp2Port('0.0.0.0:0', creds);
|
||||||
deadline);
|
});
|
||||||
call.invoke(function(event) {
|
assert(port > 0);
|
||||||
assert.strictEqual(event.type,
|
});
|
||||||
grpc.completionType.CLIENT_METADATA_READ);
|
});
|
||||||
done();
|
describe('listen', function() {
|
||||||
},function(event) {
|
var server;
|
||||||
assert.strictEqual(event.type, grpc.completionType.FINISHED);
|
before(function() {
|
||||||
var status = event.data;
|
server = new grpc.Server();
|
||||||
assert.strictEqual(status.code, grpc.status.OK);
|
server.addHttp2Port('0.0.0.0:0');
|
||||||
assert.strictEqual(status.details, status_text);
|
});
|
||||||
done();
|
after(function() {
|
||||||
}, 0);
|
server.shutdown();
|
||||||
call.startWrite(
|
});
|
||||||
new Buffer(req_text),
|
it('should listen without error', function() {
|
||||||
function(event) {
|
assert.doesNotThrow(function() {
|
||||||
assert.strictEqual(event.type,
|
server.start();
|
||||||
grpc.completionType.WRITE_ACCEPTED);
|
});
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
|
||||||
call.writesDone(function(event) {
|
|
||||||
assert.strictEqual(event.type,
|
|
||||||
grpc.completionType.FINISH_ACCEPTED);
|
|
||||||
assert.strictEqual(event.data, grpc.opError.OK);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
}, 0);
|
|
||||||
call.startRead(function(event) {
|
|
||||||
assert.strictEqual(event.type, grpc.completionType.READ);
|
|
||||||
assert.strictEqual(event.data.toString(), req_text);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* Copyright 2014, Google Inc.
|
* Copyright 2015, Google Inc.
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
var surface_server = require('../src/surface_server.js');
|
var surface_client = require('../src/client.js');
|
||||||
|
|
||||||
var surface_client = require('../src/surface_client.js');
|
|
||||||
|
|
||||||
var ProtoBuf = require('protobufjs');
|
var ProtoBuf = require('protobufjs');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue