Lint and format

This commit is contained in:
murgatroid99 2017-08-29 10:27:42 -07:00
parent f012088ecc
commit a3c1136fb4
16 changed files with 489 additions and 479 deletions

View File

@ -1,12 +1,12 @@
import {promisify} from 'util'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {CallCredentials} from './call-credentials'
import {Http2Channel} from './channel'
import {CallStream} from './call-stream'
import {Metadata} from './metadata'
import {promisify} from 'util';
import {CallCredentials} from './call-credentials';
import {CallStream} from './call-stream';
import {Http2Channel} from './channel';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
export class CallCredentialsFilter extends BaseFilter implements Filter {
constructor(private readonly credentials: CallCredentials) {
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;
constructor(channel: Http2Channel) {
this.credentials = channel.credentials.getCallCredentials();
}
createFilter(callStream: CallStream): CallCredentialsFilter {
return new CallCredentialsFilter(this.credentials.compose(callStream.getCredentials()));
return new CallCredentialsFilter(
this.credentials.compose(callStream.getCredentials()));
}
}

View File

@ -1,10 +1,10 @@
import { Metadata } from './metadata';
import {map, reduce} from 'lodash'
import {map, reduce} from 'lodash';
export type CallMetadataGenerator = (
options: Object,
cb: (err: Error | null, metadata?: Metadata) => void
) => void;
import {Metadata} from './metadata';
export type CallMetadataGenerator =
(options: Object, cb: (err: Error|null, metadata?: Metadata) => void) =>
void;
/**
* A class that represents a generic method of adding authentication-related
@ -25,32 +25,13 @@ export interface 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 {
constructor(private creds: CallCredentials[]) {}
async generateMetadata(options: Object): Promise<Metadata> {
let base: Metadata = new Metadata();
let generated: Metadata[] = await Promise.all(map(
this.creds, (cred) => cred.generateMetadata(options)));
let generated: Metadata[] = await Promise.all(
map(this.creds, (cred) => cred.generateMetadata(options)));
for (let gen of generated) {
base.merge(gen);
}
@ -62,7 +43,7 @@ class ComposedCallCredentials implements CallCredentials {
}
}
class SingleCallCredentials implements CallCredentials{
class SingleCallCredentials implements CallCredentials {
constructor(private metadataGenerator: CallMetadataGenerator) {}
async generateMetadata(options: Object): Promise<Metadata> {
@ -83,13 +64,29 @@ class SingleCallCredentials implements CallCredentials{
}
class EmptyCallCredentials implements CallCredentials {
constructor () {}
async generateMetadata(options: Object): Promise<Metadata> {
return new Metadata();
}
compose(other:CallCredentials): CallCredentials {
compose(other: CallCredentials): CallCredentials {
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();
}
}

View File

@ -1,18 +1,14 @@
import * as stream from 'stream';
import * as http2 from 'http2';
import * as http2 from 'http2';
import {Duplex} from 'stream';
import {CallCredentials} from './call-credentials';
import {Status} from './constants';
import {Filter} from './filter';
import {FilterStackFactory} from './filter-stack';
import {Metadata} from './metadata';
import {ObjectDuplex} from './object-stream';
import {Filter} from './filter'
import {FilterStackFactory} from './filter-stack';
const {
HTTP2_HEADER_STATUS,
HTTP2_HEADER_CONTENT_TYPE
} = http2.constants;
const {HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE} = http2.constants;
export type Deadline = Date | number;
@ -46,7 +42,7 @@ export interface CallStream extends ObjectDuplex<WriteObject, Buffer> {
getCredentials(): CallCredentials;
/* If the return value is null, the call has not ended yet. Otherwise, it has
* ended with the specified status */
getStatus(): StatusObject | null;
getStatus(): StatusObject|null;
addListener(event: string, listener: Function): this;
emit(event: string|symbol, ...args: any[]): boolean;
@ -85,35 +81,35 @@ enum ReadState {
READING_MESSAGE
}
export class Http2CallStream extends stream.Duplex implements CallStream {
export class Http2CallStream extends Duplex implements CallStream {
public filterStack: Filter;
private statusEmitted: boolean = false;
private http2Stream: http2.ClientHttp2Stream | null = null;
private pendingRead: boolean = false;
private pendingWrite: Buffer | null = null;
private pendingWriteCallback: Function | null = null;
private pendingFinalCallback: Function | null = null;
private statusEmitted = false;
private http2Stream: http2.ClientHttp2Stream|null = null;
private pendingRead = false;
private pendingWrite: Buffer|null = null;
private pendingWriteCallback: Function|null = null;
private pendingFinalCallback: Function|null = null;
private readState: ReadState = ReadState.NO_DATA;
private readCompressFlag: boolean = false;
private readCompressFlag = false;
private readPartialSize: Buffer = Buffer.alloc(4);
private readSizeRemaining: number = 4;
private readMessageSize: number = 0;
private readSizeRemaining = 4;
private readMessageSize = 0;
private readPartialMessage: Buffer[] = [];
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
private mappedStatusCode: Status = Status.UNKNOWN;
// 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,
private readonly options: CallStreamOptions,
filterStackFactory: FilterStackFactory) {
constructor(
private readonly methodName: string,
private readonly options: CallStreamOptions,
filterStackFactory: FilterStackFactory) {
super({objectMode: true});
this.filterStack = filterStackFactory.createFilter(this);
}
@ -131,28 +127,28 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
} else {
this.http2Stream = stream;
stream.on('response', (headers) => {
switch(headers[HTTP2_HEADER_STATUS]) {
switch (headers[HTTP2_HEADER_STATUS]) {
// TODO(murgatroid99): handle 100 and 101
case '400':
this.mappedStatusCode = Status.INTERNAL;
break;
case '401':
this.mappedStatusCode = Status.UNAUTHENTICATED;
break;
case '403':
this.mappedStatusCode = Status.PERMISSION_DENIED;
break;
case '404':
this.mappedStatusCode = Status.UNIMPLEMENTED;
break;
case '429':
case '502':
case '503':
case '504':
this.mappedStatusCode = Status.UNAVAILABLE;
break;
default:
this.mappedStatusCode = Status.UNKNOWN;
case '400':
this.mappedStatusCode = Status.INTERNAL;
break;
case '401':
this.mappedStatusCode = Status.UNAUTHENTICATED;
break;
case '403':
this.mappedStatusCode = Status.PERMISSION_DENIED;
break;
case '404':
this.mappedStatusCode = Status.UNIMPLEMENTED;
break;
case '429':
case '502':
case '503':
case '504':
this.mappedStatusCode = Status.UNAVAILABLE;
break;
default:
this.mappedStatusCode = Status.UNKNOWN;
}
delete headers[HTTP2_HEADER_STATUS];
delete headers[HTTP2_HEADER_CONTENT_TYPE];
@ -163,11 +159,14 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
this.cancelWithStatus(Status.UNKNOWN, e.message);
return;
}
this.filterStack.receiveMetadata(Promise.resolve(metadata)).then((finalMetadata) => {
this.emit('metadata', finalMetadata);
}, (error) => {
this.cancelWithStatus(Status.UNKNOWN, error.message);
});
this.filterStack.receiveMetadata(Promise.resolve(metadata))
.then(
(finalMetadata) => {
this.emit('metadata', finalMetadata);
},
(error) => {
this.cancelWithStatus(Status.UNKNOWN, error.message);
});
});
stream.on('trailers', (headers) => {
let code: Status = this.mappedStatusCode;
@ -180,7 +179,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
}
delete headers['grpc-status'];
}
let details: string = '';
let details = '';
if (headers.hasOwnProperty('grpc-message')) {
details = decodeURI(headers['grpc-message']);
}
@ -190,65 +189,73 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
} catch (e) {
metadata = new Metadata();
}
let status: StatusObject = { code, details, metadata };
this.filterStack.receiveTrailers(Promise.resolve(status)).then((finalStatus) => {
this.endCall(finalStatus);
}, (error) => {
this.endCall({
code: Status.INTERNAL,
details: 'Failed to process received status',
metadata: new Metadata()
});
});
let status: StatusObject = {code, details, metadata};
this.filterStack.receiveTrailers(Promise.resolve(status))
.then(
(finalStatus) => {
this.endCall(finalStatus);
},
(error) => {
this.endCall({
code: Status.INTERNAL,
details: 'Failed to process received status',
metadata: new Metadata()
});
});
});
stream.on('read', (data) => {
let readHead = 0;
let canPush = true;
let toRead: number;
while (readHead < data.length) {
switch(this.readState) {
case ReadState.NO_DATA:
this.readCompressFlag = (data.readUInt8(readHead) !== 0);
readHead += 1;
this.readState = ReadState.READING_SIZE;
this.readPartialSize.fill(0);
this.readSizeRemaining = 4;
this.readMessageSize = 0;
this.readMessageRemaining = 0;
this.readPartialMessage = [];
break;
case ReadState.READING_SIZE:
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
data.copy(this.readPartialSize, 4 - this.readSizeRemaining, readHead, readHead + toRead);
this.readSizeRemaining -= toRead;
readHead += toRead;
// readSizeRemaining >=0 here
if (this.readSizeRemaining === 0) {
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
this.readMessageRemaining = this.readMessageSize;
this.readState = ReadState.READING_MESSAGE;
}
break;
case ReadState.READING_MESSAGE:
toRead = Math.min(data.length - readHead, this.readMessageRemaining);
this.readPartialMessage.push(data.slice(readHead, readHead + toRead));
this.readMessageRemaining -= toRead;
readHead += toRead;
// readMessageRemaining >=0 here
if (this.readMessageRemaining === 0) {
// At this point, we have read a full message
const messageBytes = Buffer.concat(this.readPartialMessage, this.readMessageSize);
// TODO(murgatroid99): Add receive message filters
if (canPush) {
if (!this.push(messageBytes)) {
canPush = false;
(this.http2Stream as http2.ClientHttp2Stream).pause();
}
} else {
this.unpushedReadMessages.push(messageBytes);
switch (this.readState) {
case ReadState.NO_DATA:
this.readCompressFlag = (data.readUInt8(readHead) !== 0);
readHead += 1;
this.readState = ReadState.READING_SIZE;
this.readPartialSize.fill(0);
this.readSizeRemaining = 4;
this.readMessageSize = 0;
this.readMessageRemaining = 0;
this.readPartialMessage = [];
break;
case ReadState.READING_SIZE:
toRead = Math.min(data.length - readHead, this.readSizeRemaining);
data.copy(
this.readPartialSize, 4 - this.readSizeRemaining, readHead,
readHead + toRead);
this.readSizeRemaining -= toRead;
readHead += toRead;
// readSizeRemaining >=0 here
if (this.readSizeRemaining === 0) {
this.readMessageSize = this.readPartialSize.readUInt32BE(0);
this.readMessageRemaining = this.readMessageSize;
this.readState = ReadState.READING_MESSAGE;
}
break;
case ReadState.READING_MESSAGE:
toRead =
Math.min(data.length - readHead, this.readMessageRemaining);
this.readPartialMessage.push(
data.slice(readHead, readHead + toRead));
this.readMessageRemaining -= toRead;
readHead += toRead;
// readMessageRemaining >=0 here
if (this.readMessageRemaining === 0) {
// At this point, we have read a full message
const messageBytes = Buffer.concat(
this.readPartialMessage, this.readMessageSize);
// TODO(murgatroid99): Add receive message filters
if (canPush) {
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) => {
let code: Status;
let details: string = '';
switch(errorCode) {
case http2.constants.NGHTTP2_REFUSED_STREAM:
code = Status.UNAVAILABLE;
break;
case http2.constants.NGHTTP2_CANCEL:
code = Status.CANCELLED;
break;
case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM:
code = Status.RESOURCE_EXHAUSTED;
details = 'Bandwidth exhausted';
break;
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
code = Status.PERMISSION_DENIED;
details = 'Protocol not secure enough';
break;
default:
code = Status.INTERNAL;
let details = '';
switch (errorCode) {
case http2.constants.NGHTTP2_REFUSED_STREAM:
code = Status.UNAVAILABLE;
break;
case http2.constants.NGHTTP2_CANCEL:
code = Status.CANCELLED;
break;
case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM:
code = Status.RESOURCE_EXHAUSTED;
details = 'Bandwidth exhausted';
break;
case http2.constants.NGHTTP2_INADEQUATE_SECURITY:
code = Status.PERMISSION_DENIED;
details = 'Protocol not secure enough';
break;
default:
code = Status.INTERNAL;
}
this.endCall({
code: code,
details: details,
metadata: new Metadata()
});
this.endCall({code: code, details: details, metadata: new Metadata()});
});
stream.on('error', () => {
this.endCall({
@ -325,7 +328,7 @@ export class Http2CallStream extends stream.Duplex implements CallStream {
return this.options.credentials;
}
getStatus(): StatusObject | null {
getStatus(): StatusObject|null {
return this.finalStatus;
}

View File

@ -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
@ -13,19 +14,69 @@ export interface ChannelCredentials {
* @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
* instance was created with createSsl, or null if this instance was created
* 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 {
@ -37,12 +88,16 @@ export namespace ChannelCredentials {
* @param privateKey The client certificate private key, 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) {
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) {
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({
ca: rootCerts || undefined,
@ -55,60 +110,7 @@ export namespace ChannelCredentials {
/**
* Return a new ChannelCredentials instance with no credentials.
*/
export function createInsecure() : ChannelCredentials {
export function createInsecure(): ChannelCredentials {
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;
}
}

View File

@ -1,17 +1,17 @@
import {EventEmitter} from 'events';
import {SecureContext} from 'tls';
import * as http2 from 'http2';
import {SecureContext} from 'tls';
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 {DeadlineFilterFactory} from './deadline-filter'
import {CallCredentialsFilterFactory} from './call-credentials-filter'
import {CompressionFilterFactory} from './compression-filter'
import {CallCredentials} from './call-credentials';
import {CallCredentialsFilterFactory} from './call-credentials-filter';
import {CallOptions, CallStream, CallStreamOptions, Http2CallStream} from './call-stream';
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;
@ -45,7 +45,8 @@ export enum ConnectivityState {
* by a given address.
*/
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;
getConnectivityState(): ConnectivityState;
close(): void;
@ -61,11 +62,11 @@ export interface Channel extends EventEmitter {
export class Http2Channel extends EventEmitter implements Channel {
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
* connecting or trying to connect */
private subChannel : http2.ClientHttp2Session | null;
private filterStackFactory : FilterStackFactory;
private subChannel: http2.ClientHttp2Session|null;
private filterStackFactory: FilterStackFactory;
private transitionToState(newState: ConnectivityState): void {
if (newState !== this.connectivityState) {
@ -94,7 +95,7 @@ export class Http2Channel extends EventEmitter implements Channel {
private goIdle(): void {
if (this.subChannel !== null) {
this.subChannel.shutdown({graceful: true}, () => {});
this.subChannel.shutdown({graceful: true}, () => undefined);
this.subChannel = null;
}
this.transitionToState(ConnectivityState.IDLE);
@ -106,9 +107,10 @@ export class Http2Channel extends EventEmitter implements Channel {
}
}
constructor(private readonly address: url.URL,
public readonly credentials: ChannelCredentials,
private readonly options: ChannelOptions) {
constructor(
private readonly address: url.URL,
public readonly credentials: ChannelCredentials,
private readonly options: ChannelOptions) {
super();
if (credentials.getSecureContext() === null) {
address.protocol = 'http';
@ -117,41 +119,46 @@ export class Http2Channel extends EventEmitter implements Channel {
}
this.filterStackFactory = new FilterStackFactory([
new CompressionFilterFactory(this),
new CallCredentialsFilterFactory(this),
new DeadlineFilterFactory(this)
new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this)
]);
}
private startHttp2Stream(methodName: string, stream: Http2CallStream, metadata: Metadata) {
let finalMetadata: Promise<Metadata> = stream.filterStack.sendMetadata(Promise.resolve(metadata));
private startHttp2Stream(
methodName: string, stream: Http2CallStream, metadata: Metadata) {
let finalMetadata: Promise<Metadata> =
stream.filterStack.sendMetadata(Promise.resolve(metadata));
this.connect(() => {
finalMetadata.then((metadataValue) => {
let headers = metadataValue.toHttp2Headers();
headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname;
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
headers[HTTP2_HEADER_METHOD] = 'POST';
headers[HTTP2_HEADER_PATH] = methodName;
headers[HTTP2_HEADER_TE] = 'trailers';
if (stream.getStatus() === null) {
if (this.connectivityState === ConnectivityState.READY) {
let session: http2.ClientHttp2Session =
(this.subChannel as http2.ClientHttp2Session);
stream.attachHttp2Stream(session.request(headers));
} else {
/* In this case, we lost the connection while finalizing metadata.
* That should be very unusual */
setImmediate(() => {
this.startHttp2Stream(methodName, stream, metadata);
});
}
}
}, (error) => {
stream.cancelWithStatus(Status.UNKNOWN, "Failed to generate metadata");
});
finalMetadata.then(
(metadataValue) => {
let headers = metadataValue.toHttp2Headers();
headers[HTTP2_HEADER_AUTHORITY] = this.address.hostname;
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
headers[HTTP2_HEADER_METHOD] = 'POST';
headers[HTTP2_HEADER_PATH] = methodName;
headers[HTTP2_HEADER_TE] = 'trailers';
if (stream.getStatus() === null) {
if (this.connectivityState === ConnectivityState.READY) {
let session: http2.ClientHttp2Session =
(this.subChannel as http2.ClientHttp2Session);
stream.attachHttp2Stream(session.request(headers));
} else {
/* In this case, we lost the connection while finalizing
* metadata. That should be very unusual */
setImmediate(() => {
this.startHttp2Stream(methodName, stream, 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) {
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,
credentials: options.credentials || CallCredentials.createEmpty(),
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);
return stream;
}
@ -178,7 +186,7 @@ export class Http2Channel extends EventEmitter implements Channel {
}
}
getConnectivityState(): ConnectivityState{
getConnectivityState(): ConnectivityState {
return this.connectivityState;
}

View File

@ -3,7 +3,7 @@ import {URL} from 'url';
import {ClientDuplexStream, ClientDuplexStreamImpl, ClientReadableStream, ClientReadableStreamImpl, ClientUnaryCall, ClientUnaryCallImpl, ClientWritableStream, ClientWritableStreamImpl, ServiceError, ServiceErrorImpl} from './call';
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 {Status} from './constants';
import {Metadata} from './metadata';
@ -32,13 +32,13 @@ export class Client {
}
waitForReady(deadline: Date|number, callback: (error: Error|null) => void):
void {
let cb : (error: Error|null) => void = once(callback);
let callbackCalled: boolean = false;
void {
let cb: (error: Error|null) => void = once(callback);
let callbackCalled = false;
this.channel.connect(() => {
cb(null);
});
if (deadline != Infinity) {
if (deadline !== Infinity) {
let timeout: number;
let now: number = (new Date).getTime();
if (deadline instanceof Date) {

View File

@ -1,10 +1,9 @@
import {CallStream} from './call-stream'
import {Channel} from './channel'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {Metadata} from './metadata'
import {CallStream} from './call-stream';
import {Channel} from './channel';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
export class CompressionFilter extends BaseFilter implements Filter {
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
const headers: Metadata = await metadata;
headers.set('grpc-encoding', 'identity');
@ -20,8 +19,9 @@ export class CompressionFilter extends BaseFilter implements Filter {
}
}
export class CompressionFilterFactory implements FilterFactory<CompressionFilter> {
constructor(channel: Channel) {}
export class CompressionFilterFactory implements
FilterFactory<CompressionFilter> {
constructor(private readonly channel: Channel) {}
createFilter(callStream: CallStream): CompressionFilter {
return new CompressionFilter();
}

View File

@ -1,19 +1,17 @@
import {CallStream} from './call-stream'
import {Channel, Http2Channel} from './channel'
import {Filter, BaseFilter, FilterFactory} from './filter'
import {Status} from './constants'
import {Metadata} from './metadata'
import {CallStream} from './call-stream';
import {Channel, Http2Channel} from './channel';
import {Status} from './constants';
import {BaseFilter, Filter, FilterFactory} from './filter';
import {Metadata} from './metadata';
const units: [string, number][] = [
['m', 1],
['S', 1000],
['M', 60 * 1000],
['H', 60 * 60 * 1000]
]
const units: [string, number][] =
[['m', 1], ['S', 1000], ['M', 60 * 1000], ['H', 60 * 60 * 1000]];
export class DeadlineFilter extends BaseFilter implements Filter {
private deadline: number;
constructor(private readonly channel: Http2Channel, private readonly callStream: CallStream) {
constructor(
private readonly channel: Http2Channel,
private readonly callStream: CallStream) {
super();
let callDeadline = callStream.getDeadline();
if (callDeadline instanceof Date) {
@ -28,7 +26,8 @@ export class DeadlineFilter extends BaseFilter implements Filter {
}
if (this.deadline !== Infinity) {
setTimeout(() => {
callStream.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
callStream.cancelWithStatus(
Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
}, timeout);
}
}
@ -37,19 +36,20 @@ export class DeadlineFilter extends BaseFilter implements Filter {
if (this.deadline === Infinity) {
return await metadata;
}
let timeoutString : Promise<string> = new Promise<string>((resolve, reject) => {
this.channel.connect(() => {
let now = (new Date()).getTime();
let timeoutMs = this.deadline - now;
for (let [unit, factor] of units) {
let amount = timeoutMs / factor;
if (amount < 1e8) {
resolve(String(Math.ceil(amount)) + unit);
return;
}
}
});
});
let timeoutString: Promise<string> =
new Promise<string>((resolve, reject) => {
this.channel.connect(() => {
let now = (new Date()).getTime();
let timeoutMs = this.deadline - now;
for (let [unit, factor] of units) {
let amount = timeoutMs / factor;
if (amount < 1e8) {
resolve(String(Math.ceil(amount)) + unit);
return;
}
}
});
});
let finalMetadata = await metadata;
finalMetadata.set('grpc-timeout', await timeoutString);
return finalMetadata;

View File

@ -1,21 +1,26 @@
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 {Metadata} from './metadata';
export class FilterStack implements Filter {
constructor(private readonly filters: Filter[]) {}
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>) {
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> {
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>[]) {}
createFilter(callStream: CallStream): FilterStack {
return new FilterStack(map(this.factories, (factory) => factory.createFilter(callStream)));
return new FilterStack(
map(this.factories, (factory) => factory.createFilter(callStream)));
}
}

View File

@ -1,9 +1,10 @@
import {Metadata} from './metadata'
import {StatusObject, CallStream} from './call-stream'
import {CallStream, StatusObject} from './call-stream';
import {Metadata} from './metadata';
/**
* 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 {
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata>;

View File

@ -1,11 +1,9 @@
import { forOwn } from 'lodash';
import * as http2 from 'http2';
import {forOwn} from 'lodash';
export type MetadataValue = string | Buffer;
export interface MetadataObject {
[key: string]: Array<MetadataValue>;
}
export interface MetadataObject { [key: string]: Array<MetadataValue>; }
function cloneMetadataObject(repr: MetadataObject): 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');
}
if (!isLegalNonBinaryValue(value)) {
throw new Error('Metadata string value "' + value +
'" contains illegal characters');
throw new Error(
'Metadata string value "' + value +
'" contains illegal characters');
}
}
}
@ -66,7 +65,6 @@ function validate(key: string, value?: MetadataValue): void {
*/
export class Metadata {
protected internalRepr: MetadataObject = {};
constructor() {}
/**
* 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.
* @return A key/value mapping of the metadata.
*/
getMap(): { [key: string]: MetadataValue } {
const result: { [key: string]: MetadataValue } = {};
getMap(): {[key: string]: MetadataValue} {
const result: {[key: string]: MetadataValue} = {};
forOwn(this.internalRepr, (values, key) => {
if(values.length > 0) {
if (values.length > 0) {
const v = values[0];
result[key] = v instanceof Buffer ? v.slice() : v;
}
@ -195,7 +193,7 @@ export class Metadata {
if (Array.isArray(values)) {
values.forEach((value) => {
result.add(key, Buffer.from(value, 'base64'));
})
});
} else {
result.add(key, Buffer.from(values, 'base64'));
}
@ -203,7 +201,7 @@ export class Metadata {
if (Array.isArray(values)) {
values.forEach((value) => {
result.add(key, value);
})
});
} else {
result.add(key, values);
}

View File

@ -1,14 +1,14 @@
import { Readable, Writable, Duplex } from 'stream';
import {Duplex, Readable, Writable} from 'stream';
export interface IntermediateObjectReadable<T> extends Readable {
read(size?: number): any & T;
read(size?: number): any&T;
}
export interface ObjectReadable<T> extends IntermediateObjectReadable<T> {
read(size?: number): T;
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;
once(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 {
_write(chunk: any & T, encoding: string, callback: Function): void;
write(chunk: any & T, cb?: Function): boolean;
write(chunk: any & T, encoding?: any, cb?: Function): boolean;
_write(chunk: any&T, encoding: string, callback: Function): void;
write(chunk: any&T, cb?: Function): boolean;
write(chunk: any&T, encoding?: any, cb?: Function): boolean;
setDefaultEncoding(encoding: string): this;
end(): void;
end(chunk: any & T, cb?: Function): void;
end(chunk: any & T, encoding?: any, cb?: Function): void;
end(chunk: any&T, cb?: Function): void;
end(chunk: any&T, encoding?: any, cb?: Function): void;
}
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;
}
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;
_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;
emit(event: string | symbol, ...args: any[]): boolean;
emit(event: string|symbol, ...args: any[]): boolean;
on(event: string, listener: Function): this;
once(event: string, listener: Function): this;
prependListener(event: string, listener: Function): this;

View File

@ -9,8 +9,8 @@ export namespace assert2 {
try {
return fn();
} catch (e) {
assert.throws(() => { throw e });
throw e; // for type safety only
assert.throws(() => {throw e});
throw e; // for type safety only
}
}
}

View File

@ -1,11 +1,12 @@
import { Metadata } from '../src/metadata';
import { CallCredentials, CallMetadataGenerator } from '../src/call-credentials';
import * as assert from 'assert';
import {CallCredentials, CallMetadataGenerator} from '../src/call-credentials';
import {Metadata} from '../src/metadata';
// Metadata generators
function makeGenerator(props: Array<string>): CallMetadataGenerator {
return (options: { [propName: string]: string }, cb) => {
return (options: {[propName: string]: string}, cb) => {
const metadata: Metadata = new Metadata();
props.forEach((prop) => {
if (options[prop]) {
@ -13,110 +14,111 @@ function makeGenerator(props: Array<string>): CallMetadataGenerator {
}
});
cb(null, metadata);
}
};
}
function makeAfterMsElapsedGenerator(ms: number): CallMetadataGenerator {
return (_options, cb) => {
return (options, cb) => {
const metadata = new Metadata();
metadata.add('msElapsed', `${ms}`);
setTimeout(() => cb(null, metadata), ms);
};
};
}
const generateFromName: CallMetadataGenerator = makeGenerator(['name']);
const generateWithError: CallMetadataGenerator = (_options, cb) =>
cb(new Error());
const generateWithError: CallMetadataGenerator = (options, cb) =>
cb(new Error());
// Tests
describe('CallCredentials', () => {
describe('createFromMetadataGenerator', () => {
it('should accept a metadata generator', () => {
assert.doesNotThrow(() =>
CallCredentials.createFromMetadataGenerator(generateFromName));
assert.doesNotThrow(
() => CallCredentials.createFromMetadataGenerator(generateFromName));
});
});
describe('compose', () => {
it('should accept a CallCredentials object and return a new object', () => {
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials1 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const combinedCredentials = callCredentials1.compose(callCredentials2);
assert.notEqual(combinedCredentials, callCredentials1);
assert.notEqual(combinedCredentials, callCredentials2);
});
it('should be chainable', () => {
const callCredentials1 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 = CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials1 =
CallCredentials.createFromMetadataGenerator(generateFromName);
const callCredentials2 =
CallCredentials.createFromMetadataGenerator(generateFromName);
assert.doesNotThrow(() => {
callCredentials1.compose(callCredentials2)
.compose(callCredentials2)
.compose(callCredentials2);
.compose(callCredentials2)
.compose(callCredentials2);
});
});
});
describe('generateMetadata', () => {
it('should call the function passed to createFromMetadataGenerator',
async () => {
const callCredentials = CallCredentials.createFromMetadataGenerator(generateFromName);
let metadata: Metadata;
try {
metadata = await callCredentials.generateMetadata({name: 'foo'});
} catch (err) {
throw err;
}
assert.deepEqual(metadata.get('name'), ['foo']);
}
);
async () => {
const callCredentials =
CallCredentials.createFromMetadataGenerator(generateFromName);
let metadata: Metadata;
try {
metadata = await callCredentials.generateMetadata({name: 'foo'});
} catch (err) {
throw err;
}
assert.deepEqual(metadata.get('name'), ['foo']);
});
it('should emit an error if the associated metadataGenerator does',
async () => {
const callCredentials = CallCredentials.createFromMetadataGenerator(
generateWithError);
let metadata: Metadata | null = null;
try {
metadata = await callCredentials.generateMetadata({});
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(metadata, null);
}
);
async () => {
const callCredentials =
CallCredentials.createFromMetadataGenerator(generateWithError);
let metadata: Metadata|null = null;
try {
metadata = await callCredentials.generateMetadata({});
} catch (err) {
assert.ok(err instanceof Error);
}
assert.strictEqual(metadata, null);
});
it('should combine metadata from multiple generators', async () => {
const [callCreds1, callCreds2, callCreds3, callCreds4] =
[50, 100, 150, 200].map((ms) => {
const generator: CallMetadataGenerator =
makeAfterMsElapsedGenerator(ms);
return CallCredentials.createFromMetadataGenerator(generator);
});
const testCases = [{
credentials: callCreds1
.compose(callCreds2)
.compose(callCreds3)
.compose(callCreds4),
[50, 100, 150, 200].map((ms) => {
const generator: CallMetadataGenerator =
makeAfterMsElapsedGenerator(ms);
return CallCredentials.createFromMetadataGenerator(generator);
});
const testCases = [
{
credentials: callCreds1.compose(callCreds2)
.compose(callCreds3)
.compose(callCreds4),
expected: ['50', '100', '150', '200']
}, {
credentials: callCreds4
.compose(callCreds3
.compose(callCreds2
.compose(callCreds1))),
},
{
credentials: callCreds4.compose(
callCreds3.compose(callCreds2.compose(callCreds1))),
expected: ['200', '150', '100', '50']
}, {
credentials: callCreds3
.compose(callCreds4
.compose(callCreds1)
.compose(callCreds2)),
},
{
credentials: callCreds3.compose(
callCreds4.compose(callCreds1).compose(callCreds2)),
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;
const {credentials, expected} = testCase;
let metadata: Metadata;
try {
metadata = await credentials.generateMetadata(options);

View File

@ -1,9 +1,11 @@
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 { 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 {
child: CallCredentialsMock;
@ -32,66 +34,61 @@ class CallCredentialsMock implements CallCredentials {
const readFile: (...args: any[]) => Promise<Buffer> = promisify(fs.readFile);
// A promise which resolves to loaded files in the form { ca, key, cert }
const pFixtures = Promise.all([
'ca.pem',
'server1.key',
'server1.pem'
].map((file) => readFile(`test/fixtures/${file}`))
).then((result) => {
return {
ca: result[0],
key: result[1],
cert: result[2]
};
});
const pFixtures = Promise
.all(['ca.pem', 'server1.key', 'server1.pem'].map(
(file) => readFile(`test/fixtures/${file}`)))
.then((result) => {
return {ca: result[0], key: result[1], cert: result[2]};
});
describe('ChannelCredentials Implementation', () => {
describe('createInsecure', () => {
it('should return a ChannelCredentials object with no associated secure context', () => {
const creds = assert2.noThrowAndReturn(
() => ChannelCredentials.createInsecure());
assert.ok(!creds.getSecureContext());
});
it('should return a ChannelCredentials object with no associated secure context',
() => {
const creds = assert2.noThrowAndReturn(
() => ChannelCredentials.createInsecure());
assert.ok(!creds.getSecureContext());
});
});
describe('createSsl', () => {
it('should work when given no arguments', () => {
const creds: ChannelCredentials = assert2.noThrowAndReturn(
() => ChannelCredentials.createSsl());
const creds: ChannelCredentials =
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl());
assert.ok(!!creds.getSecureContext());
});
it('should work with just a CA override', async () => {
const { ca } = await pFixtures;
const creds = assert2.noThrowAndReturn(
() => ChannelCredentials.createSsl(ca));
const {ca} = await pFixtures;
const creds =
assert2.noThrowAndReturn(() => ChannelCredentials.createSsl(ca));
assert.ok(!!creds.getSecureContext());
});
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(
() => ChannelCredentials.createSsl(null, key, cert));
() => ChannelCredentials.createSsl(null, key, cert));
assert.ok(!!creds.getSecureContext());
});
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(
() => ChannelCredentials.createSsl(ca, key, cert));
() => ChannelCredentials.createSsl(ca, key, cert));
assert.ok(!!creds.getSecureContext());
});
it('should throw if just one of private key and cert chain are missing',
async () => {
const { ca, key, cert } = await pFixtures;
assert.throws(() => ChannelCredentials.createSsl(ca, key));
assert.throws(() => ChannelCredentials.createSsl(ca, key, null));
assert.throws(() => ChannelCredentials.createSsl(ca, null, cert));
assert.throws(() => ChannelCredentials.createSsl(null, key));
assert.throws(() => ChannelCredentials.createSsl(null, key, null));
assert.throws(() => ChannelCredentials.createSsl(null, null, cert));
});
async () => {
const {ca, key, cert} = await pFixtures;
assert.throws(() => ChannelCredentials.createSsl(ca, key));
assert.throws(() => ChannelCredentials.createSsl(ca, key, null));
assert.throws(() => ChannelCredentials.createSsl(ca, null, cert));
assert.throws(() => ChannelCredentials.createSsl(null, key));
assert.throws(() => ChannelCredentials.createSsl(null, key, null));
assert.throws(() => ChannelCredentials.createSsl(null, null, cert));
});
});
describe('compose', () => {
@ -99,8 +96,7 @@ describe('ChannelCredentials Implementation', () => {
const channelCreds = ChannelCredentials.createSsl();
const callCreds = new CallCredentialsMock();
const composedChannelCreds = channelCreds.compose(callCreds);
assert.strictEqual(composedChannelCreds.getCallCredentials(),
callCreds);
assert.strictEqual(composedChannelCreds.getCallCredentials(), callCreds);
});
it('should be chainable', () => {
@ -108,12 +104,12 @@ describe('ChannelCredentials Implementation', () => {
const callCreds2 = new CallCredentialsMock();
// Associate both call credentials with channelCreds
const composedChannelCreds = ChannelCredentials.createSsl()
.compose(callCreds1)
.compose(callCreds2);
.compose(callCreds1)
.compose(callCreds2);
// Build a mock object that should be an identical copy
const composedCallCreds = callCreds1.compose(callCreds2);
assert.ok(composedCallCreds.isEqual(
composedChannelCreds.getCallCredentials() as CallCredentialsMock));
composedChannelCreds.getCallCredentials() as CallCredentialsMock));
});
});
});

View File

@ -1,31 +1,30 @@
import * as assert from 'assert';
import * as http2 from 'http2';
import { range } from 'lodash';
import * as metadata from '../src/metadata';
import {range} from 'lodash';
import {Metadata} from '../src/metadata';
class Metadata extends metadata.Metadata {
class TestMetadata extends Metadata {
getInternalRepresentation() {
return this.internalRepr;
}
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata {
const result = metadata.Metadata.fromHttp2Headers(headers) as Metadata;
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata {
const result = Metadata.fromHttp2Headers(headers) as TestMetadata;
result.getInternalRepresentation =
Metadata.prototype.getInternalRepresentation;
TestMetadata.prototype.getInternalRepresentation;
return result;
}
}
const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.';
const validNonBinValueChars = range(0x20, 0x7f)
.map(code => String.fromCharCode(code))
.join('');
const validNonBinValueChars =
range(0x20, 0x7f).map(code => String.fromCharCode(code)).join('');
describe('Metadata', () => {
let metadata: Metadata;
let metadata: TestMetadata;
beforeEach(() => {
metadata = new Metadata();
metadata = new TestMetadata();
});
describe('set', () => {
@ -178,10 +177,8 @@ describe('Metadata', () => {
metadata.add('Key2', 'value2');
metadata.add('KEY3', 'value3a');
metadata.add('KEY3', 'value3b');
assert.deepEqual(metadata.getMap(),
{key1: 'value1',
key2: 'value2',
key3: 'value3a'});
assert.deepEqual(
metadata.getMap(), {key1: 'value1', key2: 'value2', key3: 'value3a'});
});
});
@ -213,7 +210,7 @@ describe('Metadata', () => {
metadata.add('Key2', 'value2a');
metadata.add('KEY3', 'value3a');
metadata.add('key4', 'value4');
const metadata2 = new Metadata();
const metadata2 = new TestMetadata();
metadata2.add('KEY1', 'value1');
metadata2.add('key2', 'value2b');
metadata2.add('key3', 'value3b');
@ -246,8 +243,7 @@ describe('Metadata', () => {
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
]
});
@ -265,27 +261,25 @@ describe('Metadata', () => {
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODw==', 'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8='
]
};
const metadataFromHeaders = Metadata.fromHttp2Headers(headers);
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
const internalRepr = metadataFromHeaders.getInternalRepresentation();
assert.deepEqual(internalRepr, {
key1: ['value1'],
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
Buffer.from(range(0, 16)),
Buffer.from(range(16, 32)),
Buffer.from(range(0, 16)), Buffer.from(range(16, 32)),
Buffer.from(range(0, 32))
]
});
});
it('creates an empty Metadata object from empty headers', () => {
const metadataFromHeaders = Metadata.fromHttp2Headers({});
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
const internalRepr = metadataFromHeaders.getInternalRepresentation();
assert.deepEqual(internalRepr, {});
});