From 691834cafe4848bb267399cd825d69186b546bf4 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 14 Mar 2018 10:37:50 -0700 Subject: [PATCH 1/5] grpc-js: fix failing unit test --- .../grpc-js-core/test/test-call-stream.ts | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/grpc-js-core/test/test-call-stream.ts b/packages/grpc-js-core/test/test-call-stream.ts index 0e142e40..3699fffb 100644 --- a/packages/grpc-js-core/test/test-call-stream.ts +++ b/packages/grpc-js-core/test/test-call-stream.ts @@ -103,16 +103,16 @@ describe('CallStream', () => { assert2.afterMustCallsSatisfied(done); }); - it('should end a call with an error if a stream was closed', (done) => { + describe('should end a call with an error if a stream was closed', () => { const c = http2.constants; const s = Status; const errorCodeMapping = { - [c.NGHTTP2_NO_ERROR]: s.INTERNAL, + [c.NGHTTP2_NO_ERROR]: s.OK, [c.NGHTTP2_PROTOCOL_ERROR]: s.INTERNAL, [c.NGHTTP2_INTERNAL_ERROR]: s.INTERNAL, [c.NGHTTP2_FLOW_CONTROL_ERROR]: s.INTERNAL, [c.NGHTTP2_SETTINGS_TIMEOUT]: s.INTERNAL, - [c.NGHTTP2_STREAM_CLOSED]: null, + [c.NGHTTP2_STREAM_CLOSED]: s.INTERNAL, [c.NGHTTP2_FRAME_SIZE_ERROR]: s.INTERNAL, [c.NGHTTP2_REFUSED_STREAM]: s.UNAVAILABLE, [c.NGHTTP2_CANCEL]: s.CANCELLED, @@ -121,21 +121,27 @@ describe('CallStream', () => { [c.NGHTTP2_ENHANCE_YOUR_CALM]: s.RESOURCE_EXHAUSTED, [c.NGHTTP2_INADEQUATE_SECURITY]: s.PERMISSION_DENIED }; - forOwn(errorCodeMapping, (value: Status | null, key) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] - }); - callStream.attachHttp2Stream(http2Stream); - if (value !== null) { - callStream.once('status', assert2.mustCall((status) => { - assert.strictEqual(status.code, value); - })); - } - http2Stream.emit('streamClosed', Number(key)); + const keys = Object.keys(errorCodeMapping).map(key => Number(key)); + keys.forEach((key) => { + const value = errorCodeMapping[key]; + it(`for error code ${key}`, () => new Promise((resolve, reject) => { + const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); + const http2Stream = new ClientHttp2StreamMock({ + payload: Buffer.alloc(0), + frameLengths: [] + }); + callStream.attachHttp2Stream(http2Stream); + callStream.once('status', (status) => { + try { + assert.strictEqual(status.code, value); + resolve(); + } catch (e) { + reject(e); + } + }); + http2Stream.emit('close', Number(key)); + })); }); - assert2.afterMustCallsSatisfied(done); }); it('should have functioning getters', (done) => { From e92224ba7b6a1ec17afcd5a6963c8ba03a27ba17 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 14 Mar 2018 09:51:32 -0700 Subject: [PATCH 2/5] grpc-js: add google credentials implementation --- packages/grpc-js-core/src/call-credentials.ts | 40 +++++----- .../grpc-js-core/src/channel-credentials.ts | 33 ++++---- packages/grpc-js-core/src/client.ts | 4 + packages/grpc-js-core/src/index.ts | 80 +++++++++++++++---- packages/grpc-js-core/src/make-client.ts | 7 +- 5 files changed, 111 insertions(+), 53 deletions(-) diff --git a/packages/grpc-js-core/src/call-credentials.ts b/packages/grpc-js-core/src/call-credentials.ts index 278cb163..f0ad0980 100644 --- a/packages/grpc-js-core/src/call-credentials.ts +++ b/packages/grpc-js-core/src/call-credentials.ts @@ -2,33 +2,33 @@ import {map, reduce} from 'lodash'; import {Metadata} from './metadata'; -export type CallMetadataGenerator = - (options: {}, cb: (err: Error|null, metadata?: Metadata) => void) => +export type CallMetadataGenerator = + (options: T, cb: (err: Error|null, metadata?: Metadata) => void) => void; /** * A class that represents a generic method of adding authentication-related * metadata on a per-request basis. */ -export interface CallCredentials { +export interface CallCredentials { /** * Asynchronously generates a new Metadata object. * @param options Options used in generating the Metadata object. */ - generateMetadata(options: {}): Promise; + generateMetadata(options: T): Promise; /** * Creates a new CallCredentials object from properties of both this and * another CallCredentials object. This object's metadata generator will be * called first. * @param callCredentials The other CallCredentials object. */ - compose(callCredentials: CallCredentials): CallCredentials; + compose(callCredentials: CallCredentials): CallCredentials; } -class ComposedCallCredentials implements CallCredentials { - constructor(private creds: CallCredentials[]) {} +class ComposedCallCredentials implements CallCredentials { + constructor(private creds: CallCredentials[]) {} - async generateMetadata(options: {}): Promise { + async generateMetadata(options: T): Promise { let base: Metadata = new Metadata(); let generated: Metadata[] = await Promise.all( map(this.creds, (cred) => cred.generateMetadata(options))); @@ -38,15 +38,15 @@ class ComposedCallCredentials implements CallCredentials { return base; } - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials(this.creds.concat([other])); + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials([...this.creds, other]); } } -class SingleCallCredentials implements CallCredentials { - constructor(private metadataGenerator: CallMetadataGenerator) {} +class SingleCallCredentials implements CallCredentials { + constructor(private metadataGenerator: CallMetadataGenerator) {} - generateMetadata(options: {}): Promise { + generateMetadata(options: T): Promise { return new Promise((resolve, reject) => { this.metadataGenerator(options, (err, metadata) => { if (metadata !== undefined) { @@ -58,17 +58,17 @@ class SingleCallCredentials implements CallCredentials { }); } - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials([this, other]); + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials([this, other]); } } -class EmptyCallCredentials implements CallCredentials { +class EmptyCallCredentials implements CallCredentials<{}> { generateMetadata(options: {}): Promise { return Promise.resolve(new Metadata()); } - compose(other: CallCredentials): CallCredentials { + compose(other: CallCredentials): CallCredentials { return other; } } @@ -81,12 +81,12 @@ export namespace CallCredentials { * generates a Metadata object based on these options, which is passed back * to the caller via a supplied (err, metadata) callback. */ - export function createFromMetadataGenerator( - metadataGenerator: CallMetadataGenerator): CallCredentials { + export function createFromMetadataGenerator( + metadataGenerator: CallMetadataGenerator): CallCredentials { return new SingleCallCredentials(metadataGenerator); } - export function createEmpty(): CallCredentials { + export function createEmpty(): CallCredentials<{}> { return new EmptyCallCredentials(); } } diff --git a/packages/grpc-js-core/src/channel-credentials.ts b/packages/grpc-js-core/src/channel-credentials.ts index 419e3d17..98986ff4 100644 --- a/packages/grpc-js-core/src/channel-credentials.ts +++ b/packages/grpc-js-core/src/channel-credentials.ts @@ -7,19 +7,19 @@ import {CallCredentials} from './call-credentials'; * as a set of per-call credentials, which are applied to every method call made * over a channel initialized with an instance of this class. */ -export interface ChannelCredentials { +export interface ChannelCredentials { /** * Returns a copy of this object with the included set of per-call credentials * expanded to include callCredentials. * @param callCredentials A CallCredentials object to associate with this * instance. */ - compose(callCredentials: CallCredentials): ChannelCredentials; + compose(callCredentials: CallCredentials): ChannelCredentials; /** * Gets the set of per-call credentials associated with this instance. */ - getCallCredentials(): CallCredentials; + getCallCredentials(): CallCredentials; /** * Gets a SecureContext object generated from input parameters if this @@ -29,28 +29,28 @@ export interface ChannelCredentials { getSecureContext(): SecureContext|null; } -abstract class ChannelCredentialsImpl implements ChannelCredentials { - protected callCredentials: CallCredentials; +abstract class ChannelCredentialsImpl implements ChannelCredentials { + protected callCredentials: CallCredentials; - protected constructor(callCredentials?: CallCredentials) { + protected constructor(callCredentials?: CallCredentials) { this.callCredentials = callCredentials || CallCredentials.createEmpty(); } - abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; + abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; - getCallCredentials(): CallCredentials { + getCallCredentials(): CallCredentials { return this.callCredentials; } abstract getSecureContext(): SecureContext|null; } -class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { - constructor(callCredentials?: CallCredentials) { +class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { + constructor(callCredentials?: CallCredentials) { super(callCredentials); } - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { throw new Error('Cannot compose insecure credentials'); } @@ -59,15 +59,15 @@ class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { } } -class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { +class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { secureContext: SecureContext; - constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { + constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { super(callCredentials); this.secureContext = secureContext; } - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { const combinedCallCredentials = this.callCredentials.compose(callCredentials); return new SecureChannelCredentialsImpl( @@ -86,7 +86,6 @@ function verifyIsBufferOrNull(obj: any, friendlyName: string): void { } export namespace ChannelCredentials { - /** * Return a new ChannelCredentials instance with a given set of credentials. * The resulting instance can be used to construct a Channel that communicates @@ -97,7 +96,7 @@ export namespace ChannelCredentials { */ export function createSsl( rootCerts?: Buffer|null, privateKey?: Buffer|null, - certChain?: Buffer|null): ChannelCredentials { + certChain?: Buffer|null): ChannelCredentials<{}> { verifyIsBufferOrNull(rootCerts, 'Root certificate'); verifyIsBufferOrNull(privateKey, 'Private key'); verifyIsBufferOrNull(certChain, 'Certificate chain'); @@ -120,7 +119,7 @@ export namespace ChannelCredentials { /** * Return a new ChannelCredentials instance with no credentials. */ - export function createInsecure(): ChannelCredentials { + export function createInsecure(): ChannelCredentials<{}> { return new InsecureChannelCredentialsImpl(); } } diff --git a/packages/grpc-js-core/src/client.ts b/packages/grpc-js-core/src/client.ts index 74de1d3f..25fa7bc8 100644 --- a/packages/grpc-js-core/src/client.ts +++ b/packages/grpc-js-core/src/client.ts @@ -16,6 +16,10 @@ export interface UnaryCallback { (err: ServiceError|null, value?: ResponseType): void; } +/** + * A generic gRPC client. Primarily useful as a base class for all generated + * clients. + */ export class Client { private readonly [kChannel]: Channel; constructor( diff --git a/packages/grpc-js-core/src/index.ts b/packages/grpc-js-core/src/index.ts index b26c390b..7cd93562 100644 --- a/packages/grpc-js-core/src/index.ts +++ b/packages/grpc-js-core/src/index.ts @@ -5,31 +5,76 @@ import { Client } from './client'; import { Status} from './constants'; import { makeClientConstructor, loadPackageDefinition } from './make-client'; import { Metadata } from './metadata'; +import { IncomingHttpHeaders } from 'http'; -const notImplementedFn = () => { throw new Error('Not implemented'); }; +export interface OAuth2Client { + getRequestMetadata: (url: string) => Promise<{ Authorization: string }>; +} + +export type GoogleCallCredentials = CallCredentials<{ service_url: string }>; + +/**** Client Credentials ****/ + +// Using assign only copies enumerable properties, which is what we want +export const credentials = Object.assign({ + /** + * Create a gRPC credential from a Google credential object. + * @param googleCredentials The authentication client to use. + * @return The resulting CallCredentials object. + */ + createFromGoogleCredential: (googleCredentials: OAuth2Client): GoogleCallCredentials => { + return CallCredentials.createFromMetadataGenerator(async (options, callback) => { + let header; + try { + header = await googleCredentials.getRequestMetadata(options.service_url); + } catch (err) { + callback(err); + return; + } + const metadata = new Metadata(); + metadata.add('authorization', header.Authorization); + callback(null, metadata); + }); + }, + + /** + * Combine a ChannelCredentials with any number of CallCredentials into a + * single ChannelCredentials object. + * @param channelCredentials The ChannelCredentials object. + * @param callCredentials Any number of CallCredentials objects. + * @return The resulting ChannelCredentials object. + */ + combineChannelCredentials: ( + channelCredentials: ChannelCredentials, + ...callCredentials: CallCredentials[]): ChannelCredentials => { + return callCredentials.reduce((acc, other) => acc.compose(other), channelCredentials); + }, + + /** + * Combine any number of CallCredentials into a single CallCredentials object. + * @param first The first CallCredentials object. + * @param additional Any number of additional CallCredentials objects. + * @return The resulting CallCredentials object. + */ + combineCallCredentials: ( + first: CallCredentials, + ...additional: CallCredentials[]): CallCredentials => { + return additional.reduce((acc, other) => acc.compose(other), first); + } +}, ChannelCredentials, CallCredentials); + +/**** Metadata ****/ -// Metadata export { Metadata }; -// Client credentials - -export const credentials = { - createSsl: ChannelCredentials.createSsl, - createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator, - createFromGoogleCredential: notImplementedFn /*TODO*/, - combineChannelCredentials: (first: ChannelCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first), - combineCallCredentials: (first: CallCredentials, ...additional: CallCredentials[]) => additional.reduce((acc, other) => acc.compose(other), first), - createInsecure: ChannelCredentials.createInsecure -}; - -// Constants +/**** Constants ****/ export { Status as status // TODO: Other constants as well }; -// Client +/**** Client ****/ export { Client, @@ -37,4 +82,9 @@ export { makeClientConstructor, makeClientConstructor as makeGenericClientConstructor }; + +/** + * Close a Client object. + * @param client The client to close. + */ export const closeClient = (client: Client) => client.close(); diff --git a/packages/grpc-js-core/src/make-client.ts b/packages/grpc-js-core/src/make-client.ts index bc3299e8..f5433f57 100644 --- a/packages/grpc-js-core/src/make-client.ts +++ b/packages/grpc-js-core/src/make-client.ts @@ -130,7 +130,12 @@ export type GrpcObject = { [index: string]: GrpcObject | ServiceClientConstructor; }; -export function loadPackageDefinition(packageDef: PackageDefinition) { +/** + * Load a gRPC package definition as a gRPC object hierarchy. + * @param packageDef The package definition object. + * @return The resulting gRPC object. + */ +export function loadPackageDefinition(packageDef: PackageDefinition): GrpcObject { const result: GrpcObject = {}; for (const serviceFqn in packageDef) { const service = packageDef[serviceFqn]; From 42ddabe398c3b720f89df20082fcecce113fb2df Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 14 Mar 2018 12:31:14 -0700 Subject: [PATCH 3/5] Remove generics from credentials --- packages/grpc-js-core/src/call-credentials.ts | 44 ++++++++++--------- .../grpc-js-core/src/channel-credentials.ts | 33 +++++++------- packages/grpc-js-core/src/index.ts | 39 ++++++++-------- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/packages/grpc-js-core/src/call-credentials.ts b/packages/grpc-js-core/src/call-credentials.ts index f0ad0980..888e4c62 100644 --- a/packages/grpc-js-core/src/call-credentials.ts +++ b/packages/grpc-js-core/src/call-credentials.ts @@ -2,33 +2,35 @@ import {map, reduce} from 'lodash'; import {Metadata} from './metadata'; -export type CallMetadataGenerator = - (options: T, cb: (err: Error|null, metadata?: Metadata) => void) => +export type CallMetadataOptions = { service_url?: string; }; + +export type CallMetadataGenerator = + (options: CallMetadataOptions, cb: (err: Error|null, metadata?: Metadata) => void) => void; /** * A class that represents a generic method of adding authentication-related * metadata on a per-request basis. */ -export interface CallCredentials { +export interface CallCredentials { /** * Asynchronously generates a new Metadata object. * @param options Options used in generating the Metadata object. */ - generateMetadata(options: T): Promise; + generateMetadata(options: CallMetadataOptions): Promise; /** * Creates a new CallCredentials object from properties of both this and * another CallCredentials object. This object's metadata generator will be * called first. * @param callCredentials The other CallCredentials object. */ - compose(callCredentials: CallCredentials): CallCredentials; + compose(callCredentials: CallCredentials): CallCredentials; } -class ComposedCallCredentials implements CallCredentials { - constructor(private creds: CallCredentials[]) {} +class ComposedCallCredentials implements CallCredentials { + constructor(private creds: CallCredentials[]) {} - async generateMetadata(options: T): Promise { + async generateMetadata(options: CallMetadataOptions): Promise { let base: Metadata = new Metadata(); let generated: Metadata[] = await Promise.all( map(this.creds, (cred) => cred.generateMetadata(options))); @@ -38,15 +40,15 @@ class ComposedCallCredentials implements CallCredentials { return base; } - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials([...this.creds, other]); + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials(this.creds.concat([other])); } } -class SingleCallCredentials implements CallCredentials { - constructor(private metadataGenerator: CallMetadataGenerator) {} +class SingleCallCredentials implements CallCredentials { + constructor(private metadataGenerator: CallMetadataGenerator) {} - generateMetadata(options: T): Promise { + generateMetadata(options: CallMetadataOptions): Promise { return new Promise((resolve, reject) => { this.metadataGenerator(options, (err, metadata) => { if (metadata !== undefined) { @@ -58,17 +60,17 @@ class SingleCallCredentials implements CallCredentials { }); } - compose(other: CallCredentials): CallCredentials { - return new ComposedCallCredentials([this, other]); + compose(other: CallCredentials): CallCredentials { + return new ComposedCallCredentials([this, other]); } } -class EmptyCallCredentials implements CallCredentials<{}> { - generateMetadata(options: {}): Promise { +class EmptyCallCredentials implements CallCredentials { + generateMetadata(options: CallMetadataOptions): Promise { return Promise.resolve(new Metadata()); } - compose(other: CallCredentials): CallCredentials { + compose(other: CallCredentials): CallCredentials { return other; } } @@ -81,12 +83,12 @@ export namespace CallCredentials { * generates a Metadata object based on these options, which is passed back * to the caller via a supplied (err, metadata) callback. */ - export function createFromMetadataGenerator( - metadataGenerator: CallMetadataGenerator): CallCredentials { + export function createFromMetadataGenerator( + metadataGenerator: CallMetadataGenerator): CallCredentials { return new SingleCallCredentials(metadataGenerator); } - export function createEmpty(): CallCredentials<{}> { + export function createEmpty(): CallCredentials { return new EmptyCallCredentials(); } } diff --git a/packages/grpc-js-core/src/channel-credentials.ts b/packages/grpc-js-core/src/channel-credentials.ts index 98986ff4..419e3d17 100644 --- a/packages/grpc-js-core/src/channel-credentials.ts +++ b/packages/grpc-js-core/src/channel-credentials.ts @@ -7,19 +7,19 @@ import {CallCredentials} from './call-credentials'; * as a set of per-call credentials, which are applied to every method call made * over a channel initialized with an instance of this class. */ -export interface ChannelCredentials { +export interface ChannelCredentials { /** * Returns a copy of this object with the included set of per-call credentials * expanded to include callCredentials. * @param callCredentials A CallCredentials object to associate with this * instance. */ - compose(callCredentials: CallCredentials): ChannelCredentials; + compose(callCredentials: CallCredentials): ChannelCredentials; /** * Gets the set of per-call credentials associated with this instance. */ - getCallCredentials(): CallCredentials; + getCallCredentials(): CallCredentials; /** * Gets a SecureContext object generated from input parameters if this @@ -29,28 +29,28 @@ export interface ChannelCredentials { getSecureContext(): SecureContext|null; } -abstract class ChannelCredentialsImpl implements ChannelCredentials { - protected callCredentials: CallCredentials; +abstract class ChannelCredentialsImpl implements ChannelCredentials { + protected callCredentials: CallCredentials; - protected constructor(callCredentials?: CallCredentials) { + protected constructor(callCredentials?: CallCredentials) { this.callCredentials = callCredentials || CallCredentials.createEmpty(); } - abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; + abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl; - getCallCredentials(): CallCredentials { + getCallCredentials(): CallCredentials { return this.callCredentials; } abstract getSecureContext(): SecureContext|null; } -class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { - constructor(callCredentials?: CallCredentials) { +class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { + constructor(callCredentials?: CallCredentials) { super(callCredentials); } - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { throw new Error('Cannot compose insecure credentials'); } @@ -59,15 +59,15 @@ class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl { } } -class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { +class SecureChannelCredentialsImpl extends ChannelCredentialsImpl { secureContext: SecureContext; - constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { + constructor(secureContext: SecureContext, callCredentials?: CallCredentials) { super(callCredentials); this.secureContext = secureContext; } - compose(callCredentials: CallCredentials): ChannelCredentialsImpl { + compose(callCredentials: CallCredentials): ChannelCredentialsImpl { const combinedCallCredentials = this.callCredentials.compose(callCredentials); return new SecureChannelCredentialsImpl( @@ -86,6 +86,7 @@ function verifyIsBufferOrNull(obj: any, friendlyName: string): void { } export namespace ChannelCredentials { + /** * Return a new ChannelCredentials instance with a given set of credentials. * The resulting instance can be used to construct a Channel that communicates @@ -96,7 +97,7 @@ export namespace ChannelCredentials { */ export function createSsl( rootCerts?: Buffer|null, privateKey?: Buffer|null, - certChain?: Buffer|null): ChannelCredentials<{}> { + certChain?: Buffer|null): ChannelCredentials { verifyIsBufferOrNull(rootCerts, 'Root certificate'); verifyIsBufferOrNull(privateKey, 'Private key'); verifyIsBufferOrNull(certChain, 'Certificate chain'); @@ -119,7 +120,7 @@ export namespace ChannelCredentials { /** * Return a new ChannelCredentials instance with no credentials. */ - export function createInsecure(): ChannelCredentials<{}> { + export function createInsecure(): ChannelCredentials { return new InsecureChannelCredentialsImpl(); } } diff --git a/packages/grpc-js-core/src/index.ts b/packages/grpc-js-core/src/index.ts index 7cd93562..4809647f 100644 --- a/packages/grpc-js-core/src/index.ts +++ b/packages/grpc-js-core/src/index.ts @@ -8,11 +8,9 @@ import { Metadata } from './metadata'; import { IncomingHttpHeaders } from 'http'; export interface OAuth2Client { - getRequestMetadata: (url: string) => Promise<{ Authorization: string }>; + getRequestMetadata: (url: string, callback: (err: Error|null, headers?: { Authorization: string }) => void) => void; } -export type GoogleCallCredentials = CallCredentials<{ service_url: string }>; - /**** Client Credentials ****/ // Using assign only copies enumerable properties, which is what we want @@ -22,18 +20,17 @@ export const credentials = Object.assign({ * @param googleCredentials The authentication client to use. * @return The resulting CallCredentials object. */ - createFromGoogleCredential: (googleCredentials: OAuth2Client): GoogleCallCredentials => { - return CallCredentials.createFromMetadataGenerator(async (options, callback) => { - let header; - try { - header = await googleCredentials.getRequestMetadata(options.service_url); - } catch (err) { - callback(err); - return; - } - const metadata = new Metadata(); - metadata.add('authorization', header.Authorization); - callback(null, metadata); + createFromGoogleCredential: (googleCredentials: OAuth2Client): CallCredentials => { + return CallCredentials.createFromMetadataGenerator((options, callback) => { + googleCredentials.getRequestMetadata(options.service_url!, (err, headers) => { + if (err) { + callback(err); + return; + } + const metadata = new Metadata(); + metadata.add('authorization', headers!.Authorization); + callback(null, metadata); + }); }); }, @@ -44,9 +41,9 @@ export const credentials = Object.assign({ * @param callCredentials Any number of CallCredentials objects. * @return The resulting ChannelCredentials object. */ - combineChannelCredentials: ( - channelCredentials: ChannelCredentials, - ...callCredentials: CallCredentials[]): ChannelCredentials => { + combineChannelCredentials: ( + channelCredentials: ChannelCredentials, + ...callCredentials: CallCredentials[]): ChannelCredentials => { return callCredentials.reduce((acc, other) => acc.compose(other), channelCredentials); }, @@ -56,9 +53,9 @@ export const credentials = Object.assign({ * @param additional Any number of additional CallCredentials objects. * @return The resulting CallCredentials object. */ - combineCallCredentials: ( - first: CallCredentials, - ...additional: CallCredentials[]): CallCredentials => { + combineCallCredentials: ( + first: CallCredentials, + ...additional: CallCredentials[]): CallCredentials => { return additional.reduce((acc, other) => acc.compose(other), first); } }, ChannelCredentials, CallCredentials); From 3b324909b72974db8b1f62bb06f19f2eb3055944 Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 14 Mar 2018 13:17:06 -0700 Subject: [PATCH 4/5] Restore stream_closed error code test --- packages/grpc-js-core/src/call-stream.ts | 3 -- .../grpc-js-core/test/test-call-stream.ts | 40 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 849d685f..3a403d68 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -271,9 +271,6 @@ export class Http2CallStream extends Duplex implements CallStream { let code: Status; let details = ''; switch (errorCode) { - case http2.constants.NGHTTP2_NO_ERROR: - code = Status.OK; - break; case http2.constants.NGHTTP2_REFUSED_STREAM: code = Status.UNAVAILABLE; break; diff --git a/packages/grpc-js-core/test/test-call-stream.ts b/packages/grpc-js-core/test/test-call-stream.ts index 3699fffb..fb8f5b02 100644 --- a/packages/grpc-js-core/test/test-call-stream.ts +++ b/packages/grpc-js-core/test/test-call-stream.ts @@ -107,12 +107,12 @@ describe('CallStream', () => { const c = http2.constants; const s = Status; const errorCodeMapping = { - [c.NGHTTP2_NO_ERROR]: s.OK, + [c.NGHTTP2_NO_ERROR]: s.INTERNAL, [c.NGHTTP2_PROTOCOL_ERROR]: s.INTERNAL, [c.NGHTTP2_INTERNAL_ERROR]: s.INTERNAL, [c.NGHTTP2_FLOW_CONTROL_ERROR]: s.INTERNAL, [c.NGHTTP2_SETTINGS_TIMEOUT]: s.INTERNAL, - [c.NGHTTP2_STREAM_CLOSED]: s.INTERNAL, + [c.NGHTTP2_STREAM_CLOSED]: null, [c.NGHTTP2_FRAME_SIZE_ERROR]: s.INTERNAL, [c.NGHTTP2_REFUSED_STREAM]: s.UNAVAILABLE, [c.NGHTTP2_CANCEL]: s.CANCELLED, @@ -124,23 +124,27 @@ describe('CallStream', () => { const keys = Object.keys(errorCodeMapping).map(key => Number(key)); keys.forEach((key) => { const value = errorCodeMapping[key]; - it(`for error code ${key}`, () => new Promise((resolve, reject) => { - const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); - const http2Stream = new ClientHttp2StreamMock({ - payload: Buffer.alloc(0), - frameLengths: [] + // A null value indicates: behavior isn't specified, so skip this test. + let maybeSkip = (fn: typeof it) => value ? fn : fn.skip; + maybeSkip(it)(`for error code ${key}`, () => { + return new Promise((resolve, reject) => { + const callStream = new Http2CallStream('foo', callStreamArgs, filterStackFactory); + const http2Stream = new ClientHttp2StreamMock({ + payload: Buffer.alloc(0), + frameLengths: [] + }); + callStream.attachHttp2Stream(http2Stream); + callStream.once('status', (status) => { + try { + assert.strictEqual(status.code, value); + resolve(); + } catch (e) { + reject(e); + } + }); + http2Stream.emit('close', Number(key)); }); - callStream.attachHttp2Stream(http2Stream); - callStream.once('status', (status) => { - try { - assert.strictEqual(status.code, value); - resolve(); - } catch (e) { - reject(e); - } - }); - http2Stream.emit('close', Number(key)); - })); + }); }); }); From 04d7704fa71c882cf085a37d5e96d098174d9c7f Mon Sep 17 00:00:00 2001 From: Kelvin Jin Date: Wed, 14 Mar 2018 16:08:16 -0700 Subject: [PATCH 5/5] Fix call credentials test and use dummy service_url --- .../src/call-credentials-filter.ts | 4 +- packages/grpc-js-core/src/call-credentials.ts | 2 +- packages/grpc-js-core/src/index.ts | 2 +- .../test/test-call-credentials.ts | 39 +++++++------------ 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/grpc-js-core/src/call-credentials-filter.ts b/packages/grpc-js-core/src/call-credentials-filter.ts index 4633ffe8..e432935b 100644 --- a/packages/grpc-js-core/src/call-credentials-filter.ts +++ b/packages/grpc-js-core/src/call-credentials-filter.ts @@ -12,8 +12,8 @@ export class CallCredentialsFilter extends BaseFilter implements Filter { } async sendMetadata(metadata: Promise): Promise { - // TODO(murgatroid99): pass real options to generateMetadata - let credsMetadata = this.credentials.generateMetadata({}); + // TODO(kjin): pass real service URL to generateMetadata + let credsMetadata = this.credentials.generateMetadata({ service_url: '' }); let resultMetadata = await metadata; resultMetadata.merge(await credsMetadata); return resultMetadata; diff --git a/packages/grpc-js-core/src/call-credentials.ts b/packages/grpc-js-core/src/call-credentials.ts index 888e4c62..0c0f6d53 100644 --- a/packages/grpc-js-core/src/call-credentials.ts +++ b/packages/grpc-js-core/src/call-credentials.ts @@ -2,7 +2,7 @@ import {map, reduce} from 'lodash'; import {Metadata} from './metadata'; -export type CallMetadataOptions = { service_url?: string; }; +export type CallMetadataOptions = { service_url: string; }; export type CallMetadataGenerator = (options: CallMetadataOptions, cb: (err: Error|null, metadata?: Metadata) => void) => diff --git a/packages/grpc-js-core/src/index.ts b/packages/grpc-js-core/src/index.ts index 4809647f..5a3f6025 100644 --- a/packages/grpc-js-core/src/index.ts +++ b/packages/grpc-js-core/src/index.ts @@ -22,7 +22,7 @@ export const credentials = Object.assign({ */ createFromGoogleCredential: (googleCredentials: OAuth2Client): CallCredentials => { return CallCredentials.createFromMetadataGenerator((options, callback) => { - googleCredentials.getRequestMetadata(options.service_url!, (err, headers) => { + googleCredentials.getRequestMetadata(options.service_url, (err, headers) => { if (err) { callback(err); return; diff --git a/packages/grpc-js-core/test/test-call-credentials.ts b/packages/grpc-js-core/test/test-call-credentials.ts index ab516ed9..64cfe140 100644 --- a/packages/grpc-js-core/test/test-call-credentials.ts +++ b/packages/grpc-js-core/test/test-call-credentials.ts @@ -5,18 +5,6 @@ import {Metadata} from '../src/metadata'; // Metadata generators -function makeGenerator(props: Array): CallMetadataGenerator { - return (options: {[propName: string]: string}, cb) => { - const metadata: Metadata = new Metadata(); - props.forEach((prop) => { - if (options[prop]) { - metadata.add(prop, options[prop]); - } - }); - cb(null, metadata); - }; -} - function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator { return (options, cb) => { const metadata = new Metadata(); @@ -25,7 +13,11 @@ function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator { }; } -const generateFromName: CallMetadataGenerator = makeGenerator(['name']); +const generateFromServiceURL: CallMetadataGenerator = (options, cb) => { + const metadata: Metadata = new Metadata(); + metadata.add('service_url', options.service_url); + cb(null, metadata); +}; const generateWithError: CallMetadataGenerator = (options, cb) => cb(new Error()); @@ -35,16 +27,16 @@ describe('CallCredentials', () => { describe('createFromMetadataGenerator', () => { it('should accept a metadata generator', () => { assert.doesNotThrow( - () => CallCredentials.createFromMetadataGenerator(generateFromName)); + () => CallCredentials.createFromMetadataGenerator(generateFromServiceURL)); }); }); describe('compose', () => { it('should accept a CallCredentials object and return a new object', () => { const callCredentials1 = - CallCredentials.createFromMetadataGenerator(generateFromName); + CallCredentials.createFromMetadataGenerator(generateFromServiceURL); const callCredentials2 = - CallCredentials.createFromMetadataGenerator(generateFromName); + CallCredentials.createFromMetadataGenerator(generateFromServiceURL); const combinedCredentials = callCredentials1.compose(callCredentials2); assert.notEqual(combinedCredentials, callCredentials1); assert.notEqual(combinedCredentials, callCredentials2); @@ -52,9 +44,9 @@ describe('CallCredentials', () => { it('should be chainable', () => { const callCredentials1 = - CallCredentials.createFromMetadataGenerator(generateFromName); + CallCredentials.createFromMetadataGenerator(generateFromServiceURL); const callCredentials2 = - CallCredentials.createFromMetadataGenerator(generateFromName); + CallCredentials.createFromMetadataGenerator(generateFromServiceURL); assert.doesNotThrow(() => { callCredentials1.compose(callCredentials2) .compose(callCredentials2) @@ -67,14 +59,14 @@ describe('CallCredentials', () => { it('should call the function passed to createFromMetadataGenerator', async () => { const callCredentials = - CallCredentials.createFromMetadataGenerator(generateFromName); + CallCredentials.createFromMetadataGenerator(generateFromServiceURL); let metadata: Metadata; try { - metadata = await callCredentials.generateMetadata({name: 'foo'}); + metadata = await callCredentials.generateMetadata({service_url: 'foo'}); } catch (err) { throw err; } - assert.deepEqual(metadata.get('name'), ['foo']); + assert.deepEqual(metadata.get('service_url'), ['foo']); }); it('should emit an error if the associated metadataGenerator does', @@ -83,7 +75,7 @@ describe('CallCredentials', () => { CallCredentials.createFromMetadataGenerator(generateWithError); let metadata: Metadata|null = null; try { - metadata = await callCredentials.generateMetadata({}); + metadata = await callCredentials.generateMetadata({service_url: ''}); } catch (err) { assert.ok(err instanceof Error); } @@ -115,13 +107,12 @@ describe('CallCredentials', () => { expected: ['150', '200', '50', '100'] } ]; - const options = {}; // Try each test case and make sure the msElapsed field is as expected await Promise.all(testCases.map(async (testCase) => { const {credentials, expected} = testCase; let metadata: Metadata; try { - metadata = await credentials.generateMetadata(options); + metadata = await credentials.generateMetadata({service_url: ''}); } catch (err) { throw err; }