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 {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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
106
src/channel.ts
106
src/channel.ts
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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, {});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue