Merge branch 'master' into native_cares_activation

This commit is contained in:
murgatroid99 2019-05-09 15:53:34 -07:00
commit 454b4183b5
38 changed files with 1620 additions and 409 deletions

3
.gitignore vendored
View File

@ -20,3 +20,6 @@ package-lock.json
# Test generated files
coverage
# Node's bash completion file
.node_bash_completion

View File

@ -22,7 +22,7 @@ Load Balancing | :heavy_check_mark: | :x:
Other Properties | `grpc` | `@grpc/grpc-js`
-----------------|--------|----------------
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 Platforms | Linux, Windows, MacOS | All
Supported Architectures | x86, x86-64, ARM7+ | All

View File

@ -15,96 +15,55 @@
*
*/
import * as _gulp from 'gulp';
import * as help from 'gulp-help';
// gulp-help monkeypatches tasks to have an additional description parameter
const gulp = help(_gulp);
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]));
import * as gulp from 'gulp';
import * as healthCheck from './packages/grpc-health-check/gulpfile';
import * as jsCore from './packages/grpc-js/gulpfile';
import * as nativeCore from './packages/grpc-native-core/gulpfile';
import * as protobuf from './packages/proto-loader/gulpfile';
import * as internalTest from './test/gulpfile';
const root = __dirname;
gulp.task('install.all', 'Install dependencies for all subdirectory packages',
['js.core.install', 'native.core.install', 'health-check.install', 'protobuf.install', 'internal.test.install']);
const installAll = gulp.parallel(jsCore.install, nativeCore.install, healthCheck.install, protobuf.install, internalTest.install);
gulp.task('install.all.windows', 'Install dependencies for all subdirectory packages for MS Windows',
['js.core.install', 'native.core.install.windows', 'health-check.install', 'protobuf.install', 'internal.test.install']);
const installAllWindows = gulp.parallel(jsCore.install, nativeCore.installWindows, healthCheck.install, protobuf.install, internalTest.install);
gulp.task('lint', 'Emit linting errors in source and test files',
['js.core.lint', 'native.core.lint']);
const lint = gulp.parallel(jsCore.lint, nativeCore.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',
['health-check.link.add']);
const link = gulp.series(healthCheck.linkAdd);
gulp.task('link', 'Link together packages', (callback) => {
/**
* We use workarounds for linking in some modules. See npm/npm#18835
*/
runSequence('link.surface', callback);
});
const setup = gulp.series(installAll, link);
gulp.task('setup', 'One-time setup for a clean repository', (callback) => {
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);
});
const setupWindows = gulp.series(installAllWindows, link);
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',
['js.core.clean.all', 'native.core.clean.all', 'health-check.clean.all',
'internal.test.clean.all', 'protobuf.clean.all']);
const cleanAll = gulp.parallel(jsCore.cleanAll, nativeCore.cleanAll, healthCheck.cleanAll, internalTest.cleanAll, protobuf.cleanAll);
gulp.task('native.test.only', 'Run tests of native code without rebuilding anything',
['native.core.test', 'health-check.test']);
const nativeTestOnly = gulp.parallel(nativeCore.test, healthCheck.test);
gulp.task('native.test', 'Run tests of native code', (callback) => {
runSequence('build', 'native.test.only', callback);
});
const nativeTest = gulp.series(build, nativeTestOnly);
gulp.task('test.only', 'Run tests without rebuilding anything',
['js.core.test', 'native.test.only', 'protobuf.test']);
const testOnly = gulp.parallel(jsCore.test, nativeTestOnly, protobuf.test);
gulp.task('test', 'Run all tests', (callback) => {
runSequence('build', 'test.only', 'internal.test.test', callback);
});
const test = gulp.series(build, testOnly, internalTest.test);
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
};

View File

@ -11,7 +11,6 @@
"devDependencies": {
"@types/execa": "^0.8.0",
"@types/gulp": "^4.0.5",
"@types/gulp-help": "0.0.34",
"@types/gulp-mocha": "0.0.31",
"@types/ncp": "^2.0.1",
"@types/node": "^8.0.32",
@ -20,8 +19,7 @@
"coveralls": "^3.0.1",
"del": "^3.0.0",
"execa": "^0.8.0",
"gulp": "^3.9.1",
"gulp-help": "^1.6.1",
"gulp": "^4.0.1",
"gulp-jsdoc3": "^1.0.1",
"gulp-jshint": "^2.0.4",
"gulp-mocha": "^4.3.1",
@ -42,7 +40,7 @@
"semver": "^5.5.0",
"symlink": "^2.1.0",
"through2": "^2.0.3",
"ts-node": "^3.3.0",
"ts-node": "^8.1.0",
"tslint": "^5.5.0",
"typescript": "~3.3.3333",
"xml2js": "^0.4.19"

View File

@ -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'}));
});

View File

@ -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
}

View File

@ -15,8 +15,7 @@
*
*/
import * as _gulp from 'gulp';
import * as help from 'gulp-help';
import * as gulp from 'gulp';
import * as fs from 'fs';
import * as mocha from 'gulp-mocha';
@ -26,9 +25,6 @@ import * as pify from 'pify';
import * as semver from 'semver';
import { ncp } from 'ncp';
// gulp-help monkeypatches tasks to have an additional description parameter
const gulp = help(_gulp);
const ncpP = pify(ncp);
Error.stackTraceLimit = Infinity;
@ -44,35 +40,29 @@ const execNpmVerb = (verb: string, ...args: string[]) =>
execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'});
const execNpmCommand = execNpmVerb.bind(null, 'run');
gulp.task('install', 'Install native core dependencies', () =>
execNpmVerb('install', '--unsafe-perm'));
const install = () => execNpmVerb('install', '--unsafe-perm');
/**
* Runs tslint on files in src/, with linting rules defined in tslint.json.
*/
gulp.task('lint', 'Emits linting errors found in src/ and test/.', () =>
execNpmCommand('check'));
const lint = () => execNpmCommand('check');
gulp.task('clean', 'Deletes transpiled code.', ['install'],
() => execNpmCommand('clean'));
const cleanFiles = () => 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
* found in tsconfig.json.
*/
gulp.task('compile', 'Transpiles src/.', () => execNpmCommand('compile'));
const compile = () => execNpmCommand('compile');
gulp.task('copy-test-fixtures', 'Copy test fixtures.', () => {
return ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`);
});
const copyTestFixtures = () => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`);
/**
* Transpiles src/ and test/, and then runs all tests.
*/
gulp.task('test', 'Runs all tests.', ['lint', 'copy-test-fixtures'], () => {
if (semver.satisfies(process.version, '^8.11.2 || >=9.4')) {
const runTests = () => {
if (semver.satisfies(process.version, '^8.13.0 || >=10.10.0')) {
return gulp.src(`${outDir}/test/**/*.js`)
.pipe(mocha({reporter: 'mocha-jenkins-reporter',
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}`);
return Promise.resolve(null);
}
});
};
const test = gulp.series(install, copyTestFixtures, runTests);
export {
install,
lint,
clean,
cleanAll,
compile,
test
}

View File

@ -1,12 +1,12 @@
{
"name": "@grpc/grpc-js",
"version": "0.3.6",
"version": "0.4.0",
"description": "gRPC Library for Node - pure JS implementation",
"homepage": "https://grpc.io/",
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
"main": "build/src/index.js",
"engines": {
"node": "^8.11.2 || >=9.4"
"node": "^8.13.0 || >=10.10.0"
},
"keywords": [],
"author": {

View File

@ -26,6 +26,7 @@ import {Filter} from './filter';
import {FilterStackFactory} from './filter-stack';
import {Metadata} from './metadata';
import {ObjectDuplex, WriteCallback} from './object-stream';
import {StreamDecoder} from './stream-decoder';
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE, NGHTTP2_CANCEL} =
http2.constants;
@ -77,12 +78,6 @@ export type Call = {
EmitterAugmentation1<'status', StatusObject>&
ObjectDuplex<WriteObject, Buffer>;
enum ReadState {
NO_DATA,
READING_SIZE,
READING_MESSAGE
}
export class Http2CallStream extends Duplex implements Call {
credentials: CallCredentials = CallCredentials.createEmpty();
filterStack: Filter;
@ -92,13 +87,7 @@ export class Http2CallStream extends Duplex implements Call {
private pendingWriteCallback: WriteCallback|null = null;
private pendingFinalCallback: Function|null = null;
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;
private decoder = new StreamDecoder();
private isReadFilterPending = false;
private canPush = false;
@ -292,62 +281,10 @@ export class Http2CallStream extends Duplex implements Call {
});
stream.on('trailers', this.handleTrailers.bind(this));
stream.on('data', (data: Buffer) => {
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 {
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');
}
const message = this.decoder.write(data);
if (message !== null) {
this.tryPush(message);
}
});
stream.on('end', () => {

View File

@ -24,9 +24,7 @@ import {ChannelOptions} from './channel-options';
import {Status} from './constants';
import {Metadata} from './metadata';
// This symbol must be exported (for now).
// See: https://github.com/Microsoft/TypeScript/issues/20080
export const kChannel = Symbol();
const CHANNEL_SYMBOL = Symbol();
export interface UnaryCallback<ResponseType> {
(err: ServiceError|null, value?: ResponseType): void;
@ -52,26 +50,26 @@ export type ClientOptions = Partial<ChannelOptions>&{
* clients.
*/
export class Client {
private readonly[kChannel]: Channel;
private readonly[CHANNEL_SYMBOL]: Channel;
constructor(
address: string, credentials: ChannelCredentials,
options: ClientOptions = {}) {
if (options.channelOverride) {
this[kChannel] = options.channelOverride;
this[CHANNEL_SYMBOL] = options.channelOverride;
} else if (options.channelFactoryOverride) {
this[kChannel] =
this[CHANNEL_SYMBOL] =
options.channelFactoryOverride(address, credentials, options);
} else {
this[kChannel] = new Http2Channel(address, credentials, options);
this[CHANNEL_SYMBOL] = new Http2Channel(address, credentials, options);
}
}
close(): void {
this[kChannel].close();
this[CHANNEL_SYMBOL].close();
}
getChannel(): Channel {
return this[kChannel];
return this[CHANNEL_SYMBOL];
}
waitForReady(deadline: Deadline, callback: (error?: Error) => void): void {
@ -82,7 +80,7 @@ export class Client {
}
let newState;
try {
newState = this[kChannel].getConnectivityState(true);
newState = this[CHANNEL_SYMBOL].getConnectivityState(true);
} catch (e) {
callback(new Error('The channel has been closed'));
return;
@ -91,7 +89,8 @@ export class Client {
callback();
} else {
try {
this[kChannel].watchConnectivityState(newState, deadline, checkState);
this[CHANNEL_SYMBOL].watchConnectivityState(
newState, deadline, checkState);
} catch (e) {
callback(new Error('The channel has been closed'));
}
@ -188,7 +187,7 @@ export class Client {
({metadata, options, callback} =
this.checkOptionalUnaryResponseArguments<ResponseType>(
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);
if (options.credentials) {
call.setCredentials(options.credentials);
@ -229,7 +228,7 @@ export class Client {
({metadata, options, callback} =
this.checkOptionalUnaryResponseArguments<ResponseType>(
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);
if (options.credentials) {
call.setCredentials(options.credentials);
@ -277,7 +276,7 @@ export class Client {
metadata?: Metadata|CallOptions,
options?: CallOptions): ClientReadableStream<ResponseType> {
({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);
if (options.credentials) {
call.setCredentials(options.credentials);
@ -304,7 +303,7 @@ export class Client {
metadata?: Metadata|CallOptions,
options?: CallOptions): ClientDuplexStream<RequestType, ResponseType> {
({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);
if (options.credentials) {
call.setCredentials(options.credentials);

View File

@ -17,7 +17,7 @@
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 {Deadline, StatusObject} from './call-stream';
import {Channel, ConnectivityState, Http2Channel} from './channel';
@ -30,7 +30,7 @@ import {Metadata} from './metadata';
import {KeyCertPair, ServerCredentials} from './server-credentials';
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)) {
throw new Error(`@grpc/grpc-js only works on Node ${supportedNodeVersions}`);
}
@ -180,7 +180,8 @@ export {
ClientWritableStream,
ClientDuplexStream,
CallOptions,
StatusObject
StatusObject,
ServiceError
};
/* tslint:disable:no-any */
@ -248,3 +249,5 @@ export const InterceptorBuilder = () => {
export const InterceptingCall = () => {
throw new Error('Not yet implemented');
};
export {GrpcObject} from './make-client';

View File

@ -16,62 +16,75 @@
*/
import {EventEmitter} from 'events';
import * as http2 from 'http2';
import {Duplex, Readable, Writable} from 'stream';
import {ServiceError} from './call';
import {StatusObject} from './call-stream';
import {Status} from './constants';
import {Deserialize, Serialize} from './make-client';
import {Metadata} from './metadata';
function noop(): void {}
export class ServerUnaryCall<RequestType> extends EventEmitter {
cancelled: boolean;
request: RequestType|null;
export type PartialServiceError = Partial<ServiceError>;
constructor(private call: ServerCall, public metadata: Metadata) {
super();
this.cancelled = false;
this.request = null; // TODO(cjihrig): Read the unary request here.
}
type DeadlineUnitIndexSignature = {
[name: string]: number
};
getPeer(): string {
throw new Error('not implemented yet');
}
sendMetadata(responseMetadata: Metadata): void {
throw new Error('not implemented yet');
}
}
const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding';
const GRPC_ENCODING_HEADER = 'grpc-encoding';
const GRPC_MESSAGE_HEADER = 'grpc-message';
const GRPC_STATUS_HEADER = 'grpc-status';
const GRPC_TIMEOUT_HEADER = 'grpc-timeout';
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 {
cancelled: boolean;
export type ServerSurfaceCall = {
cancelled: boolean; getPeer(): string;
sendMetadata(responseMetadata: Metadata): void
};
constructor(
private call: ServerCall, public metadata: Metadata,
private deserialize: Deserialize<RequestType>) {
super();
this.cancelled = false;
}
export type ServerUnaryCall<RequestType, ResponseType> =
ServerSurfaceCall&{request: RequestType | null};
export type ServerReadableStream<RequestType, ResponseType> =
ServerSurfaceCall&Readable;
export type ServerWritableStream<RequestType, ResponseType> =
ServerSurfaceCall&Writable&{request: RequestType | null};
export type ServerDuplexStream<RequestType, ResponseType> =
ServerSurfaceCall&Duplex;
getPeer(): string {
throw new Error('not implemented yet');
}
sendMetadata(responseMetadata: Metadata): void {
throw new Error('not implemented yet');
}
}
export class ServerWritableStream<RequestType, ResponseType> extends Writable {
export class ServerUnaryCallImpl<RequestType, ResponseType> extends EventEmitter
implements ServerUnaryCall<RequestType, ResponseType> {
cancelled: boolean;
request: RequestType|null;
constructor(
private call: ServerCall, public metadata: Metadata,
private serialize: Serialize<ResponseType>) {
private call: Http2ServerCallStream<RequestType, ResponseType>,
public metadata: Metadata) {
super();
this.cancelled = false;
this.request = null; // TODO(cjihrig): Read the unary request here.
this.request = null;
}
getPeer(): string {
@ -79,19 +92,20 @@ export class ServerWritableStream<RequestType, ResponseType> extends Writable {
}
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;
constructor(
private call: ServerCall, public metadata: Metadata,
private serialize: Serialize<ResponseType>,
private deserialize: Deserialize<RequestType>) {
super();
private call: Http2ServerCallStream<RequestType, ResponseType>,
public metadata: Metadata,
private _deserialize: Deserialize<RequestType>) {
super({objectMode: true});
this.cancelled = false;
}
@ -100,13 +114,103 @@ export class ServerDuplexStream<RequestType, ResponseType> extends Duplex {
}
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');
}
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 ServerCall {}
export class ServerDuplexStreamImpl<RequestType, ResponseType> extends Duplex
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.
@ -116,12 +220,12 @@ export type sendUnaryData<ResponseType> =
// User provided handler for unary calls.
export type handleUnaryCall<RequestType, ResponseType> =
(call: ServerUnaryCall<RequestType>,
(call: ServerUnaryCall<RequestType, ResponseType>,
callback: sendUnaryData<ResponseType>) => void;
// User provided handler for client streaming calls.
export type handleClientStreamingCall<RequestType, ResponseType> =
(call: ServerReadableStream<RequestType>,
(call: ServerReadableStream<RequestType, ResponseType>,
callback: sendUnaryData<ResponseType>) => void;
// User provided handler for server streaming calls.
@ -138,11 +242,242 @@ export type HandleCall<RequestType, ResponseType> =
handleServerStreamingCall<RequestType, ResponseType>|
handleBidiStreamingCall<RequestType, ResponseType>;
export type Handler<RequestType, ResponseType> = {
func: HandleCall<RequestType, ResponseType>;
export type UnaryHandler<RequestType, ResponseType> = {
func: handleUnaryCall<RequestType, ResponseType>;
serialize: Serialize<ResponseType>;
deserialize: Deserialize<RequestType>;
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';
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');
}

View File

@ -20,20 +20,25 @@ import {AddressInfo, ListenOptions} from 'net';
import {URL} from 'url';
import {ServiceError} from './call';
import {StatusObject} from './call-stream';
import {Status} from './constants';
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';
function noop(): void {}
type PartialServiceError = Partial<ServiceError>;
const unimplementedStatusResponse: PartialServiceError = {
code: Status.UNIMPLEMENTED,
details: 'The server does not implement this method',
};
// 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 UntypedHandler = Handler<any, any>;
type UntypedServiceImplementation = {
@ -41,10 +46,11 @@ type UntypedServiceImplementation = {
};
const defaultHandler = {
unary(call: ServerUnaryCall<any>, callback: sendUnaryData<any>): void {
unary(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>): void {
callback(unimplementedStatusResponse as ServiceError, null);
},
clientStream(call: ServerReadableStream<any>, callback: sendUnaryData<any>):
clientStream(
call: ServerReadableStream<any, any>, callback: sendUnaryData<any>):
void {
callback(unimplementedStatusResponse as ServiceError, null);
},
@ -120,8 +126,8 @@ export class Server {
}
const success = this.register(
attrs.path, impl, attrs.responseSerialize, attrs.requestDeserialize,
methodType);
attrs.path, impl as UntypedHandleCall, attrs.responseSerialize,
attrs.requestDeserialize, methodType);
if (success === false) {
throw new Error(`Method handler for ${attrs.path} already provided.`);
@ -162,7 +168,7 @@ export class Server {
this.http2Server = http2.createServer();
}
// TODO(cjihrig): Set up the handlers, to allow requests to be processed.
this._setupHandlers();
function onError(err: Error): void {
callback(err, -1);
@ -193,8 +199,7 @@ export class Server {
}
this.handlers.set(
name,
{func: handler, serialize, deserialize, type: type as HandlerType});
name, {func: handler, serialize, deserialize, type} as UntypedHandler);
return true;
}
@ -227,4 +232,113 @@ export class Server {
addHttp2Port(): void {
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');
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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) {
}
}

View File

@ -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 {}

View File

@ -21,8 +21,12 @@ import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import * as grpc from '../src';
import {ServerCredentials} 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';
@ -228,4 +232,158 @@ describe('Server', () => {
server.bind('localhost:0', ServerCredentials.createInsecure());
}, /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();
});
});
});
});

View File

@ -94,7 +94,10 @@
'GPR_BACKWARDS_COMPATIBILITY_MODE',
'GRPC_ARES=1',
'GRPC_UV',
'GRPC_NODE_VERSION="1.21.0-dev"'
'GRPC_NODE_VERSION="1.20.3"'
],
'defines!': [
'OPENSSL_THREADS'
],
'conditions': [
['grpc_gcov=="true"', {

View File

@ -1,2 +1,3 @@
settings:
'#': It's possible to have node_version here as a key to override the core's version.
node_version: 1.20.3

View File

@ -289,7 +289,7 @@ NAN_METHOD(Channel::GetConnectivityState) {
return Nan::ThrowError(
"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(
channel->wrapped_channel, try_to_connect));
}

View File

@ -194,7 +194,7 @@ NAN_METHOD(ChannelCredentials::CreateSsl) {
if (!info[3]->IsObject()) {
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,
Nan::New("checkServerIdentity").ToLocalChecked()).ToLocalChecked();

View File

@ -66,7 +66,7 @@ void ServerCredentials::Init(Local<Object> exports) {
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("ServerCredentials").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<Function> ctr = tpl->GetFunction();
Local<Function> ctr = Nan::GetFunction(tpl).ToLocalChecked();
Nan::Set(
ctr, Nan::New("createSsl").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(CreateSsl)).ToLocalChecked());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2017 gRPC authors.
* 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.
@ -15,18 +15,13 @@
*
*/
const _gulp = require('gulp');
const help = require('gulp-help');
// gulp-help monkeypatches tasks to have an additional description parameter
const gulp = help(_gulp);
const jsdoc = require('gulp-jsdoc3');
const jshint = require('gulp-jshint');
const mocha = require('gulp-mocha');
const execa = require('execa');
const path = require('path');
const del = require('del');
import * as gulp from 'gulp';
import * as jsdoc from 'gulp-jsdoc3';
import * as jshint from 'gulp-jshint';
import * as mocha from 'gulp-mocha';
import * as execa from 'execa';
import * as path from 'path';
import * as del from 'del';
const nativeCoreDir = __dirname;
const srcDir = path.resolve(nativeCoreDir, 'src');
@ -35,44 +30,54 @@ const testDir = path.resolve(nativeCoreDir, 'test');
const pkg = require('./package');
const jshintConfig = pkg.jshintConfig;
gulp.task('clean', 'Delete generated files', () => {
return del([path.resolve(nativeCoreDir, 'build'),
const clean = () => del([path.resolve(nativeCoreDir, 'build'),
path.resolve(nativeCoreDir, 'ext/node')]);
});
gulp.task('clean.all', 'Delete all files created by tasks',
['clean']);
const cleanAll = gulp.parallel(clean);
gulp.task('install', 'Install native core dependencies', () => {
const install = () => {
return execa('npm', ['install', '--build-from-source', '--unsafe-perm'],
{cwd: nativeCoreDir, stdio: 'inherit'});
});
};
gulp.task('install.windows', 'Install native core dependencies for MS Windows', () => {
const installWindows = () => {
return execa('npm', ['install', '--build-from-source'],
{cwd: nativeCoreDir, stdio: 'inherit'}).catch(() =>
del(path.resolve(process.env.USERPROFILE, '.node-gyp', process.versions.node, 'include/node/openssl'), { force: true }).then(() =>
execa('npm', ['install', '--build-from-source'],
{cwd: nativeCoreDir, stdio: 'inherit'})
))
});
));
};
gulp.task('lint', 'Emits linting errors', () => {
const lint = () => {
return gulp.src([`${nativeCoreDir}/index.js`, `${srcDir}/*.js`, `${testDir}/*.js`])
.pipe(jshint(pkg.jshintConfig))
.pipe(jshint.reporter('default'));
});
};
gulp.task('build', 'Build native package', () => {
const build = () => {
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'}));
});
}
gulp.task('doc.gen', 'Generate docs', (cb) => {
const test = gulp.series(build, runTests);
const docGen = (cb) => {
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));
});
};
export {
clean,
cleanAll,
install,
installWindows,
lint,
build,
test,
docGen
};

View File

@ -963,18 +963,6 @@ declare module "grpc" {
* instance.
*/
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;
}
/**

View File

@ -1,6 +1,6 @@
{
"name": "grpc",
"version": "1.21.0-dev",
"version": "1.20.3",
"author": "Google Inc.",
"description": "gRPC Library for Node",
"homepage": "https://grpc.io/",
@ -29,10 +29,11 @@
"node-pre-gyp"
],
"dependencies": {
"@types/protobufjs": "^5.0.31",
"lodash.camelcase": "^4.3.0",
"lodash.clone": "^4.5.0",
"nan": "^2.0.0",
"node-pre-gyp": "^0.12.0",
"nan": "^2.13.2",
"node-pre-gyp": "^0.13.0",
"protobufjs": "^5.0.3"
},
"devDependencies": {

View File

@ -54,6 +54,7 @@ Original error: ${e.message}`;
error.code = e.code;
throw error;
} else {
e.message = `Failed to load ${binding_path}. ${e.message}`;
throw e;
}
}

View File

@ -88,6 +88,9 @@
'GRPC_UV',
'GRPC_NODE_VERSION="${settings.get('node_version', settings.version)}"'
],
'defines!': [
'OPENSSL_THREADS'
],
'conditions': [
['grpc_gcov=="true"', {
% for arg, prop in [('CPPFLAGS', 'cflags'), ('DEFINES', 'defines'), ('LDFLAGS', 'ldflags')]:

View File

@ -31,10 +31,11 @@
"node-pre-gyp"
],
"dependencies": {
"@types/protobufjs": "^5.0.31",
"lodash.camelcase": "^4.3.0",
"lodash.clone": "^4.5.0",
"nan": "^2.0.0",
"node-pre-gyp": "^0.12.0",
"nan": "^2.13.2",
"node-pre-gyp": "^0.13.0",
"protobufjs": "^5.0.3"
},
"devDependencies": {

View File

@ -14,7 +14,7 @@
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

View File

@ -16,7 +16,7 @@
set -ex
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

View File

@ -14,7 +14,7 @@
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

View File

@ -16,7 +16,7 @@
set -ex
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
case $1 in

View File

@ -26,7 +26,7 @@ mkdir -p "${ARTIFACTS_OUT}"
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[@]}
do

View File

@ -15,8 +15,7 @@
*
*/
import * as _gulp from 'gulp';
import * as help from 'gulp-help';
import * as gulp from 'gulp';
import * as fs from 'fs';
import * as mocha from 'gulp-mocha';
@ -24,9 +23,6 @@ import * as path from 'path';
import * as execa from 'execa';
import * as semver from 'semver';
// gulp-help monkeypatches tasks to have an additional description parameter
const gulp = help(_gulp);
Error.stackTraceLimit = Infinity;
const protojsDir = __dirname;
@ -40,30 +36,29 @@ const execNpmVerb = (verb: string, ...args: string[]) =>
execa('npm', [verb, ...args], {cwd: protojsDir, stdio: 'inherit'});
const execNpmCommand = execNpmVerb.bind(null, 'run');
gulp.task('install', 'Install native core dependencies', () =>
execNpmVerb('install', '--unsafe-perm'));
const install = () => execNpmVerb('install', '--unsafe-perm');
/**
* Runs tslint on files in src/, with linting rules defined in tslint.json.
*/
gulp.task('lint', 'Emits linting errors found in src/ and test/.', () =>
execNpmCommand('check'));
const lint = () => execNpmCommand('check');
gulp.task('clean', 'Deletes transpiled code.', ['install'],
() => execNpmCommand('clean'));
const cleanFiles = () => 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
* 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.
*/
gulp.task('test', 'Runs all tests.', () => {
const runTests = () => {
if (semver.satisfies(process.version, ">=6")) {
return gulp.src(`${outDir}/test/**/*.js`)
.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}`);
return Promise.resolve(null);
}
});
}
const test = gulp.series(install, runTests);
export {
install,
lint,
clean,
cleanAll,
compile,
test
}

View File

@ -38,7 +38,7 @@ call npm install || goto :error
SET JUNIT_REPORT_STACK=1
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 use %%v
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
call .\node_modules\.bin\gulp clean.all || SET FAILED=1
call .\node_modules\.bin\gulp setup.windows || SET FAILED=1
call .\node_modules\.bin\gulp cleanAll || SET FAILED=1
call .\node_modules\.bin\gulp setupWindows || SET FAILED=1
call .\node_modules\.bin\gulp test || SET FAILED=1
)

View File

@ -26,7 +26,7 @@ set -ex
cd $ROOT
if [ ! -n "$node_versions" ] ; then
node_versions="6 7 8 9 10 11"
node_versions="6 7 8 9 10 11 12"
fi
set +ex
@ -68,7 +68,7 @@ do
node -e 'process.exit(process.version.startsWith("v'$version'") ? 0 : -1)'
# Install dependencies and link packages together.
./node_modules/.bin/gulp clean.all
./node_modules/.bin/gulp cleanAll
./node_modules/.bin/gulp setup
# npm test calls nyc gulp test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2017 gRPC authors.
* 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.
@ -15,33 +15,28 @@
*
*/
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 semver = require('semver');
const linkSync = require('../util').linkSync;
// gulp-help monkeypatches tasks to have an additional description parameter
const gulp = help(_gulp);
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 * as semver from 'semver';
const testDir = __dirname;
const apiTestDir = path.resolve(testDir, 'api');
gulp.task('install', 'Install test dependencies', () => {
const install = () => {
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,
// 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}`);
return;
return Promise.resolve();
}
const apiTestGlob = `${apiTestDir}/*.js`;
const runTestsWithFixture = (server, client) => new Promise((resolve, reject) => {
@ -50,14 +45,14 @@ gulp.task('test', 'Run API-level tests', () => {
gulp.src(apiTestGlob)
.pipe(mocha({
reporter: 'mocha-jenkins-reporter',
require: `${testDir}/fixtures/${fixture}.js`
require: [`${testDir}/fixtures/${fixture}.js`]
}))
.resume() // put the stream in flowing mode
.on('end', resolve)
.on('error', reject);
});
var runTestsArgPairs;
if (semver.satisfies(process.version, '^ 8.11.2 || >=9.4')) {
if (semver.satisfies(process.version, '^8.13.0 || >=10.10.0')) {
runTestsArgPairs = [
['native', 'native'],
['native', 'js'],
@ -72,4 +67,10 @@ gulp.task('test', 'Run API-level tests', () => {
return runTestsArgPairs.reduce((previousPromise, argPair) => {
return previousPromise.then(runTestsWithFixture.bind(null, argPair[0], argPair[1]));
}, Promise.resolve());
});
};
export {
install,
cleanAll,
test
};