mirror of https://github.com/grpc/grpc-node.git
Lint and format
This commit is contained in:
parent
f012088ecc
commit
a3c1136fb4
|
|
@ -1,12 +1,12 @@
|
||||||
import {promisify} from 'util'
|
import {promisify} from 'util';
|
||||||
import {Filter, BaseFilter, FilterFactory} from './filter'
|
|
||||||
import {CallCredentials} from './call-credentials'
|
import {CallCredentials} from './call-credentials';
|
||||||
import {Http2Channel} from './channel'
|
import {CallStream} from './call-stream';
|
||||||
import {CallStream} from './call-stream'
|
import {Http2Channel} from './channel';
|
||||||
import {Metadata} from './metadata'
|
import {BaseFilter, Filter, FilterFactory} from './filter';
|
||||||
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
export class CallCredentialsFilter extends BaseFilter implements Filter {
|
export class CallCredentialsFilter extends BaseFilter implements Filter {
|
||||||
|
|
||||||
constructor(private readonly credentials: CallCredentials) {
|
constructor(private readonly credentials: CallCredentials) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
@ -20,13 +20,15 @@ export class CallCredentialsFilter extends BaseFilter implements Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CallCredentialsFilterFactory implements FilterFactory<CallCredentialsFilter> {
|
export class CallCredentialsFilterFactory implements
|
||||||
|
FilterFactory<CallCredentialsFilter> {
|
||||||
private readonly credentials: CallCredentials;
|
private readonly credentials: CallCredentials;
|
||||||
constructor(channel: Http2Channel) {
|
constructor(channel: Http2Channel) {
|
||||||
this.credentials = channel.credentials.getCallCredentials();
|
this.credentials = channel.credentials.getCallCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
createFilter(callStream: CallStream): CallCredentialsFilter {
|
createFilter(callStream: CallStream): CallCredentialsFilter {
|
||||||
return new CallCredentialsFilter(this.credentials.compose(callStream.getCredentials()));
|
return new CallCredentialsFilter(
|
||||||
|
this.credentials.compose(callStream.getCredentials()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Metadata } from './metadata';
|
import {map, reduce} from 'lodash';
|
||||||
import {map, reduce} from 'lodash'
|
|
||||||
|
|
||||||
export type CallMetadataGenerator = (
|
import {Metadata} from './metadata';
|
||||||
options: Object,
|
|
||||||
cb: (err: Error | null, metadata?: Metadata) => void
|
export type CallMetadataGenerator =
|
||||||
) => void;
|
(options: Object, cb: (err: Error|null, metadata?: Metadata) => void) =>
|
||||||
|
void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that represents a generic method of adding authentication-related
|
* A class that represents a generic method of adding authentication-related
|
||||||
|
|
@ -25,32 +25,13 @@ export interface CallCredentials {
|
||||||
compose(callCredentials: CallCredentials): CallCredentials;
|
compose(callCredentials: CallCredentials): CallCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace CallCredentials {
|
|
||||||
/**
|
|
||||||
* Creates a new CallCredentials object from a given function that generates
|
|
||||||
* Metadata objects.
|
|
||||||
* @param metadataGenerator A function that accepts a set of options, and
|
|
||||||
* 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 {
|
|
||||||
return new SingleCallCredentials(metadataGenerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createEmpty(): CallCredentials {
|
|
||||||
return new EmptyCallCredentials();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComposedCallCredentials implements CallCredentials {
|
class ComposedCallCredentials implements CallCredentials {
|
||||||
constructor(private creds: CallCredentials[]) {}
|
constructor(private creds: CallCredentials[]) {}
|
||||||
|
|
||||||
async generateMetadata(options: Object): Promise<Metadata> {
|
async generateMetadata(options: Object): Promise<Metadata> {
|
||||||
let base: Metadata = new Metadata();
|
let base: Metadata = new Metadata();
|
||||||
let generated: Metadata[] = await Promise.all(map(
|
let generated: Metadata[] = await Promise.all(
|
||||||
this.creds, (cred) => cred.generateMetadata(options)));
|
map(this.creds, (cred) => cred.generateMetadata(options)));
|
||||||
for (let gen of generated) {
|
for (let gen of generated) {
|
||||||
base.merge(gen);
|
base.merge(gen);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +43,7 @@ class ComposedCallCredentials implements CallCredentials {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleCallCredentials implements CallCredentials{
|
class SingleCallCredentials implements CallCredentials {
|
||||||
constructor(private metadataGenerator: CallMetadataGenerator) {}
|
constructor(private metadataGenerator: CallMetadataGenerator) {}
|
||||||
|
|
||||||
async generateMetadata(options: Object): Promise<Metadata> {
|
async generateMetadata(options: Object): Promise<Metadata> {
|
||||||
|
|
@ -83,13 +64,29 @@ class SingleCallCredentials implements CallCredentials{
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyCallCredentials implements CallCredentials {
|
class EmptyCallCredentials implements CallCredentials {
|
||||||
constructor () {}
|
|
||||||
|
|
||||||
async generateMetadata(options: Object): Promise<Metadata> {
|
async generateMetadata(options: Object): Promise<Metadata> {
|
||||||
return new Metadata();
|
return new Metadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
compose(other:CallCredentials): CallCredentials {
|
compose(other: CallCredentials): CallCredentials {
|
||||||
return other;
|
return other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace CallCredentials {
|
||||||
|
/**
|
||||||
|
* Creates a new CallCredentials object from a given function that generates
|
||||||
|
* Metadata objects.
|
||||||
|
* @param metadataGenerator A function that accepts a set of options, and
|
||||||
|
* 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 {
|
||||||
|
return new SingleCallCredentials(metadataGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmpty(): CallCredentials {
|
||||||
|
return new EmptyCallCredentials();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
import * as stream from 'stream';
|
import * as http2 from 'http2';
|
||||||
|
import {Duplex} from 'stream';
|
||||||
import * as http2 from 'http2';
|
|
||||||
|
|
||||||
import {CallCredentials} from './call-credentials';
|
import {CallCredentials} from './call-credentials';
|
||||||
import {Status} from './constants';
|
import {Status} from './constants';
|
||||||
|
import {Filter} from './filter';
|
||||||
|
import {FilterStackFactory} from './filter-stack';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
import {ObjectDuplex} from './object-stream';
|
import {ObjectDuplex} from './object-stream';
|
||||||
import {Filter} from './filter'
|
|
||||||
import {FilterStackFactory} from './filter-stack';
|
|
||||||
|
|
||||||
const {
|
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants;
|
||||||
HTTP2_HEADER_STATUS,
|
|
||||||
HTTP2_HEADER_CONTENT_TYPE
|
|
||||||
} = http2.constants;
|
|
||||||
|
|
||||||
export type Deadline = Date | number;
|
export type Deadline = Date | number;
|
||||||
|
|
||||||
|
|
@ -46,7 +42,7 @@ export interface CallStream extends ObjectDuplex<WriteObject, Buffer> {
|
||||||
getCredentials(): CallCredentials;
|
getCredentials(): CallCredentials;
|
||||||
/* If the return value is null, the call has not ended yet. Otherwise, it has
|
/* If the return value is null, the call has not ended yet. Otherwise, it has
|
||||||
* ended with the specified status */
|
* ended with the specified status */
|
||||||
getStatus(): StatusObject | null;
|
getStatus(): StatusObject|null;
|
||||||
|
|
||||||
addListener(event: string, listener: Function): this;
|
addListener(event: string, listener: Function): this;
|
||||||
emit(event: string|symbol, ...args: any[]): boolean;
|
emit(event: string|symbol, ...args: any[]): boolean;
|
||||||
|
|
@ -85,35 +81,35 @@ enum ReadState {
|
||||||
READING_MESSAGE
|
READING_MESSAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Http2CallStream extends stream.Duplex implements CallStream {
|
export class Http2CallStream extends Duplex implements CallStream {
|
||||||
|
|
||||||
public filterStack: Filter;
|
public filterStack: Filter;
|
||||||
private statusEmitted: boolean = false;
|
private statusEmitted = false;
|
||||||
private http2Stream: http2.ClientHttp2Stream | null = null;
|
private http2Stream: http2.ClientHttp2Stream|null = null;
|
||||||
private pendingRead: boolean = false;
|
private pendingRead = false;
|
||||||
private pendingWrite: Buffer | null = null;
|
private pendingWrite: Buffer|null = null;
|
||||||
private pendingWriteCallback: Function | null = null;
|
private pendingWriteCallback: Function|null = null;
|
||||||
private pendingFinalCallback: Function | null = null;
|
private pendingFinalCallback: Function|null = null;
|
||||||
|
|
||||||
private readState: ReadState = ReadState.NO_DATA;
|
private readState: ReadState = ReadState.NO_DATA;
|
||||||
private readCompressFlag: boolean = false;
|
private readCompressFlag = false;
|
||||||
private readPartialSize: Buffer = Buffer.alloc(4);
|
private readPartialSize: Buffer = Buffer.alloc(4);
|
||||||
private readSizeRemaining: number = 4;
|
private readSizeRemaining = 4;
|
||||||
private readMessageSize: number = 0;
|
private readMessageSize = 0;
|
||||||
private readPartialMessage: Buffer[] = [];
|
private readPartialMessage: Buffer[] = [];
|
||||||
private readMessageRemaining = 0;
|
private readMessageRemaining = 0;
|
||||||
|
|
||||||
private unpushedReadMessages: (Buffer | null)[] = [];
|
private unpushedReadMessages: (Buffer|null)[] = [];
|
||||||
|
|
||||||
// Status code mapped from :status. To be used if grpc-status is not received
|
// Status code mapped from :status. To be used if grpc-status is not received
|
||||||
private mappedStatusCode: Status = Status.UNKNOWN;
|
private mappedStatusCode: Status = Status.UNKNOWN;
|
||||||
|
|
||||||
// This is populated (non-null) if and only if the call has ended
|
// This is populated (non-null) if and only if the call has ended
|
||||||
private finalStatus: StatusObject | null = null;
|
private finalStatus: StatusObject|null = null;
|
||||||
|
|
||||||
constructor(private readonly methodName: string,
|
constructor(
|
||||||
private readonly options: CallStreamOptions,
|
private readonly methodName: string,
|
||||||
filterStackFactory: FilterStackFactory) {
|
private readonly options: CallStreamOptions,
|
||||||
|
filterStackFactory: FilterStackFactory) {
|
||||||
super({objectMode: true});
|
super({objectMode: true});
|
||||||
this.filterStack = filterStackFactory.createFilter(this);
|
this.filterStack = filterStackFactory.createFilter(this);
|
||||||
}
|
}
|
||||||
|
|
@ -131,28 +127,28 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
} else {
|
} else {
|
||||||
this.http2Stream = stream;
|
this.http2Stream = stream;
|
||||||
stream.on('response', (headers) => {
|
stream.on('response', (headers) => {
|
||||||
switch(headers[HTTP2_HEADER_STATUS]) {
|
switch (headers[HTTP2_HEADER_STATUS]) {
|
||||||
// TODO(murgatroid99): handle 100 and 101
|
// TODO(murgatroid99): handle 100 and 101
|
||||||
case '400':
|
case '400':
|
||||||
this.mappedStatusCode = Status.INTERNAL;
|
this.mappedStatusCode = Status.INTERNAL;
|
||||||
break;
|
break;
|
||||||
case '401':
|
case '401':
|
||||||
this.mappedStatusCode = Status.UNAUTHENTICATED;
|
this.mappedStatusCode = Status.UNAUTHENTICATED;
|
||||||
break;
|
break;
|
||||||
case '403':
|
case '403':
|
||||||
this.mappedStatusCode = Status.PERMISSION_DENIED;
|
this.mappedStatusCode = Status.PERMISSION_DENIED;
|
||||||
break;
|
break;
|
||||||
case '404':
|
case '404':
|
||||||
this.mappedStatusCode = Status.UNIMPLEMENTED;
|
this.mappedStatusCode = Status.UNIMPLEMENTED;
|
||||||
break;
|
break;
|
||||||
case '429':
|
case '429':
|
||||||
case '502':
|
case '502':
|
||||||
case '503':
|
case '503':
|
||||||
case '504':
|
case '504':
|
||||||
this.mappedStatusCode = Status.UNAVAILABLE;
|
this.mappedStatusCode = Status.UNAVAILABLE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.mappedStatusCode = Status.UNKNOWN;
|
this.mappedStatusCode = Status.UNKNOWN;
|
||||||
}
|
}
|
||||||
delete headers[HTTP2_HEADER_STATUS];
|
delete headers[HTTP2_HEADER_STATUS];
|
||||||
delete headers[HTTP2_HEADER_CONTENT_TYPE];
|
delete headers[HTTP2_HEADER_CONTENT_TYPE];
|
||||||
|
|
@ -163,11 +159,14 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
this.cancelWithStatus(Status.UNKNOWN, e.message);
|
this.cancelWithStatus(Status.UNKNOWN, e.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.filterStack.receiveMetadata(Promise.resolve(metadata)).then((finalMetadata) => {
|
this.filterStack.receiveMetadata(Promise.resolve(metadata))
|
||||||
this.emit('metadata', finalMetadata);
|
.then(
|
||||||
}, (error) => {
|
(finalMetadata) => {
|
||||||
this.cancelWithStatus(Status.UNKNOWN, error.message);
|
this.emit('metadata', finalMetadata);
|
||||||
});
|
},
|
||||||
|
(error) => {
|
||||||
|
this.cancelWithStatus(Status.UNKNOWN, error.message);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
stream.on('trailers', (headers) => {
|
stream.on('trailers', (headers) => {
|
||||||
let code: Status = this.mappedStatusCode;
|
let code: Status = this.mappedStatusCode;
|
||||||
|
|
@ -180,7 +179,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
}
|
}
|
||||||
delete headers['grpc-status'];
|
delete headers['grpc-status'];
|
||||||
}
|
}
|
||||||
let details: string = '';
|
let details = '';
|
||||||
if (headers.hasOwnProperty('grpc-message')) {
|
if (headers.hasOwnProperty('grpc-message')) {
|
||||||
details = decodeURI(headers['grpc-message']);
|
details = decodeURI(headers['grpc-message']);
|
||||||
}
|
}
|
||||||
|
|
@ -190,65 +189,73 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
metadata = new Metadata();
|
metadata = new Metadata();
|
||||||
}
|
}
|
||||||
let status: StatusObject = { code, details, metadata };
|
let status: StatusObject = {code, details, metadata};
|
||||||
this.filterStack.receiveTrailers(Promise.resolve(status)).then((finalStatus) => {
|
this.filterStack.receiveTrailers(Promise.resolve(status))
|
||||||
this.endCall(finalStatus);
|
.then(
|
||||||
}, (error) => {
|
(finalStatus) => {
|
||||||
this.endCall({
|
this.endCall(finalStatus);
|
||||||
code: Status.INTERNAL,
|
},
|
||||||
details: 'Failed to process received status',
|
(error) => {
|
||||||
metadata: new Metadata()
|
this.endCall({
|
||||||
});
|
code: Status.INTERNAL,
|
||||||
});
|
details: 'Failed to process received status',
|
||||||
|
metadata: new Metadata()
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
stream.on('read', (data) => {
|
stream.on('read', (data) => {
|
||||||
let readHead = 0;
|
let readHead = 0;
|
||||||
let canPush = true;
|
let canPush = true;
|
||||||
let toRead: number;
|
let toRead: number;
|
||||||
while (readHead < data.length) {
|
while (readHead < data.length) {
|
||||||
switch(this.readState) {
|
switch (this.readState) {
|
||||||
case ReadState.NO_DATA:
|
case ReadState.NO_DATA:
|
||||||
this.readCompressFlag = (data.readUInt8(readHead) !== 0);
|
this.readCompressFlag = (data.readUInt8(readHead) !== 0);
|
||||||
readHead += 1;
|
readHead += 1;
|
||||||
this.readState = ReadState.READING_SIZE;
|
this.readState = ReadState.READING_SIZE;
|
||||||
this.readPartialSize.fill(0);
|
this.readPartialSize.fill(0);
|
||||||
this.readSizeRemaining = 4;
|
this.readSizeRemaining = 4;
|
||||||
this.readMessageSize = 0;
|
this.readMessageSize = 0;
|
||||||
this.readMessageRemaining = 0;
|
this.readMessageRemaining = 0;
|
||||||
this.readPartialMessage = [];
|
this.readPartialMessage = [];
|
||||||
break;
|
break;
|
||||||
case ReadState.READING_SIZE:
|
case ReadState.READING_SIZE:
|
||||||
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
|
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
|
||||||
data.copy(this.readPartialSize, 4 - this.readSizeRemaining, readHead, readHead + toRead);
|
data.copy(
|
||||||
this.readSizeRemaining -= toRead;
|
this.readPartialSize, 4 - this.readSizeRemaining, readHead,
|
||||||
readHead += toRead;
|
readHead + toRead);
|
||||||
// readSizeRemaining >=0 here
|
this.readSizeRemaining -= toRead;
|
||||||
if (this.readSizeRemaining === 0) {
|
readHead += toRead;
|
||||||
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
|
// readSizeRemaining >=0 here
|
||||||
this.readMessageRemaining = this.readMessageSize;
|
if (this.readSizeRemaining === 0) {
|
||||||
this.readState = ReadState.READING_MESSAGE;
|
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
|
||||||
}
|
this.readMessageRemaining = this.readMessageSize;
|
||||||
break;
|
this.readState = ReadState.READING_MESSAGE;
|
||||||
case ReadState.READING_MESSAGE:
|
}
|
||||||
toRead = Math.min(data.length - readHead, this.readMessageRemaining);
|
break;
|
||||||
this.readPartialMessage.push(data.slice(readHead, readHead + toRead));
|
case ReadState.READING_MESSAGE:
|
||||||
this.readMessageRemaining -= toRead;
|
toRead =
|
||||||
readHead += toRead;
|
Math.min(data.length - readHead, this.readMessageRemaining);
|
||||||
// readMessageRemaining >=0 here
|
this.readPartialMessage.push(
|
||||||
if (this.readMessageRemaining === 0) {
|
data.slice(readHead, readHead + toRead));
|
||||||
// At this point, we have read a full message
|
this.readMessageRemaining -= toRead;
|
||||||
const messageBytes = Buffer.concat(this.readPartialMessage, this.readMessageSize);
|
readHead += toRead;
|
||||||
// TODO(murgatroid99): Add receive message filters
|
// readMessageRemaining >=0 here
|
||||||
if (canPush) {
|
if (this.readMessageRemaining === 0) {
|
||||||
if (!this.push(messageBytes)) {
|
// At this point, we have read a full message
|
||||||
canPush = false;
|
const messageBytes = Buffer.concat(
|
||||||
(this.http2Stream as http2.ClientHttp2Stream).pause();
|
this.readPartialMessage, this.readMessageSize);
|
||||||
}
|
// TODO(murgatroid99): Add receive message filters
|
||||||
} else {
|
if (canPush) {
|
||||||
this.unpushedReadMessages.push(messageBytes);
|
if (!this.push(messageBytes)) {
|
||||||
|
canPush = false;
|
||||||
|
(this.http2Stream as http2.ClientHttp2Stream).pause();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.unpushedReadMessages.push(messageBytes);
|
||||||
|
}
|
||||||
|
this.readState = ReadState.NO_DATA;
|
||||||
}
|
}
|
||||||
this.readState = ReadState.NO_DATA;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -261,30 +268,26 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
});
|
});
|
||||||
stream.on('streamClosed', (errorCode) => {
|
stream.on('streamClosed', (errorCode) => {
|
||||||
let code: Status;
|
let code: Status;
|
||||||
let details: string = '';
|
let details = '';
|
||||||
switch(errorCode) {
|
switch (errorCode) {
|
||||||
case http2.constants.NGHTTP2_REFUSED_STREAM:
|
case http2.constants.NGHTTP2_REFUSED_STREAM:
|
||||||
code = Status.UNAVAILABLE;
|
code = Status.UNAVAILABLE;
|
||||||
break;
|
break;
|
||||||
case http2.constants.NGHTTP2_CANCEL:
|
case http2.constants.NGHTTP2_CANCEL:
|
||||||
code = Status.CANCELLED;
|
code = Status.CANCELLED;
|
||||||
break;
|
break;
|
||||||
case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM:
|
case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM:
|
||||||
code = Status.RESOURCE_EXHAUSTED;
|
code = Status.RESOURCE_EXHAUSTED;
|
||||||
details = 'Bandwidth exhausted';
|
details = 'Bandwidth exhausted';
|
||||||
break;
|
break;
|
||||||
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
|
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
|
||||||
code = Status.PERMISSION_DENIED;
|
code = Status.PERMISSION_DENIED;
|
||||||
details = 'Protocol not secure enough';
|
details = 'Protocol not secure enough';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
code = Status.INTERNAL;
|
code = Status.INTERNAL;
|
||||||
}
|
}
|
||||||
this.endCall({
|
this.endCall({code: code, details: details, metadata: new Metadata()});
|
||||||
code: code,
|
|
||||||
details: details,
|
|
||||||
metadata: new Metadata()
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
stream.on('error', () => {
|
stream.on('error', () => {
|
||||||
this.endCall({
|
this.endCall({
|
||||||
|
|
@ -325,7 +328,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
|
||||||
return this.options.credentials;
|
return this.options.credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatus(): StatusObject | null {
|
getStatus(): StatusObject|null {
|
||||||
return this.finalStatus;
|
return this.finalStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { CallCredentials } from './call-credentials';
|
import {createSecureContext, SecureContext} from 'tls';
|
||||||
import { createSecureContext, SecureContext } from 'tls';
|
|
||||||
|
import {CallCredentials} from './call-credentials';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that contains credentials for communicating over a channel, as well
|
* A class that contains credentials for communicating over a channel, as well
|
||||||
|
|
@ -13,19 +14,69 @@ export interface ChannelCredentials {
|
||||||
* @param callCredentials A CallCredentials object to associate with this
|
* @param callCredentials A CallCredentials object to associate with this
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
compose(callCredentials: CallCredentials) : ChannelCredentials;
|
compose(callCredentials: CallCredentials): ChannelCredentials;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the set of per-call credentials associated with this instance.
|
* 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
|
* Gets a SecureContext object generated from input parameters if this
|
||||||
* instance was created with createSsl, or null if this instance was created
|
* instance was created with createSsl, or null if this instance was created
|
||||||
* with createInsecure.
|
* with createInsecure.
|
||||||
*/
|
*/
|
||||||
getSecureContext() : SecureContext | null;
|
getSecureContext(): SecureContext|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ChannelCredentialsImpl implements ChannelCredentials {
|
||||||
|
protected callCredentials: CallCredentials;
|
||||||
|
|
||||||
|
protected constructor(callCredentials?: CallCredentials) {
|
||||||
|
this.callCredentials = callCredentials || CallCredentials.createEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract compose(callCredentials: CallCredentials): ChannelCredentialsImpl;
|
||||||
|
|
||||||
|
getCallCredentials(): CallCredentials {
|
||||||
|
return this.callCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getSecureContext(): SecureContext|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl {
|
||||||
|
constructor(callCredentials?: CallCredentials) {
|
||||||
|
super(callCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
compose(callCredentials: CallCredentials): ChannelCredentialsImpl {
|
||||||
|
throw new Error('Cannot compose insecure credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecureContext(): SecureContext|null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecureChannelCredentialsImpl extends ChannelCredentialsImpl {
|
||||||
|
secureContext: SecureContext;
|
||||||
|
|
||||||
|
constructor(secureContext: SecureContext, callCredentials?: CallCredentials) {
|
||||||
|
super(callCredentials);
|
||||||
|
this.secureContext = secureContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
compose(callCredentials: CallCredentials): ChannelCredentialsImpl {
|
||||||
|
const combinedCallCredentials =
|
||||||
|
this.callCredentials.compose(callCredentials);
|
||||||
|
return new SecureChannelCredentialsImpl(
|
||||||
|
this.secureContext, combinedCallCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecureContext(): SecureContext|null {
|
||||||
|
return this.secureContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ChannelCredentials {
|
export namespace ChannelCredentials {
|
||||||
|
|
@ -37,12 +88,16 @@ export namespace ChannelCredentials {
|
||||||
* @param privateKey The client certificate private key, if available.
|
* @param privateKey The client certificate private key, if available.
|
||||||
* @param certChain The client certificate key chain, if available.
|
* @param certChain The client certificate key chain, if available.
|
||||||
*/
|
*/
|
||||||
export function createSsl(rootCerts?: Buffer | null, privateKey?: Buffer | null, certChain?: Buffer | null) : ChannelCredentials {
|
export function createSsl(
|
||||||
|
rootCerts?: Buffer|null, privateKey?: Buffer|null,
|
||||||
|
certChain?: Buffer|null): ChannelCredentials {
|
||||||
if (privateKey && !certChain) {
|
if (privateKey && !certChain) {
|
||||||
throw new Error('Private key must be given with accompanying certificate chain');
|
throw new Error(
|
||||||
|
'Private key must be given with accompanying certificate chain');
|
||||||
}
|
}
|
||||||
if (!privateKey && certChain) {
|
if (!privateKey && certChain) {
|
||||||
throw new Error('Certificate chain must be given with accompanying private key');
|
throw new Error(
|
||||||
|
'Certificate chain must be given with accompanying private key');
|
||||||
}
|
}
|
||||||
const secureContext = createSecureContext({
|
const secureContext = createSecureContext({
|
||||||
ca: rootCerts || undefined,
|
ca: rootCerts || undefined,
|
||||||
|
|
@ -55,60 +110,7 @@ export namespace ChannelCredentials {
|
||||||
/**
|
/**
|
||||||
* Return a new ChannelCredentials instance with no credentials.
|
* Return a new ChannelCredentials instance with no credentials.
|
||||||
*/
|
*/
|
||||||
export function createInsecure() : ChannelCredentials {
|
export function createInsecure(): ChannelCredentials {
|
||||||
return new InsecureChannelCredentialsImpl();
|
return new InsecureChannelCredentialsImpl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class ChannelCredentialsImpl implements ChannelCredentials {
|
|
||||||
protected callCredentials: CallCredentials;
|
|
||||||
|
|
||||||
protected constructor(callCredentials?: CallCredentials) {
|
|
||||||
this.callCredentials = callCredentials || CallCredentials.createEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract compose(callCredentials: CallCredentials) : ChannelCredentialsImpl;
|
|
||||||
|
|
||||||
getCallCredentials() : CallCredentials {
|
|
||||||
return this.callCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getSecureContext() : SecureContext | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InsecureChannelCredentialsImpl extends ChannelCredentialsImpl {
|
|
||||||
constructor(callCredentials?: CallCredentials) {
|
|
||||||
super(callCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
compose(callCredentials: CallCredentials) : ChannelCredentialsImpl {
|
|
||||||
throw new Error("Cannot compose insecure credentials");
|
|
||||||
}
|
|
||||||
|
|
||||||
getSecureContext() : SecureContext | null {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SecureChannelCredentialsImpl extends ChannelCredentialsImpl {
|
|
||||||
secureContext: SecureContext;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
secureContext: SecureContext,
|
|
||||||
callCredentials?: CallCredentials
|
|
||||||
) {
|
|
||||||
super(callCredentials);
|
|
||||||
this.secureContext = secureContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
compose(callCredentials: CallCredentials) : ChannelCredentialsImpl {
|
|
||||||
const combinedCallCredentials = this.callCredentials.compose(callCredentials);
|
|
||||||
return new SecureChannelCredentialsImpl(this.secureContext,
|
|
||||||
combinedCallCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSecureContext() : SecureContext | null {
|
|
||||||
return this.secureContext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
106
src/channel.ts
106
src/channel.ts
|
|
@ -1,17 +1,17 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {SecureContext} from 'tls';
|
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
|
import {SecureContext} from 'tls';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import {CallOptions, CallStreamOptions, CallStream, Http2CallStream} from './call-stream';
|
|
||||||
import {CallCredentials} from './call-credentials';
|
|
||||||
import {ChannelCredentials} from './channel-credentials';
|
|
||||||
import {Metadata, MetadataObject} from './metadata';
|
|
||||||
import {Status} from './constants'
|
|
||||||
|
|
||||||
import {FilterStackFactory} from './filter-stack'
|
import {CallCredentials} from './call-credentials';
|
||||||
import {DeadlineFilterFactory} from './deadline-filter'
|
import {CallCredentialsFilterFactory} from './call-credentials-filter';
|
||||||
import {CallCredentialsFilterFactory} from './call-credentials-filter'
|
import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream';
|
||||||
import {CompressionFilterFactory} from './compression-filter'
|
import {ChannelCredentials} from './channel-credentials';
|
||||||
|
import {CompressionFilterFactory} from './compression-filter';
|
||||||
|
import {Status} from './constants';
|
||||||
|
import {DeadlineFilterFactory} from './deadline-filter';
|
||||||
|
import {FilterStackFactory} from './filter-stack';
|
||||||
|
import {Metadata, MetadataObject} from './metadata';
|
||||||
|
|
||||||
const IDLE_TIMEOUT_MS = 300000;
|
const IDLE_TIMEOUT_MS = 300000;
|
||||||
|
|
||||||
|
|
@ -45,7 +45,8 @@ export enum ConnectivityState {
|
||||||
* by a given address.
|
* by a given address.
|
||||||
*/
|
*/
|
||||||
export interface Channel extends EventEmitter {
|
export interface Channel extends EventEmitter {
|
||||||
createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream;
|
createStream(methodName: string, metadata: Metadata, options: CallOptions):
|
||||||
|
CallStream;
|
||||||
connect(callback: () => void): void;
|
connect(callback: () => void): void;
|
||||||
getConnectivityState(): ConnectivityState;
|
getConnectivityState(): ConnectivityState;
|
||||||
close(): void;
|
close(): void;
|
||||||
|
|
@ -61,11 +62,11 @@ export interface Channel extends EventEmitter {
|
||||||
|
|
||||||
export class Http2Channel extends EventEmitter implements Channel {
|
export class Http2Channel extends EventEmitter implements Channel {
|
||||||
private connectivityState: ConnectivityState = ConnectivityState.IDLE;
|
private connectivityState: ConnectivityState = ConnectivityState.IDLE;
|
||||||
private idleTimerId: NodeJS.Timer | null = null;
|
private idleTimerId: NodeJS.Timer|null = null;
|
||||||
/* For now, we have up to one subchannel, which will exist as long as we are
|
/* For now, we have up to one subchannel, which will exist as long as we are
|
||||||
* connecting or trying to connect */
|
* connecting or trying to connect */
|
||||||
private subChannel : http2.ClientHttp2Session | null;
|
private subChannel: http2.ClientHttp2Session|null;
|
||||||
private filterStackFactory : FilterStackFactory;
|
private filterStackFactory: FilterStackFactory;
|
||||||
|
|
||||||
private transitionToState(newState: ConnectivityState): void {
|
private transitionToState(newState: ConnectivityState): void {
|
||||||
if (newState !== this.connectivityState) {
|
if (newState !== this.connectivityState) {
|
||||||
|
|
@ -94,7 +95,7 @@ export class Http2Channel extends EventEmitter implements Channel {
|
||||||
|
|
||||||
private goIdle(): void {
|
private goIdle(): void {
|
||||||
if (this.subChannel !== null) {
|
if (this.subChannel !== null) {
|
||||||
this.subChannel.shutdown({graceful: true}, () => {});
|
this.subChannel.shutdown({graceful: true}, () => undefined);
|
||||||
this.subChannel = null;
|
this.subChannel = null;
|
||||||
}
|
}
|
||||||
this.transitionToState(ConnectivityState.IDLE);
|
this.transitionToState(ConnectivityState.IDLE);
|
||||||
|
|
@ -106,9 +107,10 @@ export class Http2Channel extends EventEmitter implements Channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly address: url.URL,
|
constructor(
|
||||||
public readonly credentials: ChannelCredentials,
|
private readonly address: url.URL,
|
||||||
private readonly options: ChannelOptions) {
|
public readonly credentials: ChannelCredentials,
|
||||||
|
private readonly options: ChannelOptions) {
|
||||||
super();
|
super();
|
||||||
if (credentials.getSecureContext() === null) {
|
if (credentials.getSecureContext() === null) {
|
||||||
address.protocol = 'http';
|
address.protocol = 'http';
|
||||||
|
|
@ -117,41 +119,46 @@ export class Http2Channel extends EventEmitter implements Channel {
|
||||||
}
|
}
|
||||||
this.filterStackFactory = new FilterStackFactory([
|
this.filterStackFactory = new FilterStackFactory([
|
||||||
new CompressionFilterFactory(this),
|
new CompressionFilterFactory(this),
|
||||||
new CallCredentialsFilterFactory(this),
|
new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this)
|
||||||
new DeadlineFilterFactory(this)
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private startHttp2Stream(methodName: string, stream: Http2CallStream, metadata: Metadata) {
|
private startHttp2Stream(
|
||||||
let finalMetadata: Promise<Metadata> = stream.filterStack.sendMetadata(Promise.resolve(metadata));
|
methodName: string, stream: Http2CallStream, metadata: Metadata) {
|
||||||
|
let finalMetadata: Promise<Metadata> =
|
||||||
|
stream.filterStack.sendMetadata(Promise.resolve(metadata));
|
||||||
this.connect(() => {
|
this.connect(() => {
|
||||||
finalMetadata.then((metadataValue) => {
|
finalMetadata.then(
|
||||||
let headers = metadataValue.toHttp2Headers();
|
(metadataValue) => {
|
||||||
headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname;
|
let headers = metadataValue.toHttp2Headers();
|
||||||
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
|
headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname;
|
||||||
headers[HTTP2_HEADER_METHOD] = 'POST';
|
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
|
||||||
headers[HTTP2_HEADER_PATH] = methodName;
|
headers[HTTP2_HEADER_METHOD] = 'POST';
|
||||||
headers[HTTP2_HEADER_TE] = 'trailers';
|
headers[HTTP2_HEADER_PATH] = methodName;
|
||||||
if (stream.getStatus() === null) {
|
headers[HTTP2_HEADER_TE] = 'trailers';
|
||||||
if (this.connectivityState === ConnectivityState.READY) {
|
if (stream.getStatus() === null) {
|
||||||
let session: http2.ClientHttp2Session =
|
if (this.connectivityState === ConnectivityState.READY) {
|
||||||
(this.subChannel as http2.ClientHttp2Session);
|
let session: http2.ClientHttp2Session =
|
||||||
stream.attachHttp2Stream(session.request(headers));
|
(this.subChannel as http2.ClientHttp2Session);
|
||||||
} else {
|
stream.attachHttp2Stream(session.request(headers));
|
||||||
/* In this case, we lost the connection while finalizing metadata.
|
} else {
|
||||||
* That should be very unusual */
|
/* In this case, we lost the connection while finalizing
|
||||||
setImmediate(() => {
|
* metadata. That should be very unusual */
|
||||||
this.startHttp2Stream(methodName, stream, metadata);
|
setImmediate(() => {
|
||||||
});
|
this.startHttp2Stream(methodName, stream, metadata);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}, (error) => {
|
}
|
||||||
stream.cancelWithStatus(Status.UNKNOWN, "Failed to generate metadata");
|
},
|
||||||
});
|
(error) => {
|
||||||
|
stream.cancelWithStatus(
|
||||||
|
Status.UNKNOWN, 'Failed to generate metadata');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createStream(methodName: string, metadata: Metadata, options: CallOptions): CallStream {
|
createStream(methodName: string, metadata: Metadata, options: CallOptions):
|
||||||
|
CallStream {
|
||||||
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
|
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
|
||||||
throw new Error('Channel has been shut down');
|
throw new Error('Channel has been shut down');
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +166,9 @@ export class Http2Channel extends EventEmitter implements Channel {
|
||||||
deadline: options.deadline === undefined ? Infinity : options.deadline,
|
deadline: options.deadline === undefined ? Infinity : options.deadline,
|
||||||
credentials: options.credentials || CallCredentials.createEmpty(),
|
credentials: options.credentials || CallCredentials.createEmpty(),
|
||||||
flags: options.flags || 0
|
flags: options.flags || 0
|
||||||
}
|
};
|
||||||
let stream: Http2CallStream = new Http2CallStream(methodName, finalOptions, this.filterStackFactory);
|
let stream: Http2CallStream =
|
||||||
|
new Http2CallStream(methodName, finalOptions, this.filterStackFactory);
|
||||||
this.startHttp2Stream(methodName, stream, metadata);
|
this.startHttp2Stream(methodName, stream, metadata);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +186,7 @@ export class Http2Channel extends EventEmitter implements Channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectivityState(): ConnectivityState{
|
getConnectivityState(): ConnectivityState {
|
||||||
return this.connectivityState;
|
return this.connectivityState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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, ServiceErrorImpl} from './call';
|
||||||
import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream';
|
import {CallOptions, CallStream, StatusObject, WriteObject} from './call-stream';
|
||||||
import {Channel, Http2Channel, ChannelOptions} from './channel';
|
import {Channel, ChannelOptions, Http2Channel} from './channel';
|
||||||
import {ChannelCredentials} from './channel-credentials';
|
import {ChannelCredentials} from './channel-credentials';
|
||||||
import {Status} from './constants';
|
import {Status} from './constants';
|
||||||
import {Metadata} from './metadata';
|
import {Metadata} from './metadata';
|
||||||
|
|
@ -32,13 +32,13 @@ export class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
|
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
|
||||||
void {
|
void {
|
||||||
let cb : (error: Error|null) => void = once(callback);
|
let cb: (error: Error|null) => void = once(callback);
|
||||||
let callbackCalled: boolean = false;
|
let callbackCalled = false;
|
||||||
this.channel.connect(() => {
|
this.channel.connect(() => {
|
||||||
cb(null);
|
cb(null);
|
||||||
});
|
});
|
||||||
if (deadline != Infinity) {
|
if (deadline !== Infinity) {
|
||||||
let timeout: number;
|
let timeout: number;
|
||||||
let now: number = (new Date).getTime();
|
let now: number = (new Date).getTime();
|
||||||
if (deadline instanceof Date) {
|
if (deadline instanceof Date) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import {CallStream} from './call-stream'
|
import {CallStream} from './call-stream';
|
||||||
import {Channel} from './channel'
|
import {Channel} from './channel';
|
||||||
import {Filter, BaseFilter, FilterFactory} from './filter'
|
import {BaseFilter, Filter, FilterFactory} from './filter';
|
||||||
import {Metadata} from './metadata'
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
export class CompressionFilter extends BaseFilter implements Filter {
|
export class CompressionFilter extends BaseFilter implements Filter {
|
||||||
|
|
||||||
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||||
const headers: Metadata = await metadata;
|
const headers: Metadata = await metadata;
|
||||||
headers.set('grpc-encoding', 'identity');
|
headers.set('grpc-encoding', 'identity');
|
||||||
|
|
@ -20,8 +19,9 @@ export class CompressionFilter extends BaseFilter implements Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompressionFilterFactory implements FilterFactory<CompressionFilter> {
|
export class CompressionFilterFactory implements
|
||||||
constructor(channel: Channel) {}
|
FilterFactory<CompressionFilter> {
|
||||||
|
constructor(private readonly channel: Channel) {}
|
||||||
createFilter(callStream: CallStream): CompressionFilter {
|
createFilter(callStream: CallStream): CompressionFilter {
|
||||||
return new CompressionFilter();
|
return new CompressionFilter();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
import {CallStream} from './call-stream'
|
import {CallStream} from './call-stream';
|
||||||
import {Channel, Http2Channel} from './channel'
|
import {Channel, Http2Channel} from './channel';
|
||||||
import {Filter, BaseFilter, FilterFactory} from './filter'
|
import {Status} from './constants';
|
||||||
import {Status} from './constants'
|
import {BaseFilter, Filter, FilterFactory} from './filter';
|
||||||
import {Metadata} from './metadata'
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
const units: [string, number][] = [
|
const units: [string, number][] =
|
||||||
['m', 1],
|
[['m', 1], ['S', 1000], ['M', 60 * 1000], ['H', 60 * 60 * 1000]];
|
||||||
['S', 1000],
|
|
||||||
['M', 60 * 1000],
|
|
||||||
['H', 60 * 60 * 1000]
|
|
||||||
]
|
|
||||||
|
|
||||||
export class DeadlineFilter extends BaseFilter implements Filter {
|
export class DeadlineFilter extends BaseFilter implements Filter {
|
||||||
private deadline: number;
|
private deadline: number;
|
||||||
constructor(private readonly channel: Http2Channel, private readonly callStream: CallStream) {
|
constructor(
|
||||||
|
private readonly channel: Http2Channel,
|
||||||
|
private readonly callStream: CallStream) {
|
||||||
super();
|
super();
|
||||||
let callDeadline = callStream.getDeadline();
|
let callDeadline = callStream.getDeadline();
|
||||||
if (callDeadline instanceof Date) {
|
if (callDeadline instanceof Date) {
|
||||||
|
|
@ -28,7 +26,8 @@ export class DeadlineFilter extends BaseFilter implements Filter {
|
||||||
}
|
}
|
||||||
if (this.deadline !== Infinity) {
|
if (this.deadline !== Infinity) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
callStream.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
|
callStream.cancelWithStatus(
|
||||||
|
Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,19 +36,20 @@ export class DeadlineFilter extends BaseFilter implements Filter {
|
||||||
if (this.deadline === Infinity) {
|
if (this.deadline === Infinity) {
|
||||||
return await metadata;
|
return await metadata;
|
||||||
}
|
}
|
||||||
let timeoutString : Promise<string> = new Promise<string>((resolve, reject) => {
|
let timeoutString: Promise<string> =
|
||||||
this.channel.connect(() => {
|
new Promise<string>((resolve, reject) => {
|
||||||
let now = (new Date()).getTime();
|
this.channel.connect(() => {
|
||||||
let timeoutMs = this.deadline - now;
|
let now = (new Date()).getTime();
|
||||||
for (let [unit, factor] of units) {
|
let timeoutMs = this.deadline - now;
|
||||||
let amount = timeoutMs / factor;
|
for (let [unit, factor] of units) {
|
||||||
if (amount < 1e8) {
|
let amount = timeoutMs / factor;
|
||||||
resolve(String(Math.ceil(amount)) + unit);
|
if (amount < 1e8) {
|
||||||
return;
|
resolve(String(Math.ceil(amount)) + unit);
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
let finalMetadata = await metadata;
|
let finalMetadata = await metadata;
|
||||||
finalMetadata.set('grpc-timeout', await timeoutString);
|
finalMetadata.set('grpc-timeout', await timeoutString);
|
||||||
return finalMetadata;
|
return finalMetadata;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
import {flow, flowRight, map} from 'lodash';
|
import {flow, flowRight, map} from 'lodash';
|
||||||
import {Metadata} from './metadata';
|
|
||||||
import {CallStream, StatusObject} from './call-stream'
|
import {CallStream, StatusObject} from './call-stream';
|
||||||
import {Filter, FilterFactory} from './filter';
|
import {Filter, FilterFactory} from './filter';
|
||||||
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
export class FilterStack implements Filter {
|
export class FilterStack implements Filter {
|
||||||
constructor(private readonly filters: Filter[]) {}
|
constructor(private readonly filters: Filter[]) {}
|
||||||
|
|
||||||
sendMetadata(metadata: Promise<Metadata>) {
|
sendMetadata(metadata: Promise<Metadata>) {
|
||||||
return flow(map(this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata);
|
return flow(map(
|
||||||
|
this.filters, (filter) => filter.sendMetadata.bind(filter)))(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveMetadata(metadata: Promise<Metadata>) {
|
receiveMetadata(metadata: Promise<Metadata>) {
|
||||||
return flowRight(map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))(metadata);
|
return flowRight(
|
||||||
|
map(this.filters, (filter) => filter.receiveMetadata.bind(filter)))(
|
||||||
|
metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
|
receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
|
||||||
return flowRight(map(this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status);
|
return flowRight(map(
|
||||||
|
this.filters, (filter) => filter.receiveTrailers.bind(filter)))(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +28,7 @@ export class FilterStackFactory implements FilterFactory<FilterStack> {
|
||||||
constructor(private readonly factories: FilterFactory<any>[]) {}
|
constructor(private readonly factories: FilterFactory<any>[]) {}
|
||||||
|
|
||||||
createFilter(callStream: CallStream): FilterStack {
|
createFilter(callStream: CallStream): FilterStack {
|
||||||
return new FilterStack(map(this.factories, (factory) => factory.createFilter(callStream)));
|
return new FilterStack(
|
||||||
|
map(this.factories, (factory) => factory.createFilter(callStream)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import {Metadata} from './metadata'
|
import {CallStream, StatusObject} from './call-stream';
|
||||||
import {StatusObject, CallStream} from './call-stream'
|
import {Metadata} from './metadata';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter classes represent related per-call logic and state that is primarily
|
* Filter classes represent related per-call logic and state that is primarily
|
||||||
* used to modify incoming and outgoing data */
|
* used to modify incoming and outgoing data
|
||||||
|
*/
|
||||||
export interface Filter {
|
export interface Filter {
|
||||||
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata>;
|
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { forOwn } from 'lodash';
|
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
|
import {forOwn} from 'lodash';
|
||||||
|
|
||||||
export type MetadataValue = string | Buffer;
|
export type MetadataValue = string | Buffer;
|
||||||
|
|
||||||
export interface MetadataObject {
|
export interface MetadataObject { [key: string]: Array<MetadataValue>; }
|
||||||
[key: string]: Array<MetadataValue>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneMetadataObject(repr: MetadataObject): MetadataObject {
|
function cloneMetadataObject(repr: MetadataObject): MetadataObject {
|
||||||
const result: MetadataObject = {};
|
const result: MetadataObject = {};
|
||||||
|
|
@ -54,8 +52,9 @@ function validate(key: string, value?: MetadataValue): void {
|
||||||
'keys that don\'t end with \'-bin\' must have String values');
|
'keys that don\'t end with \'-bin\' must have String values');
|
||||||
}
|
}
|
||||||
if (!isLegalNonBinaryValue(value)) {
|
if (!isLegalNonBinaryValue(value)) {
|
||||||
throw new Error('Metadata string value "' + value +
|
throw new Error(
|
||||||
'" contains illegal characters');
|
'Metadata string value "' + value +
|
||||||
|
'" contains illegal characters');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +65,6 @@ function validate(key: string, value?: MetadataValue): void {
|
||||||
*/
|
*/
|
||||||
export class Metadata {
|
export class Metadata {
|
||||||
protected internalRepr: MetadataObject = {};
|
protected internalRepr: MetadataObject = {};
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the given value for the given key by replacing any other values
|
* Sets the given value for the given key by replacing any other values
|
||||||
|
|
@ -130,10 +128,10 @@ export class Metadata {
|
||||||
* This reflects the most common way that people will want to see metadata.
|
* This reflects the most common way that people will want to see metadata.
|
||||||
* @return A key/value mapping of the metadata.
|
* @return A key/value mapping of the metadata.
|
||||||
*/
|
*/
|
||||||
getMap(): { [key: string]: MetadataValue } {
|
getMap(): {[key: string]: MetadataValue} {
|
||||||
const result: { [key: string]: MetadataValue } = {};
|
const result: {[key: string]: MetadataValue} = {};
|
||||||
forOwn(this.internalRepr, (values, key) => {
|
forOwn(this.internalRepr, (values, key) => {
|
||||||
if(values.length > 0) {
|
if (values.length > 0) {
|
||||||
const v = values[0];
|
const v = values[0];
|
||||||
result[key] = v instanceof Buffer ? v.slice() : v;
|
result[key] = v instanceof Buffer ? v.slice() : v;
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +193,7 @@ export class Metadata {
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
values.forEach((value) => {
|
values.forEach((value) => {
|
||||||
result.add(key, Buffer.from(value, 'base64'));
|
result.add(key, Buffer.from(value, 'base64'));
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
result.add(key, Buffer.from(values, 'base64'));
|
result.add(key, Buffer.from(values, 'base64'));
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +201,7 @@ export class Metadata {
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
values.forEach((value) => {
|
values.forEach((value) => {
|
||||||
result.add(key, value);
|
result.add(key, value);
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
result.add(key, values);
|
result.add(key, values);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { Readable, Writable, Duplex } from 'stream';
|
import {Duplex, Readable, Writable} from 'stream';
|
||||||
|
|
||||||
export interface IntermediateObjectReadable<T> extends Readable {
|
export interface IntermediateObjectReadable<T> extends Readable {
|
||||||
read(size?: number): any & T;
|
read(size?: number): any&T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectReadable<T> extends IntermediateObjectReadable<T> {
|
export interface ObjectReadable<T> extends IntermediateObjectReadable<T> {
|
||||||
read(size?: number): T;
|
read(size?: number): T;
|
||||||
|
|
||||||
addListener(event: string, listener: Function): this;
|
addListener(event: string, listener: Function): this;
|
||||||
emit(event: string | symbol, ...args: any[]): boolean;
|
emit(event: string|symbol, ...args: any[]): boolean;
|
||||||
on(event: string, listener: Function): this;
|
on(event: string, listener: Function): this;
|
||||||
once(event: string, listener: Function): this;
|
once(event: string, listener: Function): this;
|
||||||
prependListener(event: string, listener: Function): this;
|
prependListener(event: string, listener: Function): this;
|
||||||
|
|
@ -25,13 +25,13 @@ export interface ObjectReadable<T> extends IntermediateObjectReadable<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IntermediateObjectWritable<T> extends Writable {
|
export interface IntermediateObjectWritable<T> extends Writable {
|
||||||
_write(chunk: any & T, encoding: string, callback: Function): void;
|
_write(chunk: any&T, encoding: string, callback: Function): void;
|
||||||
write(chunk: any & T, cb?: Function): boolean;
|
write(chunk: any&T, cb?: Function): boolean;
|
||||||
write(chunk: any & T, encoding?: any, cb?: Function): boolean;
|
write(chunk: any&T, encoding?: any, cb?: Function): boolean;
|
||||||
setDefaultEncoding(encoding: string): this;
|
setDefaultEncoding(encoding: string): this;
|
||||||
end(): void;
|
end(): void;
|
||||||
end(chunk: any & T, cb?: Function): void;
|
end(chunk: any&T, cb?: Function): void;
|
||||||
end(chunk: any & T, encoding?: any, cb?: Function): void;
|
end(chunk: any&T, encoding?: any, cb?: Function): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectWritable<T> extends IntermediateObjectWritable<T> {
|
export interface ObjectWritable<T> extends IntermediateObjectWritable<T> {
|
||||||
|
|
@ -44,7 +44,8 @@ export interface ObjectWritable<T> extends IntermediateObjectWritable<T> {
|
||||||
end(chunk: T, encoding?: any, cb?: Function): void;
|
end(chunk: T, encoding?: any, cb?: Function): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectDuplex<T, U> extends Duplex, ObjectWritable<T>, ObjectReadable<U> {
|
export interface ObjectDuplex<T, U> extends Duplex, ObjectWritable<T>,
|
||||||
|
ObjectReadable<U> {
|
||||||
read(size?: number): U;
|
read(size?: number): U;
|
||||||
|
|
||||||
_write(chunk: T, encoding: string, callback: Function): void;
|
_write(chunk: T, encoding: string, callback: Function): void;
|
||||||
|
|
@ -56,7 +57,7 @@ export interface ObjectDuplex<T, U> extends Duplex, ObjectWritable<T>, ObjectRea
|
||||||
|
|
||||||
|
|
||||||
addListener(event: string, listener: Function): this;
|
addListener(event: string, listener: Function): this;
|
||||||
emit(event: string | symbol, ...args: any[]): boolean;
|
emit(event: string|symbol, ...args: any[]): boolean;
|
||||||
on(event: string, listener: Function): this;
|
on(event: string, listener: Function): this;
|
||||||
once(event: string, listener: Function): this;
|
once(event: string, listener: Function): this;
|
||||||
prependListener(event: string, listener: Function): this;
|
prependListener(event: string, listener: Function): this;
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ export namespace assert2 {
|
||||||
try {
|
try {
|
||||||
return fn();
|
return fn();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assert.throws(() => { throw e });
|
assert.throws(() => {throw e});
|
||||||
throw e; // for type safety only
|
throw e; // for type safety only
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { Metadata } from '../src/metadata';
|
|
||||||
import { CallCredentials, CallMetadataGenerator } from '../src/call-credentials';
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import {CallCredentials, CallMetadataGenerator} from '../src/call-credentials';
|
||||||
|
import {Metadata} from '../src/metadata';
|
||||||
|
|
||||||
// Metadata generators
|
// Metadata generators
|
||||||
|
|
||||||
function makeGenerator(props: Array<string>): CallMetadataGenerator {
|
function makeGenerator(props: Array<string>): CallMetadataGenerator {
|
||||||
return (options: { [propName: string]: string }, cb) => {
|
return (options: {[propName: string]: string}, cb) => {
|
||||||
const metadata: Metadata = new Metadata();
|
const metadata: Metadata = new Metadata();
|
||||||
props.forEach((prop) => {
|
props.forEach((prop) => {
|
||||||
if (options[prop]) {
|
if (options[prop]) {
|
||||||
|
|
@ -13,110 +14,111 @@ function makeGenerator(props: Array<string>): CallMetadataGenerator {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cb(null, metadata);
|
cb(null, metadata);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
|
function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
|
||||||
return (_options, cb) => {
|
return (options, cb) => {
|
||||||
const metadata = new Metadata();
|
const metadata = new Metadata();
|
||||||
metadata.add('msElapsed', `${ms}`);
|
metadata.add('msElapsed', `${ms}`);
|
||||||
setTimeout(() => cb(null, metadata), ms);
|
setTimeout(() => cb(null, metadata), ms);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const generateFromName: CallMetadataGenerator = makeGenerator(['name']);
|
const generateFromName: CallMetadataGenerator = makeGenerator(['name']);
|
||||||
const generateWithError: CallMetadataGenerator = (_options, cb) =>
|
const generateWithError: CallMetadataGenerator = (options, cb) =>
|
||||||
cb(new Error());
|
cb(new Error());
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
|
|
||||||
describe('CallCredentials', () => {
|
describe('CallCredentials', () => {
|
||||||
describe('createFromMetadataGenerator', () => {
|
describe('createFromMetadataGenerator', () => {
|
||||||
it('should accept a metadata generator', () => {
|
it('should accept a metadata generator', () => {
|
||||||
assert.doesNotThrow(() =>
|
assert.doesNotThrow(
|
||||||
CallCredentials.createFromMetadataGenerator(generateFromName));
|
() => CallCredentials.createFromMetadataGenerator(generateFromName));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compose', () => {
|
describe('compose', () => {
|
||||||
it('should accept a CallCredentials object and return a new object', () => {
|
it('should accept a CallCredentials object and return a new object', () => {
|
||||||
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
|
const callCredentials1 =
|
||||||
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
|
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||||
|
const callCredentials2 =
|
||||||
|
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||||
const combinedCredentials = callCredentials1.compose(callCredentials2);
|
const combinedCredentials = callCredentials1.compose(callCredentials2);
|
||||||
assert.notEqual(combinedCredentials, callCredentials1);
|
assert.notEqual(combinedCredentials, callCredentials1);
|
||||||
assert.notEqual(combinedCredentials, callCredentials2);
|
assert.notEqual(combinedCredentials, callCredentials2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be chainable', () => {
|
it('should be chainable', () => {
|
||||||
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
|
const callCredentials1 =
|
||||||
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
|
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||||
|
const callCredentials2 =
|
||||||
|
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||||
assert.doesNotThrow(() => {
|
assert.doesNotThrow(() => {
|
||||||
callCredentials1.compose(callCredentials2)
|
callCredentials1.compose(callCredentials2)
|
||||||
.compose(callCredentials2)
|
.compose(callCredentials2)
|
||||||
.compose(callCredentials2);
|
.compose(callCredentials2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateMetadata', () => {
|
describe('generateMetadata', () => {
|
||||||
it('should call the function passed to createFromMetadataGenerator',
|
it('should call the function passed to createFromMetadataGenerator',
|
||||||
async () => {
|
async () => {
|
||||||
const callCredentials = CallCredentials.createFromMetadataGenerator(generateFromName);
|
const callCredentials =
|
||||||
let metadata: Metadata;
|
CallCredentials.createFromMetadataGenerator(generateFromName);
|
||||||
try {
|
let metadata: Metadata;
|
||||||
metadata = await callCredentials.generateMetadata({name: 'foo'});
|
try {
|
||||||
} catch (err) {
|
metadata = await callCredentials.generateMetadata({name: 'foo'});
|
||||||
throw err;
|
} catch (err) {
|
||||||
}
|
throw err;
|
||||||
assert.deepEqual(metadata.get('name'), ['foo']);
|
}
|
||||||
}
|
assert.deepEqual(metadata.get('name'), ['foo']);
|
||||||
);
|
});
|
||||||
|
|
||||||
it('should emit an error if the associated metadataGenerator does',
|
it('should emit an error if the associated metadataGenerator does',
|
||||||
async () => {
|
async () => {
|
||||||
const callCredentials = CallCredentials.createFromMetadataGenerator(
|
const callCredentials =
|
||||||
generateWithError);
|
CallCredentials.createFromMetadataGenerator(generateWithError);
|
||||||
let metadata: Metadata | null = null;
|
let metadata: Metadata|null = null;
|
||||||
try {
|
try {
|
||||||
metadata = await callCredentials.generateMetadata({});
|
metadata = await callCredentials.generateMetadata({});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
assert.ok(err instanceof Error);
|
assert.ok(err instanceof Error);
|
||||||
}
|
}
|
||||||
assert.strictEqual(metadata, null);
|
assert.strictEqual(metadata, null);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
it('should combine metadata from multiple generators', async () => {
|
it('should combine metadata from multiple generators', async () => {
|
||||||
const [callCreds1, callCreds2, callCreds3, callCreds4] =
|
const [callCreds1, callCreds2, callCreds3, callCreds4] =
|
||||||
[50, 100, 150, 200].map((ms) => {
|
[50, 100, 150, 200].map((ms) => {
|
||||||
const generator: CallMetadataGenerator =
|
const generator: CallMetadataGenerator =
|
||||||
makeAfterMsElapsedGenerator(ms);
|
makeAfterMsElapsedGenerator(ms);
|
||||||
return CallCredentials.createFromMetadataGenerator(generator);
|
return CallCredentials.createFromMetadataGenerator(generator);
|
||||||
});
|
});
|
||||||
const testCases = [{
|
const testCases = [
|
||||||
credentials: callCreds1
|
{
|
||||||
.compose(callCreds2)
|
credentials: callCreds1.compose(callCreds2)
|
||||||
.compose(callCreds3)
|
.compose(callCreds3)
|
||||||
.compose(callCreds4),
|
.compose(callCreds4),
|
||||||
expected: ['50', '100', '150', '200']
|
expected: ['50', '100', '150', '200']
|
||||||
}, {
|
},
|
||||||
credentials: callCreds4
|
{
|
||||||
.compose(callCreds3
|
credentials: callCreds4.compose(
|
||||||
.compose(callCreds2
|
callCreds3.compose(callCreds2.compose(callCreds1))),
|
||||||
.compose(callCreds1))),
|
|
||||||
expected: ['200', '150', '100', '50']
|
expected: ['200', '150', '100', '50']
|
||||||
}, {
|
},
|
||||||
credentials: callCreds3
|
{
|
||||||
.compose(callCreds4
|
credentials: callCreds3.compose(
|
||||||
.compose(callCreds1)
|
callCreds4.compose(callCreds1).compose(callCreds2)),
|
||||||
.compose(callCreds2)),
|
|
||||||
expected: ['150', '200', '50', '100']
|
expected: ['150', '200', '50', '100']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const options = {};
|
const options = {};
|
||||||
// Try each test case and make sure the msElapsed field is as expected
|
// Try each test case and make sure the msElapsed field is as expected
|
||||||
await Promise.all(testCases.map(async (testCase) => {
|
await Promise.all(testCases.map(async (testCase) => {
|
||||||
const { credentials, expected } = testCase;
|
const {credentials, expected} = testCase;
|
||||||
let metadata: Metadata;
|
let metadata: Metadata;
|
||||||
try {
|
try {
|
||||||
metadata = await credentials.generateMetadata(options);
|
metadata = await credentials.generateMetadata(options);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { CallCredentials } from '../src/call-credentials';
|
|
||||||
import { ChannelCredentials } from '../src/channel-credentials';
|
|
||||||
import { mockFunction, assert2 } from './common';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { promisify } from 'util';
|
import {promisify} from 'util';
|
||||||
|
|
||||||
|
import {CallCredentials} from '../src/call-credentials';
|
||||||
|
import {ChannelCredentials} from '../src/channel-credentials';
|
||||||
|
|
||||||
|
import {assert2, mockFunction} from './common';
|
||||||
|
|
||||||
class CallCredentialsMock implements CallCredentials {
|
class CallCredentialsMock implements CallCredentials {
|
||||||
child: CallCredentialsMock;
|
child: CallCredentialsMock;
|
||||||
|
|
@ -32,66 +34,61 @@ class CallCredentialsMock implements CallCredentials {
|
||||||
|
|
||||||
const readFile: (...args: any[]) => Promise<Buffer> = promisify(fs.readFile);
|
const readFile: (...args: any[]) => Promise<Buffer> = promisify(fs.readFile);
|
||||||
// A promise which resolves to loaded files in the form { ca, key, cert }
|
// A promise which resolves to loaded files in the form { ca, key, cert }
|
||||||
const pFixtures = Promise.all([
|
const pFixtures = Promise
|
||||||
'ca.pem',
|
.all(['ca.pem', 'server1.key', 'server1.pem'].map(
|
||||||
'server1.key',
|
(file) => readFile(`test/fixtures/${file}`)))
|
||||||
'server1.pem'
|
.then((result) => {
|
||||||
].map((file) => readFile(`test/fixtures/${file}`))
|
return {ca: result[0], key: result[1], cert: result[2]};
|
||||||
).then((result) => {
|
});
|
||||||
return {
|
|
||||||
ca: result[0],
|
|
||||||
key: result[1],
|
|
||||||
cert: result[2]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ChannelCredentials Implementation', () => {
|
describe('ChannelCredentials Implementation', () => {
|
||||||
describe('createInsecure', () => {
|
describe('createInsecure', () => {
|
||||||
it('should return a ChannelCredentials object with no associated secure context', () => {
|
it('should return a ChannelCredentials object with no associated secure context',
|
||||||
const creds = assert2.noThrowAndReturn(
|
() => {
|
||||||
() => ChannelCredentials.createInsecure());
|
const creds = assert2.noThrowAndReturn(
|
||||||
assert.ok(!creds.getSecureContext());
|
() => ChannelCredentials.createInsecure());
|
||||||
});
|
assert.ok(!creds.getSecureContext());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createSsl', () => {
|
describe('createSsl', () => {
|
||||||
it('should work when given no arguments', () => {
|
it('should work when given no arguments', () => {
|
||||||
const creds: ChannelCredentials = assert2.noThrowAndReturn(
|
const creds: ChannelCredentials =
|
||||||
() => ChannelCredentials.createSsl());
|
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl());
|
||||||
assert.ok(!!creds.getSecureContext());
|
assert.ok(!!creds.getSecureContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with just a CA override', async () => {
|
it('should work with just a CA override', async () => {
|
||||||
const { ca } = await pFixtures;
|
const {ca} = await pFixtures;
|
||||||
const creds = assert2.noThrowAndReturn(
|
const creds =
|
||||||
() => ChannelCredentials.createSsl(ca));
|
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl(ca));
|
||||||
assert.ok(!!creds.getSecureContext());
|
assert.ok(!!creds.getSecureContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with just a private key and cert chain', async () => {
|
it('should work with just a private key and cert chain', async () => {
|
||||||
const { key, cert } = await pFixtures;
|
const {key, cert} = await pFixtures;
|
||||||
const creds = assert2.noThrowAndReturn(
|
const creds = assert2.noThrowAndReturn(
|
||||||
() => ChannelCredentials.createSsl(null, key, cert));
|
() => ChannelCredentials.createSsl(null, key, cert));
|
||||||
assert.ok(!!creds.getSecureContext());
|
assert.ok(!!creds.getSecureContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with all three parameters specified', async () => {
|
it('should work with all three parameters specified', async () => {
|
||||||
const { ca, key, cert } = await pFixtures;
|
const {ca, key, cert} = await pFixtures;
|
||||||
const creds = assert2.noThrowAndReturn(
|
const creds = assert2.noThrowAndReturn(
|
||||||
() => ChannelCredentials.createSsl(ca, key, cert));
|
() => ChannelCredentials.createSsl(ca, key, cert));
|
||||||
assert.ok(!!creds.getSecureContext());
|
assert.ok(!!creds.getSecureContext());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if just one of private key and cert chain are missing',
|
it('should throw if just one of private key and cert chain are missing',
|
||||||
async () => {
|
async () => {
|
||||||
const { ca, key, cert } = await pFixtures;
|
const {ca, key, cert} = await pFixtures;
|
||||||
assert.throws(() => ChannelCredentials.createSsl(ca, key));
|
assert.throws(() => ChannelCredentials.createSsl(ca, key));
|
||||||
assert.throws(() => ChannelCredentials.createSsl(ca, key, null));
|
assert.throws(() => ChannelCredentials.createSsl(ca, key, null));
|
||||||
assert.throws(() => ChannelCredentials.createSsl(ca, null, cert));
|
assert.throws(() => ChannelCredentials.createSsl(ca, null, cert));
|
||||||
assert.throws(() => ChannelCredentials.createSsl(null, key));
|
assert.throws(() => ChannelCredentials.createSsl(null, key));
|
||||||
assert.throws(() => ChannelCredentials.createSsl(null, key, null));
|
assert.throws(() => ChannelCredentials.createSsl(null, key, null));
|
||||||
assert.throws(() => ChannelCredentials.createSsl(null, null, cert));
|
assert.throws(() => ChannelCredentials.createSsl(null, null, cert));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compose', () => {
|
describe('compose', () => {
|
||||||
|
|
@ -99,8 +96,7 @@ describe('ChannelCredentials Implementation', () => {
|
||||||
const channelCreds = ChannelCredentials.createSsl();
|
const channelCreds = ChannelCredentials.createSsl();
|
||||||
const callCreds = new CallCredentialsMock();
|
const callCreds = new CallCredentialsMock();
|
||||||
const composedChannelCreds = channelCreds.compose(callCreds);
|
const composedChannelCreds = channelCreds.compose(callCreds);
|
||||||
assert.strictEqual(composedChannelCreds.getCallCredentials(),
|
assert.strictEqual(composedChannelCreds.getCallCredentials(), callCreds);
|
||||||
callCreds);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be chainable', () => {
|
it('should be chainable', () => {
|
||||||
|
|
@ -108,12 +104,12 @@ describe('ChannelCredentials Implementation', () => {
|
||||||
const callCreds2 = new CallCredentialsMock();
|
const callCreds2 = new CallCredentialsMock();
|
||||||
// Associate both call credentials with channelCreds
|
// Associate both call credentials with channelCreds
|
||||||
const composedChannelCreds = ChannelCredentials.createSsl()
|
const composedChannelCreds = ChannelCredentials.createSsl()
|
||||||
.compose(callCreds1)
|
.compose(callCreds1)
|
||||||
.compose(callCreds2);
|
.compose(callCreds2);
|
||||||
// Build a mock object that should be an identical copy
|
// Build a mock object that should be an identical copy
|
||||||
const composedCallCreds = callCreds1.compose(callCreds2);
|
const composedCallCreds = callCreds1.compose(callCreds2);
|
||||||
assert.ok(composedCallCreds.isEqual(
|
assert.ok(composedCallCreds.isEqual(
|
||||||
composedChannelCreds.getCallCredentials() as CallCredentialsMock));
|
composedChannelCreds.getCallCredentials() as CallCredentialsMock));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
import { range } from 'lodash';
|
import {range} from 'lodash';
|
||||||
import * as metadata from '../src/metadata';
|
import {Metadata} from '../src/metadata';
|
||||||
|
|
||||||
class Metadata extends metadata.Metadata {
|
class TestMetadata extends Metadata {
|
||||||
getInternalRepresentation() {
|
getInternalRepresentation() {
|
||||||
return this.internalRepr;
|
return this.internalRepr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata {
|
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata {
|
||||||
const result = metadata.Metadata.fromHttp2Headers(headers) as Metadata;
|
const result = Metadata.fromHttp2Headers(headers) as TestMetadata;
|
||||||
result.getInternalRepresentation =
|
result.getInternalRepresentation =
|
||||||
Metadata.prototype.getInternalRepresentation;
|
TestMetadata.prototype.getInternalRepresentation;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.';
|
const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.';
|
||||||
const validNonBinValueChars = range(0x20, 0x7f)
|
const validNonBinValueChars =
|
||||||
.map(code => String.fromCharCode(code))
|
range(0x20, 0x7f).map(code => String.fromCharCode(code)).join('');
|
||||||
.join('');
|
|
||||||
|
|
||||||
describe('Metadata', () => {
|
describe('Metadata', () => {
|
||||||
let metadata: Metadata;
|
let metadata: TestMetadata;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
metadata = new Metadata();
|
metadata = new TestMetadata();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('set', () => {
|
describe('set', () => {
|
||||||
|
|
@ -178,10 +177,8 @@ describe('Metadata', () => {
|
||||||
metadata.add('Key2', 'value2');
|
metadata.add('Key2', 'value2');
|
||||||
metadata.add('KEY3', 'value3a');
|
metadata.add('KEY3', 'value3a');
|
||||||
metadata.add('KEY3', 'value3b');
|
metadata.add('KEY3', 'value3b');
|
||||||
assert.deepEqual(metadata.getMap(),
|
assert.deepEqual(
|
||||||
{key1: 'value1',
|
metadata.getMap(), {key1: 'value1', key2: 'value2', key3: 'value3a'});
|
||||||
key2: 'value2',
|
|
||||||
key3: 'value3a'});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -213,7 +210,7 @@ describe('Metadata', () => {
|
||||||
metadata.add('Key2', 'value2a');
|
metadata.add('Key2', 'value2a');
|
||||||
metadata.add('KEY3', 'value3a');
|
metadata.add('KEY3', 'value3a');
|
||||||
metadata.add('key4', 'value4');
|
metadata.add('key4', 'value4');
|
||||||
const metadata2 = new Metadata();
|
const metadata2 = new TestMetadata();
|
||||||
metadata2.add('KEY1', 'value1');
|
metadata2.add('KEY1', 'value1');
|
||||||
metadata2.add('key2', 'value2b');
|
metadata2.add('key2', 'value2b');
|
||||||
metadata2.add('key3', 'value3b');
|
metadata2.add('key3', 'value3b');
|
||||||
|
|
@ -246,8 +243,7 @@ describe('Metadata', () => {
|
||||||
key2: ['value2'],
|
key2: ['value2'],
|
||||||
key3: ['value3a', 'value3b'],
|
key3: ['value3a', 'value3b'],
|
||||||
'key-bin': [
|
'key-bin': [
|
||||||
'AAECAwQFBgcICQoLDA0ODw==',
|
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
|
||||||
'EBESExQVFhcYGRobHB0eHw==',
|
|
||||||
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
|
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -265,27 +261,25 @@ describe('Metadata', () => {
|
||||||
key2: ['value2'],
|
key2: ['value2'],
|
||||||
key3: ['value3a', 'value3b'],
|
key3: ['value3a', 'value3b'],
|
||||||
'key-bin': [
|
'key-bin': [
|
||||||
'AAECAwQFBgcICQoLDA0ODw==',
|
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
|
||||||
'EBESExQVFhcYGRobHB0eHw==',
|
|
||||||
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
|
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const metadataFromHeaders = Metadata.fromHttp2Headers(headers);
|
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
|
||||||
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
||||||
assert.deepEqual(internalRepr, {
|
assert.deepEqual(internalRepr, {
|
||||||
key1: ['value1'],
|
key1: ['value1'],
|
||||||
key2: ['value2'],
|
key2: ['value2'],
|
||||||
key3: ['value3a', 'value3b'],
|
key3: ['value3a', 'value3b'],
|
||||||
'key-bin': [
|
'key-bin': [
|
||||||
Buffer.from(range(0, 16)),
|
Buffer.from(range(0, 16)), Buffer.from(range(16, 32)),
|
||||||
Buffer.from(range(16, 32)),
|
|
||||||
Buffer.from(range(0, 32))
|
Buffer.from(range(0, 32))
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates an empty Metadata object from empty headers', () => {
|
it('creates an empty Metadata object from empty headers', () => {
|
||||||
const metadataFromHeaders = Metadata.fromHttp2Headers({});
|
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
|
||||||
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
||||||
assert.deepEqual(internalRepr, {});
|
assert.deepEqual(internalRepr, {});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue