From d14b38e02233d192f786604baa8a6bf5f5daae8d Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Tue, 20 Mar 2018 09:40:59 -0700 Subject: [PATCH] Pure JS: Pass real service_url to call credentials --- .../src/call-credentials-filter.ts | 20 ++++++++-- packages/grpc-js-core/src/call-stream.ts | 5 +++ packages/grpc-js-core/src/channel.ts | 38 ++++++++++--------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/packages/grpc-js-core/src/call-credentials-filter.ts b/packages/grpc-js-core/src/call-credentials-filter.ts index e432935b..444841b7 100644 --- a/packages/grpc-js-core/src/call-credentials-filter.ts +++ b/packages/grpc-js-core/src/call-credentials-filter.ts @@ -7,13 +7,27 @@ import {BaseFilter, Filter, FilterFactory} from './filter'; import {Metadata} from './metadata'; export class CallCredentialsFilter extends BaseFilter implements Filter { - constructor(private readonly credentials: CallCredentials) { + private serviceUrl: string; + constructor( + private readonly channel: Http2Channel, + private readonly host: string, + private readonly path: string) { super(); + let splitPath: string[] = path.split('/'); + let serviceName: string = ''; + /* The standard path format is "/{serviceName}/{methodName}", so if we split + * by '/', the first item should be empty and the second should be the + * service name */ + if (splitPath.length >= 2) { + serviceName = splitPath[1]; + } + /* Currently, call credentials are only allowed on HTTPS connections, so we + * can assume that the scheme is "https" */ + this.serviceUrl = `https://${host}/${serviceName}`; } async sendMetadata(metadata: Promise): Promise { - // TODO(kjin): pass real service URL to generateMetadata - let credsMetadata = this.credentials.generateMetadata({ service_url: '' }); + let credsMetadata = this.credentials.generateMetadata({ service_url: serviceUrl }); let resultMetadata = await metadata; resultMetadata.merge(await credsMetadata); return resultMetadata; diff --git a/packages/grpc-js-core/src/call-stream.ts b/packages/grpc-js-core/src/call-stream.ts index 3a403d68..00a36a7c 100644 --- a/packages/grpc-js-core/src/call-stream.ts +++ b/packages/grpc-js-core/src/call-stream.ts @@ -17,6 +17,7 @@ export interface CallStreamOptions { deadline: Deadline; credentials: CallCredentials; flags: number; + host: string; } export type CallOptions = Partial; @@ -355,6 +356,10 @@ export class Http2CallStream extends Duplex implements CallStream { throw new Error('Not yet implemented'); } + getMethod(): string { + return this.methodName; + } + _read(size: number) { if (this.http2Stream === null) { this.pendingRead = true; diff --git a/packages/grpc-js-core/src/channel.ts b/packages/grpc-js-core/src/channel.ts index a4d04023..a041c720 100644 --- a/packages/grpc-js-core/src/channel.ts +++ b/packages/grpc-js-core/src/channel.ts @@ -82,7 +82,8 @@ export interface Channel extends EventEmitter { export class Http2Channel extends EventEmitter implements Channel { private readonly userAgent: string; - private readonly authority: url.URL; + private readonly target: url.URL; + private readonly defaultAuthority: string; private connectivityState: ConnectivityState = ConnectivityState.IDLE; /* For now, we have up to one subchannel, which will exist as long as we are * connecting or trying to connect */ @@ -146,7 +147,7 @@ export class Http2Channel extends EventEmitter implements Channel { let subChannel: http2.ClientHttp2Session; let secureContext = this.credentials.getSecureContext(); if (secureContext === null) { - subChannel = http2.connect(this.authority); + subChannel = http2.connect(this.target); } else { const connectionOptions: http2.SecureClientSessionOptions = { secureContext, @@ -161,7 +162,7 @@ export class Http2Channel extends EventEmitter implements Channel { } connectionOptions.servername = sslTargetNameOverride; } - subChannel = http2.connect(this.authority, connectionOptions); + subChannel = http2.connect(this.target, connectionOptions); } this.subChannel = subChannel; let now = new Date(); @@ -195,9 +196,15 @@ export class Http2Channel extends EventEmitter implements Channel { private readonly options: Partial) { super(); if (credentials.getSecureContext() === null) { - this.authority = new url.URL(`http://${address}`); + this.target = new url.URL(`http://${address}`); } else { - this.authority = new url.URL(`https://${address}`); + this.target = new url.URL(`https://${address}`); + } + // TODO(murgatroid99): Add more centralized handling of channel options + if (this.options['grpc.default_authority']) { + this.defaultAuthority = this.options['grpc.default_authority']; + } else { + this.defaultAuthority = this.target.host; } this.filterStackFactory = new FilterStackFactory([ new CompressionFilterFactory(this), @@ -220,20 +227,16 @@ export class Http2Channel extends EventEmitter implements Channel { } private startHttp2Stream( - methodName: string, stream: Http2CallStream, metadata: Metadata) { + authority: string, + methodName: string, + stream: Http2CallStream, + metadata: Metadata) { let finalMetadata: Promise = stream.filterStack.sendMetadata(Promise.resolve(metadata.clone())); Promise.all([finalMetadata, this.connect()]) .then(([metadataValue]) => { let headers = metadataValue.toHttp2Headers(); - let host: string; - // TODO(murgatroid99): Add more centralized handling of channel options - if (this.options['grpc.default_authority']) { - host = this.options['grpc.default_authority'] as string; - } else { - host = this.authority.hostname; - } - headers[HTTP2_HEADER_AUTHORITY] = host; + headers[HTTP2_HEADER_AUTHORITY] = authority; headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; headers[HTTP2_HEADER_METHOD] = 'POST'; @@ -249,7 +252,7 @@ export class Http2Channel extends EventEmitter implements Channel { /* In this case, we lost the connection while finalizing * metadata. That should be very unusual */ setImmediate(() => { - this.startHttp2Stream(methodName, stream, metadata); + this.startHttp2Stream(authority, methodName, stream, metadata); }); } }).catch((error: Error & { code: number }) => { @@ -267,11 +270,12 @@ export class Http2Channel extends EventEmitter implements Channel { let finalOptions: CallStreamOptions = { deadline: options.deadline === undefined ? Infinity : options.deadline, credentials: options.credentials || CallCredentials.createEmpty(), - flags: options.flags || 0 + flags: options.flags || 0, + host: options.host || defaultAuthority }; let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory); - this.startHttp2Stream(methodName, stream, metadata); + this.startHttp2Stream(finalOptions.host, methodName, stream, metadata); return stream; }