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 | ||||
| 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` | ||||
| -----------------|--------|---------------- | ||||
| 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 | ||||
|  |  | |||
							
								
								
									
										111
									
								
								gulpfile.ts
								
								
								
								
							
							
						
						
									
										111
									
								
								gulpfile.ts
								
								
								
								
							|  | @ -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 | ||||
| }; | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
|  | @ -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 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 | ||||
| } | ||||
|  | @ -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": { | ||||
|  |  | |||
|  | @ -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', () => { | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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'; | ||||
|  |  | |||
|  | @ -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'); | ||||
| } | ||||
|  |  | |||
|  | @ -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'); | ||||
| } | ||||
|  |  | |||
|  | @ -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 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(); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -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"', { | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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)); | ||||
| } | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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()); | ||||
|  |  | |||
|  | @ -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 | ||||
| }; | ||||
|  | @ -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; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  |  | |||
|  | @ -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": { | ||||
|  |  | |||
|  | @ -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; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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')]: | ||||
|  |  | |||
|  | @ -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": { | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue