Merge pull request #231 from murgatroid99/pure_js_credentials_service_url

Pure JS: Pass real service_url to call credentials
This commit is contained in:
Michael Lumish 2018-03-20 15:47:01 -07:00 committed by GitHub
commit fa32b718e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 22 deletions

View File

@ -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 credentials: CallCredentials,
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<Metadata>): Promise<Metadata> {
// TODO(kjin): pass real service URL to generateMetadata
let credsMetadata = this.credentials.generateMetadata({ service_url: '' });
let credsMetadata = this.credentials.generateMetadata({ service_url: this.serviceUrl });
let resultMetadata = await metadata;
resultMetadata.merge(await credsMetadata);
return resultMetadata;
@ -29,6 +43,8 @@ export class CallCredentialsFilterFactory implements
createFilter(callStream: CallStream): CallCredentialsFilter {
return new CallCredentialsFilter(
this.credentials.compose(callStream.getCredentials()));
this.credentials.compose(callStream.getCredentials()),
callStream.getHost(),
callStream.getMethod());
}
}

View File

@ -17,6 +17,7 @@ export interface CallStreamOptions {
deadline: Deadline;
credentials: CallCredentials;
flags: number;
host: string;
}
export type CallOptions = Partial<CallStreamOptions>;
@ -44,6 +45,8 @@ export type CallStream = {
/* If the return value is null, the call has not ended yet. Otherwise, it has
* ended with the specified status */
getStatus(): StatusObject|null;
getMethod(): string;
getHost(): string;
} & EmitterAugmentation1<'metadata', Metadata>
& EmitterAugmentation1<'status', StatusObject>
& ObjectDuplex<WriteObject, Buffer>;
@ -355,6 +358,14 @@ export class Http2CallStream extends Duplex implements CallStream {
throw new Error('Not yet implemented');
}
getMethod(): string {
return this.methodName;
}
getHost(): string {
return this.options.host;
}
_read(size: number) {
if (this.http2Stream === null) {
this.pendingRead = true;

View File

@ -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<ChannelOptions>) {
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'] as string;
} 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<Metadata> =
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 || this.defaultAuthority
};
let stream: Http2CallStream =
new Http2CallStream(methodName, finalOptions, this.filterStackFactory);
this.startHttp2Stream(methodName, stream, metadata);
this.startHttp2Stream(finalOptions.host, methodName, stream, metadata);
return stream;
}

View File

@ -77,7 +77,8 @@ describe('CallStream', () => {
const callStreamArgs = {
deadline: Infinity,
credentials: CallCredentials.createEmpty(),
flags: 0
flags: 0,
host: ''
};
const filterStackFactory = new FilterStackFactory([]);
const message = 'eat this message'; // 16 bytes