mirror of https://github.com/grpc/grpc-node.git
Merge branch 'master' into native_cares_activation
This commit is contained in:
commit
454b4183b5
|
|
@ -20,3 +20,6 @@ package-lock.json
|
||||||
|
|
||||||
# Test generated files
|
# Test generated files
|
||||||
coverage
|
coverage
|
||||||
|
|
||||||
|
# Node's bash completion file
|
||||||
|
.node_bash_completion
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Load Balancing | :heavy_check_mark: | :x:
|
||||||
Other Properties | `grpc` | `@grpc/grpc-js`
|
Other Properties | `grpc` | `@grpc/grpc-js`
|
||||||
-----------------|--------|----------------
|
-----------------|--------|----------------
|
||||||
Pure JavaScript Code | :x: | :heavy_check_mark:
|
Pure JavaScript Code | :x: | :heavy_check_mark:
|
||||||
Supported Node Versions | >= 4 | ^8.11.2 or >=9.4
|
Supported Node Versions | >= 4 | ^8.13.0 or >=10.10.0
|
||||||
Supported Electron Versions | All | >= 3
|
Supported Electron Versions | All | >= 3
|
||||||
Supported Platforms | Linux, Windows, MacOS | All
|
Supported Platforms | Linux, Windows, MacOS | All
|
||||||
Supported Architectures | x86, x86-64, ARM7+ | All
|
Supported Architectures | x86, x86-64, ARM7+ | All
|
||||||
|
|
|
||||||
111
gulpfile.ts
111
gulpfile.ts
|
|
@ -15,96 +15,55 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _gulp from 'gulp';
|
import * as gulp from 'gulp';
|
||||||
import * as help from 'gulp-help';
|
import * as healthCheck from './packages/grpc-health-check/gulpfile';
|
||||||
|
import * as jsCore from './packages/grpc-js/gulpfile';
|
||||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
import * as nativeCore from './packages/grpc-native-core/gulpfile';
|
||||||
const gulp = help(_gulp);
|
import * as protobuf from './packages/proto-loader/gulpfile';
|
||||||
|
import * as internalTest from './test/gulpfile';
|
||||||
const runSequence = require('run-sequence');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Require a module at the given path with a patched gulp object that prepends
|
|
||||||
* the given prefix to each task name.
|
|
||||||
* @param path The path to require.
|
|
||||||
* @param prefix The string to use as a prefix. This will be prepended to a task
|
|
||||||
* name with a '.' separator.
|
|
||||||
*/
|
|
||||||
function loadGulpTasksWithPrefix(path: string, prefix: string) {
|
|
||||||
const gulpTask = gulp.task;
|
|
||||||
gulp.task = ((taskName: string, ...args: any[]) => {
|
|
||||||
// Don't create a task for ${prefix}.help
|
|
||||||
if (taskName === 'help') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The only array passed to gulp.task must be a list of dependent tasks.
|
|
||||||
const newArgs = args.map(arg => Array.isArray(arg) ?
|
|
||||||
arg.map(dep => `${prefix}.${dep}`) : arg);
|
|
||||||
gulpTask(`${prefix}.${taskName}`, ...newArgs);
|
|
||||||
});
|
|
||||||
const result = require(path);
|
|
||||||
gulp.task = gulpTask;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[
|
|
||||||
['./packages/grpc-health-check/gulpfile', 'health-check'],
|
|
||||||
['./packages/grpc-js/gulpfile', 'js.core'],
|
|
||||||
['./packages/grpc-native-core/gulpfile', 'native.core'],
|
|
||||||
['./packages/proto-loader/gulpfile', 'protobuf'],
|
|
||||||
['./test/gulpfile', 'internal.test'],
|
|
||||||
].forEach((args) => loadGulpTasksWithPrefix(args[0], args[1]));
|
|
||||||
|
|
||||||
const root = __dirname;
|
const root = __dirname;
|
||||||
|
|
||||||
gulp.task('install.all', 'Install dependencies for all subdirectory packages',
|
const installAll = gulp.parallel(jsCore.install, nativeCore.install, healthCheck.install, protobuf.install, internalTest.install);
|
||||||
['js.core.install', 'native.core.install', 'health-check.install', 'protobuf.install', 'internal.test.install']);
|
|
||||||
|
|
||||||
gulp.task('install.all.windows', 'Install dependencies for all subdirectory packages for MS Windows',
|
const installAllWindows = gulp.parallel(jsCore.install, nativeCore.installWindows, healthCheck.install, protobuf.install, internalTest.install);
|
||||||
['js.core.install', 'native.core.install.windows', 'health-check.install', 'protobuf.install', 'internal.test.install']);
|
|
||||||
|
|
||||||
gulp.task('lint', 'Emit linting errors in source and test files',
|
const lint = gulp.parallel(jsCore.lint, nativeCore.lint);
|
||||||
['js.core.lint', 'native.core.lint']);
|
|
||||||
|
|
||||||
gulp.task('build', 'Build packages', ['js.core.compile', 'native.core.build', 'protobuf.compile']);
|
const build = gulp.parallel(jsCore.compile, nativeCore.build, protobuf.compile);
|
||||||
|
|
||||||
gulp.task('link.surface', 'Link to surface packages',
|
const link = gulp.series(healthCheck.linkAdd);
|
||||||
['health-check.link.add']);
|
|
||||||
|
|
||||||
gulp.task('link', 'Link together packages', (callback) => {
|
const setup = gulp.series(installAll, link);
|
||||||
/**
|
|
||||||
* We use workarounds for linking in some modules. See npm/npm#18835
|
|
||||||
*/
|
|
||||||
runSequence('link.surface', callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('setup', 'One-time setup for a clean repository', (callback) => {
|
const setupWindows = gulp.series(installAllWindows, link);
|
||||||
runSequence('install.all', 'link', callback);
|
|
||||||
});
|
|
||||||
gulp.task('setup.windows', 'One-time setup for a clean repository for MS Windows', (callback) => {
|
|
||||||
runSequence('install.all.windows', 'link', callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('clean', 'Delete generated files', ['js.core.clean', 'native.core.clean', 'protobuf.clean']);
|
const clean = gulp.parallel(jsCore.clean, nativeCore.clean, protobuf.clean);
|
||||||
|
|
||||||
gulp.task('clean.all', 'Delete all files created by tasks',
|
const cleanAll = gulp.parallel(jsCore.cleanAll, nativeCore.cleanAll, healthCheck.cleanAll, internalTest.cleanAll, protobuf.cleanAll);
|
||||||
['js.core.clean.all', 'native.core.clean.all', 'health-check.clean.all',
|
|
||||||
'internal.test.clean.all', 'protobuf.clean.all']);
|
|
||||||
|
|
||||||
gulp.task('native.test.only', 'Run tests of native code without rebuilding anything',
|
const nativeTestOnly = gulp.parallel(nativeCore.test, healthCheck.test);
|
||||||
['native.core.test', 'health-check.test']);
|
|
||||||
|
|
||||||
gulp.task('native.test', 'Run tests of native code', (callback) => {
|
const nativeTest = gulp.series(build, nativeTestOnly);
|
||||||
runSequence('build', 'native.test.only', callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test.only', 'Run tests without rebuilding anything',
|
const testOnly = gulp.parallel(jsCore.test, nativeTestOnly, protobuf.test);
|
||||||
['js.core.test', 'native.test.only', 'protobuf.test']);
|
|
||||||
|
|
||||||
gulp.task('test', 'Run all tests', (callback) => {
|
const test = gulp.series(build, testOnly, internalTest.test);
|
||||||
runSequence('build', 'test.only', 'internal.test.test', callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('doc.gen', 'Generate documentation', ['native.core.doc.gen']);
|
const docGen = gulp.series(nativeCore.docGen);
|
||||||
|
|
||||||
gulp.task('default', ['help']);
|
export {
|
||||||
|
installAll,
|
||||||
|
installAllWindows,
|
||||||
|
lint,
|
||||||
|
build,
|
||||||
|
link,
|
||||||
|
setup,
|
||||||
|
setupWindows,
|
||||||
|
clean,
|
||||||
|
cleanAll,
|
||||||
|
nativeTestOnly,
|
||||||
|
nativeTest,
|
||||||
|
test,
|
||||||
|
docGen
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/execa": "^0.8.0",
|
"@types/execa": "^0.8.0",
|
||||||
"@types/gulp": "^4.0.5",
|
"@types/gulp": "^4.0.5",
|
||||||
"@types/gulp-help": "0.0.34",
|
|
||||||
"@types/gulp-mocha": "0.0.31",
|
"@types/gulp-mocha": "0.0.31",
|
||||||
"@types/ncp": "^2.0.1",
|
"@types/ncp": "^2.0.1",
|
||||||
"@types/node": "^8.0.32",
|
"@types/node": "^8.0.32",
|
||||||
|
|
@ -20,8 +19,7 @@
|
||||||
"coveralls": "^3.0.1",
|
"coveralls": "^3.0.1",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"execa": "^0.8.0",
|
"execa": "^0.8.0",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^4.0.1",
|
||||||
"gulp-help": "^1.6.1",
|
|
||||||
"gulp-jsdoc3": "^1.0.1",
|
"gulp-jsdoc3": "^1.0.1",
|
||||||
"gulp-jshint": "^2.0.4",
|
"gulp-jshint": "^2.0.4",
|
||||||
"gulp-mocha": "^4.3.1",
|
"gulp-mocha": "^4.3.1",
|
||||||
|
|
@ -42,7 +40,7 @@
|
||||||
"semver": "^5.5.0",
|
"semver": "^5.5.0",
|
||||||
"symlink": "^2.1.0",
|
"symlink": "^2.1.0",
|
||||||
"through2": "^2.0.3",
|
"through2": "^2.0.3",
|
||||||
"ts-node": "^3.3.0",
|
"ts-node": "^8.1.0",
|
||||||
"tslint": "^5.5.0",
|
"tslint": "^5.5.0",
|
||||||
"typescript": "~3.3.3333",
|
"typescript": "~3.3.3333",
|
||||||
"xml2js": "^0.4.19"
|
"xml2js": "^0.4.19"
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2017 gRPC authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const _gulp = require('gulp');
|
|
||||||
const help = require('gulp-help');
|
|
||||||
const mocha = require('gulp-mocha');
|
|
||||||
const execa = require('execa');
|
|
||||||
const path = require('path');
|
|
||||||
const del = require('del');
|
|
||||||
const linkSync = require('../../util').linkSync;
|
|
||||||
|
|
||||||
const gulp = help(_gulp);
|
|
||||||
|
|
||||||
const healthCheckDir = __dirname;
|
|
||||||
const baseDir = path.resolve(healthCheckDir, '..', '..');
|
|
||||||
const testDir = path.resolve(healthCheckDir, 'test');
|
|
||||||
|
|
||||||
gulp.task('clean.links', 'Delete npm links', () => {
|
|
||||||
return del(path.resolve(healthCheckDir, 'node_modules/grpc'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('clean.all', 'Delete all code created by tasks',
|
|
||||||
['clean.links']);
|
|
||||||
|
|
||||||
gulp.task('install', 'Install health check dependencies', ['clean.links'], () => {
|
|
||||||
return execa('npm', ['install', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('link.add', 'Link local copy of grpc', () => {
|
|
||||||
linkSync(healthCheckDir, './node_modules/grpc', '../grpc-native-core');
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('test', 'Run health check tests',
|
|
||||||
() => {
|
|
||||||
return gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'}));
|
|
||||||
});
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as gulp from 'gulp';
|
||||||
|
import * as mocha from 'gulp-mocha';
|
||||||
|
import * as execa from 'execa';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as del from 'del';
|
||||||
|
import {linkSync} from '../../util';
|
||||||
|
|
||||||
|
const healthCheckDir = __dirname;
|
||||||
|
const baseDir = path.resolve(healthCheckDir, '..', '..');
|
||||||
|
const testDir = path.resolve(healthCheckDir, 'test');
|
||||||
|
|
||||||
|
const cleanLinks = () => del(path.resolve(healthCheckDir, 'node_modules/grpc'));
|
||||||
|
|
||||||
|
const cleanAll = gulp.parallel(cleanLinks);
|
||||||
|
|
||||||
|
const runInstall = () => execa('npm', ['install', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'});
|
||||||
|
|
||||||
|
const install = gulp.series(cleanLinks, runInstall);
|
||||||
|
|
||||||
|
const linkAdd = (callback) => {
|
||||||
|
linkSync(healthCheckDir, './node_modules/grpc', '../grpc-native-core');
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
const test = () => gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'}));
|
||||||
|
|
||||||
|
export {
|
||||||
|
cleanLinks,
|
||||||
|
cleanAll,
|
||||||
|
install,
|
||||||
|
linkAdd,
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _gulp from 'gulp';
|
import * as gulp from 'gulp';
|
||||||
import * as help from 'gulp-help';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as mocha from 'gulp-mocha';
|
import * as mocha from 'gulp-mocha';
|
||||||
|
|
@ -26,9 +25,6 @@ import * as pify from 'pify';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import { ncp } from 'ncp';
|
import { ncp } from 'ncp';
|
||||||
|
|
||||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
|
||||||
const gulp = help(_gulp);
|
|
||||||
|
|
||||||
const ncpP = pify(ncp);
|
const ncpP = pify(ncp);
|
||||||
|
|
||||||
Error.stackTraceLimit = Infinity;
|
Error.stackTraceLimit = Infinity;
|
||||||
|
|
@ -44,35 +40,29 @@ const execNpmVerb = (verb: string, ...args: string[]) =>
|
||||||
execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'});
|
execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'});
|
||||||
const execNpmCommand = execNpmVerb.bind(null, 'run');
|
const execNpmCommand = execNpmVerb.bind(null, 'run');
|
||||||
|
|
||||||
gulp.task('install', 'Install native core dependencies', () =>
|
const install = () => execNpmVerb('install', '--unsafe-perm');
|
||||||
execNpmVerb('install', '--unsafe-perm'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs tslint on files in src/, with linting rules defined in tslint.json.
|
* Runs tslint on files in src/, with linting rules defined in tslint.json.
|
||||||
*/
|
*/
|
||||||
gulp.task('lint', 'Emits linting errors found in src/ and test/.', () =>
|
const lint = () => execNpmCommand('check');
|
||||||
execNpmCommand('check'));
|
|
||||||
|
|
||||||
gulp.task('clean', 'Deletes transpiled code.', ['install'],
|
const cleanFiles = () => execNpmCommand('clean');
|
||||||
() => execNpmCommand('clean'));
|
|
||||||
|
|
||||||
gulp.task('clean.all', 'Deletes all files added by targets', ['clean']);
|
const clean = gulp.series(install, cleanFiles);
|
||||||
|
|
||||||
|
const cleanAll = gulp.parallel(clean);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transpiles TypeScript files in src/ to JavaScript according to the settings
|
* Transpiles TypeScript files in src/ to JavaScript according to the settings
|
||||||
* found in tsconfig.json.
|
* found in tsconfig.json.
|
||||||
*/
|
*/
|
||||||
gulp.task('compile', 'Transpiles src/.', () => execNpmCommand('compile'));
|
const compile = () => execNpmCommand('compile');
|
||||||
|
|
||||||
gulp.task('copy-test-fixtures', 'Copy test fixtures.', () => {
|
const copyTestFixtures = () => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`);
|
||||||
return ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
const runTests = () => {
|
||||||
* Transpiles src/ and test/, and then runs all tests.
|
if (semver.satisfies(process.version, '^8.13.0 || >=10.10.0')) {
|
||||||
*/
|
|
||||||
gulp.task('test', 'Runs all tests.', ['lint', 'copy-test-fixtures'], () => {
|
|
||||||
if (semver.satisfies(process.version, '^8.11.2 || >=9.4')) {
|
|
||||||
return gulp.src(`${outDir}/test/**/*.js`)
|
return gulp.src(`${outDir}/test/**/*.js`)
|
||||||
.pipe(mocha({reporter: 'mocha-jenkins-reporter',
|
.pipe(mocha({reporter: 'mocha-jenkins-reporter',
|
||||||
require: ['ts-node/register']}));
|
require: ['ts-node/register']}));
|
||||||
|
|
@ -80,4 +70,15 @@ gulp.task('test', 'Runs all tests.', ['lint', 'copy-test-fixtures'], () => {
|
||||||
console.log(`Skipping grpc-js tests for Node ${process.version}`);
|
console.log(`Skipping grpc-js tests for Node ${process.version}`);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const test = gulp.series(install, copyTestFixtures, runTests);
|
||||||
|
|
||||||
|
export {
|
||||||
|
install,
|
||||||
|
lint,
|
||||||
|
clean,
|
||||||
|
cleanAll,
|
||||||
|
compile,
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@grpc/grpc-js",
|
"name": "@grpc/grpc-js",
|
||||||
"version": "0.3.6",
|
"version": "0.4.0",
|
||||||
"description": "gRPC Library for Node - pure JS implementation",
|
"description": "gRPC Library for Node - pure JS implementation",
|
||||||
"homepage": "https://grpc.io/",
|
"homepage": "https://grpc.io/",
|
||||||
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
|
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
|
||||||
"main": "build/src/index.js",
|
"main": "build/src/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.11.2 || >=9.4"
|
"node": "^8.13.0 || >=10.10.0"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {Filter} from './filter';
|
||||||
import {FilterStackFactory} from './filter-stack';
|
import {FilterStackFactory} from './filter-stack';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
import {ObjectDuplex, WriteCallback} from './object-stream';
|
import {ObjectDuplex, WriteCallback} from './object-stream';
|
||||||
|
import {StreamDecoder} from './stream-decoder';
|
||||||
|
|
||||||
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE, NGHTTP2_CANCEL} =
|
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE, NGHTTP2_CANCEL} =
|
||||||
http2.constants;
|
http2.constants;
|
||||||
|
|
@ -77,12 +78,6 @@ export type Call = {
|
||||||
EmitterAugmentation1<'status', StatusObject>&
|
EmitterAugmentation1<'status', StatusObject>&
|
||||||
ObjectDuplex<WriteObject, Buffer>;
|
ObjectDuplex<WriteObject, Buffer>;
|
||||||
|
|
||||||
enum ReadState {
|
|
||||||
NO_DATA,
|
|
||||||
READING_SIZE,
|
|
||||||
READING_MESSAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Http2CallStream extends Duplex implements Call {
|
export class Http2CallStream extends Duplex implements Call {
|
||||||
credentials: CallCredentials = CallCredentials.createEmpty();
|
credentials: CallCredentials = CallCredentials.createEmpty();
|
||||||
filterStack: Filter;
|
filterStack: Filter;
|
||||||
|
|
@ -92,13 +87,7 @@ export class Http2CallStream extends Duplex implements Call {
|
||||||
private pendingWriteCallback: WriteCallback|null = null;
|
private pendingWriteCallback: WriteCallback|null = null;
|
||||||
private pendingFinalCallback: Function|null = null;
|
private pendingFinalCallback: Function|null = null;
|
||||||
|
|
||||||
private readState: ReadState = ReadState.NO_DATA;
|
private decoder = new StreamDecoder();
|
||||||
private readCompressFlag: Buffer = Buffer.alloc(1);
|
|
||||||
private readPartialSize: Buffer = Buffer.alloc(4);
|
|
||||||
private readSizeRemaining = 4;
|
|
||||||
private readMessageSize = 0;
|
|
||||||
private readPartialMessage: Buffer[] = [];
|
|
||||||
private readMessageRemaining = 0;
|
|
||||||
|
|
||||||
private isReadFilterPending = false;
|
private isReadFilterPending = false;
|
||||||
private canPush = false;
|
private canPush = false;
|
||||||
|
|
@ -292,62 +281,10 @@ export class Http2CallStream extends Duplex implements Call {
|
||||||
});
|
});
|
||||||
stream.on('trailers', this.handleTrailers.bind(this));
|
stream.on('trailers', this.handleTrailers.bind(this));
|
||||||
stream.on('data', (data: Buffer) => {
|
stream.on('data', (data: Buffer) => {
|
||||||
let readHead = 0;
|
const message = this.decoder.write(data);
|
||||||
let toRead: number;
|
|
||||||
while (readHead < data.length) {
|
if (message !== null) {
|
||||||
switch (this.readState) {
|
this.tryPush(message);
|
||||||
case ReadState.NO_DATA:
|
|
||||||
this.readCompressFlag = data.slice(readHead, readHead + 1);
|
|
||||||
readHead += 1;
|
|
||||||
this.readState = ReadState.READING_SIZE;
|
|
||||||
this.readPartialSize.fill(0);
|
|
||||||
this.readSizeRemaining = 4;
|
|
||||||
this.readMessageSize = 0;
|
|
||||||
this.readMessageRemaining = 0;
|
|
||||||
this.readPartialMessage = [];
|
|
||||||
break;
|
|
||||||
case ReadState.READING_SIZE:
|
|
||||||
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
|
|
||||||
data.copy(
|
|
||||||
this.readPartialSize, 4 - this.readSizeRemaining, readHead,
|
|
||||||
readHead + toRead);
|
|
||||||
this.readSizeRemaining -= toRead;
|
|
||||||
readHead += toRead;
|
|
||||||
// readSizeRemaining >=0 here
|
|
||||||
if (this.readSizeRemaining === 0) {
|
|
||||||
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
|
|
||||||
this.readMessageRemaining = this.readMessageSize;
|
|
||||||
if (this.readMessageRemaining > 0) {
|
|
||||||
this.readState = ReadState.READING_MESSAGE;
|
|
||||||
} else {
|
|
||||||
this.tryPush(Buffer.concat(
|
|
||||||
[this.readCompressFlag, this.readPartialSize]));
|
|
||||||
this.readState = ReadState.NO_DATA;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ReadState.READING_MESSAGE:
|
|
||||||
toRead =
|
|
||||||
Math.min(data.length - readHead, this.readMessageRemaining);
|
|
||||||
this.readPartialMessage.push(
|
|
||||||
data.slice(readHead, readHead + toRead));
|
|
||||||
this.readMessageRemaining -= toRead;
|
|
||||||
readHead += toRead;
|
|
||||||
// readMessageRemaining >=0 here
|
|
||||||
if (this.readMessageRemaining === 0) {
|
|
||||||
// At this point, we have read a full message
|
|
||||||
const framedMessageBuffers = [
|
|
||||||
this.readCompressFlag, this.readPartialSize
|
|
||||||
].concat(this.readPartialMessage);
|
|
||||||
const framedMessage = Buffer.concat(
|
|
||||||
framedMessageBuffers, this.readMessageSize + 5);
|
|
||||||
this.tryPush(framedMessage);
|
|
||||||
this.readState = ReadState.NO_DATA;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('This should never happen');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ import {ChannelOptions} from './channel-options';
|
||||||
import {Status} from './constants';
|
import {Status} from './constants';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
// This symbol must be exported (for now).
|
const CHANNEL_SYMBOL = Symbol();
|
||||||
// See: https://github.com/Microsoft/TypeScript/issues/20080
|
|
||||||
export const kChannel = Symbol();
|
|
||||||
|
|
||||||
export interface UnaryCallback<ResponseType> {
|
export interface UnaryCallback<ResponseType> {
|
||||||
(err: ServiceError|null, value?: ResponseType): void;
|
(err: ServiceError|null, value?: ResponseType): void;
|
||||||
|
|
@ -52,26 +50,26 @@ export type ClientOptions = Partial<ChannelOptions>&{
|
||||||
* clients.
|
* clients.
|
||||||
*/
|
*/
|
||||||
export class Client {
|
export class Client {
|
||||||
private readonly[kChannel]: Channel;
|
private readonly[CHANNEL_SYMBOL]: Channel;
|
||||||
constructor(
|
constructor(
|
||||||
address: string, credentials: ChannelCredentials,
|
address: string, credentials: ChannelCredentials,
|
||||||
options: ClientOptions = {}) {
|
options: ClientOptions = {}) {
|
||||||
if (options.channelOverride) {
|
if (options.channelOverride) {
|
||||||
this[kChannel] = options.channelOverride;
|
this[CHANNEL_SYMBOL] = options.channelOverride;
|
||||||
} else if (options.channelFactoryOverride) {
|
} else if (options.channelFactoryOverride) {
|
||||||
this[kChannel] =
|
this[CHANNEL_SYMBOL] =
|
||||||
options.channelFactoryOverride(address, credentials, options);
|
options.channelFactoryOverride(address, credentials, options);
|
||||||
} else {
|
} else {
|
||||||
this[kChannel] = new Http2Channel(address, credentials, options);
|
this[CHANNEL_SYMBOL] = new Http2Channel(address, credentials, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
this[kChannel].close();
|
this[CHANNEL_SYMBOL].close();
|
||||||
}
|
}
|
||||||
|
|
||||||
getChannel(): Channel {
|
getChannel(): Channel {
|
||||||
return this[kChannel];
|
return this[CHANNEL_SYMBOL];
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForReady(deadline: Deadline, callback: (error?: Error) => void): void {
|
waitForReady(deadline: Deadline, callback: (error?: Error) => void): void {
|
||||||
|
|
@ -82,7 +80,7 @@ export class Client {
|
||||||
}
|
}
|
||||||
let newState;
|
let newState;
|
||||||
try {
|
try {
|
||||||
newState = this[kChannel].getConnectivityState(true);
|
newState = this[CHANNEL_SYMBOL].getConnectivityState(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback(new Error('The channel has been closed'));
|
callback(new Error('The channel has been closed'));
|
||||||
return;
|
return;
|
||||||
|
|
@ -91,7 +89,8 @@ export class Client {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
this[kChannel].watchConnectivityState(newState, deadline, checkState);
|
this[CHANNEL_SYMBOL].watchConnectivityState(
|
||||||
|
newState, deadline, checkState);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback(new Error('The channel has been closed'));
|
callback(new Error('The channel has been closed'));
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +187,7 @@ export class Client {
|
||||||
({metadata, options, callback} =
|
({metadata, options, callback} =
|
||||||
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
||||||
metadata, options, callback));
|
metadata, options, callback));
|
||||||
const call: Call = this[kChannel].createCall(
|
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||||
method, options.deadline, options.host, null, options.propagate_flags);
|
method, options.deadline, options.host, null, options.propagate_flags);
|
||||||
if (options.credentials) {
|
if (options.credentials) {
|
||||||
call.setCredentials(options.credentials);
|
call.setCredentials(options.credentials);
|
||||||
|
|
@ -229,7 +228,7 @@ export class Client {
|
||||||
({metadata, options, callback} =
|
({metadata, options, callback} =
|
||||||
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
this.checkOptionalUnaryResponseArguments<ResponseType>(
|
||||||
metadata, options, callback));
|
metadata, options, callback));
|
||||||
const call: Call = this[kChannel].createCall(
|
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||||
method, options.deadline, options.host, null, options.propagate_flags);
|
method, options.deadline, options.host, null, options.propagate_flags);
|
||||||
if (options.credentials) {
|
if (options.credentials) {
|
||||||
call.setCredentials(options.credentials);
|
call.setCredentials(options.credentials);
|
||||||
|
|
@ -277,7 +276,7 @@ export class Client {
|
||||||
metadata?: Metadata|CallOptions,
|
metadata?: Metadata|CallOptions,
|
||||||
options?: CallOptions): ClientReadableStream<ResponseType> {
|
options?: CallOptions): ClientReadableStream<ResponseType> {
|
||||||
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
||||||
const call: Call = this[kChannel].createCall(
|
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||||
method, options.deadline, options.host, null, options.propagate_flags);
|
method, options.deadline, options.host, null, options.propagate_flags);
|
||||||
if (options.credentials) {
|
if (options.credentials) {
|
||||||
call.setCredentials(options.credentials);
|
call.setCredentials(options.credentials);
|
||||||
|
|
@ -304,7 +303,7 @@ export class Client {
|
||||||
metadata?: Metadata|CallOptions,
|
metadata?: Metadata|CallOptions,
|
||||||
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
|
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
|
||||||
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
({metadata, options} = this.checkMetadataAndOptions(metadata, options));
|
||||||
const call: Call = this[kChannel].createCall(
|
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||||
method, options.deadline, options.host, null, options.propagate_flags);
|
method, options.deadline, options.host, null, options.propagate_flags);
|
||||||
if (options.credentials) {
|
if (options.credentials) {
|
||||||
call.setCredentials(options.credentials);
|
call.setCredentials(options.credentials);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {ClientDuplexStream, ClientReadableStream, ClientUnaryCall, ClientWritableStream} from './call';
|
import {ClientDuplexStream, ClientReadableStream, ClientUnaryCall, ClientWritableStream, ServiceError} from './call';
|
||||||
import {CallCredentials} from './call-credentials';
|
import {CallCredentials} from './call-credentials';
|
||||||
import {Deadline, StatusObject} from './call-stream';
|
import {Deadline, StatusObject} from './call-stream';
|
||||||
import {Channel, ConnectivityState, Http2Channel} from './channel';
|
import {Channel, ConnectivityState, Http2Channel} from './channel';
|
||||||
|
|
@ -30,7 +30,7 @@ import {Metadata} from './metadata';
|
||||||
import {KeyCertPair, ServerCredentials} from './server-credentials';
|
import {KeyCertPair, ServerCredentials} from './server-credentials';
|
||||||
import {StatusBuilder} from './status-builder';
|
import {StatusBuilder} from './status-builder';
|
||||||
|
|
||||||
const supportedNodeVersions = '^8.11.2 || >=9.4';
|
const supportedNodeVersions = '^8.13.0 || >=10.10.0';
|
||||||
if (!semver.satisfies(process.version, supportedNodeVersions)) {
|
if (!semver.satisfies(process.version, supportedNodeVersions)) {
|
||||||
throw new Error(`@grpc/grpc-js only works on Node ${supportedNodeVersions}`);
|
throw new Error(`@grpc/grpc-js only works on Node ${supportedNodeVersions}`);
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +180,8 @@ export {
|
||||||
ClientWritableStream,
|
ClientWritableStream,
|
||||||
ClientDuplexStream,
|
ClientDuplexStream,
|
||||||
CallOptions,
|
CallOptions,
|
||||||
StatusObject
|
StatusObject,
|
||||||
|
ServiceError
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tslint:disable:no-any */
|
/* tslint:disable:no-any */
|
||||||
|
|
@ -248,3 +249,5 @@ export const InterceptorBuilder = () => {
|
||||||
export const InterceptingCall = () => {
|
export const InterceptingCall = () => {
|
||||||
throw new Error('Not yet implemented');
|
throw new Error('Not yet implemented');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export {GrpcObject} from './make-client';
|
||||||
|
|
|
||||||
|
|
@ -16,62 +16,75 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
import * as http2 from 'http2';
|
||||||
import {Duplex, Readable, Writable} from 'stream';
|
import {Duplex, Readable, Writable} from 'stream';
|
||||||
|
|
||||||
import {ServiceError} from './call';
|
import {ServiceError} from './call';
|
||||||
|
import {StatusObject} from './call-stream';
|
||||||
|
import {Status} from './constants';
|
||||||
import {Deserialize, Serialize} from './make-client';
|
import {Deserialize, Serialize} from './make-client';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
|
function noop(): void {}
|
||||||
|
|
||||||
export class ServerUnaryCall<RequestType> extends EventEmitter {
|
export type PartialServiceError = Partial<ServiceError>;
|
||||||
cancelled: boolean;
|
|
||||||
request: RequestType|null;
|
|
||||||
|
|
||||||
constructor(private call: ServerCall, public metadata: Metadata) {
|
type DeadlineUnitIndexSignature = {
|
||||||
super();
|
[name: string]: number
|
||||||
this.cancelled = false;
|
};
|
||||||
this.request = null; // TODO(cjihrig): Read the unary request here.
|
|
||||||
}
|
|
||||||
|
|
||||||
getPeer(): string {
|
const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding';
|
||||||
throw new Error('not implemented yet');
|
const GRPC_ENCODING_HEADER = 'grpc-encoding';
|
||||||
}
|
const GRPC_MESSAGE_HEADER = 'grpc-message';
|
||||||
|
const GRPC_STATUS_HEADER = 'grpc-status';
|
||||||
sendMetadata(responseMetadata: Metadata): void {
|
const GRPC_TIMEOUT_HEADER = 'grpc-timeout';
|
||||||
throw new Error('not implemented yet');
|
const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/;
|
||||||
}
|
const deadlineUnitsToMs: DeadlineUnitIndexSignature = {
|
||||||
}
|
H: 3600000,
|
||||||
|
M: 60000,
|
||||||
|
S: 1000,
|
||||||
|
m: 1,
|
||||||
|
u: 0.001,
|
||||||
|
n: 0.000001
|
||||||
|
};
|
||||||
|
const defaultResponseHeaders = {
|
||||||
|
// TODO(cjihrig): Remove these encoding headers from the default response
|
||||||
|
// once compression is integrated.
|
||||||
|
[GRPC_ACCEPT_ENCODING_HEADER]: 'identity',
|
||||||
|
[GRPC_ENCODING_HEADER]: 'identity',
|
||||||
|
[http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK,
|
||||||
|
[http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto'
|
||||||
|
};
|
||||||
|
const defaultResponseOptions = {
|
||||||
|
waitForTrailers: true
|
||||||
|
} as http2.ServerStreamResponseOptions;
|
||||||
|
|
||||||
|
|
||||||
export class ServerReadableStream<RequestType> extends Readable {
|
export type ServerSurfaceCall = {
|
||||||
cancelled: boolean;
|
cancelled: boolean; getPeer(): string;
|
||||||
|
sendMetadata(responseMetadata: Metadata): void
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
export type ServerUnaryCall<RequestType, ResponseType> =
|
||||||
private call: ServerCall, public metadata: Metadata,
|
ServerSurfaceCall&{request: RequestType | null};
|
||||||
private deserialize: Deserialize<RequestType>) {
|
export type ServerReadableStream<RequestType, ResponseType> =
|
||||||
super();
|
ServerSurfaceCall&Readable;
|
||||||
this.cancelled = false;
|
export type ServerWritableStream<RequestType, ResponseType> =
|
||||||
}
|
ServerSurfaceCall&Writable&{request: RequestType | null};
|
||||||
|
export type ServerDuplexStream<RequestType, ResponseType> =
|
||||||
|
ServerSurfaceCall&Duplex;
|
||||||
|
|
||||||
getPeer(): string {
|
export class ServerUnaryCallImpl<RequestType, ResponseType> extends EventEmitter
|
||||||
throw new Error('not implemented yet');
|
implements ServerUnaryCall<RequestType, ResponseType> {
|
||||||
}
|
|
||||||
|
|
||||||
sendMetadata(responseMetadata: Metadata): void {
|
|
||||||
throw new Error('not implemented yet');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class ServerWritableStream<RequestType, ResponseType> extends Writable {
|
|
||||||
cancelled: boolean;
|
cancelled: boolean;
|
||||||
request: RequestType|null;
|
request: RequestType|null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private call: ServerCall, public metadata: Metadata,
|
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
private serialize: Serialize<ResponseType>) {
|
public metadata: Metadata) {
|
||||||
super();
|
super();
|
||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
this.request = null; // TODO(cjihrig): Read the unary request here.
|
this.request = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPeer(): string {
|
getPeer(): string {
|
||||||
|
|
@ -79,19 +92,20 @@ export class ServerWritableStream<RequestType, ResponseType> extends Writable {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMetadata(responseMetadata: Metadata): void {
|
sendMetadata(responseMetadata: Metadata): void {
|
||||||
throw new Error('not implemented yet');
|
this.call.sendMetadata(responseMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class ServerDuplexStream<RequestType, ResponseType> extends Duplex {
|
export class ServerReadableStreamImpl<RequestType, ResponseType> extends
|
||||||
|
Readable implements ServerReadableStream<RequestType, ResponseType> {
|
||||||
cancelled: boolean;
|
cancelled: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private call: ServerCall, public metadata: Metadata,
|
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
private serialize: Serialize<ResponseType>,
|
public metadata: Metadata,
|
||||||
private deserialize: Deserialize<RequestType>) {
|
private _deserialize: Deserialize<RequestType>) {
|
||||||
super();
|
super({objectMode: true});
|
||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,13 +114,103 @@ export class ServerDuplexStream<RequestType, ResponseType> extends Duplex {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMetadata(responseMetadata: Metadata): void {
|
sendMetadata(responseMetadata: Metadata): void {
|
||||||
|
this.call.sendMetadata(responseMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ServerWritableStreamImpl<RequestType, ResponseType> extends
|
||||||
|
Writable implements ServerWritableStream<RequestType, ResponseType> {
|
||||||
|
cancelled: boolean;
|
||||||
|
request: RequestType|null;
|
||||||
|
private trailingMetadata: Metadata;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
public metadata: Metadata, private _serialize: Serialize<ResponseType>) {
|
||||||
|
super({objectMode: true});
|
||||||
|
this.cancelled = false;
|
||||||
|
this.request = null;
|
||||||
|
this.trailingMetadata = new Metadata();
|
||||||
|
|
||||||
|
this.on('error', (err) => {
|
||||||
|
this.call.sendError(err as ServiceError);
|
||||||
|
this.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeer(): string {
|
||||||
throw new Error('not implemented yet');
|
throw new Error('not implemented yet');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendMetadata(responseMetadata: Metadata): void {
|
||||||
|
this.call.sendMetadata(responseMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _write(
|
||||||
|
chunk: ResponseType, encoding: string,
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
callback: (...args: any[]) => void) {
|
||||||
|
try {
|
||||||
|
const response = await this.call.serializeMessage(chunk);
|
||||||
|
|
||||||
|
if (!this.call.write(response)) {
|
||||||
|
this.call.once('drain', callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
err.code = Status.INTERNAL;
|
||||||
|
this.emit('error', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
_final(callback: Function): void {
|
||||||
|
this.call.sendStatus(
|
||||||
|
{code: Status.OK, details: 'OK', metadata: this.trailingMetadata});
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
end(metadata?: any) {
|
||||||
|
if (metadata) {
|
||||||
|
this.trailingMetadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(input: ResponseType): Buffer|null {
|
||||||
|
if (input === null || input === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._serialize(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Internal class that wraps the HTTP2 request.
|
export class ServerDuplexStreamImpl<RequestType, ResponseType> extends Duplex
|
||||||
export class ServerCall {}
|
implements ServerDuplexStream<RequestType, ResponseType> {
|
||||||
|
cancelled: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
public metadata: Metadata, private _serialize: Serialize<ResponseType>,
|
||||||
|
private _deserialize: Deserialize<RequestType>) {
|
||||||
|
super({objectMode: true});
|
||||||
|
this.cancelled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeer(): string {
|
||||||
|
throw new Error('not implemented yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMetadata(responseMetadata: Metadata): void {
|
||||||
|
this.call.sendMetadata(responseMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Unary response callback signature.
|
// Unary response callback signature.
|
||||||
|
|
@ -116,12 +220,12 @@ export type sendUnaryData<ResponseType> =
|
||||||
|
|
||||||
// User provided handler for unary calls.
|
// User provided handler for unary calls.
|
||||||
export type handleUnaryCall<RequestType, ResponseType> =
|
export type handleUnaryCall<RequestType, ResponseType> =
|
||||||
(call: ServerUnaryCall<RequestType>,
|
(call: ServerUnaryCall<RequestType, ResponseType>,
|
||||||
callback: sendUnaryData<ResponseType>) => void;
|
callback: sendUnaryData<ResponseType>) => void;
|
||||||
|
|
||||||
// User provided handler for client streaming calls.
|
// User provided handler for client streaming calls.
|
||||||
export type handleClientStreamingCall<RequestType, ResponseType> =
|
export type handleClientStreamingCall<RequestType, ResponseType> =
|
||||||
(call: ServerReadableStream<RequestType>,
|
(call: ServerReadableStream<RequestType, ResponseType>,
|
||||||
callback: sendUnaryData<ResponseType>) => void;
|
callback: sendUnaryData<ResponseType>) => void;
|
||||||
|
|
||||||
// User provided handler for server streaming calls.
|
// User provided handler for server streaming calls.
|
||||||
|
|
@ -138,11 +242,242 @@ export type HandleCall<RequestType, ResponseType> =
|
||||||
handleServerStreamingCall<RequestType, ResponseType>|
|
handleServerStreamingCall<RequestType, ResponseType>|
|
||||||
handleBidiStreamingCall<RequestType, ResponseType>;
|
handleBidiStreamingCall<RequestType, ResponseType>;
|
||||||
|
|
||||||
export type Handler<RequestType, ResponseType> = {
|
export type UnaryHandler<RequestType, ResponseType> = {
|
||||||
func: HandleCall<RequestType, ResponseType>;
|
func: handleUnaryCall<RequestType, ResponseType>;
|
||||||
serialize: Serialize<ResponseType>;
|
serialize: Serialize<ResponseType>;
|
||||||
deserialize: Deserialize<RequestType>;
|
deserialize: Deserialize<RequestType>;
|
||||||
type: HandlerType;
|
type: HandlerType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ClientStreamingHandler<RequestType, ResponseType> = {
|
||||||
|
func: handleClientStreamingCall<RequestType, ResponseType>;
|
||||||
|
serialize: Serialize<ResponseType>;
|
||||||
|
deserialize: Deserialize<RequestType>;
|
||||||
|
type: HandlerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ServerStreamingHandler<RequestType, ResponseType> = {
|
||||||
|
func: handleServerStreamingCall<RequestType, ResponseType>;
|
||||||
|
serialize: Serialize<ResponseType>;
|
||||||
|
deserialize: Deserialize<RequestType>;
|
||||||
|
type: HandlerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BidiStreamingHandler<RequestType, ResponseType> = {
|
||||||
|
func: handleBidiStreamingCall<RequestType, ResponseType>;
|
||||||
|
serialize: Serialize<ResponseType>;
|
||||||
|
deserialize: Deserialize<RequestType>;
|
||||||
|
type: HandlerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Handler<RequestType, ResponseType> =
|
||||||
|
UnaryHandler<RequestType, ResponseType>|
|
||||||
|
ClientStreamingHandler<RequestType, ResponseType>|
|
||||||
|
ServerStreamingHandler<RequestType, ResponseType>|
|
||||||
|
BidiStreamingHandler<RequestType, ResponseType>;
|
||||||
|
|
||||||
export type HandlerType = 'bidi'|'clientStream'|'serverStream'|'unary';
|
export type HandlerType = 'bidi'|'clientStream'|'serverStream'|'unary';
|
||||||
|
|
||||||
|
const noopTimer: NodeJS.Timer = setTimeout(() => {}, 0);
|
||||||
|
|
||||||
|
// Internal class that wraps the HTTP2 request.
|
||||||
|
export class Http2ServerCallStream<RequestType, ResponseType> extends
|
||||||
|
EventEmitter {
|
||||||
|
cancelled = false;
|
||||||
|
deadline: NodeJS.Timer = noopTimer;
|
||||||
|
private wantTrailers = false;
|
||||||
|
private metadataSent = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stream: http2.ServerHttp2Stream,
|
||||||
|
private handler: Handler<RequestType, ResponseType>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.stream.once('error', (err: ServiceError) => {
|
||||||
|
err.code = Status.INTERNAL;
|
||||||
|
this.sendError(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stream.once('close', () => {
|
||||||
|
if (this.stream.rstCode === http2.constants.NGHTTP2_CANCEL) {
|
||||||
|
this.cancelled = true;
|
||||||
|
this.emit('cancelled', 'cancelled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stream.on('drain', () => {
|
||||||
|
this.emit('drain');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMetadata(customMetadata?: Metadata) {
|
||||||
|
if (this.metadataSent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.metadataSent = true;
|
||||||
|
const custom = customMetadata ? customMetadata.toHttp2Headers() : null;
|
||||||
|
// TODO(cjihrig): Include compression headers.
|
||||||
|
const headers = Object.assign(defaultResponseHeaders, custom);
|
||||||
|
this.stream.respond(headers, defaultResponseOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMetadata(headers: http2.IncomingHttpHeaders) {
|
||||||
|
const metadata = Metadata.fromHttp2Headers(headers);
|
||||||
|
|
||||||
|
// TODO(cjihrig): Receive compression metadata.
|
||||||
|
|
||||||
|
const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER);
|
||||||
|
|
||||||
|
if (timeoutHeader.length > 0) {
|
||||||
|
const match = timeoutHeader[0].toString().match(DEADLINE_REGEX);
|
||||||
|
|
||||||
|
if (match === null) {
|
||||||
|
const err = new Error('Invalid deadline') as ServiceError;
|
||||||
|
err.code = Status.OUT_OF_RANGE;
|
||||||
|
this.sendError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0;
|
||||||
|
|
||||||
|
this.deadline = setTimeout(handleExpiredDeadline, timeout, this);
|
||||||
|
metadata.remove(GRPC_TIMEOUT_HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveUnaryMessage(): Promise<RequestType> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const stream = this.stream;
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let totalLength = 0;
|
||||||
|
|
||||||
|
stream.on('data', (data: Buffer) => {
|
||||||
|
chunks.push(data);
|
||||||
|
totalLength += data.byteLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.once('end', async () => {
|
||||||
|
try {
|
||||||
|
const requestBytes = Buffer.concat(chunks, totalLength);
|
||||||
|
|
||||||
|
resolve(await this.deserializeMessage(requestBytes));
|
||||||
|
} catch (err) {
|
||||||
|
err.code = Status.INTERNAL;
|
||||||
|
this.sendError(err);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeMessage(value: ResponseType) {
|
||||||
|
const messageBuffer = this.handler.serialize(value);
|
||||||
|
|
||||||
|
// TODO(cjihrig): Call compression aware serializeMessage().
|
||||||
|
const byteLength = messageBuffer.byteLength;
|
||||||
|
const output = Buffer.allocUnsafe(byteLength + 5);
|
||||||
|
output.writeUInt8(0, 0);
|
||||||
|
output.writeUInt32BE(byteLength, 1);
|
||||||
|
messageBuffer.copy(output, 5);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deserializeMessage(bytes: Buffer) {
|
||||||
|
// TODO(cjihrig): Call compression aware deserializeMessage().
|
||||||
|
const receivedMessage = bytes.slice(5);
|
||||||
|
|
||||||
|
return this.handler.deserialize(receivedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendUnaryMessage(
|
||||||
|
err: ServiceError|null, value: ResponseType|null, metadata?: Metadata,
|
||||||
|
flags?: number) {
|
||||||
|
if (!metadata) {
|
||||||
|
metadata = new Metadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
err.metadata = metadata;
|
||||||
|
this.sendError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.serializeMessage(value!);
|
||||||
|
|
||||||
|
this.write(response);
|
||||||
|
this.sendStatus({code: Status.OK, details: 'OK', metadata});
|
||||||
|
} catch (err) {
|
||||||
|
err.code = Status.INTERNAL;
|
||||||
|
this.sendError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStatus(statusObj: StatusObject) {
|
||||||
|
if (this.cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(this.deadline);
|
||||||
|
|
||||||
|
if (!this.wantTrailers) {
|
||||||
|
this.wantTrailers = true;
|
||||||
|
this.stream.once('wantTrailers', () => {
|
||||||
|
const trailersToSend = Object.assign(
|
||||||
|
{
|
||||||
|
[GRPC_STATUS_HEADER]: statusObj.code,
|
||||||
|
[GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string)
|
||||||
|
},
|
||||||
|
statusObj.metadata.toHttp2Headers());
|
||||||
|
|
||||||
|
this.stream.sendTrailers(trailersToSend);
|
||||||
|
});
|
||||||
|
this.sendMetadata();
|
||||||
|
this.stream.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendError(error: ServiceError) {
|
||||||
|
const status: StatusObject = {
|
||||||
|
code: Status.UNKNOWN,
|
||||||
|
details: error.hasOwnProperty('message') ? error.message :
|
||||||
|
'Unknown Error',
|
||||||
|
metadata: error.hasOwnProperty('metadata') ? error.metadata :
|
||||||
|
new Metadata()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error.hasOwnProperty('code') && Number.isInteger(error.code)) {
|
||||||
|
status.code = error.code;
|
||||||
|
|
||||||
|
if (error.hasOwnProperty('details')) {
|
||||||
|
status.details = error.details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(chunk: Buffer) {
|
||||||
|
if (this.cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMetadata();
|
||||||
|
return this.stream.write(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable:no-any
|
||||||
|
type UntypedServerCall = Http2ServerCallStream<any, any>;
|
||||||
|
|
||||||
|
function handleExpiredDeadline(call: UntypedServerCall) {
|
||||||
|
const err = new Error('Deadline exceeded') as ServiceError;
|
||||||
|
err.code = Status.DEADLINE_EXCEEDED;
|
||||||
|
|
||||||
|
call.sendError(err);
|
||||||
|
call.cancelled = true;
|
||||||
|
call.emit('cancelled', 'deadline');
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,25 @@ import {AddressInfo, ListenOptions} from 'net';
|
||||||
import {URL} from 'url';
|
import {URL} from 'url';
|
||||||
|
|
||||||
import {ServiceError} from './call';
|
import {ServiceError} from './call';
|
||||||
|
import {StatusObject} from './call-stream';
|
||||||
import {Status} from './constants';
|
import {Status} from './constants';
|
||||||
import {Deserialize, Serialize, ServiceDefinition} from './make-client';
|
import {Deserialize, Serialize, ServiceDefinition} from './make-client';
|
||||||
import {HandleCall, Handler, HandlerType, sendUnaryData, ServerDuplexStream, ServerReadableStream, ServerUnaryCall, ServerWritableStream} from './server-call';
|
import {Metadata} from './metadata';
|
||||||
|
import {BidiStreamingHandler, ClientStreamingHandler, HandleCall, Handler, HandlerType, Http2ServerCallStream, PartialServiceError, sendUnaryData, ServerDuplexStream, ServerDuplexStreamImpl, ServerReadableStream, ServerReadableStreamImpl, ServerStreamingHandler, ServerUnaryCall, ServerUnaryCallImpl, ServerWritableStream, ServerWritableStreamImpl, UnaryHandler} from './server-call';
|
||||||
import {ServerCredentials} from './server-credentials';
|
import {ServerCredentials} from './server-credentials';
|
||||||
|
|
||||||
function noop(): void {}
|
function noop(): void {}
|
||||||
|
|
||||||
type PartialServiceError = Partial<ServiceError>;
|
|
||||||
const unimplementedStatusResponse: PartialServiceError = {
|
const unimplementedStatusResponse: PartialServiceError = {
|
||||||
code: Status.UNIMPLEMENTED,
|
code: Status.UNIMPLEMENTED,
|
||||||
details: 'The server does not implement this method',
|
details: 'The server does not implement this method',
|
||||||
};
|
};
|
||||||
|
|
||||||
// tslint:disable:no-any
|
// tslint:disable:no-any
|
||||||
|
type UntypedUnaryHandler = UnaryHandler<any, any>;
|
||||||
|
type UntypedClientStreamingHandler = ClientStreamingHandler<any, any>;
|
||||||
|
type UntypedServerStreamingHandler = ServerStreamingHandler<any, any>;
|
||||||
|
type UntypedBidiStreamingHandler = BidiStreamingHandler<any, any>;
|
||||||
type UntypedHandleCall = HandleCall<any, any>;
|
type UntypedHandleCall = HandleCall<any, any>;
|
||||||
type UntypedHandler = Handler<any, any>;
|
type UntypedHandler = Handler<any, any>;
|
||||||
type UntypedServiceImplementation = {
|
type UntypedServiceImplementation = {
|
||||||
|
|
@ -41,10 +46,11 @@ type UntypedServiceImplementation = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultHandler = {
|
const defaultHandler = {
|
||||||
unary(call: ServerUnaryCall<any>, callback: sendUnaryData<any>): void {
|
unary(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>): void {
|
||||||
callback(unimplementedStatusResponse as ServiceError, null);
|
callback(unimplementedStatusResponse as ServiceError, null);
|
||||||
},
|
},
|
||||||
clientStream(call: ServerReadableStream<any>, callback: sendUnaryData<any>):
|
clientStream(
|
||||||
|
call: ServerReadableStream<any, any>, callback: sendUnaryData<any>):
|
||||||
void {
|
void {
|
||||||
callback(unimplementedStatusResponse as ServiceError, null);
|
callback(unimplementedStatusResponse as ServiceError, null);
|
||||||
},
|
},
|
||||||
|
|
@ -120,8 +126,8 @@ export class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = this.register(
|
const success = this.register(
|
||||||
attrs.path, impl, attrs.responseSerialize, attrs.requestDeserialize,
|
attrs.path, impl as UntypedHandleCall, attrs.responseSerialize,
|
||||||
methodType);
|
attrs.requestDeserialize, methodType);
|
||||||
|
|
||||||
if (success === false) {
|
if (success === false) {
|
||||||
throw new Error(`Method handler for ${attrs.path} already provided.`);
|
throw new Error(`Method handler for ${attrs.path} already provided.`);
|
||||||
|
|
@ -162,7 +168,7 @@ export class Server {
|
||||||
this.http2Server = http2.createServer();
|
this.http2Server = http2.createServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(cjihrig): Set up the handlers, to allow requests to be processed.
|
this._setupHandlers();
|
||||||
|
|
||||||
function onError(err: Error): void {
|
function onError(err: Error): void {
|
||||||
callback(err, -1);
|
callback(err, -1);
|
||||||
|
|
@ -193,8 +199,7 @@ export class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handlers.set(
|
this.handlers.set(
|
||||||
name,
|
name, {func: handler, serialize, deserialize, type} as UntypedHandler);
|
||||||
{func: handler, serialize, deserialize, type: type as HandlerType});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,4 +232,113 @@ export class Server {
|
||||||
addHttp2Port(): void {
|
addHttp2Port(): void {
|
||||||
throw new Error('Not yet implemented');
|
throw new Error('Not yet implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setupHandlers(): void {
|
||||||
|
if (this.http2Server === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.http2Server.on(
|
||||||
|
'stream',
|
||||||
|
(stream: http2.ServerHttp2Stream,
|
||||||
|
headers: http2.IncomingHttpHeaders) => {
|
||||||
|
if (!this.started) {
|
||||||
|
stream.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const path = headers[http2.constants.HTTP2_HEADER_PATH] as string;
|
||||||
|
const handler = this.handlers.get(path);
|
||||||
|
|
||||||
|
if (handler === undefined) {
|
||||||
|
throw unimplementedStatusResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const call = new Http2ServerCallStream(stream, handler);
|
||||||
|
const metadata: Metadata =
|
||||||
|
call.receiveMetadata(headers) as Metadata;
|
||||||
|
|
||||||
|
switch (handler.type) {
|
||||||
|
case 'unary':
|
||||||
|
handleUnary(call, handler as UntypedUnaryHandler, metadata);
|
||||||
|
break;
|
||||||
|
case 'clientStream':
|
||||||
|
handleClientStreaming(
|
||||||
|
call, handler as UntypedClientStreamingHandler, metadata);
|
||||||
|
break;
|
||||||
|
case 'serverStream':
|
||||||
|
handleServerStreaming(
|
||||||
|
call, handler as UntypedServerStreamingHandler, metadata);
|
||||||
|
break;
|
||||||
|
case 'bidi':
|
||||||
|
handleBidiStreaming(
|
||||||
|
call, handler as UntypedBidiStreamingHandler, metadata);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown handler type: ${handler.type}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const call = new Http2ServerCallStream(stream, null!);
|
||||||
|
err.code = Status.INTERNAL;
|
||||||
|
call.sendError(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function handleUnary<RequestType, ResponseType>(
|
||||||
|
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
handler: UnaryHandler<RequestType, ResponseType>,
|
||||||
|
metadata: Metadata): Promise<void> {
|
||||||
|
const emitter =
|
||||||
|
new ServerUnaryCallImpl<RequestType, ResponseType>(call, metadata);
|
||||||
|
const request = await call.receiveUnaryMessage();
|
||||||
|
|
||||||
|
if (request === undefined || call.cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.request = request;
|
||||||
|
handler.func(
|
||||||
|
emitter,
|
||||||
|
(err: ServiceError|null, value: ResponseType|null, trailer?: Metadata,
|
||||||
|
flags?: number) => {
|
||||||
|
call.sendUnaryMessage(err, value, trailer, flags);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleClientStreaming<RequestType, ResponseType>(
|
||||||
|
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
handler: ClientStreamingHandler<RequestType, ResponseType>,
|
||||||
|
metadata: Metadata): void {
|
||||||
|
throw new Error('not implemented yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function handleServerStreaming<RequestType, ResponseType>(
|
||||||
|
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
handler: ServerStreamingHandler<RequestType, ResponseType>,
|
||||||
|
metadata: Metadata): Promise<void> {
|
||||||
|
const request = await call.receiveUnaryMessage();
|
||||||
|
|
||||||
|
if (request === undefined || call.cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = new ServerWritableStreamImpl<RequestType, ResponseType>(
|
||||||
|
call, metadata, handler.serialize);
|
||||||
|
|
||||||
|
stream.request = request;
|
||||||
|
handler.func(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleBidiStreaming<RequestType, ResponseType>(
|
||||||
|
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||||
|
handler: BidiStreamingHandler<RequestType, ResponseType>,
|
||||||
|
metadata: Metadata): void {
|
||||||
|
throw new Error('not implemented yet');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum ReadState {
|
||||||
|
NO_DATA,
|
||||||
|
READING_SIZE,
|
||||||
|
READING_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class StreamDecoder {
|
||||||
|
private readState: ReadState = ReadState.NO_DATA;
|
||||||
|
private readCompressFlag: Buffer = Buffer.alloc(1);
|
||||||
|
private readPartialSize: Buffer = Buffer.alloc(4);
|
||||||
|
private readSizeRemaining = 4;
|
||||||
|
private readMessageSize = 0;
|
||||||
|
private readPartialMessage: Buffer[] = [];
|
||||||
|
private readMessageRemaining = 0;
|
||||||
|
|
||||||
|
|
||||||
|
write(data: Buffer): Buffer|null {
|
||||||
|
let readHead = 0;
|
||||||
|
let toRead: number;
|
||||||
|
|
||||||
|
while (readHead < data.length) {
|
||||||
|
switch (this.readState) {
|
||||||
|
case ReadState.NO_DATA:
|
||||||
|
this.readCompressFlag = data.slice(readHead, readHead + 1);
|
||||||
|
readHead += 1;
|
||||||
|
this.readState = ReadState.READING_SIZE;
|
||||||
|
this.readPartialSize.fill(0);
|
||||||
|
this.readSizeRemaining = 4;
|
||||||
|
this.readMessageSize = 0;
|
||||||
|
this.readMessageRemaining = 0;
|
||||||
|
this.readPartialMessage = [];
|
||||||
|
break;
|
||||||
|
case ReadState.READING_SIZE:
|
||||||
|
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
|
||||||
|
data.copy(
|
||||||
|
this.readPartialSize, 4 - this.readSizeRemaining, readHead,
|
||||||
|
readHead + toRead);
|
||||||
|
this.readSizeRemaining -= toRead;
|
||||||
|
readHead += toRead;
|
||||||
|
// readSizeRemaining >=0 here
|
||||||
|
if (this.readSizeRemaining === 0) {
|
||||||
|
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
|
||||||
|
this.readMessageRemaining = this.readMessageSize;
|
||||||
|
if (this.readMessageRemaining > 0) {
|
||||||
|
this.readState = ReadState.READING_MESSAGE;
|
||||||
|
} else {
|
||||||
|
const message = Buffer.concat(
|
||||||
|
[this.readCompressFlag, this.readPartialSize], 5);
|
||||||
|
|
||||||
|
this.readState = ReadState.NO_DATA;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ReadState.READING_MESSAGE:
|
||||||
|
toRead = Math.min(data.length - readHead, this.readMessageRemaining);
|
||||||
|
this.readPartialMessage.push(data.slice(readHead, readHead + toRead));
|
||||||
|
this.readMessageRemaining -= toRead;
|
||||||
|
readHead += toRead;
|
||||||
|
// readMessageRemaining >=0 here
|
||||||
|
if (this.readMessageRemaining === 0) {
|
||||||
|
// At this point, we have read a full message
|
||||||
|
const framedMessageBuffers = [
|
||||||
|
this.readCompressFlag, this.readPartialSize
|
||||||
|
].concat(this.readPartialMessage);
|
||||||
|
const framedMessage =
|
||||||
|
Buffer.concat(framedMessageBuffers, this.readMessageSize + 5);
|
||||||
|
|
||||||
|
this.readState = ReadState.NO_DATA;
|
||||||
|
return framedMessage;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Unexpected read state');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message EchoMessage {
|
||||||
|
string value = 1;
|
||||||
|
int32 value2 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service EchoService {
|
||||||
|
rpc Echo (EchoMessage) returns (EchoMessage);
|
||||||
|
|
||||||
|
rpc EchoClientStream (stream EchoMessage) returns (EchoMessage);
|
||||||
|
|
||||||
|
rpc EchoServerStream (EchoMessage) returns (stream EchoMessage);
|
||||||
|
|
||||||
|
rpc EchoBidiStream (stream EchoMessage) returns (stream EchoMessage);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
bool error = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
int32 count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service TestService {
|
||||||
|
rpc Unary (Request) returns (Response) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc ClientStream (stream Request) returns (Response) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc ServerStream (Request) returns (stream Response) {
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc BidiStream (stream Request) returns (stream Response) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,522 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Allow `any` data type for testing runtime type checking.
|
||||||
|
// tslint:disable no-any
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
import * as grpc from '../src';
|
||||||
|
import {ServiceError} from '../src/call';
|
||||||
|
import {ServiceClient, ServiceClientConstructor} from '../src/make-client';
|
||||||
|
import {Server} from '../src/server';
|
||||||
|
import {sendUnaryData, ServerDuplexStream, ServerReadableStream, ServerUnaryCall, ServerWritableStream} from '../src/server-call';
|
||||||
|
|
||||||
|
import {loadProtoFile} from './common';
|
||||||
|
|
||||||
|
const protoFile = join(__dirname, 'fixtures', 'test_service.proto');
|
||||||
|
const testServiceDef = loadProtoFile(protoFile);
|
||||||
|
const testServiceClient =
|
||||||
|
testServiceDef.TestService as ServiceClientConstructor;
|
||||||
|
const clientInsecureCreds = grpc.credentials.createInsecure();
|
||||||
|
const serverInsecureCreds = grpc.ServerCredentials.createInsecure();
|
||||||
|
|
||||||
|
|
||||||
|
describe('Client malformed response handling', () => {
|
||||||
|
let server: Server;
|
||||||
|
let client: ServiceClient;
|
||||||
|
const badArg = Buffer.from([0xFF]);
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
const malformedTestService = {
|
||||||
|
unary: {
|
||||||
|
path: '/TestService/Unary',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: false,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: identity
|
||||||
|
},
|
||||||
|
clientStream: {
|
||||||
|
path: '/TestService/ClientStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: false,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: identity
|
||||||
|
},
|
||||||
|
serverStream: {
|
||||||
|
path: '/TestService/ServerStream',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: true,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: identity
|
||||||
|
},
|
||||||
|
bidiStream: {
|
||||||
|
path: '/TestService/BidiStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: true,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: identity
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
|
||||||
|
server.addService(malformedTestService, {
|
||||||
|
unary(call: ServerUnaryCall<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
cb(null, badArg);
|
||||||
|
},
|
||||||
|
|
||||||
|
clientStream(
|
||||||
|
stream: ServerReadableStream<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
stream.on('data', noop);
|
||||||
|
stream.on('end', () => {
|
||||||
|
cb(null, badArg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
serverStream(stream: ServerWritableStream<any, any>) {
|
||||||
|
stream.write(badArg);
|
||||||
|
stream.end();
|
||||||
|
},
|
||||||
|
|
||||||
|
bidiStream(stream: ServerDuplexStream<any, any>) {
|
||||||
|
stream.on('data', () => {
|
||||||
|
// Ignore requests
|
||||||
|
stream.write(badArg);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
client = new testServiceClient(`localhost:${port}`, clientInsecureCreds);
|
||||||
|
server.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
client.close();
|
||||||
|
server.tryShutdown(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get an INTERNAL status with a unary call', (done) => {
|
||||||
|
client.unary({}, (err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get an INTERNAL status with a server stream call', (done) => {
|
||||||
|
const call = client.serverStream({});
|
||||||
|
|
||||||
|
call.on('data', noop);
|
||||||
|
call.on('error', (err: ServiceError) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Server serialization failure handling', () => {
|
||||||
|
let client: ServiceClient;
|
||||||
|
let server: Server;
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
function serializeFail(obj: any) {
|
||||||
|
throw new Error('Serialization failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const malformedTestService = {
|
||||||
|
unary: {
|
||||||
|
path: '/TestService/Unary',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: false,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: serializeFail
|
||||||
|
},
|
||||||
|
clientStream: {
|
||||||
|
path: '/TestService/ClientStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: false,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: serializeFail
|
||||||
|
},
|
||||||
|
serverStream: {
|
||||||
|
path: '/TestService/ServerStream',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: true,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: serializeFail
|
||||||
|
},
|
||||||
|
bidiStream: {
|
||||||
|
path: '/TestService/BidiStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: true,
|
||||||
|
requestDeserialize: identity,
|
||||||
|
responseSerialize: serializeFail
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
server.addService(malformedTestService as any, {
|
||||||
|
unary(call: ServerUnaryCall<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
cb(null, {});
|
||||||
|
},
|
||||||
|
|
||||||
|
clientStream(
|
||||||
|
stream: ServerReadableStream<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
stream.on('data', noop);
|
||||||
|
stream.on('end', () => {
|
||||||
|
cb(null, {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
serverStream(stream: ServerWritableStream<any, any>) {
|
||||||
|
stream.write({});
|
||||||
|
stream.end();
|
||||||
|
},
|
||||||
|
|
||||||
|
bidiStream(stream: ServerDuplexStream<any, any>) {
|
||||||
|
stream.on('data', () => {
|
||||||
|
// Ignore requests
|
||||||
|
stream.write({});
|
||||||
|
});
|
||||||
|
stream.on('end', () => {
|
||||||
|
stream.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
client = new testServiceClient(`localhost:${port}`, clientInsecureCreds);
|
||||||
|
server.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
client.close();
|
||||||
|
server.tryShutdown(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get an INTERNAL status with a unary call', (done) => {
|
||||||
|
client.unary({}, (err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get an INTERNAL status with a server stream call', (done) => {
|
||||||
|
const call = client.serverStream({});
|
||||||
|
|
||||||
|
call.on('data', noop);
|
||||||
|
call.on('error', (err: ServiceError) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Other conditions', () => {
|
||||||
|
let client: ServiceClient;
|
||||||
|
let server: Server;
|
||||||
|
let port: number;
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
const trailerMetadata = new grpc.Metadata();
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
trailerMetadata.add('trailer-present', 'yes');
|
||||||
|
|
||||||
|
server.addService(testServiceClient.service, {
|
||||||
|
unary(call: ServerUnaryCall<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
const req = call.request;
|
||||||
|
|
||||||
|
if (req.error) {
|
||||||
|
const details = req.message || 'Requested error';
|
||||||
|
|
||||||
|
cb({code: grpc.status.UNKNOWN, details} as ServiceError, null,
|
||||||
|
trailerMetadata);
|
||||||
|
} else {
|
||||||
|
cb(null, {count: 1}, trailerMetadata);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clientStream(
|
||||||
|
stream: ServerReadableStream<any, any>, cb: sendUnaryData<any>) {
|
||||||
|
let count = 0;
|
||||||
|
let errored = false;
|
||||||
|
|
||||||
|
stream.on('data', (data: any) => {
|
||||||
|
if (data.error) {
|
||||||
|
const message = data.message || 'Requested error';
|
||||||
|
errored = true;
|
||||||
|
cb(new Error(message) as ServiceError, null, trailerMetadata);
|
||||||
|
} else {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
if (!errored) {
|
||||||
|
cb(null, {count}, trailerMetadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
serverStream(stream: ServerWritableStream<any, any>) {
|
||||||
|
const req = stream.request;
|
||||||
|
|
||||||
|
if (req.error) {
|
||||||
|
stream.emit('error', {
|
||||||
|
code: grpc.status.UNKNOWN,
|
||||||
|
details: req.message || 'Requested error',
|
||||||
|
metadata: trailerMetadata
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
stream.write({count: i});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.end(trailerMetadata);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
bidiStream(stream: ServerDuplexStream<any, any>) {
|
||||||
|
let count = 0;
|
||||||
|
stream.on('data', (data: any) => {
|
||||||
|
if (data.error) {
|
||||||
|
const message = data.message || 'Requested error';
|
||||||
|
const err = new Error(message) as ServiceError;
|
||||||
|
|
||||||
|
err.metadata = trailerMetadata.clone();
|
||||||
|
err.metadata.add('count', '' + count);
|
||||||
|
stream.emit('error', err);
|
||||||
|
} else {
|
||||||
|
stream.write({count});
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
stream.end(trailerMetadata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
port = _port;
|
||||||
|
client = new testServiceClient(`localhost:${port}`, clientInsecureCreds);
|
||||||
|
server.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
client.close();
|
||||||
|
server.tryShutdown(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Server receiving bad input', () => {
|
||||||
|
let misbehavingClient: ServiceClient;
|
||||||
|
const badArg = Buffer.from([0xFF]);
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
const testServiceAttrs = {
|
||||||
|
unary: {
|
||||||
|
path: '/TestService/Unary',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: false,
|
||||||
|
requestSerialize: identity,
|
||||||
|
responseDeserialize: identity
|
||||||
|
},
|
||||||
|
clientStream: {
|
||||||
|
path: '/TestService/ClientStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: false,
|
||||||
|
requestSerialize: identity,
|
||||||
|
responseDeserialize: identity
|
||||||
|
},
|
||||||
|
serverStream: {
|
||||||
|
path: '/TestService/ServerStream',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: true,
|
||||||
|
requestSerialize: identity,
|
||||||
|
responseDeserialize: identity
|
||||||
|
},
|
||||||
|
bidiStream: {
|
||||||
|
path: '/TestService/BidiStream',
|
||||||
|
requestStream: true,
|
||||||
|
responseStream: true,
|
||||||
|
requestSerialize: identity,
|
||||||
|
responseDeserialize: identity
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const client =
|
||||||
|
grpc.makeGenericClientConstructor(testServiceAttrs, 'TestService');
|
||||||
|
|
||||||
|
misbehavingClient = new client(`localhost:${port}`, clientInsecureCreds);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
misbehavingClient.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond correctly to a unary call', (done) => {
|
||||||
|
misbehavingClient.unary(badArg, (err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond correctly to a server stream', (done) => {
|
||||||
|
const call = misbehavingClient.serverStream(badArg);
|
||||||
|
|
||||||
|
call.on('data', (data: any) => {
|
||||||
|
assert.fail(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
call.on('error', (err: ServiceError) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.INTERNAL);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Trailing metadata', () => {
|
||||||
|
it('should be present when a unary call succeeds', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
const call =
|
||||||
|
client.unary({error: false}, (err: ServiceError, data: any) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count === 2) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
call.on('status', (status: grpc.StatusObject) => {
|
||||||
|
assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count === 2) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be present when a unary call fails', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
const call =
|
||||||
|
client.unary({error: true}, (err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count === 2) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
call.on('status', (status: grpc.StatusObject) => {
|
||||||
|
assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (count === 2) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be present when a server stream call succeeds', (done) => {
|
||||||
|
const call = client.serverStream({error: false});
|
||||||
|
|
||||||
|
call.on('data', noop);
|
||||||
|
call.on('status', (status: grpc.StatusObject) => {
|
||||||
|
assert.strictEqual(status.code, grpc.status.OK);
|
||||||
|
assert.deepStrictEqual(status.metadata.get('trailer-present'), ['yes']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be present when a server stream call fails', (done) => {
|
||||||
|
const call = client.serverStream({error: true});
|
||||||
|
|
||||||
|
call.on('data', noop);
|
||||||
|
call.on('error', (error: ServiceError) => {
|
||||||
|
assert.deepStrictEqual(error.metadata.get('trailer-present'), ['yes']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Error object should contain the status', () => {
|
||||||
|
it('for a unary call', (done) => {
|
||||||
|
client.unary({error: true}, (err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.UNKNOWN);
|
||||||
|
assert.strictEqual(err.details, 'Requested error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('for a server stream call', (done) => {
|
||||||
|
const call = client.serverStream({error: true});
|
||||||
|
|
||||||
|
call.on('data', noop);
|
||||||
|
call.on('error', (error: ServiceError) => {
|
||||||
|
assert.strictEqual(error.code, grpc.status.UNKNOWN);
|
||||||
|
assert.strictEqual(error.details, 'Requested error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('for a UTF-8 error message', (done) => {
|
||||||
|
client.unary(
|
||||||
|
{error: true, message: '測試字符串'},
|
||||||
|
(err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.UNKNOWN);
|
||||||
|
assert.strictEqual(err.details, '測試字符串');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function identity(arg: any): any {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function noop(): void {}
|
||||||
|
|
@ -21,8 +21,12 @@ import * as assert from 'assert';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import * as grpc from '../src';
|
||||||
import {ServerCredentials} from '../src';
|
import {ServerCredentials} from '../src';
|
||||||
|
import {ServiceError} from '../src/call';
|
||||||
|
import {ServiceClient, ServiceClientConstructor} from '../src/make-client';
|
||||||
import {Server} from '../src/server';
|
import {Server} from '../src/server';
|
||||||
|
import {sendUnaryData, ServerDuplexStream, ServerReadableStream, ServerUnaryCall, ServerWritableStream} from '../src/server-call';
|
||||||
|
|
||||||
import {loadProtoFile} from './common';
|
import {loadProtoFile} from './common';
|
||||||
|
|
||||||
|
|
@ -228,4 +232,158 @@ describe('Server', () => {
|
||||||
server.bind('localhost:0', ServerCredentials.createInsecure());
|
server.bind('localhost:0', ServerCredentials.createInsecure());
|
||||||
}, /Not implemented. Use bindAsync\(\) instead/);
|
}, /Not implemented. Use bindAsync\(\) instead/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Default handlers', () => {
|
||||||
|
let server: Server;
|
||||||
|
let client: ServiceClient;
|
||||||
|
|
||||||
|
const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto');
|
||||||
|
const mathClient = (loadProtoFile(mathProtoFile).math as any).Math;
|
||||||
|
const mathServiceAttrs = mathClient.service;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
server = new Server();
|
||||||
|
server.addService(mathServiceAttrs, {});
|
||||||
|
server.bindAsync(
|
||||||
|
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
client = new mathClient(
|
||||||
|
`localhost:${port}`, grpc.credentials.createInsecure());
|
||||||
|
server.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond to a unary call with UNIMPLEMENTED', (done) => {
|
||||||
|
client.div(
|
||||||
|
{divisor: 4, dividend: 3}, (error: ServiceError, response: any) => {
|
||||||
|
assert(error);
|
||||||
|
assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond to a server stream with UNIMPLEMENTED', (done) => {
|
||||||
|
const call = client.fib({limit: 5});
|
||||||
|
|
||||||
|
call.on('data', (value: any) => {
|
||||||
|
assert.fail('No messages expected');
|
||||||
|
});
|
||||||
|
|
||||||
|
call.on('error', (err: ServiceError) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Echo service', () => {
|
||||||
|
let server: Server;
|
||||||
|
let client: ServiceClient;
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto');
|
||||||
|
const echoService =
|
||||||
|
loadProtoFile(protoFile).EchoService as ServiceClientConstructor;
|
||||||
|
|
||||||
|
server = new Server();
|
||||||
|
server.addService(echoService.service, {
|
||||||
|
echo(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>) {
|
||||||
|
callback(null, call.request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bindAsync(
|
||||||
|
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
client = new echoService(
|
||||||
|
`localhost:${port}`, grpc.credentials.createInsecure());
|
||||||
|
server.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
client.close();
|
||||||
|
server.tryShutdown(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should echo the recieved message directly', (done) => {
|
||||||
|
client.echo(
|
||||||
|
{value: 'test value', value2: 3},
|
||||||
|
(error: ServiceError, response: any) => {
|
||||||
|
assert.ifError(error);
|
||||||
|
assert.deepStrictEqual(response, {value: 'test value', value2: 3});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Generic client and server', () => {
|
||||||
|
function toString(val: any) {
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBuffer(str: string) {
|
||||||
|
return Buffer.from(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function capitalize(str: string) {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringServiceAttrs = {
|
||||||
|
capitalize: {
|
||||||
|
path: '/string/capitalize',
|
||||||
|
requestStream: false,
|
||||||
|
responseStream: false,
|
||||||
|
requestSerialize: toBuffer,
|
||||||
|
requestDeserialize: toString,
|
||||||
|
responseSerialize: toBuffer,
|
||||||
|
responseDeserialize: toString
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('String client and server', () => {
|
||||||
|
let client: ServiceClient;
|
||||||
|
let server: Server;
|
||||||
|
|
||||||
|
before((done) => {
|
||||||
|
server = new Server();
|
||||||
|
|
||||||
|
server.addService(stringServiceAttrs as any, {
|
||||||
|
capitalize(
|
||||||
|
call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>) {
|
||||||
|
callback(null, capitalize(call.request));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.bindAsync(
|
||||||
|
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
server.start();
|
||||||
|
const clientConstr = grpc.makeGenericClientConstructor(
|
||||||
|
stringServiceAttrs as any,
|
||||||
|
'unused_but_lets_appease_typescript_anyway');
|
||||||
|
client = new clientConstr(
|
||||||
|
`localhost:${port}`, grpc.credentials.createInsecure());
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after((done) => {
|
||||||
|
client.close();
|
||||||
|
server.tryShutdown(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should respond with a capitalized string', (done) => {
|
||||||
|
client.capitalize('abc', (err: ServiceError, response: string) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.strictEqual(response, 'Abc');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,10 @@
|
||||||
'GPR_BACKWARDS_COMPATIBILITY_MODE',
|
'GPR_BACKWARDS_COMPATIBILITY_MODE',
|
||||||
'GRPC_ARES=1',
|
'GRPC_ARES=1',
|
||||||
'GRPC_UV',
|
'GRPC_UV',
|
||||||
'GRPC_NODE_VERSION="1.21.0-dev"'
|
'GRPC_NODE_VERSION="1.20.3"'
|
||||||
|
],
|
||||||
|
'defines!': [
|
||||||
|
'OPENSSL_THREADS'
|
||||||
],
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['grpc_gcov=="true"', {
|
['grpc_gcov=="true"', {
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
settings:
|
settings:
|
||||||
'#': It's possible to have node_version here as a key to override the core's version.
|
'#': It's possible to have node_version here as a key to override the core's version.
|
||||||
|
node_version: 1.20.3
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ NAN_METHOD(Channel::GetConnectivityState) {
|
||||||
return Nan::ThrowError(
|
return Nan::ThrowError(
|
||||||
"Cannot call getConnectivityState on a closed Channel");
|
"Cannot call getConnectivityState on a closed Channel");
|
||||||
}
|
}
|
||||||
int try_to_connect = (int)info[0]->Equals(Nan::True());
|
int try_to_connect = (int)info[0]->StrictEquals(Nan::True());
|
||||||
info.GetReturnValue().Set(grpc_channel_check_connectivity_state(
|
info.GetReturnValue().Set(grpc_channel_check_connectivity_state(
|
||||||
channel->wrapped_channel, try_to_connect));
|
channel->wrapped_channel, try_to_connect));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ NAN_METHOD(ChannelCredentials::CreateSsl) {
|
||||||
if (!info[3]->IsObject()) {
|
if (!info[3]->IsObject()) {
|
||||||
return Nan::ThrowTypeError("createSsl's fourth argument must be an object");
|
return Nan::ThrowTypeError("createSsl's fourth argument must be an object");
|
||||||
}
|
}
|
||||||
Local<Object> object = info[3]->ToObject();
|
Local<Object> object = Nan::To<Object>(info[3]).ToLocalChecked();
|
||||||
|
|
||||||
Local<Value> checkServerIdentityValue = Nan::Get(object,
|
Local<Value> checkServerIdentityValue = Nan::Get(object,
|
||||||
Nan::New("checkServerIdentity").ToLocalChecked()).ToLocalChecked();
|
Nan::New("checkServerIdentity").ToLocalChecked()).ToLocalChecked();
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ void ServerCredentials::Init(Local<Object> exports) {
|
||||||
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
|
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
|
||||||
tpl->SetClassName(Nan::New("ServerCredentials").ToLocalChecked());
|
tpl->SetClassName(Nan::New("ServerCredentials").ToLocalChecked());
|
||||||
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
Local<Function> ctr = tpl->GetFunction();
|
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
|
||||||
Nan::Set(
|
Nan::Set(
|
||||||
ctr, Nan::New("createSsl").ToLocalChecked(),
|
ctr, Nan::New("createSsl").ToLocalChecked(),
|
||||||
Nan::GetFunction(Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked());
|
Nan::GetFunction(Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked());
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2017 gRPC authors.
|
* Copyright 2019 gRPC authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -15,18 +15,13 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const _gulp = require('gulp');
|
import * as gulp from 'gulp';
|
||||||
const help = require('gulp-help');
|
import * as jsdoc from 'gulp-jsdoc3';
|
||||||
|
import * as jshint from 'gulp-jshint';
|
||||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
import * as mocha from 'gulp-mocha';
|
||||||
const gulp = help(_gulp);
|
import * as execa from 'execa';
|
||||||
|
import * as path from 'path';
|
||||||
const jsdoc = require('gulp-jsdoc3');
|
import * as del from 'del';
|
||||||
const jshint = require('gulp-jshint');
|
|
||||||
const mocha = require('gulp-mocha');
|
|
||||||
const execa = require('execa');
|
|
||||||
const path = require('path');
|
|
||||||
const del = require('del');
|
|
||||||
|
|
||||||
const nativeCoreDir = __dirname;
|
const nativeCoreDir = __dirname;
|
||||||
const srcDir = path.resolve(nativeCoreDir, 'src');
|
const srcDir = path.resolve(nativeCoreDir, 'src');
|
||||||
|
|
@ -35,44 +30,54 @@ const testDir = path.resolve(nativeCoreDir, 'test');
|
||||||
const pkg = require('./package');
|
const pkg = require('./package');
|
||||||
const jshintConfig = pkg.jshintConfig;
|
const jshintConfig = pkg.jshintConfig;
|
||||||
|
|
||||||
gulp.task('clean', 'Delete generated files', () => {
|
const clean = () => del([path.resolve(nativeCoreDir, 'build'),
|
||||||
return del([path.resolve(nativeCoreDir, 'build'),
|
|
||||||
path.resolve(nativeCoreDir, 'ext/node')]);
|
path.resolve(nativeCoreDir, 'ext/node')]);
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('clean.all', 'Delete all files created by tasks',
|
const cleanAll = gulp.parallel(clean);
|
||||||
['clean']);
|
|
||||||
|
|
||||||
gulp.task('install', 'Install native core dependencies', () => {
|
const install = () => {
|
||||||
return execa('npm', ['install', '--build-from-source', '--unsafe-perm'],
|
return execa('npm', ['install', '--build-from-source', '--unsafe-perm'],
|
||||||
{cwd: nativeCoreDir, stdio: 'inherit'});
|
{cwd: nativeCoreDir, stdio: 'inherit'});
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('install.windows', 'Install native core dependencies for MS Windows', () => {
|
const installWindows = () => {
|
||||||
return execa('npm', ['install', '--build-from-source'],
|
return execa('npm', ['install', '--build-from-source'],
|
||||||
{cwd: nativeCoreDir, stdio: 'inherit'}).catch(() =>
|
{cwd: nativeCoreDir, stdio: 'inherit'}).catch(() =>
|
||||||
del(path.resolve(process.env.USERPROFILE, '.node-gyp', process.versions.node, 'include/node/openssl'), { force: true }).then(() =>
|
del(path.resolve(process.env.USERPROFILE, '.node-gyp', process.versions.node, 'include/node/openssl'), { force: true }).then(() =>
|
||||||
execa('npm', ['install', '--build-from-source'],
|
execa('npm', ['install', '--build-from-source'],
|
||||||
{cwd: nativeCoreDir, stdio: 'inherit'})
|
{cwd: nativeCoreDir, stdio: 'inherit'})
|
||||||
))
|
));
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('lint', 'Emits linting errors', () => {
|
const lint = () => {
|
||||||
return gulp.src([`${nativeCoreDir}/index.js`, `${srcDir}/*.js`, `${testDir}/*.js`])
|
return gulp.src([`${nativeCoreDir}/index.js`, `${srcDir}/*.js`, `${testDir}/*.js`])
|
||||||
.pipe(jshint(pkg.jshintConfig))
|
.pipe(jshint(pkg.jshintConfig))
|
||||||
.pipe(jshint.reporter('default'));
|
.pipe(jshint.reporter('default'));
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('build', 'Build native package', () => {
|
const build = () => {
|
||||||
return execa('npm', ['run', 'build'], {cwd: nativeCoreDir, stdio: 'inherit'});
|
return execa('npm', ['run', 'build'], {cwd: nativeCoreDir, stdio: 'inherit'});
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('test', 'Run all tests', ['build'], () => {
|
const runTests = () => {
|
||||||
return gulp.src(`${testDir}/*.js`).pipe(mocha({timeout: 5000, reporter: 'mocha-jenkins-reporter'}));
|
return gulp.src(`${testDir}/*.js`).pipe(mocha({timeout: 5000, reporter: 'mocha-jenkins-reporter'}));
|
||||||
});
|
}
|
||||||
|
|
||||||
gulp.task('doc.gen', 'Generate docs', (cb) => {
|
const test = gulp.series(build, runTests);
|
||||||
|
|
||||||
|
const docGen = (cb) => {
|
||||||
var config = require('./jsdoc_conf.json');
|
var config = require('./jsdoc_conf.json');
|
||||||
gulp.src([`${nativeCoreDir}/README.md`, `${nativeCoreDir}/index.js`, `${srcDir}/*.js`], {read: false})
|
return gulp.src([`${nativeCoreDir}/README.md`, `${nativeCoreDir}/index.js`, `${srcDir}/*.js`], {read: false})
|
||||||
.pipe(jsdoc(config, cb));
|
.pipe(jsdoc(config, cb));
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
clean,
|
||||||
|
cleanAll,
|
||||||
|
install,
|
||||||
|
installWindows,
|
||||||
|
lint,
|
||||||
|
build,
|
||||||
|
test,
|
||||||
|
docGen
|
||||||
|
};
|
||||||
|
|
@ -963,18 +963,6 @@ declare module "grpc" {
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
compose(callCredentials: CallCredentials): ChannelCredentials;
|
compose(callCredentials: CallCredentials): ChannelCredentials;
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the set of per-call credentials associated with this instance.
|
|
||||||
*/
|
|
||||||
getCallCredentials(): CallCredentials;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a SecureContext object generated from input parameters if this
|
|
||||||
* instance was created with createSsl, or null if this instance was created
|
|
||||||
* with createInsecure.
|
|
||||||
*/
|
|
||||||
getSecureContext(): SecureContext | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "grpc",
|
"name": "grpc",
|
||||||
"version": "1.21.0-dev",
|
"version": "1.20.3",
|
||||||
"author": "Google Inc.",
|
"author": "Google Inc.",
|
||||||
"description": "gRPC Library for Node",
|
"description": "gRPC Library for Node",
|
||||||
"homepage": "https://grpc.io/",
|
"homepage": "https://grpc.io/",
|
||||||
|
|
@ -29,10 +29,11 @@
|
||||||
"node-pre-gyp"
|
"node-pre-gyp"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/protobufjs": "^5.0.31",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"lodash.clone": "^4.5.0",
|
"lodash.clone": "^4.5.0",
|
||||||
"nan": "^2.0.0",
|
"nan": "^2.13.2",
|
||||||
"node-pre-gyp": "^0.12.0",
|
"node-pre-gyp": "^0.13.0",
|
||||||
"protobufjs": "^5.0.3"
|
"protobufjs": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ Original error: ${e.message}`;
|
||||||
error.code = e.code;
|
error.code = e.code;
|
||||||
throw error;
|
throw error;
|
||||||
} else {
|
} else {
|
||||||
|
e.message = `Failed to load ${binding_path}. ${e.message}`;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@
|
||||||
'GRPC_UV',
|
'GRPC_UV',
|
||||||
'GRPC_NODE_VERSION="${settings.get('node_version', settings.version)}"'
|
'GRPC_NODE_VERSION="${settings.get('node_version', settings.version)}"'
|
||||||
],
|
],
|
||||||
|
'defines!': [
|
||||||
|
'OPENSSL_THREADS'
|
||||||
|
],
|
||||||
'conditions': [
|
'conditions': [
|
||||||
['grpc_gcov=="true"', {
|
['grpc_gcov=="true"', {
|
||||||
% for arg, prop in [('CPPFLAGS', 'cflags'), ('DEFINES', 'defines'), ('LDFLAGS', 'ldflags')]:
|
% for arg, prop in [('CPPFLAGS', 'cflags'), ('DEFINES', 'defines'), ('LDFLAGS', 'ldflags')]:
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,11 @@
|
||||||
"node-pre-gyp"
|
"node-pre-gyp"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/protobufjs": "^5.0.31",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"lodash.clone": "^4.5.0",
|
"lodash.clone": "^4.5.0",
|
||||||
"nan": "^2.0.0",
|
"nan": "^2.13.2",
|
||||||
"node-pre-gyp": "^0.12.0",
|
"node-pre-gyp": "^0.13.0",
|
||||||
"protobufjs": "^5.0.3"
|
"protobufjs": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
set arch_list=ia32 x64
|
set arch_list=ia32 x64
|
||||||
|
|
||||||
set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0
|
set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 5.0.0
|
||||||
|
|
||||||
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
arch_list=( ia32 x64 )
|
arch_list=( ia32 x64 )
|
||||||
electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 )
|
electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 5.0.0 )
|
||||||
|
|
||||||
umask 022
|
umask 022
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
set arch_list=ia32 x64
|
set arch_list=ia32 x64
|
||||||
|
|
||||||
set node_versions=4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0
|
set node_versions=4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0 12.0.0
|
||||||
|
|
||||||
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
arch_list=( ia32 x64 )
|
arch_list=( ia32 x64 )
|
||||||
node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0 )
|
node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0 12.0.0 )
|
||||||
|
|
||||||
while true ; do
|
while true ; do
|
||||||
case $1 in
|
case $1 in
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ mkdir -p "${ARTIFACTS_OUT}"
|
||||||
|
|
||||||
npm update
|
npm update
|
||||||
|
|
||||||
node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0 )
|
node_versions=( 4.0.0 5.0.0 6.0.0 7.0.0 8.0.0 9.0.0 10.0.0 11.0.0 12.0.0 )
|
||||||
|
|
||||||
for version in ${node_versions[@]}
|
for version in ${node_versions[@]}
|
||||||
do
|
do
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _gulp from 'gulp';
|
import * as gulp from 'gulp';
|
||||||
import * as help from 'gulp-help';
|
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as mocha from 'gulp-mocha';
|
import * as mocha from 'gulp-mocha';
|
||||||
|
|
@ -24,9 +23,6 @@ import * as path from 'path';
|
||||||
import * as execa from 'execa';
|
import * as execa from 'execa';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
|
||||||
const gulp = help(_gulp);
|
|
||||||
|
|
||||||
Error.stackTraceLimit = Infinity;
|
Error.stackTraceLimit = Infinity;
|
||||||
|
|
||||||
const protojsDir = __dirname;
|
const protojsDir = __dirname;
|
||||||
|
|
@ -40,30 +36,29 @@ const execNpmVerb = (verb: string, ...args: string[]) =>
|
||||||
execa('npm', [verb, ...args], {cwd: protojsDir, stdio: 'inherit'});
|
execa('npm', [verb, ...args], {cwd: protojsDir, stdio: 'inherit'});
|
||||||
const execNpmCommand = execNpmVerb.bind(null, 'run');
|
const execNpmCommand = execNpmVerb.bind(null, 'run');
|
||||||
|
|
||||||
gulp.task('install', 'Install native core dependencies', () =>
|
const install = () => execNpmVerb('install', '--unsafe-perm');
|
||||||
execNpmVerb('install', '--unsafe-perm'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs tslint on files in src/, with linting rules defined in tslint.json.
|
* Runs tslint on files in src/, with linting rules defined in tslint.json.
|
||||||
*/
|
*/
|
||||||
gulp.task('lint', 'Emits linting errors found in src/ and test/.', () =>
|
const lint = () => execNpmCommand('check');
|
||||||
execNpmCommand('check'));
|
|
||||||
|
|
||||||
gulp.task('clean', 'Deletes transpiled code.', ['install'],
|
const cleanFiles = () => execNpmCommand('clean');
|
||||||
() => execNpmCommand('clean'));
|
|
||||||
|
|
||||||
gulp.task('clean.all', 'Deletes all files added by targets', ['clean']);
|
const clean = gulp.series(install, cleanFiles);
|
||||||
|
|
||||||
|
const cleanAll = gulp.parallel(clean);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transpiles TypeScript files in src/ and test/ to JavaScript according to the settings
|
* Transpiles TypeScript files in src/ and test/ to JavaScript according to the settings
|
||||||
* found in tsconfig.json.
|
* found in tsconfig.json.
|
||||||
*/
|
*/
|
||||||
gulp.task('compile', 'Transpiles src/ and test/.', () => execNpmCommand('compile'));
|
const compile = () => execNpmCommand('compile');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transpiles src/ and test/, and then runs all tests.
|
* Transpiles src/ and test/, and then runs all tests.
|
||||||
*/
|
*/
|
||||||
gulp.task('test', 'Runs all tests.', () => {
|
const runTests = () => {
|
||||||
if (semver.satisfies(process.version, ">=6")) {
|
if (semver.satisfies(process.version, ">=6")) {
|
||||||
return gulp.src(`${outDir}/test/**/*.js`)
|
return gulp.src(`${outDir}/test/**/*.js`)
|
||||||
.pipe(mocha({reporter: 'mocha-jenkins-reporter',
|
.pipe(mocha({reporter: 'mocha-jenkins-reporter',
|
||||||
|
|
@ -72,4 +67,15 @@ gulp.task('test', 'Runs all tests.', () => {
|
||||||
console.log(`Skipping proto-loader tests for Node ${process.version}`);
|
console.log(`Skipping proto-loader tests for Node ${process.version}`);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const test = gulp.series(install, runTests);
|
||||||
|
|
||||||
|
export {
|
||||||
|
install,
|
||||||
|
lint,
|
||||||
|
clean,
|
||||||
|
cleanAll,
|
||||||
|
compile,
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ call npm install || goto :error
|
||||||
SET JUNIT_REPORT_STACK=1
|
SET JUNIT_REPORT_STACK=1
|
||||||
SET FAILED=0
|
SET FAILED=0
|
||||||
|
|
||||||
for %%v in (6 7 8 9 10 11) do (
|
for %%v in (6 7 8 9 10 11 12) do (
|
||||||
call nvm install %%v
|
call nvm install %%v
|
||||||
call nvm use %%v
|
call nvm use %%v
|
||||||
if "%%v"=="4" (
|
if "%%v"=="4" (
|
||||||
|
|
@ -53,8 +53,8 @@ for %%v in (6 7 8 9 10 11) do (
|
||||||
|
|
||||||
node -e "process.exit(process.version.startsWith('v%%v') ? 0 : -1)" || goto :error
|
node -e "process.exit(process.version.startsWith('v%%v') ? 0 : -1)" || goto :error
|
||||||
|
|
||||||
call .\node_modules\.bin\gulp clean.all || SET FAILED=1
|
call .\node_modules\.bin\gulp cleanAll || SET FAILED=1
|
||||||
call .\node_modules\.bin\gulp setup.windows || SET FAILED=1
|
call .\node_modules\.bin\gulp setupWindows || SET FAILED=1
|
||||||
call .\node_modules\.bin\gulp test || SET FAILED=1
|
call .\node_modules\.bin\gulp test || SET FAILED=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ set -ex
|
||||||
cd $ROOT
|
cd $ROOT
|
||||||
|
|
||||||
if [ ! -n "$node_versions" ] ; then
|
if [ ! -n "$node_versions" ] ; then
|
||||||
node_versions="6 7 8 9 10 11"
|
node_versions="6 7 8 9 10 11 12"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set +ex
|
set +ex
|
||||||
|
|
@ -68,7 +68,7 @@ do
|
||||||
node -e 'process.exit(process.version.startsWith("v'$version'") ? 0 : -1)'
|
node -e 'process.exit(process.version.startsWith("v'$version'") ? 0 : -1)'
|
||||||
|
|
||||||
# Install dependencies and link packages together.
|
# Install dependencies and link packages together.
|
||||||
./node_modules/.bin/gulp clean.all
|
./node_modules/.bin/gulp cleanAll
|
||||||
./node_modules/.bin/gulp setup
|
./node_modules/.bin/gulp setup
|
||||||
|
|
||||||
# npm test calls nyc gulp test
|
# npm test calls nyc gulp test
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2017 gRPC authors.
|
* Copyright 2019 gRPC authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -15,33 +15,28 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const _gulp = require('gulp');
|
import * as gulp from 'gulp';
|
||||||
const help = require('gulp-help');
|
import * as mocha from 'gulp-mocha';
|
||||||
const mocha = require('gulp-mocha');
|
import * as execa from 'execa';
|
||||||
const execa = require('execa');
|
import * as path from 'path';
|
||||||
const path = require('path');
|
import * as del from 'del';
|
||||||
const del = require('del');
|
import * as semver from 'semver';
|
||||||
const semver = require('semver');
|
|
||||||
const linkSync = require('../util').linkSync;
|
|
||||||
|
|
||||||
// gulp-help monkeypatches tasks to have an additional description parameter
|
|
||||||
const gulp = help(_gulp);
|
|
||||||
|
|
||||||
const testDir = __dirname;
|
const testDir = __dirname;
|
||||||
const apiTestDir = path.resolve(testDir, 'api');
|
const apiTestDir = path.resolve(testDir, 'api');
|
||||||
|
|
||||||
gulp.task('install', 'Install test dependencies', () => {
|
const install = () => {
|
||||||
return execa('npm', ['install'], {cwd: testDir, stdio: 'inherit'});
|
return execa('npm', ['install'], {cwd: testDir, stdio: 'inherit'});
|
||||||
});
|
};
|
||||||
|
|
||||||
gulp.task('clean.all', 'Delete all files created by tasks', () => {});
|
const cleanAll = () => Promise.resolve();
|
||||||
|
|
||||||
gulp.task('test', 'Run API-level tests', () => {
|
const test = () => {
|
||||||
// run mocha tests matching a glob with a pre-required fixture,
|
// run mocha tests matching a glob with a pre-required fixture,
|
||||||
// returning the associated gulp stream
|
// returning the associated gulp stream
|
||||||
if (!semver.satisfies(process.version, '>=9.4')) {
|
if (!semver.satisfies(process.version, '>=10.10.0')) {
|
||||||
console.log(`Skipping cross-implementation tests for Node ${process.version}`);
|
console.log(`Skipping cross-implementation tests for Node ${process.version}`);
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const apiTestGlob = `${apiTestDir}/*.js`;
|
const apiTestGlob = `${apiTestDir}/*.js`;
|
||||||
const runTestsWithFixture = (server, client) => new Promise((resolve, reject) => {
|
const runTestsWithFixture = (server, client) => new Promise((resolve, reject) => {
|
||||||
|
|
@ -50,14 +45,14 @@ gulp.task('test', 'Run API-level tests', () => {
|
||||||
gulp.src(apiTestGlob)
|
gulp.src(apiTestGlob)
|
||||||
.pipe(mocha({
|
.pipe(mocha({
|
||||||
reporter: 'mocha-jenkins-reporter',
|
reporter: 'mocha-jenkins-reporter',
|
||||||
require: `${testDir}/fixtures/${fixture}.js`
|
require: [`${testDir}/fixtures/${fixture}.js`]
|
||||||
}))
|
}))
|
||||||
.resume() // put the stream in flowing mode
|
.resume() // put the stream in flowing mode
|
||||||
.on('end', resolve)
|
.on('end', resolve)
|
||||||
.on('error', reject);
|
.on('error', reject);
|
||||||
});
|
});
|
||||||
var runTestsArgPairs;
|
var runTestsArgPairs;
|
||||||
if (semver.satisfies(process.version, '^ 8.11.2 || >=9.4')) {
|
if (semver.satisfies(process.version, '^8.13.0 || >=10.10.0')) {
|
||||||
runTestsArgPairs = [
|
runTestsArgPairs = [
|
||||||
['native', 'native'],
|
['native', 'native'],
|
||||||
['native', 'js'],
|
['native', 'js'],
|
||||||
|
|
@ -72,4 +67,10 @@ gulp.task('test', 'Run API-level tests', () => {
|
||||||
return runTestsArgPairs.reduce((previousPromise, argPair) => {
|
return runTestsArgPairs.reduce((previousPromise, argPair) => {
|
||||||
return previousPromise.then(runTestsWithFixture.bind(null, argPair[0], argPair[1]));
|
return previousPromise.then(runTestsWithFixture.bind(null, argPair[0], argPair[1]));
|
||||||
}, Promise.resolve());
|
}, Promise.resolve());
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
install,
|
||||||
|
cleanAll,
|
||||||
|
test
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue