From c2fd15584fdcdf5a8d438f33985a974c7e45981c Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Tue, 19 Dec 2017 11:57:35 -0800 Subject: [PATCH 01/11] grpc-js-core: docs for call.ts --- packages/grpc-js-core/src/call.ts | 29 +++++++++++++++++++++-------- packages/grpc-js-core/src/client.ts | 4 ++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js-core/src/call.ts b/packages/grpc-js-core/src/call.ts index 5abc0479..87a8d9ae 100644 --- a/packages/grpc-js-core/src/call.ts +++ b/packages/grpc-js-core/src/call.ts @@ -7,16 +7,17 @@ import {Status} from './constants'; import {Metadata} from './metadata'; import {ObjectReadable, ObjectWritable} from './object-stream'; -export interface ServiceError extends Error { +/** + * A type extending the built-in Error object with additional fields. + */ +export type ServiceError = { code?: number; metadata?: Metadata; -} - -export class ServiceErrorImpl extends Error implements ServiceError { - code?: number; - metadata?: Metadata; -} +} & Error; +/** + * A base type for all user-facing values returned by client-side method calls. + */ export type Call = { cancel(): void; getPeer(): string; @@ -24,16 +25,28 @@ export type Call = { & EmitterAugmentation1<'status', StatusObject> & EventEmitter; +/** + * A type representing the return value of a unary method call. + */ export type ClientUnaryCall = Call; +/** + * A type representing the return value of a server stream method call. + */ export type ClientReadableStream = { deserialize: (chunk: Buffer) => ResponseType; } & Call & ObjectReadable; +/** + * A type representing the return value of a client stream method call. + */ export type ClientWritableStream = { serialize: (value: RequestType) => Buffer; } & Call & ObjectWritable; +/** + * A type representing the return value of a bidirectional stream method call. + */ export type ClientDuplexStream = ClientWritableStream & ClientReadableStream; @@ -78,7 +91,7 @@ function setUpReadableStream( call.on('status', (status: StatusObject) => { stream.emit('status', status); if (status.code !== Status.OK) { - const error = new ServiceErrorImpl(status.details); + const error: ServiceError = new Error(status.details); error.code = status.code; error.metadata = status.metadata; stream.emit('error', error); diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index 862d212c..ecfacccd 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -1,7 +1,7 @@ import {once} from 'lodash'; import {URL} from 'url'; -import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError, ServiceErrorImpl} from './call'; +import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError} from './call'; import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream'; import {Channel, ChannelOptions, Http2Channel} from './channel'; import {ChannelCredentials} from './channel-credentials'; @@ -83,7 +83,7 @@ export class Client { if (status.code === Status.OK) { callback(null, responseMessage as ResponseType); } else { - const error = new ServiceErrorImpl(status.details); + const error: ServiceError = new Error(status.details); error.code = status.code; error.metadata = status.metadata; callback(error); From 5bd0386d8d72488371e280c15c5af2e82b94eab3 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Tue, 19 Dec 2017 13:57:41 -0800 Subject: [PATCH 02/11] grpc-js-core: support propagation cancellation --- packages/grpc-js-core/src/call-stream.ts | 2 -- packages/grpc-js-core/src/channel.ts | 25 +++++++++++++++++++++++- packages/grpc-js-core/src/client.ts | 4 ++-- packages/grpc-js-core/src/constants.ts | 8 ++++++++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 877978a8..3a18d473 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -19,8 +19,6 @@ export interface CallStreamOptions { flags: number; } -export type CallOptions = Partial; - export interface StatusObject { code: Status; details: string; diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index 51ec1fa5..13ccab33 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -3,16 +3,26 @@ import * as http2 from 'http2'; import {checkServerIdentity, SecureContext, PeerCertificate} from 'tls'; import * as url from 'url'; +import {Call} from './call'; import {CallCredentials} from './call-credentials'; import {CallCredentialsFilterFactory} from './call-credentials-filter'; -import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; +import {CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; import {ChannelCredentials} from './channel-credentials'; import {CompressionFilterFactory} from './compression-filter'; +import {EmitterAugmentation0} from './events'; import {Status} from './constants'; import {DeadlineFilterFactory} from './deadline-filter'; import {FilterStackFactory} from './filter-stack'; import {Metadata, MetadataObject} from './metadata'; import { MetadataStatusFilterFactory } from './metadata-status-filter'; +import { PropagateFlags } from './index'; + +export type CallOptions = { + // Represents a parent server call. + // For our purposes we only need to know of the 'cancelled' event. + parent?: EmitterAugmentation0<'cancelled'> & EventEmitter; + propagate_flags?: number; +} & Partial; const IDLE_TIMEOUT_MS = 300000; @@ -249,6 +259,19 @@ export class Http2Channel extends EventEmitter implements Channel { let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory); this.startHttp2Stream(methodName, stream, metadata); + + // handle propagation flags + const propagateFlags = typeof options.propagate_flags === 'number' ? + options.propagate_flags : PropagateFlags.DEFAULTS; + if (options.parent) { + // TODO(kjin): Implement other propagation flags. + if (propagateFlags & PropagateFlags.CANCELLATION) { + options.parent.on('cancelled', () => { + stream.cancelWithStatus(Status.CANCELLED, 'Cancellation propagated from parent call'); + }); + } + } + return stream; } diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index ecfacccd..c14328a9 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -2,8 +2,8 @@ import {once} from 'lodash'; import {URL} from 'url'; import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError} from './call'; -import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream'; -import {Channel, ChannelOptions, Http2Channel} from './channel'; +import {CallStream, StatusObject, WriteObject} from './call-stream'; +import {CallOptions, Channel, ChannelOptions, Http2Channel} from './channel'; import {ChannelCredentials} from './channel-credentials'; import {Status} from './constants'; import {Metadata} from './metadata'; diff --git a/packages/grpc-js-core/src/constants.ts b/packages/grpc-js-core/src/constants.ts index a72f3388..3b18d870 100644 --- a/packages/grpc-js-core/src/constants.ts +++ b/packages/grpc-js-core/src/constants.ts @@ -17,3 +17,11 @@ export enum Status { DATA_LOSS, UNAUTHENTICATED } + +export enum PropagateFlags { + DEADLINE = 1, + CENSUS_STATS_CONTEXT = 2, + CENSUS_TRACING_CONTEXT = 4, + CANCELLATION = 8, + DEFAULTS = 65535 +} From b31f345c8d984aee633f20e34e3ee5b03b6c86bc Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Thu, 25 Jan 2018 11:26:02 -0800 Subject: [PATCH 03/11] Revert "grpc-js-core: support propagation cancellation" This reverts commit 8a31711431071084a3ad972b122a27653fe78be2. --- packages/grpc-js-core/src/call-stream.ts | 2 ++ packages/grpc-js-core/src/channel.ts | 25 +----------------------- packages/grpc-js-core/src/client.ts | 4 ++-- packages/grpc-js-core/src/constants.ts | 8 -------- 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 3a18d473..877978a8 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -19,6 +19,8 @@ export interface CallStreamOptions { flags: number; } +export type CallOptions = Partial; + export interface StatusObject { code: Status; details: string; diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index 13ccab33..51ec1fa5 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -3,26 +3,16 @@ import * as http2 from 'http2'; import {checkServerIdentity, SecureContext, PeerCertificate} from 'tls'; import * as url from 'url'; -import {Call} from './call'; import {CallCredentials} from './call-credentials'; import {CallCredentialsFilterFactory} from './call-credentials-filter'; -import {CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; +import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream'; import {ChannelCredentials} from './channel-credentials'; import {CompressionFilterFactory} from './compression-filter'; -import {EmitterAugmentation0} from './events'; import {Status} from './constants'; import {DeadlineFilterFactory} from './deadline-filter'; import {FilterStackFactory} from './filter-stack'; import {Metadata, MetadataObject} from './metadata'; import { MetadataStatusFilterFactory } from './metadata-status-filter'; -import { PropagateFlags } from './index'; - -export type CallOptions = { - // Represents a parent server call. - // For our purposes we only need to know of the 'cancelled' event. - parent?: EmitterAugmentation0<'cancelled'> & EventEmitter; - propagate_flags?: number; -} & Partial; const IDLE_TIMEOUT_MS = 300000; @@ -259,19 +249,6 @@ export class Http2Channel extends EventEmitter implements Channel { let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory); this.startHttp2Stream(methodName, stream, metadata); - - // handle propagation flags - const propagateFlags = typeof options.propagate_flags === 'number' ? - options.propagate_flags : PropagateFlags.DEFAULTS; - if (options.parent) { - // TODO(kjin): Implement other propagation flags. - if (propagateFlags & PropagateFlags.CANCELLATION) { - options.parent.on('cancelled', () => { - stream.cancelWithStatus(Status.CANCELLED, 'Cancellation propagated from parent call'); - }); - } - } - return stream; } diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index c14328a9..ecfacccd 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -2,8 +2,8 @@ import {once} from 'lodash'; import {URL} from 'url'; import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError} from './call'; -import {CallStream, StatusObject, WriteObject} from './call-stream'; -import {CallOptions, Channel, ChannelOptions, Http2Channel} from './channel'; +import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream'; +import {Channel, ChannelOptions, Http2Channel} from './channel'; import {ChannelCredentials} from './channel-credentials'; import {Status} from './constants'; import {Metadata} from './metadata'; diff --git a/packages/grpc-js-core/src/constants.ts b/packages/grpc-js-core/src/constants.ts index 3b18d870..a72f3388 100644 --- a/packages/grpc-js-core/src/constants.ts +++ b/packages/grpc-js-core/src/constants.ts @@ -17,11 +17,3 @@ export enum Status { DATA_LOSS, UNAUTHENTICATED } - -export enum PropagateFlags { - DEADLINE = 1, - CENSUS_STATS_CONTEXT = 2, - CENSUS_TRACING_CONTEXT = 4, - CANCELLATION = 8, - DEFAULTS = 65535 -} From bae93fff389a5fffdd7fac3c28a7febea355a0cf Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Tue, 19 Dec 2017 15:33:36 -0800 Subject: [PATCH 04/11] grpc-js-core: split incoming headers on comma for metadata --- packages/grpc-js-core/src/metadata.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-core/src/metadata.ts b/packages/grpc-js-core/src/metadata.ts index dbdb4e91..84e9160e 100644 --- a/packages/grpc-js-core/src/metadata.ts +++ b/packages/grpc-js-core/src/metadata.ts @@ -181,6 +181,11 @@ export class Metadata { }); return result; } + + // For compatibility with the other Metadata implementation + private _getCoreRepresentation() { + return this.internalRepr; + } /** * Returns a new Metadata object based fields in a given IncomingHttpHeaders @@ -196,7 +201,8 @@ export class Metadata { result.add(key, Buffer.from(value, 'base64')); }); } else if (values !== undefined) { - result.add(key, Buffer.from(values, 'base64')); + values.split(',').map(v => v.trim()).forEach(v => + result.add(key, Buffer.from(v, 'base64'))); } } else { if (Array.isArray(values)) { @@ -204,7 +210,8 @@ export class Metadata { result.add(key, value); }); } else if (values !== undefined) { - result.add(key, values); + values.split(',').map(v => v.trim()).forEach(v => + result.add(key, v)); } } }); From e351cfe8c4934e13ee4dbc2b274d594739340eba Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Tue, 19 Dec 2017 16:35:05 -0800 Subject: [PATCH 05/11] grpc-js-core: add user-agent --- packages/grpc-js-core/src/channel.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index 51ec1fa5..cd88e44d 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -14,6 +14,8 @@ import {FilterStackFactory} from './filter-stack'; import {Metadata, MetadataObject} from './metadata'; import { MetadataStatusFilterFactory } from './metadata-status-filter'; +const { version: clientVersion } = require('../../package'); + const IDLE_TIMEOUT_MS = 300000; const MIN_CONNECT_TIMEOUT_MS = 20000; @@ -28,7 +30,8 @@ const { HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_SCHEME, - HTTP2_HEADER_TE + HTTP2_HEADER_TE, + HTTP2_HEADER_USER_AGENT } = http2.constants; /** @@ -209,6 +212,7 @@ export class Http2Channel extends EventEmitter implements Channel { .then(([metadataValue]) => { let headers = metadataValue.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = this.authority.hostname; + headers[HTTP2_HEADER_USER_AGENT] = `grpc-node/${clientVersion}`; headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; headers[HTTP2_HEADER_METHOD] = 'POST'; headers[HTTP2_HEADER_PATH] = methodName; From b8bfc0fcd6181614893d8e54408647e89f96c7ac Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 20 Dec 2017 14:31:13 -0800 Subject: [PATCH 06/11] grpc-js-core: keep up-to-date with node-master --- packages/grpc-js-core/src/channel.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index cd88e44d..6f6b72de 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -115,7 +115,7 @@ export class Http2Channel extends EventEmitter implements Channel { case ConnectivityState.IDLE: case ConnectivityState.SHUTDOWN: if (this.subChannel) { - this.subChannel.shutdown({graceful: true}); + (this.subChannel as any).close({graceful: true}); this.subChannel.removeListener('connect', this.subChannelConnectCallback); this.subChannel.removeListener('close', this.subChannelCloseCallback); this.subChannel = null; @@ -153,7 +153,7 @@ export class Http2Channel extends EventEmitter implements Channel { return checkServerIdentity(sslTargetNameOverride, cert); } } - subChannel = http2.connect(this.authority, connectionOptions); + subChannel = http2.connect(this.authority, connectionOptions) as typeof subChannel; } this.subChannel = subChannel; let now = new Date(); @@ -162,7 +162,7 @@ export class Http2Channel extends EventEmitter implements Channel { MIN_CONNECT_TIMEOUT_MS); let connectionTimerId: NodeJS.Timer = setTimeout(() => { // This should trigger the 'close' event, which will send us back to TRANSIENT_FAILURE - subChannel.shutdown(); + (subChannel as any).close(); }, connectionTimeout); this.subChannelConnectCallback = () => { // Connection succeeded @@ -221,9 +221,7 @@ export class Http2Channel extends EventEmitter implements Channel { if (this.connectivityState === ConnectivityState.READY) { const session: http2.ClientHttp2Session = this.subChannel!; // Prevent the HTTP/2 session from keeping the process alive. - // TODO(kjin): Monitor nodejs/node#17620, which adds unref - // directly to the Http2Session object. - session.socket.unref(); + (session as any).unref(); stream.attachHttp2Stream(session.request(headers)); } else { /* In this case, we lost the connection while finalizing From 6b6443d215873a7704bb28a39463519077dd5e06 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Fri, 22 Dec 2017 14:49:51 -0800 Subject: [PATCH 07/11] grpc-js-core: clone local metadata before applying filters --- packages/grpc-js-core/src/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index 6f6b72de..16294b2e 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -207,7 +207,7 @@ export class Http2Channel extends EventEmitter implements Channel { private startHttp2Stream( methodName: string, stream: Http2CallStream, metadata: Metadata) { let finalMetadata: Promise = - stream.filterStack.sendMetadata(Promise.resolve(metadata)); + stream.filterStack.sendMetadata(Promise.resolve(metadata.clone())); Promise.all([finalMetadata, this.connect()]) .then(([metadataValue]) => { let headers = metadataValue.toHttp2Headers(); From 1d1aa273f009383d43523a9d83ff7a7f47789a4b Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Fri, 22 Dec 2017 15:30:17 -0800 Subject: [PATCH 08/11] grpc-js-core: clear deadline timers --- packages/grpc-js-core/src/client.ts | 6 +++++- packages/grpc-js-core/src/deadline-filter.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index ecfacccd..f3a1de26 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -35,7 +35,11 @@ export class Client { void { let cb: (error: Error|null) => void = once(callback); let callbackCalled = false; + let timer: NodeJS.Timer | null = null; this.channel.connect().then(() => { + if (timer) { + clearTimeout(timer); + } cb(null); }); if (deadline !== Infinity) { @@ -49,7 +53,7 @@ export class Client { if (timeout < 0) { timeout = 0; } - setTimeout(() => { + timer = setTimeout(() => { cb(new Error('Failed to connect before the deadline')); }, timeout); } diff --git a/packages/grpc-js-core/src/deadline-filter.ts b/packages/grpc-js-core/src/deadline-filter.ts index c3ed045b..428ed8a9 100644 --- a/packages/grpc-js-core/src/deadline-filter.ts +++ b/packages/grpc-js-core/src/deadline-filter.ts @@ -20,6 +20,7 @@ function getDeadline(deadline: number) { } export class DeadlineFilter extends BaseFilter implements Filter { + private timer: NodeJS.Timer | null = null; private deadline: number; constructor( private readonly channel: Http2Channel, @@ -37,10 +38,11 @@ export class DeadlineFilter extends BaseFilter implements Filter { timeout = 0; } if (this.deadline !== Infinity) { - setTimeout(() => { + this.timer = setTimeout(() => { callStream.cancelWithStatus( Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); }, timeout); + callStream.on('status', () => clearTimeout(this.timer as NodeJS.Timer)); } } From 063e11162ea5cf635cc2cd70a5dc5909a3032611 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Fri, 22 Dec 2017 15:30:37 -0800 Subject: [PATCH 09/11] grpc-js-core: change rstStream to close --- packages/grpc-js-core/src/call-stream.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 877978a8..3fc9c12e 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -9,7 +9,7 @@ import {FilterStackFactory} from './filter-stack'; import {Metadata} from './metadata'; import {ObjectDuplex} from './object-stream'; -const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants; +const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE, NGHTTP2_CANCEL} = http2.constants; export type Deadline = Date | number; @@ -156,7 +156,7 @@ export class Http2CallStream extends Duplex implements CallStream { attachHttp2Stream(stream: http2.ClientHttp2Stream): void { if (this.finalStatus !== null) { - stream.rstWithCancel(); + (stream as any).close(NGHTTP2_CANCEL); } else { this.http2Stream = stream; stream.on('response', (headers, flags) => { @@ -328,7 +328,7 @@ export class Http2CallStream extends Duplex implements CallStream { if (this.http2Stream !== null && !this.http2Stream.destroyed) { /* TODO(murgatroid99): Determine if we want to send different RST_STREAM * codes based on the status code */ - this.http2Stream.rstWithCancel(); + (this.http2Stream as any).close(NGHTTP2_CANCEL); } } From 5e00d18362373ab9c7f000716db80b7d950c49d8 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 31 Jan 2018 13:55:14 -0800 Subject: [PATCH 10/11] address comments --- packages/grpc-js-core/src/call.ts | 12 +++++------- packages/grpc-js-core/src/channel.ts | 23 ++++++++++++++++++----- packages/grpc-js-core/src/client.ts | 13 ++----------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/grpc-js-core/src/call.ts b/packages/grpc-js-core/src/call.ts index 87a8d9ae..af305b24 100644 --- a/packages/grpc-js-core/src/call.ts +++ b/packages/grpc-js-core/src/call.ts @@ -6,14 +6,12 @@ import {CallStream, StatusObject, WriteObject} from './call-stream'; import {Status} from './constants'; import {Metadata} from './metadata'; import {ObjectReadable, ObjectWritable} from './object-stream'; +import * as _ from 'lodash'; /** * A type extending the built-in Error object with additional fields. */ -export type ServiceError = { - code?: number; - metadata?: Metadata; -} & Error; +export type ServiceError = StatusObject & Error; /** * A base type for all user-facing values returned by client-side method calls. @@ -91,9 +89,9 @@ function setUpReadableStream( call.on('status', (status: StatusObject) => { stream.emit('status', status); if (status.code !== Status.OK) { - const error: ServiceError = new Error(status.details); - error.code = status.code; - error.metadata = status.metadata; + const statusName = _.invert(Status)[status.code]; + const message: string = `${status.code} ${statusName}: ${status.details}`; + const error: ServiceError = Object.assign(new Error(status.details), status); stream.emit('error', error); } }); diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index 16294b2e..bc5d95d7 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -37,7 +37,12 @@ const { /** * An interface that contains options used when initializing a Channel instance. */ -export interface ChannelOptions { [index: string]: string|number; } +export interface ChannelOptions { + 'grpc.ssl_target_name_override': string; + 'grpc.primary_user_agent': string; + 'grpc.secondary_user_agent': string; + [key: string]: string | number; +} export enum ConnectivityState { CONNECTING, @@ -75,6 +80,7 @@ export interface Channel extends EventEmitter { } export class Http2Channel extends EventEmitter implements Channel { + private readonly userAgent: string; private readonly authority: url.URL; private connectivityState: ConnectivityState = ConnectivityState.IDLE; /* For now, we have up to one subchannel, which will exist as long as we are @@ -148,12 +154,12 @@ export class Http2Channel extends EventEmitter implements Channel { // to override the target hostname when checking server identity. // This option is used for testing only. if (this.options['grpc.ssl_target_name_override']) { - const sslTargetNameOverride = this.options['grpc.ssl_target_name_override'] as string; + const sslTargetNameOverride = this.options['grpc.ssl_target_name_override']!; connectionOptions.checkServerIdentity = (host: string, cert: PeerCertificate): Error | undefined => { return checkServerIdentity(sslTargetNameOverride, cert); } } - subChannel = http2.connect(this.authority, connectionOptions) as typeof subChannel; + subChannel = http2.connect(this.authority, connectionOptions); } this.subChannel = subChannel; let now = new Date(); @@ -184,7 +190,7 @@ export class Http2Channel extends EventEmitter implements Channel { constructor( address: string, public readonly credentials: ChannelCredentials, - private readonly options: ChannelOptions) { + private readonly options: Partial) { super(); if (credentials.getSecureContext() === null) { this.authority = new url.URL(`http://${address}`); @@ -202,6 +208,13 @@ export class Http2Channel extends EventEmitter implements Channel { * a value of type NodeJS.Timer. */ this.backoffTimerId = setTimeout(() => {}, 0); clearTimeout(this.backoffTimerId); + + // Build user-agent string. + this.userAgent = [ + options['grpc.primary_user_agent'], + `grpc-node-js/${clientVersion}`, + options['grpc.secondary_user_agent'] + ].filter(e => e).join(' '); // remove falsey values first } private startHttp2Stream( @@ -212,7 +225,7 @@ export class Http2Channel extends EventEmitter implements Channel { .then(([metadataValue]) => { let headers = metadataValue.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = this.authority.hostname; - headers[HTTP2_HEADER_USER_AGENT] = `grpc-node/${clientVersion}`; + headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; headers[HTTP2_HEADER_METHOD] = 'POST'; headers[HTTP2_HEADER_PATH] = methodName; diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index f3a1de26..5f9c670b 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -16,14 +16,7 @@ export class Client { private readonly channel: Channel; constructor( address: string, credentials: ChannelCredentials, - options: ChannelOptions = {}) { - if (options['grpc.primary_user_agent']) { - options['grpc.primary_user_agent'] += ' '; - } else { - options['grpc.primary_user_agent'] = ''; - } - // TODO(murgatroid99): Figure out how to get version number - // options['grpc.primary_user_agent'] += 'grpc-node/' + version; + options: Partial = {}) { this.channel = new Http2Channel(address, credentials, options); } @@ -87,9 +80,7 @@ export class Client { if (status.code === Status.OK) { callback(null, responseMessage as ResponseType); } else { - const error: ServiceError = new Error(status.details); - error.code = status.code; - error.metadata = status.metadata; + const error: ServiceError = Object.assign(new Error(status.details), status); callback(error); } }); From 8108cc606e6d86a3f3e46507d0069231f0a593ee Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Tue, 13 Feb 2018 16:43:44 -0500 Subject: [PATCH 11/11] grpc-js-core: update type definitions --- packages/grpc-js-core/package.json | 2 +- packages/grpc-js-core/src/call-stream.ts | 4 ++-- packages/grpc-js-core/src/channel.ts | 6 +++--- packages/grpc-js-core/test/test-call-stream.ts | 3 +++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-core/package.json b/packages/grpc-js-core/package.json index e0d48bd4..ffdc2b4a 100644 --- a/packages/grpc-js-core/package.json +++ b/packages/grpc-js-core/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@types/lodash": "^4.14.77", "@types/mocha": "^2.2.43", - "@types/node": "^8.0.55", + "@types/node": "^9.4.6", "clang-format": "^1.0.55", "gts": "^0.5.1", "typescript": "~2.7.0" diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 3fc9c12e..849d685f 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -156,7 +156,7 @@ export class Http2CallStream extends Duplex implements CallStream { attachHttp2Stream(stream: http2.ClientHttp2Stream): void { if (this.finalStatus !== null) { - (stream as any).close(NGHTTP2_CANCEL); + stream.close(NGHTTP2_CANCEL); } else { this.http2Stream = stream; stream.on('response', (headers, flags) => { @@ -328,7 +328,7 @@ export class Http2CallStream extends Duplex implements CallStream { if (this.http2Stream !== null && !this.http2Stream.destroyed) { /* TODO(murgatroid99): Determine if we want to send different RST_STREAM * codes based on the status code */ - (this.http2Stream as any).close(NGHTTP2_CANCEL); + this.http2Stream.close(NGHTTP2_CANCEL); } } diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index bc5d95d7..193ae9a5 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -121,7 +121,7 @@ export class Http2Channel extends EventEmitter implements Channel { case ConnectivityState.IDLE: case ConnectivityState.SHUTDOWN: if (this.subChannel) { - (this.subChannel as any).close({graceful: true}); + this.subChannel.close(); this.subChannel.removeListener('connect', this.subChannelConnectCallback); this.subChannel.removeListener('close', this.subChannelCloseCallback); this.subChannel = null; @@ -168,7 +168,7 @@ export class Http2Channel extends EventEmitter implements Channel { MIN_CONNECT_TIMEOUT_MS); let connectionTimerId: NodeJS.Timer = setTimeout(() => { // This should trigger the 'close' event, which will send us back to TRANSIENT_FAILURE - (subChannel as any).close(); + subChannel.close(); }, connectionTimeout); this.subChannelConnectCallback = () => { // Connection succeeded @@ -234,7 +234,7 @@ export class Http2Channel extends EventEmitter implements Channel { if (this.connectivityState === ConnectivityState.READY) { const session: http2.ClientHttp2Session = this.subChannel!; // Prevent the HTTP/2 session from keeping the process alive. - (session as any).unref(); + session.unref(); stream.attachHttp2Stream(session.request(headers)); } else { /* In this case, we lost the connection while finalizing diff --git a/packages/grpc-js-core/test/test-call-stream.ts b/packages/grpc-js-core/test/test-call-stream.ts index 91d694dc..0e142e40 100644 --- a/packages/grpc-js-core/test/test-call-stream.ts +++ b/packages/grpc-js-core/test/test-call-stream.ts @@ -39,10 +39,13 @@ class ClientHttp2StreamMock extends stream.Duplex implements http2.ClientHttp2St bytesRead = 0; dataFrame = 0; aborted: boolean = false; + closed: boolean = false; destroyed: boolean = false; + pending: boolean = false; rstCode: number = 0; session: http2.Http2Session = {} as any; state: http2.StreamState = {} as any; + close = mockFunction; priority = mockFunction; rstStream = mockFunction; rstWithNoError = mockFunction;