mirror of
https://github.com/musix-org/musix-oss
synced 2025-07-07 13:50:49 +00:00
Modules
This commit is contained in:
105
node_modules/@grpc/grpc-js/src/backoff-timeout.ts
generated
vendored
Normal file
105
node_modules/@grpc/grpc-js/src/backoff-timeout.ts
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
const INITIAL_BACKOFF_MS = 1000;
|
||||
const BACKOFF_MULTIPLIER = 1.6;
|
||||
const MAX_BACKOFF_MS = 120000;
|
||||
const BACKOFF_JITTER = 0.2;
|
||||
|
||||
/**
|
||||
* Get a number uniformly at random in the range [min, max)
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
function uniformRandom(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export interface BackoffOptions {
|
||||
initialDelay?: number;
|
||||
multiplier?: number;
|
||||
jitter?: number;
|
||||
maxDelay?: number;
|
||||
}
|
||||
|
||||
export class BackoffTimeout {
|
||||
private initialDelay: number = INITIAL_BACKOFF_MS;
|
||||
private multiplier: number = BACKOFF_MULTIPLIER;
|
||||
private maxDelay: number = MAX_BACKOFF_MS;
|
||||
private jitter: number = BACKOFF_JITTER;
|
||||
private nextDelay: number;
|
||||
private timerId: NodeJS.Timer;
|
||||
private running = false;
|
||||
|
||||
constructor(private callback: () => void, options?: BackoffOptions) {
|
||||
if (options) {
|
||||
if (options.initialDelay) {
|
||||
this.initialDelay = options.initialDelay;
|
||||
}
|
||||
if (options.multiplier) {
|
||||
this.multiplier = options.multiplier;
|
||||
}
|
||||
if (options.jitter) {
|
||||
this.jitter = options.jitter;
|
||||
}
|
||||
if (options.maxDelay) {
|
||||
this.maxDelay = options.maxDelay;
|
||||
}
|
||||
}
|
||||
this.nextDelay = this.initialDelay;
|
||||
this.timerId = setTimeout(() => {}, 0);
|
||||
clearTimeout(this.timerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the callback after the current amount of delay time
|
||||
*/
|
||||
runOnce() {
|
||||
this.running = true;
|
||||
this.timerId = setTimeout(() => {
|
||||
this.callback();
|
||||
this.running = false;
|
||||
}, this.nextDelay);
|
||||
const nextBackoff = Math.min(
|
||||
this.nextDelay * this.multiplier,
|
||||
this.maxDelay
|
||||
);
|
||||
const jitterMagnitude = nextBackoff * this.jitter;
|
||||
this.nextDelay =
|
||||
nextBackoff + uniformRandom(-jitterMagnitude, jitterMagnitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the timer. The callback will not be called until `runOnce` is called
|
||||
* again.
|
||||
*/
|
||||
stop() {
|
||||
clearTimeout(this.timerId);
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the delay time to its initial value.
|
||||
*/
|
||||
reset() {
|
||||
this.nextDelay = this.initialDelay;
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
}
|
66
node_modules/@grpc/grpc-js/src/call-credentials-filter.ts
generated
vendored
Normal file
66
node_modules/@grpc/grpc-js/src/call-credentials-filter.ts
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Call } from './call-stream';
|
||||
import { Channel } from './channel';
|
||||
import { BaseFilter, Filter, FilterFactory } from './filter';
|
||||
import { Metadata } from './metadata';
|
||||
|
||||
export class CallCredentialsFilter extends BaseFilter implements Filter {
|
||||
private serviceUrl: string;
|
||||
constructor(
|
||||
private readonly channel: Channel,
|
||||
private readonly stream: Call
|
||||
) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
this.stream = stream;
|
||||
const splitPath: string[] = stream.getMethod().split('/');
|
||||
let serviceName = '';
|
||||
/* The standard path format is "/{serviceName}/{methodName}", so if we split
|
||||
* by '/', the first item should be empty and the second should be the
|
||||
* service name */
|
||||
if (splitPath.length >= 2) {
|
||||
serviceName = splitPath[1];
|
||||
}
|
||||
/* Currently, call credentials are only allowed on HTTPS connections, so we
|
||||
* can assume that the scheme is "https" */
|
||||
this.serviceUrl = `https://${stream.getHost()}/${serviceName}`;
|
||||
}
|
||||
|
||||
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
const credentials = this.stream.getCredentials();
|
||||
const credsMetadata = credentials.generateMetadata({
|
||||
service_url: this.serviceUrl,
|
||||
});
|
||||
const resultMetadata = await metadata;
|
||||
resultMetadata.merge(await credsMetadata);
|
||||
return resultMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
export class CallCredentialsFilterFactory
|
||||
implements FilterFactory<CallCredentialsFilter> {
|
||||
constructor(private readonly channel: Channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
createFilter(callStream: Call): CallCredentialsFilter {
|
||||
return new CallCredentialsFilter(this.channel, callStream);
|
||||
}
|
||||
}
|
153
node_modules/@grpc/grpc-js/src/call-credentials.ts
generated
vendored
Normal file
153
node_modules/@grpc/grpc-js/src/call-credentials.ts
generated
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Metadata } from './metadata';
|
||||
import { Call } from '.';
|
||||
|
||||
export interface CallMetadataOptions {
|
||||
service_url: string;
|
||||
}
|
||||
|
||||
export type CallMetadataGenerator = (
|
||||
options: CallMetadataOptions,
|
||||
cb: (err: Error | null, metadata?: Metadata) => void
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* A class that represents a generic method of adding authentication-related
|
||||
* metadata on a per-request basis.
|
||||
*/
|
||||
export abstract class CallCredentials {
|
||||
/**
|
||||
* Asynchronously generates a new Metadata object.
|
||||
* @param options Options used in generating the Metadata object.
|
||||
*/
|
||||
abstract generateMetadata(options: CallMetadataOptions): Promise<Metadata>;
|
||||
/**
|
||||
* Creates a new CallCredentials object from properties of both this and
|
||||
* another CallCredentials object. This object's metadata generator will be
|
||||
* called first.
|
||||
* @param callCredentials The other CallCredentials object.
|
||||
*/
|
||||
abstract compose(callCredentials: CallCredentials): CallCredentials;
|
||||
|
||||
/**
|
||||
* Check whether two call credentials objects are equal. Separate
|
||||
* SingleCallCredentials with identical metadata generator functions are
|
||||
* equal.
|
||||
* @param other The other CallCredentials object to compare with.
|
||||
*/
|
||||
abstract _equals(other: CallCredentials): boolean;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static createFromMetadataGenerator(
|
||||
metadataGenerator: CallMetadataGenerator
|
||||
): CallCredentials {
|
||||
return new SingleCallCredentials(metadataGenerator);
|
||||
}
|
||||
|
||||
static createEmpty(): CallCredentials {
|
||||
return new EmptyCallCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
class ComposedCallCredentials extends CallCredentials {
|
||||
constructor(private creds: CallCredentials[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
async generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
const base: Metadata = new Metadata();
|
||||
const generated: Metadata[] = await Promise.all(
|
||||
this.creds.map(cred => cred.generateMetadata(options))
|
||||
);
|
||||
for (const gen of generated) {
|
||||
base.merge(gen);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
compose(other: CallCredentials): CallCredentials {
|
||||
return new ComposedCallCredentials(this.creds.concat([other]));
|
||||
}
|
||||
|
||||
_equals(other: CallCredentials): boolean {
|
||||
if (this === other) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof ComposedCallCredentials) {
|
||||
return this.creds.every((value, index) =>
|
||||
value._equals(other.creds[index])
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SingleCallCredentials extends CallCredentials {
|
||||
constructor(private metadataGenerator: CallMetadataGenerator) {
|
||||
super();
|
||||
}
|
||||
|
||||
generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
return new Promise<Metadata>((resolve, reject) => {
|
||||
this.metadataGenerator(options, (err, metadata) => {
|
||||
if (metadata !== undefined) {
|
||||
resolve(metadata);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
compose(other: CallCredentials): CallCredentials {
|
||||
return new ComposedCallCredentials([this, other]);
|
||||
}
|
||||
|
||||
_equals(other: CallCredentials): boolean {
|
||||
if (this === other) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof SingleCallCredentials) {
|
||||
return this.metadataGenerator === other.metadataGenerator;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyCallCredentials extends CallCredentials {
|
||||
generateMetadata(options: CallMetadataOptions): Promise<Metadata> {
|
||||
return Promise.resolve(new Metadata());
|
||||
}
|
||||
|
||||
compose(other: CallCredentials): CallCredentials {
|
||||
return other;
|
||||
}
|
||||
|
||||
_equals(other: CallCredentials): boolean {
|
||||
return other instanceof EmptyCallCredentials;
|
||||
}
|
||||
}
|
534
node_modules/@grpc/grpc-js/src/call-stream.ts
generated
vendored
Normal file
534
node_modules/@grpc/grpc-js/src/call-stream.ts
generated
vendored
Normal file
@ -0,0 +1,534 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as http2 from 'http2';
|
||||
import { Duplex } from 'stream';
|
||||
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Status } from './constants';
|
||||
import { EmitterAugmentation1 } from './events';
|
||||
import { Filter } from './filter';
|
||||
import { FilterStackFactory } from './filter-stack';
|
||||
import { Metadata } from './metadata';
|
||||
import { ObjectDuplex, WriteCallback } from './object-stream';
|
||||
import { StreamDecoder } from './stream-decoder';
|
||||
import { ChannelImplementation } from './channel';
|
||||
import { Subchannel } from './subchannel';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
const TRACER_NAME = 'call_stream';
|
||||
|
||||
const {
|
||||
HTTP2_HEADER_STATUS,
|
||||
HTTP2_HEADER_CONTENT_TYPE,
|
||||
NGHTTP2_CANCEL,
|
||||
} = http2.constants;
|
||||
|
||||
export type Deadline = Date | number;
|
||||
|
||||
export interface CallStreamOptions {
|
||||
deadline: Deadline;
|
||||
flags: number;
|
||||
host: string;
|
||||
parentCall: Call | null;
|
||||
}
|
||||
|
||||
export type PartialCallStreamOptions = Partial<CallStreamOptions>;
|
||||
|
||||
export interface StatusObject {
|
||||
code: Status;
|
||||
details: string;
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
export const enum WriteFlags {
|
||||
BufferHint = 1,
|
||||
NoCompress = 2,
|
||||
WriteThrough = 4,
|
||||
}
|
||||
|
||||
export interface WriteObject {
|
||||
message: Buffer;
|
||||
flags?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface represents a duplex stream associated with a single gRPC call.
|
||||
*/
|
||||
export type Call = {
|
||||
cancelWithStatus(status: Status, details: string): void;
|
||||
getPeer(): string;
|
||||
sendMetadata(metadata: Metadata): void;
|
||||
|
||||
getDeadline(): Deadline;
|
||||
getCredentials(): CallCredentials;
|
||||
setCredentials(credentials: CallCredentials): void;
|
||||
/* If the return value is null, the call has not ended yet. Otherwise, it has
|
||||
* ended with the specified status */
|
||||
getStatus(): StatusObject | null;
|
||||
getMethod(): string;
|
||||
getHost(): string;
|
||||
} & EmitterAugmentation1<'metadata', Metadata> &
|
||||
EmitterAugmentation1<'status', StatusObject> &
|
||||
ObjectDuplex<WriteObject, Buffer>;
|
||||
|
||||
export class Http2CallStream extends Duplex implements Call {
|
||||
credentials: CallCredentials;
|
||||
filterStack: Filter;
|
||||
private http2Stream: http2.ClientHttp2Stream | null = null;
|
||||
private pendingRead = false;
|
||||
private pendingWrite: Buffer | null = null;
|
||||
private pendingWriteCallback: WriteCallback | null = null;
|
||||
private pendingFinalCallback: Function | null = null;
|
||||
|
||||
private decoder = new StreamDecoder();
|
||||
|
||||
private isReadFilterPending = false;
|
||||
private canPush = false;
|
||||
|
||||
private unpushedReadMessages: Array<Buffer | null> = [];
|
||||
private unfilteredReadMessages: Array<Buffer | null> = [];
|
||||
|
||||
// Status code mapped from :status. To be used if grpc-status is not received
|
||||
private mappedStatusCode: Status = Status.UNKNOWN;
|
||||
|
||||
// Promise objects that are re-assigned to resolving promises when headers
|
||||
// or trailers received. Processing headers/trailers is asynchronous, so we
|
||||
// can use these objects to await their completion. This helps us establish
|
||||
// order of precedence when obtaining the status of the call.
|
||||
private handlingHeaders = Promise.resolve();
|
||||
private handlingTrailers = Promise.resolve();
|
||||
|
||||
// This is populated (non-null) if and only if the call has ended
|
||||
private finalStatus: StatusObject | null = null;
|
||||
|
||||
private subchannel: Subchannel | null = null;
|
||||
private disconnectListener: () => void;
|
||||
|
||||
constructor(
|
||||
private readonly methodName: string,
|
||||
private readonly channel: ChannelImplementation,
|
||||
private readonly options: CallStreamOptions,
|
||||
filterStackFactory: FilterStackFactory,
|
||||
private readonly channelCallCredentials: CallCredentials,
|
||||
private readonly callNumber: number
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
this.filterStack = filterStackFactory.createFilter(this);
|
||||
this.credentials = channelCallCredentials;
|
||||
this.disconnectListener = () => {
|
||||
this.endCall({
|
||||
code: Status.UNAVAILABLE,
|
||||
details: 'Connection dropped',
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private trace(text: string): void {
|
||||
logging.trace(
|
||||
LogVerbosity.DEBUG,
|
||||
TRACER_NAME,
|
||||
'[' + this.callNumber + '] ' + text
|
||||
);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
push(chunk: any, encoding?: string): boolean {
|
||||
this.trace(
|
||||
'pushing to reader message of length ' +
|
||||
(chunk instanceof Buffer ? chunk.length : null)
|
||||
);
|
||||
return super.push(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* On first call, emits a 'status' event with the given StatusObject.
|
||||
* Subsequent calls are no-ops.
|
||||
* @param status The status of the call.
|
||||
*/
|
||||
private endCall(status: StatusObject): void {
|
||||
if (this.finalStatus === null) {
|
||||
this.trace(
|
||||
'ended with status: code=' +
|
||||
status.code +
|
||||
' details="' +
|
||||
status.details +
|
||||
'"'
|
||||
);
|
||||
this.finalStatus = status;
|
||||
/* We do this asynchronously to ensure that no async function is in the
|
||||
* call stack when we return control to the application. If an async
|
||||
* function is in the call stack, any exception thrown by the application
|
||||
* (or our tests) will bubble up and turn into promise rejection, which
|
||||
* will result in an UnhandledPromiseRejectionWarning. Because that is
|
||||
* a warning, the error will be effectively swallowed and execution will
|
||||
* continue */
|
||||
process.nextTick(() => {
|
||||
this.emit('status', status);
|
||||
});
|
||||
if (this.subchannel) {
|
||||
this.subchannel.callUnref();
|
||||
this.subchannel.removeDisconnectListener(this.disconnectListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleFilterError(error: Error) {
|
||||
this.cancelWithStatus(Status.INTERNAL, error.message);
|
||||
}
|
||||
|
||||
private handleFilteredRead(message: Buffer) {
|
||||
/* If we the call has already ended, we don't want to do anything with
|
||||
* this message. Dropping it on the floor is correct behavior */
|
||||
if (this.finalStatus !== null) {
|
||||
return;
|
||||
}
|
||||
this.isReadFilterPending = false;
|
||||
if (this.canPush) {
|
||||
if (!this.push(message)) {
|
||||
this.canPush = false;
|
||||
(this.http2Stream as http2.ClientHttp2Stream).pause();
|
||||
}
|
||||
} else {
|
||||
this.trace(
|
||||
'unpushedReadMessages.push message of length ' + message.length
|
||||
);
|
||||
this.unpushedReadMessages.push(message);
|
||||
}
|
||||
if (this.unfilteredReadMessages.length > 0) {
|
||||
/* nextMessage is guaranteed not to be undefined because
|
||||
unfilteredReadMessages is non-empty */
|
||||
const nextMessage = this.unfilteredReadMessages.shift() as Buffer | null;
|
||||
this.filterReceivedMessage(nextMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private filterReceivedMessage(framedMessage: Buffer | null) {
|
||||
/* If we the call has already ended, we don't want to do anything with
|
||||
* this message. Dropping it on the floor is correct behavior */
|
||||
if (this.finalStatus !== null) {
|
||||
return;
|
||||
}
|
||||
if (framedMessage === null) {
|
||||
if (this.canPush) {
|
||||
this.push(null);
|
||||
} else {
|
||||
this.unpushedReadMessages.push(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.trace('filterReceivedMessage of length ' + framedMessage.length);
|
||||
this.isReadFilterPending = true;
|
||||
this.filterStack
|
||||
.receiveMessage(Promise.resolve(framedMessage))
|
||||
.then(
|
||||
this.handleFilteredRead.bind(this),
|
||||
this.handleFilterError.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
private tryPush(messageBytes: Buffer | null): void {
|
||||
if (this.isReadFilterPending) {
|
||||
this.trace(
|
||||
'[' +
|
||||
this.callNumber +
|
||||
'] unfilteredReadMessages.push message of length ' +
|
||||
(messageBytes && messageBytes.length)
|
||||
);
|
||||
this.unfilteredReadMessages.push(messageBytes);
|
||||
} else {
|
||||
this.filterReceivedMessage(messageBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private handleTrailers(headers: http2.IncomingHttpHeaders) {
|
||||
this.trace('received HTTP/2 trailing headers frame');
|
||||
const code: Status = this.mappedStatusCode;
|
||||
const details = '';
|
||||
let metadata: Metadata;
|
||||
try {
|
||||
metadata = Metadata.fromHttp2Headers(headers);
|
||||
} catch (e) {
|
||||
metadata = new Metadata();
|
||||
}
|
||||
const status: StatusObject = { code, details, metadata };
|
||||
this.handlingTrailers = (async () => {
|
||||
let finalStatus;
|
||||
try {
|
||||
// Attempt to assign final status.
|
||||
finalStatus = await this.filterStack.receiveTrailers(
|
||||
Promise.resolve(status)
|
||||
);
|
||||
} catch (error) {
|
||||
await this.handlingHeaders;
|
||||
// This is a no-op if the call was already ended when handling headers.
|
||||
this.endCall({
|
||||
code: Status.INTERNAL,
|
||||
details: 'Failed to process received status',
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// It's possible that headers were received but not fully handled yet.
|
||||
// Give the headers handler an opportunity to end the call first,
|
||||
// if an error occurred.
|
||||
await this.handlingHeaders;
|
||||
// This is a no-op if the call was already ended when handling headers.
|
||||
this.endCall(finalStatus);
|
||||
})();
|
||||
}
|
||||
|
||||
attachHttp2Stream(
|
||||
stream: http2.ClientHttp2Stream,
|
||||
subchannel: Subchannel
|
||||
): void {
|
||||
if (this.finalStatus !== null) {
|
||||
stream.close(NGHTTP2_CANCEL);
|
||||
} else {
|
||||
this.trace(
|
||||
'attachHttp2Stream from subchannel ' + subchannel.getAddress()
|
||||
);
|
||||
this.http2Stream = stream;
|
||||
this.subchannel = subchannel;
|
||||
subchannel.addDisconnectListener(this.disconnectListener);
|
||||
subchannel.callRef();
|
||||
stream.on('response', (headers, flags) => {
|
||||
this.trace('received HTTP/2 headers frame');
|
||||
switch (headers[':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;
|
||||
}
|
||||
|
||||
if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) {
|
||||
this.handleTrailers(headers);
|
||||
} else {
|
||||
let metadata: Metadata;
|
||||
try {
|
||||
metadata = Metadata.fromHttp2Headers(headers);
|
||||
} catch (error) {
|
||||
this.endCall({
|
||||
code: Status.UNKNOWN,
|
||||
details: error.message,
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.handlingHeaders = this.filterStack
|
||||
.receiveMetadata(Promise.resolve(metadata))
|
||||
.then(finalMetadata => {
|
||||
this.emit('metadata', finalMetadata);
|
||||
})
|
||||
.catch(error => {
|
||||
this.destroyHttp2Stream();
|
||||
this.endCall({
|
||||
code: Status.UNKNOWN,
|
||||
details: error.message,
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
stream.on('trailers', this.handleTrailers.bind(this));
|
||||
stream.on('data', (data: Buffer) => {
|
||||
this.trace('receive HTTP/2 data frame of length ' + data.length);
|
||||
const messages = this.decoder.write(data);
|
||||
|
||||
for (const message of messages) {
|
||||
this.trace('parsed message of length ' + message.length);
|
||||
this.tryPush(message);
|
||||
}
|
||||
});
|
||||
stream.on('end', () => {
|
||||
this.trace('received HTTP/2 end of data flag');
|
||||
this.tryPush(null);
|
||||
});
|
||||
stream.on('close', async () => {
|
||||
this.trace('HTTP/2 stream closed with code ' + stream.rstCode);
|
||||
let code: Status;
|
||||
let details = '';
|
||||
switch (stream.rstCode) {
|
||||
case http2.constants.NGHTTP2_REFUSED_STREAM:
|
||||
code = Status.UNAVAILABLE;
|
||||
details = 'Stream refused by server';
|
||||
break;
|
||||
case http2.constants.NGHTTP2_CANCEL:
|
||||
code = Status.CANCELLED;
|
||||
details = 'Call 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 guarantees that if trailers were received, the value of the
|
||||
// 'grpc-status' header takes precedence for emitted status data.
|
||||
await this.handlingTrailers;
|
||||
// This is a no-op if trailers were received at all.
|
||||
// This is OK, because status codes emitted here correspond to more
|
||||
// catastrophic issues that prevent us from receiving trailers in the
|
||||
// first place.
|
||||
this.endCall({ code, details, metadata: new Metadata() });
|
||||
});
|
||||
stream.on('error', (err: Error) => {
|
||||
/* We need an error handler here to stop "Uncaught Error" exceptions
|
||||
* from bubbling up. However, errors here should all correspond to
|
||||
* "close" events, where we will handle the error more granularly */
|
||||
});
|
||||
if (!this.pendingRead) {
|
||||
stream.pause();
|
||||
}
|
||||
if (this.pendingWrite) {
|
||||
if (!this.pendingWriteCallback) {
|
||||
throw new Error('Invalid state in write handling code');
|
||||
}
|
||||
stream.write(this.pendingWrite, this.pendingWriteCallback);
|
||||
}
|
||||
if (this.pendingFinalCallback) {
|
||||
this.trace('calling end() on HTTP/2 stream');
|
||||
stream.end(this.pendingFinalCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMetadata(metadata: Metadata): void {
|
||||
this.trace('Sending metadata');
|
||||
this.channel._startCallStream(this, metadata);
|
||||
}
|
||||
|
||||
private destroyHttp2Stream() {
|
||||
// The http2 stream could already have been destroyed if cancelWithStatus
|
||||
// is called in response to an internal http2 error.
|
||||
if (this.http2Stream !== null && !this.http2Stream.destroyed) {
|
||||
/* TODO(murgatroid99): Determine if we want to send different RST_STREAM
|
||||
* codes based on the status code */
|
||||
this.http2Stream.close(NGHTTP2_CANCEL);
|
||||
}
|
||||
}
|
||||
|
||||
cancelWithStatus(status: Status, details: string): void {
|
||||
this.destroyHttp2Stream();
|
||||
(async () => {
|
||||
// If trailers are currently being processed, the call should be ended
|
||||
// by handleTrailers instead.
|
||||
await this.handlingTrailers;
|
||||
this.endCall({ code: status, details, metadata: new Metadata() });
|
||||
})();
|
||||
}
|
||||
|
||||
getDeadline(): Deadline {
|
||||
return this.options.deadline;
|
||||
}
|
||||
|
||||
getCredentials(): CallCredentials {
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
setCredentials(credentials: CallCredentials): void {
|
||||
this.credentials = this.channelCallCredentials.compose(credentials);
|
||||
}
|
||||
|
||||
getStatus(): StatusObject | null {
|
||||
return this.finalStatus;
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
|
||||
getMethod(): string {
|
||||
return this.methodName;
|
||||
}
|
||||
|
||||
getHost(): string {
|
||||
return this.options.host;
|
||||
}
|
||||
|
||||
_read(size: number) {
|
||||
/* If we have already emitted a status, we should not emit any more
|
||||
* messages and we should communicate that the stream has ended */
|
||||
if (this.finalStatus !== null) {
|
||||
this.push(null);
|
||||
return;
|
||||
}
|
||||
this.canPush = true;
|
||||
if (this.http2Stream === null) {
|
||||
this.pendingRead = true;
|
||||
} else {
|
||||
while (this.unpushedReadMessages.length > 0) {
|
||||
const nextMessage = this.unpushedReadMessages.shift();
|
||||
this.canPush = this.push(nextMessage);
|
||||
if (nextMessage === null || !this.canPush) {
|
||||
this.canPush = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Only resume reading from the http2Stream if we don't have any pending
|
||||
* messages to emit, and we haven't gotten the signal to stop pushing
|
||||
* messages */
|
||||
this.http2Stream.resume();
|
||||
}
|
||||
}
|
||||
|
||||
_write(chunk: WriteObject, encoding: string, cb: WriteCallback) {
|
||||
this.trace('write() called with message of length ' + chunk.message.length);
|
||||
this.filterStack.sendMessage(Promise.resolve(chunk)).then(message => {
|
||||
if (this.http2Stream === null) {
|
||||
this.pendingWrite = message.message;
|
||||
this.pendingWriteCallback = cb;
|
||||
} else {
|
||||
this.http2Stream.write(message.message, cb);
|
||||
}
|
||||
}, this.handleFilterError.bind(this));
|
||||
}
|
||||
|
||||
_final(cb: Function) {
|
||||
this.trace('end() called');
|
||||
if (this.http2Stream === null) {
|
||||
this.pendingFinalCallback = cb;
|
||||
} else {
|
||||
this.trace('calling end() on HTTP/2 stream');
|
||||
this.http2Stream.end(cb);
|
||||
}
|
||||
}
|
||||
}
|
256
node_modules/@grpc/grpc-js/src/call.ts
generated
vendored
Normal file
256
node_modules/@grpc/grpc-js/src/call.ts
generated
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { Duplex, Readable, Writable } from 'stream';
|
||||
|
||||
import { Call, StatusObject, WriteObject } from './call-stream';
|
||||
import { Status } from './constants';
|
||||
import { EmitterAugmentation1 } from './events';
|
||||
import { Metadata } from './metadata';
|
||||
import { ObjectReadable, ObjectWritable } from './object-stream';
|
||||
|
||||
/**
|
||||
* A type extending the built-in Error object with additional fields.
|
||||
*/
|
||||
export type ServiceError = StatusObject & Error;
|
||||
|
||||
/**
|
||||
* A base type for all user-facing values returned by client-side method calls.
|
||||
*/
|
||||
export type SurfaceCall = {
|
||||
cancel(): void;
|
||||
getPeer(): string;
|
||||
} & EmitterAugmentation1<'metadata', Metadata> &
|
||||
EmitterAugmentation1<'status', StatusObject> &
|
||||
EventEmitter;
|
||||
|
||||
/**
|
||||
* A type representing the return value of a unary method call.
|
||||
*/
|
||||
export type ClientUnaryCall = SurfaceCall;
|
||||
|
||||
/**
|
||||
* A type representing the return value of a server stream method call.
|
||||
*/
|
||||
export type ClientReadableStream<ResponseType> = {
|
||||
deserialize: (chunk: Buffer) => ResponseType;
|
||||
} & SurfaceCall &
|
||||
ObjectReadable<ResponseType>;
|
||||
|
||||
/**
|
||||
* A type representing the return value of a client stream method call.
|
||||
*/
|
||||
export type ClientWritableStream<RequestType> = {
|
||||
serialize: (value: RequestType) => Buffer;
|
||||
} & SurfaceCall &
|
||||
ObjectWritable<RequestType>;
|
||||
|
||||
/**
|
||||
* A type representing the return value of a bidirectional stream method call.
|
||||
*/
|
||||
export type ClientDuplexStream<
|
||||
RequestType,
|
||||
ResponseType
|
||||
> = ClientWritableStream<RequestType> & ClientReadableStream<ResponseType>;
|
||||
|
||||
/**
|
||||
* Construct a ServiceError from a StatusObject. This function exists primarily
|
||||
* as an attempt to make the error stack trace clearly communicate that the
|
||||
* error is not necessarily a problem in gRPC itself.
|
||||
* @param status
|
||||
*/
|
||||
export function callErrorFromStatus(status: StatusObject): ServiceError {
|
||||
const message = `${status.code} ${Status[status.code]}: ${status.details}`;
|
||||
return Object.assign(new Error(message), status);
|
||||
}
|
||||
|
||||
export class ClientUnaryCallImpl extends EventEmitter
|
||||
implements ClientUnaryCall {
|
||||
constructor(private readonly call: Call) {
|
||||
super();
|
||||
call.on('metadata', (metadata: Metadata) => {
|
||||
this.emit('metadata', metadata);
|
||||
});
|
||||
call.on('status', (status: StatusObject) => {
|
||||
this.emit('status', status);
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client');
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
return this.call.getPeer();
|
||||
}
|
||||
}
|
||||
|
||||
function setUpReadableStream<ResponseType>(
|
||||
stream: ClientReadableStream<ResponseType>,
|
||||
call: Call,
|
||||
deserialize: (chunk: Buffer) => ResponseType
|
||||
): void {
|
||||
let statusEmitted = false;
|
||||
call.on('data', (data: Buffer) => {
|
||||
let deserialized: ResponseType;
|
||||
try {
|
||||
deserialized = deserialize(data);
|
||||
} catch (e) {
|
||||
call.cancelWithStatus(Status.INTERNAL, 'Failed to parse server response');
|
||||
return;
|
||||
}
|
||||
if (!stream.push(deserialized)) {
|
||||
call.pause();
|
||||
}
|
||||
});
|
||||
call.on('end', () => {
|
||||
if (statusEmitted) {
|
||||
stream.push(null);
|
||||
} else {
|
||||
call.once('status', () => {
|
||||
stream.push(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
call.on('status', (status: StatusObject) => {
|
||||
if (status.code !== Status.OK) {
|
||||
stream.emit('error', callErrorFromStatus(status));
|
||||
}
|
||||
stream.emit('status', status);
|
||||
statusEmitted = true;
|
||||
});
|
||||
call.pause();
|
||||
}
|
||||
|
||||
export class ClientReadableStreamImpl<ResponseType> extends Readable
|
||||
implements ClientReadableStream<ResponseType> {
|
||||
constructor(
|
||||
private readonly call: Call,
|
||||
readonly deserialize: (chunk: Buffer) => ResponseType
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
call.on('metadata', (metadata: Metadata) => {
|
||||
this.emit('metadata', metadata);
|
||||
});
|
||||
setUpReadableStream<ResponseType>(this, call, deserialize);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client');
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
return this.call.getPeer();
|
||||
}
|
||||
|
||||
_read(_size: number): void {
|
||||
this.call.resume();
|
||||
}
|
||||
}
|
||||
|
||||
function tryWrite<RequestType>(
|
||||
call: Call,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
chunk: RequestType,
|
||||
encoding: string,
|
||||
cb: Function
|
||||
) {
|
||||
let message: Buffer;
|
||||
const flags: number = Number(encoding);
|
||||
try {
|
||||
message = serialize(chunk);
|
||||
} catch (e) {
|
||||
call.cancelWithStatus(Status.INTERNAL, 'Serialization failure');
|
||||
cb(e);
|
||||
return;
|
||||
}
|
||||
const writeObj: WriteObject = { message };
|
||||
if (!Number.isNaN(flags)) {
|
||||
writeObj.flags = flags;
|
||||
}
|
||||
call.write(writeObj, cb);
|
||||
}
|
||||
|
||||
export class ClientWritableStreamImpl<RequestType> extends Writable
|
||||
implements ClientWritableStream<RequestType> {
|
||||
constructor(
|
||||
private readonly call: Call,
|
||||
readonly serialize: (value: RequestType) => Buffer
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
call.on('metadata', (metadata: Metadata) => {
|
||||
this.emit('metadata', metadata);
|
||||
});
|
||||
call.on('status', (status: StatusObject) => {
|
||||
this.emit('status', status);
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client');
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
return this.call.getPeer();
|
||||
}
|
||||
|
||||
_write(chunk: RequestType, encoding: string, cb: Function) {
|
||||
tryWrite<RequestType>(this.call, this.serialize, chunk, encoding, cb);
|
||||
}
|
||||
|
||||
_final(cb: Function) {
|
||||
this.call.end();
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientDuplexStreamImpl<RequestType, ResponseType> extends Duplex
|
||||
implements ClientDuplexStream<RequestType, ResponseType> {
|
||||
constructor(
|
||||
private readonly call: Call,
|
||||
readonly serialize: (value: RequestType) => Buffer,
|
||||
readonly deserialize: (chunk: Buffer) => ResponseType
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
call.on('metadata', (metadata: Metadata) => {
|
||||
this.emit('metadata', metadata);
|
||||
});
|
||||
setUpReadableStream<ResponseType>(this, call, deserialize);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.call.cancelWithStatus(Status.CANCELLED, 'Cancelled on client');
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
return this.call.getPeer();
|
||||
}
|
||||
|
||||
_read(_size: number): void {
|
||||
this.call.resume();
|
||||
}
|
||||
|
||||
_write(chunk: RequestType, encoding: string, cb: Function) {
|
||||
tryWrite<RequestType>(this.call, this.serialize, chunk, encoding, cb);
|
||||
}
|
||||
|
||||
_final(cb: Function) {
|
||||
this.call.end();
|
||||
cb();
|
||||
}
|
||||
}
|
279
node_modules/@grpc/grpc-js/src/channel-credentials.ts
generated
vendored
Normal file
279
node_modules/@grpc/grpc-js/src/channel-credentials.ts
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { ConnectionOptions, createSecureContext, PeerCertificate } from 'tls';
|
||||
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import {CIPHER_SUITES, getDefaultRootsData} from './tls-helpers';
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
function verifyIsBufferOrNull(obj: any, friendlyName: string): void {
|
||||
if (obj && !(obj instanceof Buffer)) {
|
||||
throw new TypeError(`${friendlyName}, if provided, must be a Buffer.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A certificate as received by the checkServerIdentity callback.
|
||||
*/
|
||||
export interface Certificate {
|
||||
/**
|
||||
* The raw certificate in DER form.
|
||||
*/
|
||||
raw: Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that will receive the expected hostname and presented peer
|
||||
* certificate as parameters. The callback should return an error to
|
||||
* indicate that the presented certificate is considered invalid and
|
||||
* otherwise returned undefined.
|
||||
*/
|
||||
export type CheckServerIdentityCallback = (
|
||||
hostname: string,
|
||||
cert: Certificate
|
||||
) => Error | undefined;
|
||||
|
||||
function bufferOrNullEqual(buf1: Buffer | null, buf2: Buffer | null) {
|
||||
if (buf1 === null && buf2 === null) {
|
||||
return true;
|
||||
} else {
|
||||
return buf1 !== null && buf2 !== null && buf1.equals(buf2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional peer verification options that can be set when creating
|
||||
* SSL credentials.
|
||||
*/
|
||||
export interface VerifyOptions {
|
||||
/**
|
||||
* If set, this callback will be invoked after the usual hostname verification
|
||||
* has been performed on the peer certificate.
|
||||
*/
|
||||
checkServerIdentity?: CheckServerIdentityCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that contains credentials for communicating over a channel, as well
|
||||
* as a set of per-call credentials, which are applied to every method call made
|
||||
* over a channel initialized with an instance of this class.
|
||||
*/
|
||||
export abstract class ChannelCredentials {
|
||||
protected callCredentials: CallCredentials;
|
||||
|
||||
protected constructor(callCredentials?: CallCredentials) {
|
||||
this.callCredentials = callCredentials || CallCredentials.createEmpty();
|
||||
}
|
||||
/**
|
||||
* Returns a copy of this object with the included set of per-call credentials
|
||||
* expanded to include callCredentials.
|
||||
* @param callCredentials A CallCredentials object to associate with this
|
||||
* instance.
|
||||
*/
|
||||
abstract compose(callCredentials: CallCredentials): ChannelCredentials;
|
||||
|
||||
/**
|
||||
* Gets the set of per-call credentials associated with this instance.
|
||||
*/
|
||||
_getCallCredentials(): CallCredentials {
|
||||
return this.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.
|
||||
*/
|
||||
abstract _getConnectionOptions(): ConnectionOptions | null;
|
||||
|
||||
/**
|
||||
* Indicates whether this credentials object creates a secure channel.
|
||||
*/
|
||||
abstract _isSecure(): boolean;
|
||||
|
||||
/**
|
||||
* Check whether two channel credentials objects are equal. Two secure
|
||||
* credentials are equal if they were constructed with the same parameters.
|
||||
* @param other The other ChannelCredentials Object
|
||||
*/
|
||||
abstract _equals(other: ChannelCredentials): boolean;
|
||||
|
||||
/**
|
||||
* Return a new ChannelCredentials instance with a given set of credentials.
|
||||
* The resulting instance can be used to construct a Channel that communicates
|
||||
* over TLS.
|
||||
* @param rootCerts The root certificate data.
|
||||
* @param privateKey The client certificate private key, if available.
|
||||
* @param certChain The client certificate key chain, if available.
|
||||
*/
|
||||
static createSsl(
|
||||
rootCerts?: Buffer | null,
|
||||
privateKey?: Buffer | null,
|
||||
certChain?: Buffer | null,
|
||||
verifyOptions?: VerifyOptions
|
||||
): ChannelCredentials {
|
||||
verifyIsBufferOrNull(rootCerts, 'Root certificate');
|
||||
verifyIsBufferOrNull(privateKey, 'Private key');
|
||||
verifyIsBufferOrNull(certChain, 'Certificate chain');
|
||||
if (privateKey && !certChain) {
|
||||
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'
|
||||
);
|
||||
}
|
||||
return new SecureChannelCredentialsImpl(
|
||||
rootCerts || getDefaultRootsData(),
|
||||
privateKey || null,
|
||||
certChain || null,
|
||||
verifyOptions || {}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new ChannelCredentials instance with no credentials.
|
||||
*/
|
||||
static createInsecure(): ChannelCredentials {
|
||||
return new InsecureChannelCredentialsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
class InsecureChannelCredentialsImpl extends ChannelCredentials {
|
||||
constructor(callCredentials?: CallCredentials) {
|
||||
super(callCredentials);
|
||||
}
|
||||
|
||||
compose(callCredentials: CallCredentials): ChannelCredentials {
|
||||
throw new Error('Cannot compose insecure credentials');
|
||||
}
|
||||
|
||||
_getConnectionOptions(): ConnectionOptions | null {
|
||||
return null;
|
||||
}
|
||||
_isSecure(): boolean {
|
||||
return false;
|
||||
}
|
||||
_equals(other: ChannelCredentials): boolean {
|
||||
return other instanceof InsecureChannelCredentialsImpl;
|
||||
}
|
||||
}
|
||||
|
||||
class SecureChannelCredentialsImpl extends ChannelCredentials {
|
||||
connectionOptions: ConnectionOptions;
|
||||
|
||||
constructor(
|
||||
private rootCerts: Buffer | null,
|
||||
private privateKey: Buffer | null,
|
||||
private certChain: Buffer | null,
|
||||
private verifyOptions: VerifyOptions
|
||||
) {
|
||||
super();
|
||||
const secureContext = createSecureContext({
|
||||
ca: rootCerts || undefined,
|
||||
key: privateKey || undefined,
|
||||
cert: certChain || undefined,
|
||||
ciphers: CIPHER_SUITES
|
||||
});
|
||||
this.connectionOptions = { secureContext };
|
||||
if (verifyOptions && verifyOptions.checkServerIdentity) {
|
||||
this.connectionOptions.checkServerIdentity = (
|
||||
host: string,
|
||||
cert: PeerCertificate
|
||||
) => {
|
||||
return verifyOptions.checkServerIdentity!(host, { raw: cert.raw });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
compose(callCredentials: CallCredentials): ChannelCredentials {
|
||||
const combinedCallCredentials = this.callCredentials.compose(
|
||||
callCredentials
|
||||
);
|
||||
return new ComposedChannelCredentialsImpl(this, combinedCallCredentials);
|
||||
}
|
||||
|
||||
_getConnectionOptions(): ConnectionOptions | null {
|
||||
return this.connectionOptions;
|
||||
}
|
||||
_isSecure(): boolean {
|
||||
return true;
|
||||
}
|
||||
_equals(other: ChannelCredentials): boolean {
|
||||
if (this === other) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof SecureChannelCredentialsImpl) {
|
||||
if (!bufferOrNullEqual(this.rootCerts, other.rootCerts)) {
|
||||
return false;
|
||||
}
|
||||
if (!bufferOrNullEqual(this.privateKey, other.privateKey)) {
|
||||
return false;
|
||||
}
|
||||
if (!bufferOrNullEqual(this.certChain, other.certChain)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
this.verifyOptions.checkServerIdentity ===
|
||||
other.verifyOptions.checkServerIdentity
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ComposedChannelCredentialsImpl extends ChannelCredentials {
|
||||
constructor(
|
||||
private channelCredentials: SecureChannelCredentialsImpl,
|
||||
callCreds: CallCredentials
|
||||
) {
|
||||
super(callCreds);
|
||||
}
|
||||
compose(callCredentials: CallCredentials) {
|
||||
const combinedCallCredentials = this.callCredentials.compose(
|
||||
callCredentials
|
||||
);
|
||||
return new ComposedChannelCredentialsImpl(
|
||||
this.channelCredentials,
|
||||
combinedCallCredentials
|
||||
);
|
||||
}
|
||||
|
||||
_getConnectionOptions(): ConnectionOptions | null {
|
||||
return this.channelCredentials._getConnectionOptions();
|
||||
}
|
||||
_isSecure(): boolean {
|
||||
return true;
|
||||
}
|
||||
_equals(other: ChannelCredentials): boolean {
|
||||
if (this === other) {
|
||||
return true;
|
||||
}
|
||||
if (other instanceof ComposedChannelCredentialsImpl) {
|
||||
return (
|
||||
this.channelCredentials._equals(other.channelCredentials) &&
|
||||
this.callCredentials._equals(other.callCredentials)
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
72
node_modules/@grpc/grpc-js/src/channel-options.ts
generated
vendored
Normal file
72
node_modules/@grpc/grpc-js/src/channel-options.ts
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* An interface that contains options used when initializing a Channel instance.
|
||||
*/
|
||||
export interface ChannelOptions {
|
||||
'grpc.ssl_target_name_override'?: string;
|
||||
'grpc.primary_user_agent'?: string;
|
||||
'grpc.secondary_user_agent'?: string;
|
||||
'grpc.default_authority'?: string;
|
||||
'grpc.keepalive_time_ms'?: number;
|
||||
'grpc.keepalive_timeout_ms'?: number;
|
||||
'grpc.service_config'?: string;
|
||||
'grpc.max_concurrent_streams'?: number;
|
||||
'grpc.initial_reconnect_backoff_ms'?: number;
|
||||
'grpc.max_reconnect_backoff_ms'?: number;
|
||||
'grpc.use_local_subchannel_pool'?: number;
|
||||
[key: string]: string | number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for checking provided options at runtime. This is an object for
|
||||
* easier membership checking.
|
||||
*/
|
||||
export const recognizedOptions = {
|
||||
'grpc.ssl_target_name_override': true,
|
||||
'grpc.primary_user_agent': true,
|
||||
'grpc.secondary_user_agent': true,
|
||||
'grpc.default_authority': true,
|
||||
'grpc.keepalive_time_ms': true,
|
||||
'grpc.keepalive_timeout_ms': true,
|
||||
'grpc.service_config': true,
|
||||
'grpc.max_concurrent_streams': true,
|
||||
'grpc.initial_reconnect_backoff_ms': true,
|
||||
'grpc.max_reconnect_backoff_ms': true,
|
||||
'grpc.use_local_subchannel_pool': true,
|
||||
};
|
||||
|
||||
export function channelOptionsEqual(
|
||||
options1: ChannelOptions,
|
||||
options2: ChannelOptions
|
||||
) {
|
||||
const keys1 = Object.keys(options1).sort();
|
||||
const keys2 = Object.keys(options2).sort();
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < keys1.length; i += 1) {
|
||||
if (keys1[i] !== keys2[i]) {
|
||||
return false;
|
||||
}
|
||||
if (options1[keys1[i]] !== options2[keys2[i]]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
401
node_modules/@grpc/grpc-js/src/channel.ts
generated
vendored
Normal file
401
node_modules/@grpc/grpc-js/src/channel.ts
generated
vendored
Normal file
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
Deadline,
|
||||
Call,
|
||||
Http2CallStream,
|
||||
CallStreamOptions,
|
||||
} from './call-stream';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { ResolvingLoadBalancer } from './resolving-load-balancer';
|
||||
import { SubchannelPool, getSubchannelPool } from './subchannel-pool';
|
||||
import { ChannelControlHelper } from './load-balancer';
|
||||
import { UnavailablePicker, Picker, PickResultType } from './picker';
|
||||
import { Metadata } from './metadata';
|
||||
import { Status, LogVerbosity } from './constants';
|
||||
import { FilterStackFactory } from './filter-stack';
|
||||
import { CallCredentialsFilterFactory } from './call-credentials-filter';
|
||||
import { DeadlineFilterFactory } from './deadline-filter';
|
||||
import { MetadataStatusFilterFactory } from './metadata-status-filter';
|
||||
import { CompressionFilterFactory } from './compression-filter';
|
||||
import { getDefaultAuthority } from './resolver';
|
||||
import { LoadBalancingConfig } from './load-balancing-config';
|
||||
import { ServiceConfig, validateServiceConfig } from './service-config';
|
||||
import { trace } from './logging';
|
||||
|
||||
export enum ConnectivityState {
|
||||
CONNECTING,
|
||||
READY,
|
||||
TRANSIENT_FAILURE,
|
||||
IDLE,
|
||||
SHUTDOWN,
|
||||
}
|
||||
|
||||
let nextCallNumber = 0;
|
||||
|
||||
function getNewCallNumber(): number {
|
||||
const callNumber = nextCallNumber;
|
||||
nextCallNumber += 1;
|
||||
if (nextCallNumber >= Number.MAX_SAFE_INTEGER) {
|
||||
nextCallNumber = 0;
|
||||
}
|
||||
return callNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface that represents a communication channel to a server specified
|
||||
* by a given address.
|
||||
*/
|
||||
export interface Channel {
|
||||
/**
|
||||
* Close the channel. This has the same functionality as the existing
|
||||
* grpc.Client.prototype.close
|
||||
*/
|
||||
close(): void;
|
||||
/**
|
||||
* Return the target that this channel connects to
|
||||
*/
|
||||
getTarget(): string;
|
||||
/**
|
||||
* Get the channel's current connectivity state. This method is here mainly
|
||||
* because it is in the existing internal Channel class, and there isn't
|
||||
* another good place to put it.
|
||||
* @param tryToConnect If true, the channel will start connecting if it is
|
||||
* idle. Otherwise, idle channels will only start connecting when a
|
||||
* call starts.
|
||||
*/
|
||||
getConnectivityState(tryToConnect: boolean): ConnectivityState;
|
||||
/**
|
||||
* Watch for connectivity state changes. This is also here mainly because
|
||||
* it is in the existing external Channel class.
|
||||
* @param currentState The state to watch for transitions from. This should
|
||||
* always be populated by calling getConnectivityState immediately
|
||||
* before.
|
||||
* @param deadline A deadline for waiting for a state change
|
||||
* @param callback Called with no error when a state change, or with an
|
||||
* error if the deadline passes without a state change.
|
||||
*/
|
||||
watchConnectivityState(
|
||||
currentState: ConnectivityState,
|
||||
deadline: Date | number,
|
||||
callback: (error?: Error) => void
|
||||
): void;
|
||||
/**
|
||||
* Create a call object. Call is an opaque type that is used by the Client
|
||||
* class. This function is called by the gRPC library when starting a
|
||||
* request. Implementers should return an instance of Call that is returned
|
||||
* from calling createCall on an instance of the provided Channel class.
|
||||
* @param method The full method string to request.
|
||||
* @param deadline The call deadline
|
||||
* @param host A host string override for making the request
|
||||
* @param parentCall A server call to propagate some information from
|
||||
* @param propagateFlags A bitwise combination of elements of grpc.propagate
|
||||
* that indicates what information to propagate from parentCall.
|
||||
*/
|
||||
createCall(
|
||||
method: string,
|
||||
deadline: Deadline | null | undefined,
|
||||
host: string | null | undefined,
|
||||
parentCall: Call | null | undefined,
|
||||
propagateFlags: number | null | undefined
|
||||
): Call;
|
||||
}
|
||||
|
||||
interface ConnectivityStateWatcher {
|
||||
currentState: ConnectivityState;
|
||||
timer: NodeJS.Timeout;
|
||||
callback: (error?: Error) => void;
|
||||
}
|
||||
|
||||
export class ChannelImplementation implements Channel {
|
||||
private resolvingLoadBalancer: ResolvingLoadBalancer;
|
||||
private subchannelPool: SubchannelPool;
|
||||
private connectivityState: ConnectivityState = ConnectivityState.IDLE;
|
||||
private currentPicker: Picker = new UnavailablePicker();
|
||||
private pickQueue: Array<{
|
||||
callStream: Http2CallStream;
|
||||
callMetadata: Metadata;
|
||||
}> = [];
|
||||
private connectivityStateWatchers: ConnectivityStateWatcher[] = [];
|
||||
private defaultAuthority: string;
|
||||
private filterStackFactory: FilterStackFactory;
|
||||
constructor(
|
||||
private target: string,
|
||||
private readonly credentials: ChannelCredentials,
|
||||
private readonly options: ChannelOptions
|
||||
) {
|
||||
/* The global boolean parameter to getSubchannelPool has the inverse meaning to what
|
||||
* the grpc.use_local_subchannel_pool channel option means. */
|
||||
this.subchannelPool = getSubchannelPool((options['grpc.use_local_subchannel_pool'] ?? 0) === 0);
|
||||
const channelControlHelper: ChannelControlHelper = {
|
||||
createSubchannel: (
|
||||
subchannelAddress: string,
|
||||
subchannelArgs: ChannelOptions
|
||||
) => {
|
||||
return this.subchannelPool.getOrCreateSubchannel(
|
||||
this.target,
|
||||
subchannelAddress,
|
||||
Object.assign({}, this.options, subchannelArgs),
|
||||
this.credentials
|
||||
);
|
||||
},
|
||||
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
|
||||
this.currentPicker = picker;
|
||||
const queueCopy = this.pickQueue.slice();
|
||||
this.pickQueue = [];
|
||||
for (const { callStream, callMetadata } of queueCopy) {
|
||||
this.tryPick(callStream, callMetadata);
|
||||
}
|
||||
this.updateState(connectivityState);
|
||||
},
|
||||
requestReresolution: () => {
|
||||
// This should never be called.
|
||||
throw new Error(
|
||||
'Resolving load balancer should never call requestReresolution'
|
||||
);
|
||||
},
|
||||
};
|
||||
// TODO(murgatroid99): check channel arg for default service config
|
||||
let defaultServiceConfig: ServiceConfig = {
|
||||
loadBalancingConfig: [],
|
||||
methodConfig: [],
|
||||
};
|
||||
if (options['grpc.service_config']) {
|
||||
defaultServiceConfig = validateServiceConfig(
|
||||
JSON.parse(options['grpc.service_config']!)
|
||||
);
|
||||
}
|
||||
this.resolvingLoadBalancer = new ResolvingLoadBalancer(
|
||||
target,
|
||||
channelControlHelper,
|
||||
defaultServiceConfig
|
||||
);
|
||||
this.filterStackFactory = new FilterStackFactory([
|
||||
new CallCredentialsFilterFactory(this),
|
||||
new DeadlineFilterFactory(this),
|
||||
new MetadataStatusFilterFactory(this),
|
||||
new CompressionFilterFactory(this),
|
||||
]);
|
||||
// TODO(murgatroid99): Add more centralized handling of channel options
|
||||
if (this.options['grpc.default_authority']) {
|
||||
this.defaultAuthority = this.options['grpc.default_authority'] as string;
|
||||
} else {
|
||||
this.defaultAuthority = getDefaultAuthority(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the picker output for the given call and corresponding metadata,
|
||||
* and take any relevant actions. Should not be called while iterating
|
||||
* over pickQueue.
|
||||
* @param callStream
|
||||
* @param callMetadata
|
||||
*/
|
||||
private tryPick(callStream: Http2CallStream, callMetadata: Metadata) {
|
||||
const pickResult = this.currentPicker.pick({ metadata: callMetadata });
|
||||
switch (pickResult.pickResultType) {
|
||||
case PickResultType.COMPLETE:
|
||||
if (pickResult.subchannel === null) {
|
||||
callStream.cancelWithStatus(
|
||||
Status.UNAVAILABLE,
|
||||
'Request dropped by load balancing policy'
|
||||
);
|
||||
// End the call with an error
|
||||
} else {
|
||||
/* If the subchannel disconnects between calling pick and getting
|
||||
* the filter stack metadata, the call will end with an error. */
|
||||
callStream.filterStack
|
||||
.sendMetadata(Promise.resolve(callMetadata))
|
||||
.then(
|
||||
finalMetadata => {
|
||||
if (
|
||||
pickResult.subchannel!.getConnectivityState() ===
|
||||
ConnectivityState.READY
|
||||
) {
|
||||
try {
|
||||
pickResult.subchannel!.startCallStream(
|
||||
finalMetadata,
|
||||
callStream
|
||||
);
|
||||
} catch (error) {
|
||||
callStream.cancelWithStatus(
|
||||
Status.UNAVAILABLE,
|
||||
'Failed to start call on picked subchannel'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
callStream.cancelWithStatus(
|
||||
Status.UNAVAILABLE,
|
||||
'Connection dropped while starting call'
|
||||
);
|
||||
}
|
||||
},
|
||||
(error: Error & { code: number }) => {
|
||||
// We assume the error code isn't 0 (Status.OK)
|
||||
callStream.cancelWithStatus(
|
||||
error.code || Status.UNKNOWN,
|
||||
`Getting metadata from plugin failed with error: ${error.message}`
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
break;
|
||||
case PickResultType.QUEUE:
|
||||
this.pickQueue.push({ callStream, callMetadata });
|
||||
break;
|
||||
case PickResultType.TRANSIENT_FAILURE:
|
||||
if (callMetadata.getOptions().waitForReady) {
|
||||
this.pickQueue.push({ callStream, callMetadata });
|
||||
} else {
|
||||
callStream.cancelWithStatus(
|
||||
pickResult.status!.code,
|
||||
pickResult.status!.details
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid state: unknown pickResultType ${pickResult.pickResultType}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private removeConnectivityStateWatcher(
|
||||
watcherObject: ConnectivityStateWatcher
|
||||
) {
|
||||
const watcherIndex = this.connectivityStateWatchers.findIndex(
|
||||
value => value === watcherObject
|
||||
);
|
||||
if (watcherIndex >= 0) {
|
||||
this.connectivityStateWatchers.splice(watcherIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private updateState(newState: ConnectivityState): void {
|
||||
trace(
|
||||
LogVerbosity.DEBUG,
|
||||
'connectivity_state',
|
||||
this.target +
|
||||
' ' +
|
||||
ConnectivityState[this.connectivityState] +
|
||||
' -> ' +
|
||||
ConnectivityState[newState]
|
||||
);
|
||||
this.connectivityState = newState;
|
||||
const watchersCopy = this.connectivityStateWatchers.slice();
|
||||
for (const watcherObject of watchersCopy) {
|
||||
if (newState !== watcherObject.currentState) {
|
||||
watcherObject.callback();
|
||||
clearTimeout(watcherObject.timer);
|
||||
this.removeConnectivityStateWatcher(watcherObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_startCallStream(stream: Http2CallStream, metadata: Metadata) {
|
||||
this.tryPick(stream, metadata.clone());
|
||||
}
|
||||
|
||||
close() {
|
||||
this.resolvingLoadBalancer.destroy();
|
||||
this.updateState(ConnectivityState.SHUTDOWN);
|
||||
|
||||
this.subchannelPool.unrefUnusedSubchannels();
|
||||
}
|
||||
|
||||
getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
getConnectivityState(tryToConnect: boolean) {
|
||||
const connectivityState = this.connectivityState;
|
||||
if (tryToConnect) {
|
||||
this.resolvingLoadBalancer.exitIdle();
|
||||
}
|
||||
return connectivityState;
|
||||
}
|
||||
|
||||
watchConnectivityState(
|
||||
currentState: ConnectivityState,
|
||||
deadline: Date | number,
|
||||
callback: (error?: Error) => void
|
||||
): void {
|
||||
const deadlineDate: Date =
|
||||
deadline instanceof Date ? deadline : new Date(deadline);
|
||||
const now = new Date();
|
||||
if (deadlineDate <= now) {
|
||||
process.nextTick(
|
||||
callback,
|
||||
new Error('Deadline passed without connectivity state change')
|
||||
);
|
||||
return;
|
||||
}
|
||||
const watcherObject = {
|
||||
currentState,
|
||||
callback,
|
||||
timer: setTimeout(() => {
|
||||
this.removeConnectivityStateWatcher(watcherObject);
|
||||
callback(
|
||||
new Error('Deadline passed without connectivity state change')
|
||||
);
|
||||
}, deadlineDate.getTime() - now.getTime()),
|
||||
};
|
||||
this.connectivityStateWatchers.push(watcherObject);
|
||||
}
|
||||
|
||||
createCall(
|
||||
method: string,
|
||||
deadline: Deadline | null | undefined,
|
||||
host: string | null | undefined,
|
||||
parentCall: Call | null | undefined,
|
||||
propagateFlags: number | null | undefined
|
||||
): Call {
|
||||
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
|
||||
throw new Error('Channel has been shut down');
|
||||
}
|
||||
const callNumber = getNewCallNumber();
|
||||
trace(
|
||||
LogVerbosity.DEBUG,
|
||||
'channel',
|
||||
this.target +
|
||||
' createCall [' +
|
||||
callNumber +
|
||||
'] method="' +
|
||||
method +
|
||||
'", deadline=' +
|
||||
deadline
|
||||
);
|
||||
const finalOptions: CallStreamOptions = {
|
||||
deadline:
|
||||
deadline === null || deadline === undefined ? Infinity : deadline,
|
||||
flags: propagateFlags || 0,
|
||||
host: host || this.defaultAuthority,
|
||||
parentCall: parentCall || null,
|
||||
};
|
||||
const stream: Http2CallStream = new Http2CallStream(
|
||||
method,
|
||||
this,
|
||||
finalOptions,
|
||||
this.filterStackFactory,
|
||||
this.credentials._getCallCredentials(),
|
||||
callNumber
|
||||
);
|
||||
return stream;
|
||||
}
|
||||
}
|
412
node_modules/@grpc/grpc-js/src/client.ts
generated
vendored
Normal file
412
node_modules/@grpc/grpc-js/src/client.ts
generated
vendored
Normal file
@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
ClientDuplexStream,
|
||||
ClientDuplexStreamImpl,
|
||||
ClientReadableStream,
|
||||
ClientReadableStreamImpl,
|
||||
ClientUnaryCall,
|
||||
ClientUnaryCallImpl,
|
||||
ClientWritableStream,
|
||||
ClientWritableStreamImpl,
|
||||
ServiceError,
|
||||
callErrorFromStatus,
|
||||
} from './call';
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Call, Deadline, StatusObject, WriteObject } from './call-stream';
|
||||
import { Channel, ConnectivityState, ChannelImplementation } from './channel';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { Status } from './constants';
|
||||
import { Metadata } from './metadata';
|
||||
|
||||
const CHANNEL_SYMBOL = Symbol();
|
||||
|
||||
export interface UnaryCallback<ResponseType> {
|
||||
(err: ServiceError | null, value?: ResponseType): void;
|
||||
}
|
||||
|
||||
export interface CallOptions {
|
||||
deadline?: Deadline;
|
||||
host?: string;
|
||||
/* There should be a parent option here that will accept a server call,
|
||||
* but the server is not yet implemented so it makes no sense to have it */
|
||||
propagate_flags?: number;
|
||||
credentials?: CallCredentials;
|
||||
}
|
||||
|
||||
export type ClientOptions = Partial<ChannelOptions> & {
|
||||
channelOverride?: Channel;
|
||||
channelFactoryOverride?: (
|
||||
address: string,
|
||||
credentials: ChannelCredentials,
|
||||
options: ClientOptions
|
||||
) => Channel;
|
||||
};
|
||||
|
||||
/**
|
||||
* A generic gRPC client. Primarily useful as a base class for all generated
|
||||
* clients.
|
||||
*/
|
||||
export class Client {
|
||||
private readonly [CHANNEL_SYMBOL]: Channel;
|
||||
constructor(
|
||||
address: string,
|
||||
credentials: ChannelCredentials,
|
||||
options: ClientOptions = {}
|
||||
) {
|
||||
if (options.channelOverride) {
|
||||
this[CHANNEL_SYMBOL] = options.channelOverride;
|
||||
} else if (options.channelFactoryOverride) {
|
||||
this[CHANNEL_SYMBOL] = options.channelFactoryOverride(
|
||||
address,
|
||||
credentials,
|
||||
options
|
||||
);
|
||||
} else {
|
||||
this[CHANNEL_SYMBOL] = new ChannelImplementation(
|
||||
address,
|
||||
credentials,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this[CHANNEL_SYMBOL].close();
|
||||
}
|
||||
|
||||
getChannel(): Channel {
|
||||
return this[CHANNEL_SYMBOL];
|
||||
}
|
||||
|
||||
waitForReady(deadline: Deadline, callback: (error?: Error) => void): void {
|
||||
const checkState = (err?: Error) => {
|
||||
if (err) {
|
||||
callback(new Error('Failed to connect before the deadline'));
|
||||
return;
|
||||
}
|
||||
let newState;
|
||||
try {
|
||||
newState = this[CHANNEL_SYMBOL].getConnectivityState(true);
|
||||
} catch (e) {
|
||||
callback(new Error('The channel has been closed'));
|
||||
return;
|
||||
}
|
||||
if (newState === ConnectivityState.READY) {
|
||||
callback();
|
||||
} else {
|
||||
try {
|
||||
this[CHANNEL_SYMBOL].watchConnectivityState(
|
||||
newState,
|
||||
deadline,
|
||||
checkState
|
||||
);
|
||||
} catch (e) {
|
||||
callback(new Error('The channel has been closed'));
|
||||
}
|
||||
}
|
||||
};
|
||||
setImmediate(checkState);
|
||||
}
|
||||
|
||||
private handleUnaryResponse<ResponseType>(
|
||||
call: Call,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): void {
|
||||
let responseMessage: ResponseType | null = null;
|
||||
call.on('data', (data: Buffer) => {
|
||||
if (responseMessage != null) {
|
||||
call.cancelWithStatus(Status.INTERNAL, 'Too many responses received');
|
||||
}
|
||||
try {
|
||||
responseMessage = deserialize(data);
|
||||
} catch (e) {
|
||||
call.cancelWithStatus(
|
||||
Status.INTERNAL,
|
||||
'Failed to parse server response'
|
||||
);
|
||||
}
|
||||
});
|
||||
call.on('status', (status: StatusObject) => {
|
||||
/* We assume that call emits status after it emits end, and that it
|
||||
* accounts for any cancelWithStatus calls up until it emits status.
|
||||
* Therefore, considering the above event handlers, status.code should be
|
||||
* OK if and only if we have a non-null responseMessage */
|
||||
if (status.code === Status.OK) {
|
||||
callback(null, responseMessage as ResponseType);
|
||||
} else {
|
||||
callback(callErrorFromStatus(status));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private checkOptionalUnaryResponseArguments<ResponseType>(
|
||||
arg1: Metadata | CallOptions | UnaryCallback<ResponseType>,
|
||||
arg2?: CallOptions | UnaryCallback<ResponseType>,
|
||||
arg3?: UnaryCallback<ResponseType>
|
||||
): {
|
||||
metadata: Metadata;
|
||||
options: CallOptions;
|
||||
callback: UnaryCallback<ResponseType>;
|
||||
} {
|
||||
if (arg1 instanceof Function) {
|
||||
return { metadata: new Metadata(), options: {}, callback: arg1 };
|
||||
} else if (arg2 instanceof Function) {
|
||||
if (arg1 instanceof Metadata) {
|
||||
return { metadata: arg1, options: {}, callback: arg2 };
|
||||
} else {
|
||||
return { metadata: new Metadata(), options: arg1, callback: arg2 };
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!(
|
||||
arg1 instanceof Metadata &&
|
||||
arg2 instanceof Object &&
|
||||
arg3 instanceof Function
|
||||
)
|
||||
) {
|
||||
throw new Error('Incorrect arguments passed');
|
||||
}
|
||||
return { metadata: arg1, options: arg2, callback: arg3 };
|
||||
}
|
||||
}
|
||||
|
||||
makeUnaryRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
metadata: Metadata,
|
||||
options: CallOptions,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientUnaryCall;
|
||||
makeUnaryRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
metadata: Metadata,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientUnaryCall;
|
||||
makeUnaryRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
options: CallOptions,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientUnaryCall;
|
||||
makeUnaryRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientUnaryCall;
|
||||
makeUnaryRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
metadata: Metadata | CallOptions | UnaryCallback<ResponseType>,
|
||||
options?: CallOptions | UnaryCallback<ResponseType>,
|
||||
callback?: UnaryCallback<ResponseType>
|
||||
): ClientUnaryCall {
|
||||
({ metadata, options, callback } = this.checkOptionalUnaryResponseArguments<
|
||||
ResponseType
|
||||
>(metadata, options, callback));
|
||||
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||
method,
|
||||
options.deadline,
|
||||
options.host,
|
||||
null,
|
||||
options.propagate_flags
|
||||
);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
const message: Buffer = serialize(argument);
|
||||
const writeObj: WriteObject = { message };
|
||||
call.sendMetadata(metadata);
|
||||
call.write(writeObj);
|
||||
call.end();
|
||||
this.handleUnaryResponse<ResponseType>(call, deserialize, callback);
|
||||
return new ClientUnaryCallImpl(call);
|
||||
}
|
||||
|
||||
makeClientStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
metadata: Metadata,
|
||||
options: CallOptions,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientWritableStream<RequestType>;
|
||||
makeClientStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
metadata: Metadata,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientWritableStream<RequestType>;
|
||||
makeClientStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
options: CallOptions,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientWritableStream<RequestType>;
|
||||
makeClientStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
callback: UnaryCallback<ResponseType>
|
||||
): ClientWritableStream<RequestType>;
|
||||
makeClientStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
metadata: Metadata | CallOptions | UnaryCallback<ResponseType>,
|
||||
options?: CallOptions | UnaryCallback<ResponseType>,
|
||||
callback?: UnaryCallback<ResponseType>
|
||||
): ClientWritableStream<RequestType> {
|
||||
({ metadata, options, callback } = this.checkOptionalUnaryResponseArguments<
|
||||
ResponseType
|
||||
>(metadata, options, callback));
|
||||
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||
method,
|
||||
options.deadline,
|
||||
options.host,
|
||||
null,
|
||||
options.propagate_flags
|
||||
);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
call.sendMetadata(metadata);
|
||||
this.handleUnaryResponse<ResponseType>(call, deserialize, callback);
|
||||
return new ClientWritableStreamImpl<RequestType>(call, serialize);
|
||||
}
|
||||
|
||||
private checkMetadataAndOptions(
|
||||
arg1?: Metadata | CallOptions,
|
||||
arg2?: CallOptions
|
||||
): { metadata: Metadata; options: CallOptions } {
|
||||
let metadata: Metadata;
|
||||
let options: CallOptions;
|
||||
if (arg1 instanceof Metadata) {
|
||||
metadata = arg1;
|
||||
if (arg2) {
|
||||
options = arg2;
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
} else {
|
||||
if (arg1) {
|
||||
options = arg1;
|
||||
} else {
|
||||
options = {};
|
||||
}
|
||||
metadata = new Metadata();
|
||||
}
|
||||
return { metadata, options };
|
||||
}
|
||||
|
||||
makeServerStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
metadata: Metadata,
|
||||
options?: CallOptions
|
||||
): ClientReadableStream<ResponseType>;
|
||||
makeServerStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
options?: CallOptions
|
||||
): ClientReadableStream<ResponseType>;
|
||||
makeServerStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
argument: RequestType,
|
||||
metadata?: Metadata | CallOptions,
|
||||
options?: CallOptions
|
||||
): ClientReadableStream<ResponseType> {
|
||||
({ metadata, options } = this.checkMetadataAndOptions(metadata, options));
|
||||
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||
method,
|
||||
options.deadline,
|
||||
options.host,
|
||||
null,
|
||||
options.propagate_flags
|
||||
);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
const message: Buffer = serialize(argument);
|
||||
const writeObj: WriteObject = { message };
|
||||
call.sendMetadata(metadata);
|
||||
call.write(writeObj);
|
||||
call.end();
|
||||
return new ClientReadableStreamImpl<ResponseType>(call, deserialize);
|
||||
}
|
||||
|
||||
makeBidiStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
metadata: Metadata,
|
||||
options?: CallOptions
|
||||
): ClientDuplexStream<RequestType, ResponseType>;
|
||||
makeBidiStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
options?: CallOptions
|
||||
): ClientDuplexStream<RequestType, ResponseType>;
|
||||
makeBidiStreamRequest<RequestType, ResponseType>(
|
||||
method: string,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
deserialize: (value: Buffer) => ResponseType,
|
||||
metadata?: Metadata | CallOptions,
|
||||
options?: CallOptions
|
||||
): ClientDuplexStream<RequestType, ResponseType> {
|
||||
({ metadata, options } = this.checkMetadataAndOptions(metadata, options));
|
||||
const call: Call = this[CHANNEL_SYMBOL].createCall(
|
||||
method,
|
||||
options.deadline,
|
||||
options.host,
|
||||
null,
|
||||
options.propagate_flags
|
||||
);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
call.sendMetadata(metadata);
|
||||
return new ClientDuplexStreamImpl<RequestType, ResponseType>(
|
||||
call,
|
||||
serialize,
|
||||
deserialize
|
||||
);
|
||||
}
|
||||
}
|
224
node_modules/@grpc/grpc-js/src/compression-filter.ts
generated
vendored
Normal file
224
node_modules/@grpc/grpc-js/src/compression-filter.ts
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as zlib from 'zlib';
|
||||
|
||||
import { Call, WriteFlags, WriteObject } from './call-stream';
|
||||
import { Channel } from './channel';
|
||||
import { BaseFilter, Filter, FilterFactory } from './filter';
|
||||
import { Metadata, MetadataValue } from './metadata';
|
||||
|
||||
abstract class CompressionHandler {
|
||||
protected abstract compressMessage(message: Buffer): Promise<Buffer>;
|
||||
protected abstract decompressMessage(data: Buffer): Promise<Buffer>;
|
||||
/**
|
||||
* @param message Raw uncompressed message bytes
|
||||
* @param compress Indicates whether the message should be compressed
|
||||
* @return Framed message, compressed if applicable
|
||||
*/
|
||||
async writeMessage(message: Buffer, compress: boolean): Promise<Buffer> {
|
||||
let messageBuffer = message;
|
||||
if (compress) {
|
||||
messageBuffer = await this.compressMessage(messageBuffer);
|
||||
}
|
||||
const output = Buffer.allocUnsafe(messageBuffer.length + 5);
|
||||
output.writeUInt8(compress ? 1 : 0, 0);
|
||||
output.writeUInt32BE(messageBuffer.length, 1);
|
||||
messageBuffer.copy(output, 5);
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* @param data Framed message, possibly compressed
|
||||
* @return Uncompressed message
|
||||
*/
|
||||
async readMessage(data: Buffer): Promise<Buffer> {
|
||||
const compressed = data.readUInt8(0) === 1;
|
||||
let messageBuffer = data.slice(5);
|
||||
if (compressed) {
|
||||
messageBuffer = await this.decompressMessage(messageBuffer);
|
||||
}
|
||||
return messageBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
class IdentityHandler extends CompressionHandler {
|
||||
async compressMessage(message: Buffer) {
|
||||
return message;
|
||||
}
|
||||
|
||||
async writeMessage(message: Buffer, compress: boolean): Promise<Buffer> {
|
||||
const output = Buffer.allocUnsafe(message.length + 5);
|
||||
/* With "identity" compression, messages should always be marked as
|
||||
* uncompressed */
|
||||
output.writeUInt8(0, 0);
|
||||
output.writeUInt32BE(message.length, 1);
|
||||
message.copy(output, 5);
|
||||
return output;
|
||||
}
|
||||
|
||||
decompressMessage(message: Buffer): Promise<Buffer> {
|
||||
return Promise.reject<Buffer>(
|
||||
new Error(
|
||||
'Received compressed message but "grpc-encoding" header was identity'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DeflateHandler extends CompressionHandler {
|
||||
compressMessage(message: Buffer) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
zlib.deflate(message, (err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
decompressMessage(message: Buffer) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
zlib.inflate(message, (err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class GzipHandler extends CompressionHandler {
|
||||
compressMessage(message: Buffer) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
zlib.gzip(message, (err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
decompressMessage(message: Buffer) {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
zlib.unzip(message, (err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class UnknownHandler extends CompressionHandler {
|
||||
constructor(private readonly compressionName: string) {
|
||||
super();
|
||||
}
|
||||
compressMessage(message: Buffer): Promise<Buffer> {
|
||||
return Promise.reject<Buffer>(
|
||||
new Error(
|
||||
`Received message compressed with unsupported compression method ${this.compressionName}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
decompressMessage(message: Buffer): Promise<Buffer> {
|
||||
// This should be unreachable
|
||||
return Promise.reject<Buffer>(
|
||||
new Error(`Compression method not supported: ${this.compressionName}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getCompressionHandler(compressionName: string): CompressionHandler {
|
||||
switch (compressionName) {
|
||||
case 'identity':
|
||||
return new IdentityHandler();
|
||||
case 'deflate':
|
||||
return new DeflateHandler();
|
||||
case 'gzip':
|
||||
return new GzipHandler();
|
||||
default:
|
||||
return new UnknownHandler(compressionName);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompressionFilter extends BaseFilter implements Filter {
|
||||
private sendCompression: CompressionHandler = new IdentityHandler();
|
||||
private receiveCompression: CompressionHandler = new IdentityHandler();
|
||||
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
const headers: Metadata = await metadata;
|
||||
headers.set('grpc-encoding', 'identity');
|
||||
headers.set('grpc-accept-encoding', 'identity,deflate,gzip');
|
||||
return headers;
|
||||
}
|
||||
|
||||
async receiveMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
const headers: Metadata = await metadata;
|
||||
const receiveEncoding: MetadataValue[] = headers.get('grpc-encoding');
|
||||
if (receiveEncoding.length > 0) {
|
||||
const encoding: MetadataValue = receiveEncoding[0];
|
||||
if (typeof encoding === 'string') {
|
||||
this.receiveCompression = getCompressionHandler(encoding);
|
||||
}
|
||||
}
|
||||
headers.remove('grpc-encoding');
|
||||
headers.remove('grpc-accept-encoding');
|
||||
return headers;
|
||||
}
|
||||
|
||||
async sendMessage(message: Promise<WriteObject>): Promise<WriteObject> {
|
||||
/* This filter is special. The input message is the bare message bytes,
|
||||
* and the output is a framed and possibly compressed message. For this
|
||||
* reason, this filter should be at the bottom of the filter stack */
|
||||
const resolvedMessage: WriteObject = await message;
|
||||
const compress =
|
||||
resolvedMessage.flags === undefined
|
||||
? false
|
||||
: (resolvedMessage.flags & WriteFlags.NoCompress) === 0;
|
||||
return {
|
||||
message: await this.sendCompression.writeMessage(
|
||||
resolvedMessage.message,
|
||||
compress
|
||||
),
|
||||
flags: resolvedMessage.flags,
|
||||
};
|
||||
}
|
||||
|
||||
async receiveMessage(message: Promise<Buffer>) {
|
||||
/* This filter is also special. The input message is framed and possibly
|
||||
* compressed, and the output message is deframed and uncompressed. So
|
||||
* this is another reason that this filter should be at the bottom of the
|
||||
* filter stack. */
|
||||
return this.receiveCompression.readMessage(await message);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompressionFilterFactory
|
||||
implements FilterFactory<CompressionFilter> {
|
||||
constructor(private readonly channel: Channel) {}
|
||||
createFilter(callStream: Call): CompressionFilter {
|
||||
return new CompressionFilter();
|
||||
}
|
||||
}
|
42
node_modules/@grpc/grpc-js/src/constants.ts
generated
vendored
Normal file
42
node_modules/@grpc/grpc-js/src/constants.ts
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
export enum Status {
|
||||
OK = 0,
|
||||
CANCELLED,
|
||||
UNKNOWN,
|
||||
INVALID_ARGUMENT,
|
||||
DEADLINE_EXCEEDED,
|
||||
NOT_FOUND,
|
||||
ALREADY_EXISTS,
|
||||
PERMISSION_DENIED,
|
||||
RESOURCE_EXHAUSTED,
|
||||
FAILED_PRECONDITION,
|
||||
ABORTED,
|
||||
OUT_OF_RANGE,
|
||||
UNIMPLEMENTED,
|
||||
INTERNAL,
|
||||
UNAVAILABLE,
|
||||
DATA_LOSS,
|
||||
UNAUTHENTICATED,
|
||||
}
|
||||
|
||||
export enum LogVerbosity {
|
||||
DEBUG = 0,
|
||||
INFO,
|
||||
ERROR,
|
||||
}
|
93
node_modules/@grpc/grpc-js/src/deadline-filter.ts
generated
vendored
Normal file
93
node_modules/@grpc/grpc-js/src/deadline-filter.ts
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Call } from './call-stream';
|
||||
import { ConnectivityState, Channel } from './channel';
|
||||
import { Status } from './constants';
|
||||
import { BaseFilter, Filter, FilterFactory } from './filter';
|
||||
import { Metadata } from './metadata';
|
||||
|
||||
const units: Array<[string, number]> = [
|
||||
['m', 1],
|
||||
['S', 1000],
|
||||
['M', 60 * 1000],
|
||||
['H', 60 * 60 * 1000],
|
||||
];
|
||||
|
||||
function getDeadline(deadline: number) {
|
||||
const now = new Date().getTime();
|
||||
const timeoutMs = Math.max(deadline - now, 0);
|
||||
for (const [unit, factor] of units) {
|
||||
const amount = timeoutMs / factor;
|
||||
if (amount < 1e8) {
|
||||
return String(Math.ceil(amount)) + unit;
|
||||
}
|
||||
}
|
||||
throw new Error('Deadline is too far in the future');
|
||||
}
|
||||
|
||||
export class DeadlineFilter extends BaseFilter implements Filter {
|
||||
private timer: NodeJS.Timer | null = null;
|
||||
private deadline: number;
|
||||
constructor(
|
||||
private readonly channel: Channel,
|
||||
private readonly callStream: Call
|
||||
) {
|
||||
super();
|
||||
const callDeadline = callStream.getDeadline();
|
||||
if (callDeadline instanceof Date) {
|
||||
this.deadline = callDeadline.getTime();
|
||||
} else {
|
||||
this.deadline = callDeadline;
|
||||
}
|
||||
const now: number = new Date().getTime();
|
||||
let timeout = this.deadline - now;
|
||||
if (timeout < 0) {
|
||||
timeout = 0;
|
||||
}
|
||||
if (this.deadline !== Infinity) {
|
||||
this.timer = setTimeout(() => {
|
||||
callStream.cancelWithStatus(
|
||||
Status.DEADLINE_EXCEEDED,
|
||||
'Deadline exceeded'
|
||||
);
|
||||
}, timeout);
|
||||
callStream.on('status', () => clearTimeout(this.timer as NodeJS.Timer));
|
||||
}
|
||||
}
|
||||
|
||||
async sendMetadata(metadata: Promise<Metadata>) {
|
||||
if (this.deadline === Infinity) {
|
||||
return metadata;
|
||||
}
|
||||
/* The input metadata promise depends on the original channel.connect()
|
||||
* promise, so when it is complete that implies that the channel is
|
||||
* connected */
|
||||
const finalMetadata = await metadata;
|
||||
const timeoutString = getDeadline(this.deadline);
|
||||
finalMetadata.set('grpc-timeout', timeoutString);
|
||||
return finalMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
export class DeadlineFilterFactory implements FilterFactory<DeadlineFilter> {
|
||||
constructor(private readonly channel: Channel) {}
|
||||
|
||||
createFilter(callStream: Call): DeadlineFilter {
|
||||
return new DeadlineFilter(this.channel, callStream);
|
||||
}
|
||||
}
|
26
node_modules/@grpc/grpc-js/src/events.ts
generated
vendored
Normal file
26
node_modules/@grpc/grpc-js/src/events.ts
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
export interface EmitterAugmentation1<Name extends string | symbol, Arg> {
|
||||
addListener(event: Name, listener: (arg1: Arg) => void): this;
|
||||
emit(event: Name, arg1: Arg): boolean;
|
||||
on(event: Name, listener: (arg1: Arg) => void): this;
|
||||
once(event: Name, listener: (arg1: Arg) => void): this;
|
||||
prependListener(event: Name, listener: (arg1: Arg) => void): this;
|
||||
prependOnceListener(event: Name, listener: (arg1: Arg) => void): this;
|
||||
removeListener(event: Name, listener: (arg1: Arg) => void): this;
|
||||
}
|
84
node_modules/@grpc/grpc-js/src/filter-stack.ts
generated
vendored
Normal file
84
node_modules/@grpc/grpc-js/src/filter-stack.ts
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Call, StatusObject, WriteObject } 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>) {
|
||||
let result: Promise<Metadata> = metadata;
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
result = this.filters[i].sendMetadata(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
receiveMetadata(metadata: Promise<Metadata>) {
|
||||
let result: Promise<Metadata> = metadata;
|
||||
|
||||
for (let i = this.filters.length - 1; i >= 0; i--) {
|
||||
result = this.filters[i].receiveMetadata(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
sendMessage(message: Promise<WriteObject>): Promise<WriteObject> {
|
||||
let result: Promise<WriteObject> = message;
|
||||
|
||||
for (let i = 0; i < this.filters.length; i++) {
|
||||
result = this.filters[i].sendMessage(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
receiveMessage(message: Promise<Buffer>): Promise<Buffer> {
|
||||
let result: Promise<Buffer> = message;
|
||||
|
||||
for (let i = this.filters.length - 1; i >= 0; i--) {
|
||||
result = this.filters[i].receiveMessage(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
|
||||
let result: Promise<StatusObject> = status;
|
||||
|
||||
for (let i = this.filters.length - 1; i >= 0; i--) {
|
||||
result = this.filters[i].receiveTrailers(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class FilterStackFactory implements FilterFactory<FilterStack> {
|
||||
constructor(private readonly factories: Array<FilterFactory<Filter>>) {}
|
||||
|
||||
createFilter(callStream: Call): FilterStack {
|
||||
return new FilterStack(
|
||||
this.factories.map(factory => factory.createFilter(callStream))
|
||||
);
|
||||
}
|
||||
}
|
61
node_modules/@grpc/grpc-js/src/filter.ts
generated
vendored
Normal file
61
node_modules/@grpc/grpc-js/src/filter.ts
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Call, StatusObject, WriteObject } 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
|
||||
*/
|
||||
export interface Filter {
|
||||
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata>;
|
||||
|
||||
receiveMetadata(metadata: Promise<Metadata>): Promise<Metadata>;
|
||||
|
||||
sendMessage(message: Promise<WriteObject>): Promise<WriteObject>;
|
||||
|
||||
receiveMessage(message: Promise<Buffer>): Promise<Buffer>;
|
||||
|
||||
receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject>;
|
||||
}
|
||||
|
||||
export abstract class BaseFilter {
|
||||
async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
async receiveMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
async sendMessage(message: Promise<WriteObject>): Promise<WriteObject> {
|
||||
return message;
|
||||
}
|
||||
|
||||
async receiveMessage(message: Promise<Buffer>): Promise<Buffer> {
|
||||
return message;
|
||||
}
|
||||
|
||||
async receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FilterFactory<T extends Filter> {
|
||||
createFilter(callStream: Call): T;
|
||||
}
|
320
node_modules/@grpc/grpc-js/src/index.ts
generated
vendored
Normal file
320
node_modules/@grpc/grpc-js/src/index.ts
generated
vendored
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as semver from 'semver';
|
||||
|
||||
import {
|
||||
ClientDuplexStream,
|
||||
ClientReadableStream,
|
||||
ClientUnaryCall,
|
||||
ClientWritableStream,
|
||||
ServiceError,
|
||||
} from './call';
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Deadline, StatusObject } from './call-stream';
|
||||
import { Channel, ConnectivityState, ChannelImplementation } from './channel';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { CallOptions, Client } from './client';
|
||||
import { LogVerbosity, Status } from './constants';
|
||||
import * as logging from './logging';
|
||||
import {
|
||||
Deserialize,
|
||||
loadPackageDefinition,
|
||||
makeClientConstructor,
|
||||
Serialize,
|
||||
ServiceDefinition,
|
||||
} from './make-client';
|
||||
import { Metadata } from './metadata';
|
||||
import {
|
||||
Server,
|
||||
UntypedHandleCall,
|
||||
UntypedServiceImplementation,
|
||||
} from './server';
|
||||
import { KeyCertPair, ServerCredentials } from './server-credentials';
|
||||
import { StatusBuilder } from './status-builder';
|
||||
import {
|
||||
handleBidiStreamingCall,
|
||||
handleServerStreamingCall,
|
||||
handleUnaryCall,
|
||||
ServerUnaryCall,
|
||||
ServerReadableStream,
|
||||
ServerWritableStream,
|
||||
ServerDuplexStream,
|
||||
} from './server-call';
|
||||
|
||||
const supportedNodeVersions = require('../../package.json').engines.node;
|
||||
if (!semver.satisfies(process.version, supportedNodeVersions)) {
|
||||
throw new Error(`@grpc/grpc-js only works on Node ${supportedNodeVersions}`);
|
||||
}
|
||||
|
||||
interface IndexedObject {
|
||||
[key: string]: any; // tslint:disable-line no-any
|
||||
[key: number]: any; // tslint:disable-line no-any
|
||||
}
|
||||
|
||||
function mixin(...sources: IndexedObject[]) {
|
||||
const result: { [key: string]: Function } = {};
|
||||
for (const source of sources) {
|
||||
for (const propName of Object.getOwnPropertyNames(source)) {
|
||||
const property: any = source[propName]; // tslint:disable-line no-any
|
||||
if (typeof property === 'function') {
|
||||
result[propName] = property;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface OAuth2Client {
|
||||
getRequestMetadata: (
|
||||
url: string,
|
||||
callback: (
|
||||
err: Error | null,
|
||||
headers?: {
|
||||
[index: string]: string;
|
||||
}
|
||||
) => void
|
||||
) => void;
|
||||
getRequestHeaders: (url?: string) => Promise<{ [index: string]: string }>;
|
||||
}
|
||||
|
||||
/**** Client Credentials ****/
|
||||
|
||||
// Using assign only copies enumerable properties, which is what we want
|
||||
export const credentials = mixin(
|
||||
{
|
||||
/**
|
||||
* Create a gRPC credential from a Google credential object.
|
||||
* @param googleCredentials The authentication client to use.
|
||||
* @return The resulting CallCredentials object.
|
||||
*/
|
||||
createFromGoogleCredential: (
|
||||
googleCredentials: OAuth2Client
|
||||
): CallCredentials => {
|
||||
return CallCredentials.createFromMetadataGenerator(
|
||||
(options, callback) => {
|
||||
// google-auth-library pre-v2.0.0 does not have getRequestHeaders
|
||||
// but has getRequestMetadata, which is deprecated in v2.0.0
|
||||
let getHeaders: Promise<{ [index: string]: string }>;
|
||||
if (typeof googleCredentials.getRequestHeaders === 'function') {
|
||||
getHeaders = googleCredentials.getRequestHeaders(
|
||||
options.service_url
|
||||
);
|
||||
} else {
|
||||
getHeaders = new Promise((resolve, reject) => {
|
||||
googleCredentials.getRequestMetadata(
|
||||
options.service_url,
|
||||
(err, headers) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(headers);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
getHeaders.then(
|
||||
headers => {
|
||||
const metadata = new Metadata();
|
||||
for (const key of Object.keys(headers)) {
|
||||
metadata.add(key, headers[key]);
|
||||
}
|
||||
callback(null, metadata);
|
||||
},
|
||||
err => {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine a ChannelCredentials with any number of CallCredentials into a
|
||||
* single ChannelCredentials object.
|
||||
* @param channelCredentials The ChannelCredentials object.
|
||||
* @param callCredentials Any number of CallCredentials objects.
|
||||
* @return The resulting ChannelCredentials object.
|
||||
*/
|
||||
combineChannelCredentials: (
|
||||
channelCredentials: ChannelCredentials,
|
||||
...callCredentials: CallCredentials[]
|
||||
): ChannelCredentials => {
|
||||
return callCredentials.reduce(
|
||||
(acc, other) => acc.compose(other),
|
||||
channelCredentials
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Combine any number of CallCredentials into a single CallCredentials
|
||||
* object.
|
||||
* @param first The first CallCredentials object.
|
||||
* @param additional Any number of additional CallCredentials objects.
|
||||
* @return The resulting CallCredentials object.
|
||||
*/
|
||||
combineCallCredentials: (
|
||||
first: CallCredentials,
|
||||
...additional: CallCredentials[]
|
||||
): CallCredentials => {
|
||||
return additional.reduce((acc, other) => acc.compose(other), first);
|
||||
},
|
||||
},
|
||||
ChannelCredentials,
|
||||
CallCredentials
|
||||
);
|
||||
|
||||
/**** Metadata ****/
|
||||
|
||||
export { Metadata };
|
||||
|
||||
/**** Constants ****/
|
||||
|
||||
export {
|
||||
LogVerbosity as logVerbosity,
|
||||
Status as status,
|
||||
ConnectivityState as connectivityState,
|
||||
// TODO: Other constants as well
|
||||
};
|
||||
|
||||
/**** Client ****/
|
||||
|
||||
export {
|
||||
Client,
|
||||
loadPackageDefinition,
|
||||
makeClientConstructor,
|
||||
makeClientConstructor as makeGenericClientConstructor,
|
||||
ChannelImplementation as Channel,
|
||||
};
|
||||
|
||||
/**
|
||||
* Close a Client object.
|
||||
* @param client The client to close.
|
||||
*/
|
||||
export const closeClient = (client: Client) => client.close();
|
||||
|
||||
export const waitForClientReady = (
|
||||
client: Client,
|
||||
deadline: Date | number,
|
||||
callback: (error?: Error) => void
|
||||
) => client.waitForReady(deadline, callback);
|
||||
|
||||
/* Interfaces */
|
||||
|
||||
export {
|
||||
ChannelCredentials,
|
||||
CallCredentials,
|
||||
Deadline,
|
||||
Serialize as serialize,
|
||||
Deserialize as deserialize,
|
||||
ClientUnaryCall,
|
||||
ClientReadableStream,
|
||||
ClientWritableStream,
|
||||
ClientDuplexStream,
|
||||
CallOptions,
|
||||
StatusObject,
|
||||
ServiceError,
|
||||
ServerUnaryCall,
|
||||
ServerReadableStream,
|
||||
ServerWritableStream,
|
||||
ServerDuplexStream,
|
||||
ServiceDefinition,
|
||||
UntypedHandleCall,
|
||||
UntypedServiceImplementation,
|
||||
};
|
||||
|
||||
/**** Server ****/
|
||||
|
||||
export { handleBidiStreamingCall, handleServerStreamingCall, handleUnaryCall };
|
||||
|
||||
/* tslint:disable:no-any */
|
||||
export type Call =
|
||||
| ClientUnaryCall
|
||||
| ClientReadableStream<any>
|
||||
| ClientWritableStream<any>
|
||||
| ClientDuplexStream<any, any>;
|
||||
/* tslint:enable:no-any */
|
||||
|
||||
export type MetadataListener = (metadata: Metadata, next: Function) => void;
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
export type MessageListener = (message: any, next: Function) => void;
|
||||
|
||||
export type StatusListener = (status: StatusObject, next: Function) => void;
|
||||
|
||||
export interface Listener {
|
||||
onReceiveMetadata?: MetadataListener;
|
||||
onReceiveMessage?: MessageListener;
|
||||
onReceiveStatus?: StatusListener;
|
||||
}
|
||||
|
||||
/**** Unimplemented function stubs ****/
|
||||
|
||||
/* tslint:disable:no-any variable-name */
|
||||
|
||||
export const loadObject = (value: any, options: any) => {
|
||||
throw new Error(
|
||||
'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead'
|
||||
);
|
||||
};
|
||||
|
||||
export const load = (filename: any, format: any, options: any) => {
|
||||
throw new Error(
|
||||
'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead'
|
||||
);
|
||||
};
|
||||
|
||||
export const setLogger = (logger: Partial<Console>): void => {
|
||||
logging.setLogger(logger);
|
||||
};
|
||||
|
||||
export const setLogVerbosity = (verbosity: LogVerbosity): void => {
|
||||
logging.setLoggerVerbosity(verbosity);
|
||||
};
|
||||
|
||||
export { Server };
|
||||
export { ServerCredentials };
|
||||
export { KeyCertPair };
|
||||
|
||||
export const getClientChannel = (client: Client) => {
|
||||
return Client.prototype.getChannel.call(client);
|
||||
};
|
||||
|
||||
export { StatusBuilder };
|
||||
|
||||
export const ListenerBuilder = () => {
|
||||
throw new Error('Not yet implemented');
|
||||
};
|
||||
|
||||
export const InterceptorBuilder = () => {
|
||||
throw new Error('Not yet implemented');
|
||||
};
|
||||
|
||||
export const InterceptingCall = () => {
|
||||
throw new Error('Not yet implemented');
|
||||
};
|
||||
|
||||
export { GrpcObject } from './make-client';
|
||||
|
||||
import * as resolver from './resolver';
|
||||
import * as load_balancer from './load-balancer';
|
||||
|
||||
(() => {
|
||||
resolver.registerAll();
|
||||
load_balancer.registerAll();
|
||||
})();
|
433
node_modules/@grpc/grpc-js/src/load-balancer-pick-first.ts
generated
vendored
Normal file
433
node_modules/@grpc/grpc-js/src/load-balancer-pick-first.ts
generated
vendored
Normal file
@ -0,0 +1,433 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
LoadBalancer,
|
||||
ChannelControlHelper,
|
||||
registerLoadBalancerType,
|
||||
} from './load-balancer';
|
||||
import { ConnectivityState } from './channel';
|
||||
import {
|
||||
QueuePicker,
|
||||
Picker,
|
||||
PickArgs,
|
||||
CompletePickResult,
|
||||
PickResultType,
|
||||
UnavailablePicker,
|
||||
} from './picker';
|
||||
import { LoadBalancingConfig } from './load-balancing-config';
|
||||
import { Subchannel, ConnectivityStateListener } from './subchannel';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
const TRACER_NAME = 'pick_first';
|
||||
|
||||
function trace(text: string): void {
|
||||
logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
||||
}
|
||||
|
||||
const TYPE_NAME = 'pick_first';
|
||||
|
||||
/**
|
||||
* Delay after starting a connection on a subchannel before starting a
|
||||
* connection on the next subchannel in the list, for Happy Eyeballs algorithm.
|
||||
*/
|
||||
const CONNECTION_DELAY_INTERVAL_MS = 250;
|
||||
|
||||
/**
|
||||
* Picker for a `PickFirstLoadBalancer` in the READY state. Always returns the
|
||||
* picked subchannel.
|
||||
*/
|
||||
class PickFirstPicker implements Picker {
|
||||
constructor(private subchannel: Subchannel) {}
|
||||
|
||||
pick(pickArgs: PickArgs): CompletePickResult {
|
||||
return {
|
||||
pickResultType: PickResultType.COMPLETE,
|
||||
subchannel: this.subchannel,
|
||||
status: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectivityStateCounts {
|
||||
[ConnectivityState.CONNECTING]: number;
|
||||
[ConnectivityState.IDLE]: number;
|
||||
[ConnectivityState.READY]: number;
|
||||
[ConnectivityState.SHUTDOWN]: number;
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: number;
|
||||
}
|
||||
|
||||
export class PickFirstLoadBalancer implements LoadBalancer {
|
||||
/**
|
||||
* The list of backend addresses most recently passed to `updateAddressList`.
|
||||
*/
|
||||
private latestAddressList: string[] = [];
|
||||
/**
|
||||
* The list of subchannels this load balancer is currently attempting to
|
||||
* connect to.
|
||||
*/
|
||||
private subchannels: Subchannel[] = [];
|
||||
/**
|
||||
* The current connectivity state of the load balancer.
|
||||
*/
|
||||
private currentState: ConnectivityState = ConnectivityState.IDLE;
|
||||
/**
|
||||
* The index within the `subchannels` array of the subchannel with the most
|
||||
* recently started connection attempt.
|
||||
*/
|
||||
private currentSubchannelIndex = 0;
|
||||
|
||||
private subchannelStateCounts: ConnectivityStateCounts;
|
||||
/**
|
||||
* The currently picked subchannel used for making calls. Populated if
|
||||
* and only if the load balancer's current state is READY. In that case,
|
||||
* the subchannel's current state is also READY.
|
||||
*/
|
||||
private currentPick: Subchannel | null = null;
|
||||
/**
|
||||
* Listener callback attached to each subchannel in the `subchannels` list
|
||||
* while establishing a connection.
|
||||
*/
|
||||
private subchannelStateListener: ConnectivityStateListener;
|
||||
/**
|
||||
* Listener callback attached to the current picked subchannel.
|
||||
*/
|
||||
private pickedSubchannelStateListener: ConnectivityStateListener;
|
||||
/**
|
||||
* Timer reference for the timer tracking when to start
|
||||
*/
|
||||
private connectionDelayTimeout: NodeJS.Timeout;
|
||||
|
||||
private triedAllSubchannels = false;
|
||||
|
||||
/**
|
||||
* Load balancer that attempts to connect to each backend in the address list
|
||||
* in order, and picks the first one that connects, using it for every
|
||||
* request.
|
||||
* @param channelControlHelper `ChannelControlHelper` instance provided by
|
||||
* this load balancer's owner.
|
||||
*/
|
||||
constructor(private channelControlHelper: ChannelControlHelper) {
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
this.subchannelStateCounts = {
|
||||
[ConnectivityState.CONNECTING]: 0,
|
||||
[ConnectivityState.IDLE]: 0,
|
||||
[ConnectivityState.READY]: 0,
|
||||
[ConnectivityState.SHUTDOWN]: 0,
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: 0,
|
||||
};
|
||||
this.subchannelStateListener = (
|
||||
subchannel: Subchannel,
|
||||
previousState: ConnectivityState,
|
||||
newState: ConnectivityState
|
||||
) => {
|
||||
this.subchannelStateCounts[previousState] -= 1;
|
||||
this.subchannelStateCounts[newState] += 1;
|
||||
/* If the subchannel we most recently attempted to start connecting
|
||||
* to goes into TRANSIENT_FAILURE, immediately try to start
|
||||
* connecting to the next one instead of waiting for the connection
|
||||
* delay timer. */
|
||||
if (
|
||||
subchannel === this.subchannels[this.currentSubchannelIndex] &&
|
||||
newState === ConnectivityState.TRANSIENT_FAILURE
|
||||
) {
|
||||
this.startNextSubchannelConnecting();
|
||||
}
|
||||
if (newState === ConnectivityState.READY) {
|
||||
this.pickSubchannel(subchannel);
|
||||
return;
|
||||
} else {
|
||||
if (
|
||||
this.triedAllSubchannels &&
|
||||
this.subchannelStateCounts[ConnectivityState.IDLE] ===
|
||||
this.subchannels.length
|
||||
) {
|
||||
/* If all of the subchannels are IDLE we should go back to a
|
||||
* basic IDLE state where there is no subchannel list to avoid
|
||||
* holding unused resources */
|
||||
this.resetSubchannelList();
|
||||
}
|
||||
if (this.currentPick === null) {
|
||||
if (this.triedAllSubchannels) {
|
||||
let newLBState: ConnectivityState;
|
||||
if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
|
||||
newLBState = ConnectivityState.CONNECTING;
|
||||
} else if (
|
||||
this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] >
|
||||
0
|
||||
) {
|
||||
newLBState = ConnectivityState.TRANSIENT_FAILURE;
|
||||
} else {
|
||||
newLBState = ConnectivityState.IDLE;
|
||||
}
|
||||
if (newLBState !== this.currentState) {
|
||||
if (newLBState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||
this.updateState(newLBState, new UnavailablePicker());
|
||||
} else {
|
||||
this.updateState(newLBState, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.updateState(
|
||||
ConnectivityState.CONNECTING,
|
||||
new QueuePicker(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.pickedSubchannelStateListener = (
|
||||
subchannel: Subchannel,
|
||||
previousState: ConnectivityState,
|
||||
newState: ConnectivityState
|
||||
) => {
|
||||
if (newState !== ConnectivityState.READY) {
|
||||
this.currentPick = null;
|
||||
subchannel.unref();
|
||||
subchannel.removeConnectivityStateListener(
|
||||
this.pickedSubchannelStateListener
|
||||
);
|
||||
if (this.subchannels.length > 0) {
|
||||
if (this.triedAllSubchannels) {
|
||||
let newLBState: ConnectivityState;
|
||||
if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
|
||||
newLBState = ConnectivityState.CONNECTING;
|
||||
} else if (
|
||||
this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] >
|
||||
0
|
||||
) {
|
||||
newLBState = ConnectivityState.TRANSIENT_FAILURE;
|
||||
} else {
|
||||
newLBState = ConnectivityState.IDLE;
|
||||
}
|
||||
if (newLBState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||
this.updateState(newLBState, new UnavailablePicker());
|
||||
} else {
|
||||
this.updateState(newLBState, new QueuePicker(this));
|
||||
}
|
||||
} else {
|
||||
this.updateState(
|
||||
ConnectivityState.CONNECTING,
|
||||
new QueuePicker(this)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
/* We don't need to backoff here because this only happens if a
|
||||
* subchannel successfully connects then disconnects, so it will not
|
||||
* create a loop of attempting to connect to an unreachable backend
|
||||
*/
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
this.connectionDelayTimeout = setTimeout(() => {}, 0);
|
||||
clearTimeout(this.connectionDelayTimeout);
|
||||
}
|
||||
|
||||
private startNextSubchannelConnecting() {
|
||||
if (this.triedAllSubchannels) {
|
||||
return;
|
||||
}
|
||||
for (const [index, subchannel] of this.subchannels.entries()) {
|
||||
if (index > this.currentSubchannelIndex) {
|
||||
const subchannelState = subchannel.getConnectivityState();
|
||||
if (
|
||||
subchannelState === ConnectivityState.IDLE ||
|
||||
subchannelState === ConnectivityState.CONNECTING
|
||||
) {
|
||||
this.startConnecting(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.triedAllSubchannels = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have a single subchannel in the `subchannels` list start connecting.
|
||||
* @param subchannelIndex The index into the `subchannels` list.
|
||||
*/
|
||||
private startConnecting(subchannelIndex: number) {
|
||||
clearTimeout(this.connectionDelayTimeout);
|
||||
this.currentSubchannelIndex = subchannelIndex;
|
||||
if (
|
||||
this.subchannels[subchannelIndex].getConnectivityState() ===
|
||||
ConnectivityState.IDLE
|
||||
) {
|
||||
trace(
|
||||
'Start connecting to subchannel with address ' +
|
||||
this.subchannels[subchannelIndex].getAddress()
|
||||
);
|
||||
process.nextTick(() => {
|
||||
this.subchannels[subchannelIndex].startConnecting();
|
||||
});
|
||||
}
|
||||
this.connectionDelayTimeout = setTimeout(() => {
|
||||
this.startNextSubchannelConnecting();
|
||||
}, CONNECTION_DELAY_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private pickSubchannel(subchannel: Subchannel) {
|
||||
trace('Pick subchannel with address ' + subchannel.getAddress());
|
||||
if (this.currentPick !== null) {
|
||||
this.currentPick.unref();
|
||||
this.currentPick.removeConnectivityStateListener(
|
||||
this.pickedSubchannelStateListener
|
||||
);
|
||||
}
|
||||
this.currentPick = subchannel;
|
||||
this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel));
|
||||
subchannel.addConnectivityStateListener(this.pickedSubchannelStateListener);
|
||||
subchannel.ref();
|
||||
this.resetSubchannelList();
|
||||
clearTimeout(this.connectionDelayTimeout);
|
||||
}
|
||||
|
||||
private updateState(newState: ConnectivityState, picker: Picker) {
|
||||
trace(
|
||||
ConnectivityState[this.currentState] +
|
||||
' -> ' +
|
||||
ConnectivityState[newState]
|
||||
);
|
||||
this.currentState = newState;
|
||||
this.channelControlHelper.updateState(newState, picker);
|
||||
}
|
||||
|
||||
private resetSubchannelList() {
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.removeConnectivityStateListener(this.subchannelStateListener);
|
||||
subchannel.unref();
|
||||
}
|
||||
this.currentSubchannelIndex = 0;
|
||||
this.subchannelStateCounts = {
|
||||
[ConnectivityState.CONNECTING]: 0,
|
||||
[ConnectivityState.IDLE]: 0,
|
||||
[ConnectivityState.READY]: 0,
|
||||
[ConnectivityState.SHUTDOWN]: 0,
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: 0,
|
||||
};
|
||||
this.subchannels = [];
|
||||
this.triedAllSubchannels = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start connecting to the address list most recently passed to
|
||||
* `updateAddressList`.
|
||||
*/
|
||||
private connectToAddressList(): void {
|
||||
this.resetSubchannelList();
|
||||
trace('Connect to address list ' + this.latestAddressList);
|
||||
this.subchannels = this.latestAddressList.map(address =>
|
||||
this.channelControlHelper.createSubchannel(address, {})
|
||||
);
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.ref();
|
||||
}
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.addConnectivityStateListener(this.subchannelStateListener);
|
||||
if (subchannel.getConnectivityState() === ConnectivityState.READY) {
|
||||
this.pickSubchannel(subchannel);
|
||||
this.resetSubchannelList();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const [index, subchannel] of this.subchannels.entries()) {
|
||||
const subchannelState = subchannel.getConnectivityState();
|
||||
if (
|
||||
subchannelState === ConnectivityState.IDLE ||
|
||||
subchannelState === ConnectivityState.CONNECTING
|
||||
) {
|
||||
this.startConnecting(index);
|
||||
if (this.currentPick === null) {
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If the code reaches this point, every subchannel must be in TRANSIENT_FAILURE
|
||||
if (this.currentPick === null) {
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressList(
|
||||
addressList: string[],
|
||||
lbConfig: LoadBalancingConfig | null
|
||||
): void {
|
||||
// lbConfig has no useful information for pick first load balancing
|
||||
/* To avoid unnecessary churn, we only do something with this address list
|
||||
* if we're not currently trying to establish a connection, or if the new
|
||||
* address list is different from the existing one */
|
||||
if (
|
||||
this.subchannels.length === 0 ||
|
||||
!this.latestAddressList.every(
|
||||
(value, index) => addressList[index] === value
|
||||
)
|
||||
) {
|
||||
this.latestAddressList = addressList;
|
||||
this.connectToAddressList();
|
||||
}
|
||||
}
|
||||
|
||||
exitIdle() {
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.startConnecting();
|
||||
}
|
||||
if (this.currentState === ConnectivityState.IDLE) {
|
||||
if (this.latestAddressList.length > 0) {
|
||||
this.connectToAddressList();
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.currentState === ConnectivityState.IDLE ||
|
||||
this.triedAllSubchannels
|
||||
) {
|
||||
this.channelControlHelper.requestReresolution();
|
||||
}
|
||||
}
|
||||
|
||||
resetBackoff() {
|
||||
/* The pick first load balancer does not have a connection backoff, so this
|
||||
* does nothing */
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.resetSubchannelList();
|
||||
if (this.currentPick !== null) {
|
||||
this.currentPick.unref();
|
||||
this.currentPick.removeConnectivityStateListener(
|
||||
this.pickedSubchannelStateListener
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTypeName(): string {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper) {
|
||||
this.channelControlHelper = channelControlHelper;
|
||||
}
|
||||
}
|
||||
|
||||
export function setup(): void {
|
||||
registerLoadBalancerType(TYPE_NAME, PickFirstLoadBalancer);
|
||||
}
|
215
node_modules/@grpc/grpc-js/src/load-balancer-round-robin.ts
generated
vendored
Normal file
215
node_modules/@grpc/grpc-js/src/load-balancer-round-robin.ts
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
LoadBalancer,
|
||||
ChannelControlHelper,
|
||||
registerLoadBalancerType,
|
||||
} from './load-balancer';
|
||||
import { ConnectivityState } from './channel';
|
||||
import {
|
||||
QueuePicker,
|
||||
Picker,
|
||||
PickArgs,
|
||||
CompletePickResult,
|
||||
PickResultType,
|
||||
UnavailablePicker,
|
||||
} from './picker';
|
||||
import { LoadBalancingConfig } from './load-balancing-config';
|
||||
import { Subchannel, ConnectivityStateListener } from './subchannel';
|
||||
|
||||
const TYPE_NAME = 'round_robin';
|
||||
|
||||
class RoundRobinPicker implements Picker {
|
||||
constructor(
|
||||
private readonly subchannelList: Subchannel[],
|
||||
private nextIndex = 0
|
||||
) {}
|
||||
|
||||
pick(pickArgs: PickArgs): CompletePickResult {
|
||||
const pickedSubchannel = this.subchannelList[this.nextIndex];
|
||||
this.nextIndex = (this.nextIndex + 1) % this.subchannelList.length;
|
||||
return {
|
||||
pickResultType: PickResultType.COMPLETE,
|
||||
subchannel: pickedSubchannel,
|
||||
status: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check what the next subchannel returned would be. Used by the load
|
||||
* balancer implementation to preserve this part of the picker state if
|
||||
* possible when a subchannel connects or disconnects.
|
||||
*/
|
||||
peekNextSubchannel(): Subchannel {
|
||||
return this.subchannelList[this.nextIndex];
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectivityStateCounts {
|
||||
[ConnectivityState.CONNECTING]: number;
|
||||
[ConnectivityState.IDLE]: number;
|
||||
[ConnectivityState.READY]: number;
|
||||
[ConnectivityState.SHUTDOWN]: number;
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: number;
|
||||
}
|
||||
|
||||
export class RoundRobinLoadBalancer implements LoadBalancer {
|
||||
private subchannels: Subchannel[] = [];
|
||||
|
||||
private currentState: ConnectivityState = ConnectivityState.IDLE;
|
||||
|
||||
private subchannelStateListener: ConnectivityStateListener;
|
||||
|
||||
private subchannelStateCounts: ConnectivityStateCounts;
|
||||
|
||||
private currentReadyPicker: RoundRobinPicker | null = null;
|
||||
|
||||
constructor(private channelControlHelper: ChannelControlHelper) {
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
this.subchannelStateCounts = {
|
||||
[ConnectivityState.CONNECTING]: 0,
|
||||
[ConnectivityState.IDLE]: 0,
|
||||
[ConnectivityState.READY]: 0,
|
||||
[ConnectivityState.SHUTDOWN]: 0,
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: 0,
|
||||
};
|
||||
this.subchannelStateListener = (
|
||||
subchannel: Subchannel,
|
||||
previousState: ConnectivityState,
|
||||
newState: ConnectivityState
|
||||
) => {
|
||||
this.subchannelStateCounts[previousState] -= 1;
|
||||
this.subchannelStateCounts[newState] += 1;
|
||||
this.calculateAndUpdateState();
|
||||
|
||||
if (newState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||
this.channelControlHelper.requestReresolution();
|
||||
}
|
||||
if (
|
||||
newState === ConnectivityState.TRANSIENT_FAILURE ||
|
||||
newState === ConnectivityState.IDLE
|
||||
) {
|
||||
subchannel.startConnecting();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private calculateAndUpdateState() {
|
||||
if (this.subchannelStateCounts[ConnectivityState.READY] > 0) {
|
||||
const readySubchannels = this.subchannels.filter(
|
||||
subchannel =>
|
||||
subchannel.getConnectivityState() === ConnectivityState.READY
|
||||
);
|
||||
let index = 0;
|
||||
if (this.currentReadyPicker !== null) {
|
||||
index = readySubchannels.indexOf(
|
||||
this.currentReadyPicker.peekNextSubchannel()
|
||||
);
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
this.updateState(
|
||||
ConnectivityState.READY,
|
||||
new RoundRobinPicker(readySubchannels, index)
|
||||
);
|
||||
} else if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
} else if (
|
||||
this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0
|
||||
) {
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker()
|
||||
);
|
||||
} else {
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
|
||||
private updateState(newState: ConnectivityState, picker: Picker) {
|
||||
if (newState === ConnectivityState.READY) {
|
||||
this.currentReadyPicker = picker as RoundRobinPicker;
|
||||
} else {
|
||||
this.currentReadyPicker = null;
|
||||
}
|
||||
this.currentState = newState;
|
||||
this.channelControlHelper.updateState(newState, picker);
|
||||
}
|
||||
|
||||
private resetSubchannelList() {
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.removeConnectivityStateListener(this.subchannelStateListener);
|
||||
subchannel.unref();
|
||||
}
|
||||
this.subchannelStateCounts = {
|
||||
[ConnectivityState.CONNECTING]: 0,
|
||||
[ConnectivityState.IDLE]: 0,
|
||||
[ConnectivityState.READY]: 0,
|
||||
[ConnectivityState.SHUTDOWN]: 0,
|
||||
[ConnectivityState.TRANSIENT_FAILURE]: 0,
|
||||
};
|
||||
this.subchannels = [];
|
||||
}
|
||||
|
||||
updateAddressList(
|
||||
addressList: string[],
|
||||
lbConfig: LoadBalancingConfig | null
|
||||
): void {
|
||||
this.resetSubchannelList();
|
||||
this.subchannels = addressList.map(address =>
|
||||
this.channelControlHelper.createSubchannel(address, {})
|
||||
);
|
||||
for (const subchannel of this.subchannels) {
|
||||
const subchannelState = subchannel.getConnectivityState();
|
||||
this.subchannelStateCounts[subchannelState] += 1;
|
||||
if (
|
||||
subchannelState === ConnectivityState.IDLE ||
|
||||
subchannelState === ConnectivityState.TRANSIENT_FAILURE
|
||||
) {
|
||||
subchannel.startConnecting();
|
||||
}
|
||||
}
|
||||
this.calculateAndUpdateState();
|
||||
}
|
||||
|
||||
exitIdle(): void {
|
||||
for (const subchannel of this.subchannels) {
|
||||
subchannel.startConnecting();
|
||||
}
|
||||
}
|
||||
resetBackoff(): void {
|
||||
/* The pick first load balancer does not have a connection backoff, so this
|
||||
* does nothing */
|
||||
}
|
||||
destroy(): void {
|
||||
this.resetSubchannelList();
|
||||
}
|
||||
getTypeName(): string {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
replaceChannelControlHelper(
|
||||
channelControlHelper: ChannelControlHelper
|
||||
): void {
|
||||
this.channelControlHelper = channelControlHelper;
|
||||
}
|
||||
}
|
||||
|
||||
export function setup() {
|
||||
registerLoadBalancerType(TYPE_NAME, RoundRobinLoadBalancer);
|
||||
}
|
133
node_modules/@grpc/grpc-js/src/load-balancer.ts
generated
vendored
Normal file
133
node_modules/@grpc/grpc-js/src/load-balancer.ts
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { Subchannel } from './subchannel';
|
||||
import { ConnectivityState } from './channel';
|
||||
import { Picker } from './picker';
|
||||
import { LoadBalancingConfig } from './load-balancing-config';
|
||||
import * as load_balancer_pick_first from './load-balancer-pick-first';
|
||||
import * as load_balancer_round_robin from './load-balancer-round-robin';
|
||||
|
||||
/**
|
||||
* A collection of functions associated with a channel that a load balancer
|
||||
* can call as necessary.
|
||||
*/
|
||||
export interface ChannelControlHelper {
|
||||
/**
|
||||
* Returns a subchannel connected to the specified address.
|
||||
* @param subchannelAddress The address to connect to
|
||||
* @param subchannelArgs Extra channel arguments specified by the load balancer
|
||||
*/
|
||||
createSubchannel(
|
||||
subchannelAddress: string,
|
||||
subchannelArgs: ChannelOptions
|
||||
): Subchannel;
|
||||
/**
|
||||
* Passes a new subchannel picker up to the channel. This is called if either
|
||||
* the connectivity state changes or if a different picker is needed for any
|
||||
* other reason.
|
||||
* @param connectivityState New connectivity state
|
||||
* @param picker New picker
|
||||
*/
|
||||
updateState(connectivityState: ConnectivityState, picker: Picker): void;
|
||||
/**
|
||||
* Request new data from the resolver.
|
||||
*/
|
||||
requestReresolution(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks one or more connected subchannels and determines which subchannel
|
||||
* each request should use.
|
||||
*/
|
||||
export interface LoadBalancer {
|
||||
/**
|
||||
* Gives the load balancer a new list of addresses to start connecting to.
|
||||
* The load balancer will start establishing connections with the new list,
|
||||
* but will continue using any existing connections until the new connections
|
||||
* are established
|
||||
* @param addressList The new list of addresses to connect to
|
||||
* @param lbConfig The load balancing config object from the service config,
|
||||
* if one was provided
|
||||
*/
|
||||
updateAddressList(
|
||||
addressList: string[],
|
||||
lbConfig: LoadBalancingConfig | null
|
||||
): void;
|
||||
/**
|
||||
* If the load balancer is currently in the IDLE state, start connecting.
|
||||
*/
|
||||
exitIdle(): void;
|
||||
/**
|
||||
* If the load balancer is currently in the CONNECTING or TRANSIENT_FAILURE
|
||||
* state, reset the current connection backoff timeout to its base value and
|
||||
* transition to CONNECTING if in TRANSIENT_FAILURE.
|
||||
*/
|
||||
resetBackoff(): void;
|
||||
/**
|
||||
* The load balancer unrefs all of its subchannels and stops calling methods
|
||||
* of its channel control helper.
|
||||
*/
|
||||
destroy(): void;
|
||||
/**
|
||||
* Get the type name for this load balancer type. Must be constant across an
|
||||
* entire load balancer implementation class and must match the name that the
|
||||
* balancer implementation class was registered with.
|
||||
*/
|
||||
getTypeName(): string;
|
||||
/**
|
||||
* Replace the existing ChannelControlHelper with a new one
|
||||
* @param channelControlHelper The new ChannelControlHelper to use from now on
|
||||
*/
|
||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper): void;
|
||||
}
|
||||
|
||||
export interface LoadBalancerConstructor {
|
||||
new (channelControlHelper: ChannelControlHelper): LoadBalancer;
|
||||
}
|
||||
|
||||
const registeredLoadBalancerTypes: {
|
||||
[name: string]: LoadBalancerConstructor;
|
||||
} = {};
|
||||
|
||||
export function registerLoadBalancerType(
|
||||
typeName: string,
|
||||
loadBalancerType: LoadBalancerConstructor
|
||||
) {
|
||||
registeredLoadBalancerTypes[typeName] = loadBalancerType;
|
||||
}
|
||||
|
||||
export function createLoadBalancer(
|
||||
typeName: string,
|
||||
channelControlHelper: ChannelControlHelper
|
||||
): LoadBalancer | null {
|
||||
if (typeName in registeredLoadBalancerTypes) {
|
||||
return new registeredLoadBalancerTypes[typeName](channelControlHelper);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function isLoadBalancerNameRegistered(typeName: string): boolean {
|
||||
return typeName in registeredLoadBalancerTypes;
|
||||
}
|
||||
|
||||
export function registerAll() {
|
||||
load_balancer_pick_first.setup();
|
||||
load_balancer_round_robin.setup();
|
||||
}
|
113
node_modules/@grpc/grpc-js/src/load-balancing-config.ts
generated
vendored
Normal file
113
node_modules/@grpc/grpc-js/src/load-balancing-config.ts
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/* This file is an implementation of gRFC A24:
|
||||
* https://github.com/grpc/proposal/blob/master/A24-lb-policy-config.md. Each
|
||||
* function here takes an object with unknown structure and returns its
|
||||
* specific object type if the input has the right structure, and throws an
|
||||
* error otherwise. */
|
||||
|
||||
/* The any type is purposely used here. All functions validate their input at
|
||||
* runtime */
|
||||
/* tslint:disable:no-any */
|
||||
|
||||
export interface RoundRobinConfig {}
|
||||
|
||||
export interface XdsConfig {
|
||||
balancerName: string;
|
||||
childPolicy: LoadBalancingConfig[];
|
||||
fallbackPolicy: LoadBalancingConfig[];
|
||||
}
|
||||
|
||||
export interface GrpcLbConfig {
|
||||
childPolicy: LoadBalancingConfig[];
|
||||
}
|
||||
|
||||
export interface LoadBalancingConfig {
|
||||
/* Exactly one of these must be set for a config to be valid */
|
||||
round_robin?: RoundRobinConfig;
|
||||
xds?: XdsConfig;
|
||||
grpclb?: GrpcLbConfig;
|
||||
}
|
||||
|
||||
/* In these functions we assume the input came from a JSON object. Therefore we
|
||||
* expect that the prototype is uninteresting and that `in` can be used
|
||||
* effectively */
|
||||
|
||||
function validateXdsConfig(xds: any): XdsConfig {
|
||||
if (!('balancerName' in xds) || typeof xds.balancerName !== 'string') {
|
||||
throw new Error('Invalid xds config: invalid balancerName');
|
||||
}
|
||||
const xdsConfig: XdsConfig = {
|
||||
balancerName: xds.balancerName,
|
||||
childPolicy: [],
|
||||
fallbackPolicy: [],
|
||||
};
|
||||
if ('childPolicy' in xds) {
|
||||
if (!Array.isArray(xds.childPolicy)) {
|
||||
throw new Error('Invalid xds config: invalid childPolicy');
|
||||
}
|
||||
for (const policy of xds.childPolicy) {
|
||||
xdsConfig.childPolicy.push(validateConfig(policy));
|
||||
}
|
||||
}
|
||||
if ('fallbackPolicy' in xds) {
|
||||
if (!Array.isArray(xds.fallbackPolicy)) {
|
||||
throw new Error('Invalid xds config: invalid fallbackPolicy');
|
||||
}
|
||||
for (const policy of xds.fallbackPolicy) {
|
||||
xdsConfig.fallbackPolicy.push(validateConfig(policy));
|
||||
}
|
||||
}
|
||||
return xdsConfig;
|
||||
}
|
||||
|
||||
function validateGrpcLbConfig(grpclb: any): GrpcLbConfig {
|
||||
const grpcLbConfig: GrpcLbConfig = {
|
||||
childPolicy: [],
|
||||
};
|
||||
if ('childPolicy' in grpclb) {
|
||||
if (!Array.isArray(grpclb.childPolicy)) {
|
||||
throw new Error('Invalid xds config: invalid childPolicy');
|
||||
}
|
||||
for (const policy of grpclb.childPolicy) {
|
||||
grpcLbConfig.childPolicy.push(validateConfig(policy));
|
||||
}
|
||||
}
|
||||
return grpcLbConfig;
|
||||
}
|
||||
|
||||
export function validateConfig(obj: any): LoadBalancingConfig {
|
||||
if ('round_robin' in obj) {
|
||||
if ('xds' in obj || 'grpclb' in obj) {
|
||||
throw new Error('Multiple load balancing policies configured');
|
||||
}
|
||||
if (obj['round_robin'] instanceof Object) {
|
||||
return { round_robin: {} };
|
||||
}
|
||||
}
|
||||
if ('xds' in obj) {
|
||||
if ('grpclb' in obj) {
|
||||
throw new Error('Multiple load balancing policies configured');
|
||||
}
|
||||
return { xds: validateXdsConfig(obj.xds) };
|
||||
}
|
||||
if ('grpclb' in obj) {
|
||||
return { grpclb: validateGrpcLbConfig(obj.grpclb) };
|
||||
}
|
||||
throw new Error('No recognized load balancing policy configured');
|
||||
}
|
71
node_modules/@grpc/grpc-js/src/logging.ts
generated
vendored
Normal file
71
node_modules/@grpc/grpc-js/src/logging.ts
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
let _logger: Partial<Console> = console;
|
||||
let _logVerbosity: LogVerbosity = LogVerbosity.ERROR;
|
||||
|
||||
if (process.env.GRPC_VERBOSITY) {
|
||||
switch (process.env.GRPC_VERBOSITY) {
|
||||
case 'DEBUG':
|
||||
_logVerbosity = LogVerbosity.DEBUG;
|
||||
break;
|
||||
case 'INFO':
|
||||
_logVerbosity = LogVerbosity.INFO;
|
||||
break;
|
||||
case 'ERROR':
|
||||
_logVerbosity = LogVerbosity.ERROR;
|
||||
break;
|
||||
default:
|
||||
// Ignore any other values
|
||||
}
|
||||
}
|
||||
|
||||
export const getLogger = (): Partial<Console> => {
|
||||
return _logger;
|
||||
};
|
||||
|
||||
export const setLogger = (logger: Partial<Console>): void => {
|
||||
_logger = logger;
|
||||
};
|
||||
|
||||
export const setLoggerVerbosity = (verbosity: LogVerbosity): void => {
|
||||
_logVerbosity = verbosity;
|
||||
};
|
||||
|
||||
// tslint:disable-next-line no-any
|
||||
export const log = (severity: LogVerbosity, ...args: any[]): void => {
|
||||
if (severity >= _logVerbosity && typeof _logger.error === 'function') {
|
||||
_logger.error(...args);
|
||||
}
|
||||
};
|
||||
|
||||
const enabledTracers = process.env.GRPC_TRACE
|
||||
? process.env.GRPC_TRACE.split(',')
|
||||
: [];
|
||||
const allEnabled = enabledTracers.includes('all');
|
||||
|
||||
export function trace(
|
||||
severity: LogVerbosity,
|
||||
tracer: string,
|
||||
text: string
|
||||
): void {
|
||||
if (allEnabled || enabledTracers.includes(tracer)) {
|
||||
log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text);
|
||||
}
|
||||
}
|
205
node_modules/@grpc/grpc-js/src/make-client.ts
generated
vendored
Normal file
205
node_modules/@grpc/grpc-js/src/make-client.ts
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { Client } from './client';
|
||||
|
||||
export interface Serialize<T> {
|
||||
(value: T): Buffer;
|
||||
}
|
||||
|
||||
export interface Deserialize<T> {
|
||||
(bytes: Buffer): T;
|
||||
}
|
||||
|
||||
export interface MethodDefinition<RequestType, ResponseType> {
|
||||
path: string;
|
||||
requestStream: boolean;
|
||||
responseStream: boolean;
|
||||
requestSerialize: Serialize<RequestType>;
|
||||
responseSerialize: Serialize<ResponseType>;
|
||||
requestDeserialize: Deserialize<RequestType>;
|
||||
responseDeserialize: Deserialize<ResponseType>;
|
||||
originalName?: string;
|
||||
}
|
||||
|
||||
export interface ServiceDefinition {
|
||||
// tslint:disable-next-line no-any
|
||||
[index: string]: MethodDefinition<any, any>;
|
||||
}
|
||||
|
||||
export interface ProtobufTypeDefinition {
|
||||
format: string;
|
||||
type: object;
|
||||
fileDescriptorProtos: Buffer[];
|
||||
}
|
||||
|
||||
export interface PackageDefinition {
|
||||
[index: string]: ServiceDefinition | ProtobufTypeDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map with short names for each of the requester maker functions. Used in
|
||||
* makeClientConstructor
|
||||
* @private
|
||||
*/
|
||||
const requesterFuncs = {
|
||||
unary: Client.prototype.makeUnaryRequest,
|
||||
server_stream: Client.prototype.makeServerStreamRequest,
|
||||
client_stream: Client.prototype.makeClientStreamRequest,
|
||||
bidi: Client.prototype.makeBidiStreamRequest,
|
||||
};
|
||||
|
||||
export interface ServiceClient extends Client {
|
||||
[methodName: string]: Function;
|
||||
}
|
||||
|
||||
export interface ServiceClientConstructor {
|
||||
new (
|
||||
address: string,
|
||||
credentials: ChannelCredentials,
|
||||
options?: Partial<ChannelOptions>
|
||||
): ServiceClient;
|
||||
service: ServiceDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a constructor for a client with the given methods, as specified in
|
||||
* the methods argument. The resulting class will have an instance method for
|
||||
* each method in the service, which is a partial application of one of the
|
||||
* [Client]{@link grpc.Client} request methods, depending on `requestSerialize`
|
||||
* and `responseSerialize`, with the `method`, `serialize`, and `deserialize`
|
||||
* arguments predefined.
|
||||
* @param methods An object mapping method names to
|
||||
* method attributes
|
||||
* @param serviceName The fully qualified name of the service
|
||||
* @param classOptions An options object.
|
||||
* @return New client constructor, which is a subclass of
|
||||
* {@link grpc.Client}, and has the same arguments as that constructor.
|
||||
*/
|
||||
export function makeClientConstructor(
|
||||
methods: ServiceDefinition,
|
||||
serviceName: string,
|
||||
classOptions?: {}
|
||||
): ServiceClientConstructor {
|
||||
if (!classOptions) {
|
||||
classOptions = {};
|
||||
}
|
||||
|
||||
class ServiceClientImpl extends Client implements ServiceClient {
|
||||
static service: ServiceDefinition;
|
||||
[methodName: string]: Function;
|
||||
}
|
||||
|
||||
Object.keys(methods).forEach(name => {
|
||||
const attrs = methods[name];
|
||||
let methodType: keyof typeof requesterFuncs;
|
||||
// TODO(murgatroid99): Verify that we don't need this anymore
|
||||
if (typeof name === 'string' && name.charAt(0) === '$') {
|
||||
throw new Error('Method names cannot start with $');
|
||||
}
|
||||
if (attrs.requestStream) {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'bidi';
|
||||
} else {
|
||||
methodType = 'client_stream';
|
||||
}
|
||||
} else {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'server_stream';
|
||||
} else {
|
||||
methodType = 'unary';
|
||||
}
|
||||
}
|
||||
const serialize = attrs.requestSerialize;
|
||||
const deserialize = attrs.responseDeserialize;
|
||||
const methodFunc = partial(
|
||||
requesterFuncs[methodType],
|
||||
attrs.path,
|
||||
serialize,
|
||||
deserialize
|
||||
);
|
||||
ServiceClientImpl.prototype[name] = methodFunc;
|
||||
// Associate all provided attributes with the method
|
||||
Object.assign(ServiceClientImpl.prototype[name], attrs);
|
||||
if (attrs.originalName) {
|
||||
ServiceClientImpl.prototype[attrs.originalName] =
|
||||
ServiceClientImpl.prototype[name];
|
||||
}
|
||||
});
|
||||
|
||||
ServiceClientImpl.service = methods;
|
||||
|
||||
return ServiceClientImpl;
|
||||
}
|
||||
|
||||
function partial(
|
||||
fn: Function,
|
||||
path: string,
|
||||
serialize: Function,
|
||||
deserialize: Function
|
||||
): Function {
|
||||
// tslint:disable-next-line:no-any
|
||||
return function(this: any, ...args: any[]) {
|
||||
return fn.call(this, path, serialize, deserialize, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
export interface GrpcObject {
|
||||
[index: string]:
|
||||
| GrpcObject
|
||||
| ServiceClientConstructor
|
||||
| ProtobufTypeDefinition;
|
||||
}
|
||||
|
||||
function isProtobufTypeDefinition(
|
||||
obj: ServiceDefinition | ProtobufTypeDefinition
|
||||
): obj is ProtobufTypeDefinition {
|
||||
return 'format' in obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a gRPC package definition as a gRPC object hierarchy.
|
||||
* @param packageDef The package definition object.
|
||||
* @return The resulting gRPC object.
|
||||
*/
|
||||
export function loadPackageDefinition(
|
||||
packageDef: PackageDefinition
|
||||
): GrpcObject {
|
||||
const result: GrpcObject = {};
|
||||
for (const serviceFqn in packageDef) {
|
||||
if (packageDef.hasOwnProperty(serviceFqn)) {
|
||||
const service = packageDef[serviceFqn];
|
||||
const nameComponents = serviceFqn.split('.');
|
||||
const serviceName = nameComponents[nameComponents.length - 1];
|
||||
let current = result;
|
||||
for (const packageName of nameComponents.slice(0, -1)) {
|
||||
if (!current[packageName]) {
|
||||
current[packageName] = {};
|
||||
}
|
||||
current = current[packageName] as GrpcObject;
|
||||
}
|
||||
if (isProtobufTypeDefinition(service)) {
|
||||
current[serviceName] = service;
|
||||
} else {
|
||||
current[serviceName] = makeClientConstructor(service, serviceName, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
54
node_modules/@grpc/grpc-js/src/metadata-status-filter.ts
generated
vendored
Normal file
54
node_modules/@grpc/grpc-js/src/metadata-status-filter.ts
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Call } from './call-stream';
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Channel } from './channel';
|
||||
import { Status } from './constants';
|
||||
import { BaseFilter, Filter, FilterFactory } from './filter';
|
||||
|
||||
export class MetadataStatusFilter extends BaseFilter implements Filter {
|
||||
async receiveTrailers(status: Promise<StatusObject>): Promise<StatusObject> {
|
||||
// tslint:disable-next-line:prefer-const
|
||||
let { code, details, metadata } = await status;
|
||||
if (code !== Status.UNKNOWN) {
|
||||
// we already have a known status, so don't assign a new one.
|
||||
return { code, details, metadata };
|
||||
}
|
||||
const metadataMap = metadata.getMap();
|
||||
if (typeof metadataMap['grpc-status'] === 'string') {
|
||||
const receivedCode = Number(metadataMap['grpc-status']);
|
||||
if (receivedCode in Status) {
|
||||
code = receivedCode;
|
||||
}
|
||||
metadata.remove('grpc-status');
|
||||
}
|
||||
if (typeof metadataMap['grpc-message'] === 'string') {
|
||||
details = decodeURI(metadataMap['grpc-message'] as string);
|
||||
metadata.remove('grpc-message');
|
||||
}
|
||||
return { code, details, metadata };
|
||||
}
|
||||
}
|
||||
|
||||
export class MetadataStatusFilterFactory
|
||||
implements FilterFactory<MetadataStatusFilter> {
|
||||
constructor(private readonly channel: Channel) {}
|
||||
createFilter(callStream: Call): MetadataStatusFilter {
|
||||
return new MetadataStatusFilter();
|
||||
}
|
||||
}
|
295
node_modules/@grpc/grpc-js/src/metadata.ts
generated
vendored
Normal file
295
node_modules/@grpc/grpc-js/src/metadata.ts
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as http2 from 'http2';
|
||||
import { log } from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/;
|
||||
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
||||
|
||||
export type MetadataValue = string | Buffer;
|
||||
export type MetadataObject = Map<string, MetadataValue[]>;
|
||||
|
||||
function isLegalKey(key: string): boolean {
|
||||
return LEGAL_KEY_REGEX.test(key);
|
||||
}
|
||||
|
||||
function isLegalNonBinaryValue(value: string): boolean {
|
||||
return LEGAL_NON_BINARY_VALUE_REGEX.test(value);
|
||||
}
|
||||
|
||||
function isBinaryKey(key: string): boolean {
|
||||
return key.endsWith('-bin');
|
||||
}
|
||||
|
||||
function isCustomMetadata(key: string): boolean {
|
||||
return !key.startsWith('grpc-');
|
||||
}
|
||||
|
||||
function normalizeKey(key: string): string {
|
||||
return key.toLowerCase();
|
||||
}
|
||||
|
||||
function validate(key: string, value?: MetadataValue): void {
|
||||
if (!isLegalKey(key)) {
|
||||
throw new Error('Metadata key "' + key + '" contains illegal characters');
|
||||
}
|
||||
if (value != null) {
|
||||
if (isBinaryKey(key)) {
|
||||
if (!(value instanceof Buffer)) {
|
||||
throw new Error("keys that end with '-bin' must have Buffer values");
|
||||
}
|
||||
} else {
|
||||
if (value instanceof Buffer) {
|
||||
throw new Error(
|
||||
"keys that don't end with '-bin' must have String values"
|
||||
);
|
||||
}
|
||||
if (!isLegalNonBinaryValue(value)) {
|
||||
throw new Error(
|
||||
'Metadata string value "' + value + '" contains illegal characters'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface MetadataOptions {
|
||||
/* Signal that the request is idempotent. Defaults to false */
|
||||
idempotentRequest?: boolean;
|
||||
/* Signal that the call should not return UNAVAILABLE before it has
|
||||
* started. Defaults to false. */
|
||||
waitForReady?: boolean;
|
||||
/* Signal that the call is cacheable. GRPC is free to use GET verb.
|
||||
* Defaults to false */
|
||||
cacheableRequest?: boolean;
|
||||
/* Signal that the initial metadata should be corked. Defaults to false. */
|
||||
corked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for storing metadata. Keys are normalized to lowercase ASCII.
|
||||
*/
|
||||
export class Metadata {
|
||||
protected internalRepr: MetadataObject = new Map<string, MetadataValue[]>();
|
||||
private options: MetadataOptions;
|
||||
|
||||
constructor(options?: MetadataOptions) {
|
||||
if (options === undefined) {
|
||||
this.options = {};
|
||||
} else {
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given value for the given key by replacing any other values
|
||||
* associated with that key. Normalizes the key.
|
||||
* @param key The key to whose value should be set.
|
||||
* @param value The value to set. Must be a buffer if and only
|
||||
* if the normalized key ends with '-bin'.
|
||||
*/
|
||||
set(key: string, value: MetadataValue): void {
|
||||
key = normalizeKey(key);
|
||||
validate(key, value);
|
||||
this.internalRepr.set(key, [value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given value for the given key by appending to a list of previous
|
||||
* values associated with that key. Normalizes the key.
|
||||
* @param key The key for which a new value should be appended.
|
||||
* @param value The value to add. Must be a buffer if and only
|
||||
* if the normalized key ends with '-bin'.
|
||||
*/
|
||||
add(key: string, value: MetadataValue): void {
|
||||
key = normalizeKey(key);
|
||||
validate(key, value);
|
||||
|
||||
const existingValue: MetadataValue[] | undefined = this.internalRepr.get(
|
||||
key
|
||||
);
|
||||
|
||||
if (existingValue === undefined) {
|
||||
this.internalRepr.set(key, [value]);
|
||||
} else {
|
||||
existingValue.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given key and any associated values. Normalizes the key.
|
||||
* @param key The key whose values should be removed.
|
||||
*/
|
||||
remove(key: string): void {
|
||||
key = normalizeKey(key);
|
||||
validate(key);
|
||||
this.internalRepr.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all values associated with the key. Normalizes the key.
|
||||
* @param key The key whose value should be retrieved.
|
||||
* @return A list of values associated with the given key.
|
||||
*/
|
||||
get(key: string): MetadataValue[] {
|
||||
key = normalizeKey(key);
|
||||
validate(key);
|
||||
return this.internalRepr.get(key) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a plain object mapping each key to the first value associated with it.
|
||||
* 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 } = {};
|
||||
|
||||
this.internalRepr.forEach((values, key) => {
|
||||
if (values.length > 0) {
|
||||
const v = values[0];
|
||||
result[key] = v instanceof Buffer ? v.slice() : v;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the metadata object.
|
||||
* @return The newly cloned object.
|
||||
*/
|
||||
clone(): Metadata {
|
||||
const newMetadata = new Metadata();
|
||||
const newInternalRepr = newMetadata.internalRepr;
|
||||
|
||||
this.internalRepr.forEach((value, key) => {
|
||||
const clonedValue: MetadataValue[] = value.map(v => {
|
||||
if (v instanceof Buffer) {
|
||||
return Buffer.from(v);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
|
||||
newInternalRepr.set(key, clonedValue);
|
||||
});
|
||||
|
||||
return newMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all key-value pairs from a given Metadata object into this one.
|
||||
* If both this object and the given object have values in the same key,
|
||||
* values from the other Metadata object will be appended to this object's
|
||||
* values.
|
||||
* @param other A Metadata object.
|
||||
*/
|
||||
merge(other: Metadata): void {
|
||||
other.internalRepr.forEach((values, key) => {
|
||||
const mergedValue: MetadataValue[] = (
|
||||
this.internalRepr.get(key) || []
|
||||
).concat(values);
|
||||
|
||||
this.internalRepr.set(key, mergedValue);
|
||||
});
|
||||
}
|
||||
|
||||
setOptions(options: MetadataOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
getOptions(): MetadataOptions {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OutgoingHttpHeaders object that can be used with the http2 API.
|
||||
*/
|
||||
toHttp2Headers(): http2.OutgoingHttpHeaders {
|
||||
// NOTE: Node <8.9 formats http2 headers incorrectly.
|
||||
const result: http2.OutgoingHttpHeaders = {};
|
||||
this.internalRepr.forEach((values, key) => {
|
||||
// We assume that the user's interaction with this object is limited to
|
||||
// through its public API (i.e. keys and values are already validated).
|
||||
result[key] = values.map(value => {
|
||||
if (value instanceof Buffer) {
|
||||
return value.toString('base64');
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// For compatibility with the other Metadata implementation
|
||||
private _getCoreRepresentation() {
|
||||
return this.internalRepr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Metadata object based fields in a given IncomingHttpHeaders
|
||||
* object.
|
||||
* @param headers An IncomingHttpHeaders object.
|
||||
*/
|
||||
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata {
|
||||
const result = new Metadata();
|
||||
Object.keys(headers).forEach(key => {
|
||||
// Reserved headers (beginning with `:`) are not valid keys.
|
||||
if (key.charAt(0) === ':') {
|
||||
return;
|
||||
}
|
||||
|
||||
const values = headers[key];
|
||||
|
||||
try {
|
||||
if (isBinaryKey(key)) {
|
||||
if (Array.isArray(values)) {
|
||||
values.forEach(value => {
|
||||
result.add(key, Buffer.from(value, 'base64'));
|
||||
});
|
||||
} else if (values !== undefined) {
|
||||
if (isCustomMetadata(key)) {
|
||||
values.split(',').forEach(v => {
|
||||
result.add(key, Buffer.from(v.trim(), 'base64'));
|
||||
});
|
||||
} else {
|
||||
result.add(key, Buffer.from(values, 'base64'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(values)) {
|
||||
values.forEach(value => {
|
||||
result.add(key, value);
|
||||
});
|
||||
} else if (values !== undefined) {
|
||||
if (isCustomMetadata(key)) {
|
||||
values.split(',').forEach(v => result.add(key, v.trim()));
|
||||
} else {
|
||||
result.add(key, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Failed to add metadata entry ${key}: ${values}. ${error.message}. For more information see https://github.com/grpc/grpc-node/issues/1173`;
|
||||
log(LogVerbosity.ERROR, message);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
65
node_modules/@grpc/grpc-js/src/object-stream.ts
generated
vendored
Normal file
65
node_modules/@grpc/grpc-js/src/object-stream.ts
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Duplex, Readable, Writable } from 'stream';
|
||||
import { EmitterAugmentation1 } from './events';
|
||||
|
||||
// tslint:disable:no-any
|
||||
|
||||
export type WriteCallback = (error: Error | null | undefined) => void;
|
||||
|
||||
export interface IntermediateObjectReadable<T> extends Readable {
|
||||
read(size?: number): any & T;
|
||||
}
|
||||
|
||||
export type ObjectReadable<T> = {
|
||||
read(size?: number): T;
|
||||
} & EmitterAugmentation1<'data', T> &
|
||||
IntermediateObjectReadable<T>;
|
||||
|
||||
export interface IntermediateObjectWritable<T> extends Writable {
|
||||
_write(chunk: any & T, encoding: string, callback: Function): void;
|
||||
write(chunk: any & T, cb?: WriteCallback): boolean;
|
||||
write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean;
|
||||
setDefaultEncoding(encoding: string): this;
|
||||
end(): void;
|
||||
end(chunk: any & T, cb?: Function): void;
|
||||
end(chunk: any & T, encoding?: any, cb?: Function): void;
|
||||
}
|
||||
|
||||
export interface ObjectWritable<T> extends IntermediateObjectWritable<T> {
|
||||
_write(chunk: T, encoding: string, callback: Function): void;
|
||||
write(chunk: T, cb?: Function): boolean;
|
||||
write(chunk: T, encoding?: any, cb?: Function): boolean;
|
||||
setDefaultEncoding(encoding: string): this;
|
||||
end(): void;
|
||||
end(chunk: T, cb?: Function): void;
|
||||
end(chunk: T, encoding?: any, cb?: Function): void;
|
||||
}
|
||||
|
||||
export type ObjectDuplex<T, U> = {
|
||||
read(size?: number): U;
|
||||
|
||||
_write(chunk: T, encoding: string, callback: Function): void;
|
||||
write(chunk: T, cb?: Function): boolean;
|
||||
write(chunk: T, encoding?: any, cb?: Function): boolean;
|
||||
end(): void;
|
||||
end(chunk: T, cb?: Function): void;
|
||||
end(chunk: T, encoding?: any, cb?: Function): void;
|
||||
} & Duplex &
|
||||
ObjectWritable<T> &
|
||||
ObjectReadable<U>;
|
127
node_modules/@grpc/grpc-js/src/picker.ts
generated
vendored
Normal file
127
node_modules/@grpc/grpc-js/src/picker.ts
generated
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Subchannel } from './subchannel';
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Metadata } from './metadata';
|
||||
import { Status } from './constants';
|
||||
import { LoadBalancer } from './load-balancer';
|
||||
|
||||
export enum PickResultType {
|
||||
COMPLETE,
|
||||
QUEUE,
|
||||
TRANSIENT_FAILURE,
|
||||
}
|
||||
|
||||
export interface PickResult {
|
||||
pickResultType: PickResultType;
|
||||
/**
|
||||
* The subchannel to use as the transport for the call. Only meaningful if
|
||||
* `pickResultType` is COMPLETE. If null, indicates that the call should be
|
||||
* dropped.
|
||||
*/
|
||||
subchannel: Subchannel | null;
|
||||
/**
|
||||
* The status object to end the call with. Populated if and only if
|
||||
* `pickResultType` is TRANSIENT_FAILURE.
|
||||
*/
|
||||
status: StatusObject | null;
|
||||
}
|
||||
|
||||
export interface CompletePickResult extends PickResult {
|
||||
pickResultType: PickResultType.COMPLETE;
|
||||
subchannel: Subchannel | null;
|
||||
status: null;
|
||||
}
|
||||
|
||||
export interface QueuePickResult extends PickResult {
|
||||
pickResultType: PickResultType.QUEUE;
|
||||
subchannel: null;
|
||||
status: null;
|
||||
}
|
||||
|
||||
export interface TransientFailurePickResult extends PickResult {
|
||||
pickResultType: PickResultType.TRANSIENT_FAILURE;
|
||||
subchannel: null;
|
||||
status: StatusObject;
|
||||
}
|
||||
|
||||
export interface PickArgs {
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* A proxy object representing the momentary state of a load balancer. Picks
|
||||
* subchannels or returns other information based on that state. Should be
|
||||
* replaced every time the load balancer changes state.
|
||||
*/
|
||||
export interface Picker {
|
||||
pick(pickArgs: PickArgs): PickResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard picker representing a load balancer in the TRANSIENT_FAILURE
|
||||
* state. Always responds to every pick request with an UNAVAILABLE status.
|
||||
*/
|
||||
export class UnavailablePicker implements Picker {
|
||||
private status: StatusObject;
|
||||
constructor(status?: StatusObject) {
|
||||
if (status !== undefined) {
|
||||
this.status = status;
|
||||
} else {
|
||||
this.status = {
|
||||
code: Status.UNAVAILABLE,
|
||||
details: 'No connection established',
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
}
|
||||
}
|
||||
pick(pickArgs: PickArgs): TransientFailurePickResult {
|
||||
return {
|
||||
pickResultType: PickResultType.TRANSIENT_FAILURE,
|
||||
subchannel: null,
|
||||
status: this.status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard picker representing a load balancer in the IDLE or CONNECTING
|
||||
* state. Always responds to every pick request with a QUEUE pick result
|
||||
* indicating that the pick should be tried again with the next `Picker`. Also
|
||||
* reports back to the load balancer that a connection should be established
|
||||
* once any pick is attempted.
|
||||
*/
|
||||
export class QueuePicker {
|
||||
private calledExitIdle = false;
|
||||
// Constructed with a load balancer. Calls exitIdle on it the first time pick is called
|
||||
constructor(private loadBalancer: LoadBalancer) {}
|
||||
|
||||
pick(pickArgs: PickArgs): QueuePickResult {
|
||||
if (!this.calledExitIdle) {
|
||||
process.nextTick(() => {
|
||||
this.loadBalancer.exitIdle();
|
||||
});
|
||||
this.calledExitIdle = true;
|
||||
}
|
||||
return {
|
||||
pickResultType: PickResultType.QUEUE,
|
||||
subchannel: null,
|
||||
status: null,
|
||||
};
|
||||
}
|
||||
}
|
324
node_modules/@grpc/grpc-js/src/resolver-dns.ts
generated
vendored
Normal file
324
node_modules/@grpc/grpc-js/src/resolver-dns.ts
generated
vendored
Normal file
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Resolver,
|
||||
ResolverListener,
|
||||
registerResolver,
|
||||
registerDefaultResolver,
|
||||
} from './resolver';
|
||||
import * as dns from 'dns';
|
||||
import * as semver from 'semver';
|
||||
import * as util from 'util';
|
||||
import { extractAndSelectServiceConfig, ServiceConfig } from './service-config';
|
||||
import { ServiceError } from './call';
|
||||
import { Status } from './constants';
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Metadata } from './metadata';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
const TRACER_NAME = 'dns_resolver';
|
||||
|
||||
function trace(text: string): void {
|
||||
logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
||||
}
|
||||
|
||||
/* These regular expressions match IP addresses with optional ports in different
|
||||
* formats. In each case, capture group 1 contains the address, and capture
|
||||
* group 2 contains the port number, if present */
|
||||
/**
|
||||
* Matches 4 groups of up to 3 digits each, separated by periods, optionally
|
||||
* followed by a colon and a number.
|
||||
*/
|
||||
const IPV4_REGEX = /^(\d{1,3}(?:\.\d{1,3}){3})(?::(\d+))?$/;
|
||||
/**
|
||||
* Matches any number of groups of up to 4 hex digits (case insensitive)
|
||||
* separated by 1 or more colons. This variant does not match a port number.
|
||||
*/
|
||||
const IPV6_REGEX = /^([0-9a-f]{0,4}(?::{1,2}[0-9a-f]{0,4})+)$/i;
|
||||
/**
|
||||
* Matches the same as the IPv6_REGEX, surrounded by square brackets, and
|
||||
* optionally followed by a colon and a number.
|
||||
*/
|
||||
const IPV6_BRACKET_REGEX = /^\[([0-9a-f]{0,4}(?::{1,2}[0-9a-f]{0,4})+)\](?::(\d+))?$/i;
|
||||
|
||||
/**
|
||||
* Matches `[dns:][//authority/]host[:port]`, where `authority` and `host` are
|
||||
* both arbitrary sequences of dot-separated strings of alphanumeric characters
|
||||
* and `port` is a sequence of digits. Group 1 contains the hostname and group
|
||||
* 2 contains the port number if provided.
|
||||
*/
|
||||
const DNS_REGEX = /^(?:dns:)?(?:\/\/(?:[a-zA-Z0-9-]+\.?)+\/)?((?:[a-zA-Z0-9-]+\.?)+)(?::(\d+))?$/;
|
||||
|
||||
/**
|
||||
* The default TCP port to connect to if not explicitly specified in the target.
|
||||
*/
|
||||
const DEFAULT_PORT = '443';
|
||||
|
||||
/**
|
||||
* The range of Node versions in which the Node issue
|
||||
* https://github.com/nodejs/node/issues/28216 has been fixed. In other
|
||||
* versions, IPv6 literal addresses cannot be used to establish HTTP/2
|
||||
* connections.
|
||||
*/
|
||||
const IPV6_SUPPORT_RANGE = '>= 12.6';
|
||||
|
||||
/**
|
||||
* Get a promise that always resolves with either the result of the function
|
||||
* or the error if it failed.
|
||||
* @param fn
|
||||
*/
|
||||
function resolvePromisify<TArg, TResult, TError>(
|
||||
fn: (
|
||||
arg: TArg,
|
||||
callback: (error: TError | null, result: TResult) => void
|
||||
) => void
|
||||
): (arg: TArg) => Promise<TResult | TError> {
|
||||
return arg =>
|
||||
new Promise<TResult | TError>((resolve, reject) => {
|
||||
fn(arg, (error, result) => {
|
||||
if (error) {
|
||||
resolve(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const resolveTxtPromise = resolvePromisify<
|
||||
string,
|
||||
string[][],
|
||||
NodeJS.ErrnoException
|
||||
>(dns.resolveTxt);
|
||||
const dnsLookupPromise = util.promisify(dns.lookup);
|
||||
|
||||
/**
|
||||
* Attempt to parse a target string as an IP address
|
||||
* @param target
|
||||
* @return An "IP:port" string in an array if parsing was successful, `null` otherwise
|
||||
*/
|
||||
function parseIP(target: string): string[] | null {
|
||||
/* These three regular expressions are all mutually exclusive, so we just
|
||||
* want the first one that matches the target string, if any do. */
|
||||
const ipv4Match = IPV4_REGEX.exec(target);
|
||||
const match =
|
||||
ipv4Match || IPV6_REGEX.exec(target) || IPV6_BRACKET_REGEX.exec(target);
|
||||
if (match === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ipv6 addresses should be bracketed
|
||||
const addr = ipv4Match ? match[1] : `[${match[1]}]`;
|
||||
let port: string;
|
||||
if (match[2]) {
|
||||
port = match[2];
|
||||
} else {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
return [`${addr}:${port}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge any number of arrays into a single alternating array
|
||||
* @param arrays
|
||||
*/
|
||||
function mergeArrays<T>(...arrays: T[][]): T[] {
|
||||
const result: T[] = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i <
|
||||
Math.max.apply(
|
||||
null,
|
||||
arrays.map(array => array.length)
|
||||
);
|
||||
i++
|
||||
) {
|
||||
for (const array of arrays) {
|
||||
if (i < array.length) {
|
||||
result.push(array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver implementation that handles DNS names and IP addresses.
|
||||
*/
|
||||
class DnsResolver implements Resolver {
|
||||
private readonly ipResult: string[] | null;
|
||||
private readonly dnsHostname: string | null;
|
||||
private readonly port: string | null;
|
||||
/* The promise results here contain, in order, the A record, the AAAA record,
|
||||
* and either the TXT record or an error if TXT resolution failed */
|
||||
private pendingResultPromise: Promise<
|
||||
[dns.LookupAddress[], string[][] | NodeJS.ErrnoException]
|
||||
> | null = null;
|
||||
private percentage: number;
|
||||
private defaultResolutionError: StatusObject;
|
||||
constructor(private target: string, private listener: ResolverListener) {
|
||||
trace('Resolver constructed for target ' + target);
|
||||
this.ipResult = parseIP(target);
|
||||
const dnsMatch = DNS_REGEX.exec(target);
|
||||
if (dnsMatch === null) {
|
||||
this.dnsHostname = null;
|
||||
this.port = null;
|
||||
} else {
|
||||
this.dnsHostname = dnsMatch[1];
|
||||
if (dnsMatch[2]) {
|
||||
this.port = dnsMatch[2];
|
||||
} else {
|
||||
this.port = DEFAULT_PORT;
|
||||
}
|
||||
}
|
||||
this.percentage = Math.random() * 100;
|
||||
|
||||
this.defaultResolutionError = {
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Name resolution failed for target ${this.target}`,
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* If the target is an IP address, just provide that address as a result.
|
||||
* Otherwise, initiate A, AAAA, and TXT
|
||||
*/
|
||||
private startResolution() {
|
||||
if (this.ipResult !== null) {
|
||||
trace('Returning IP address for target ' + this.target);
|
||||
setImmediate(() => {
|
||||
this.listener.onSuccessfulResolution(this.ipResult!, null, null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.dnsHostname !== null) {
|
||||
const hostname: string = this.dnsHostname;
|
||||
/* We lookup both address families here and then split them up later
|
||||
* because when looking up a single family, dns.lookup outputs an error
|
||||
* if the name exists but there are no records for that family, and that
|
||||
* error is indistinguishable from other kinds of errors */
|
||||
const addressResult = dnsLookupPromise(hostname, { all: true });
|
||||
/* We handle the TXT query promise differently than the others because
|
||||
* the name resolution attempt as a whole is a success even if the TXT
|
||||
* lookup fails */
|
||||
const txtResult = resolveTxtPromise(hostname);
|
||||
this.pendingResultPromise = Promise.all([addressResult, txtResult]);
|
||||
this.pendingResultPromise.then(
|
||||
([addressList, txtRecord]) => {
|
||||
this.pendingResultPromise = null;
|
||||
const ip4Addresses: string[] = addressList
|
||||
.filter(addr => addr.family === 4)
|
||||
.map(addr => `${addr.address}:${this.port}`);
|
||||
let ip6Addresses: string[];
|
||||
if (semver.satisfies(process.version, IPV6_SUPPORT_RANGE)) {
|
||||
ip6Addresses = addressList
|
||||
.filter(addr => addr.family === 6)
|
||||
.map(addr => `[${addr.address}]:${this.port}`);
|
||||
} else {
|
||||
ip6Addresses = [];
|
||||
}
|
||||
const allAddresses: string[] = mergeArrays(
|
||||
ip4Addresses,
|
||||
ip6Addresses
|
||||
);
|
||||
trace(
|
||||
'Resolved addresses for target ' + this.target + ': ' + allAddresses
|
||||
);
|
||||
if (allAddresses.length === 0) {
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
return;
|
||||
}
|
||||
let serviceConfig: ServiceConfig | null = null;
|
||||
let serviceConfigError: StatusObject | null = null;
|
||||
if (txtRecord instanceof Error) {
|
||||
serviceConfigError = {
|
||||
code: Status.UNAVAILABLE,
|
||||
details: 'TXT query failed',
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
} else {
|
||||
try {
|
||||
serviceConfig = extractAndSelectServiceConfig(
|
||||
txtRecord,
|
||||
this.percentage
|
||||
);
|
||||
} catch (err) {
|
||||
serviceConfigError = {
|
||||
code: Status.UNAVAILABLE,
|
||||
details: 'Parsing service config failed',
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
}
|
||||
}
|
||||
this.listener.onSuccessfulResolution(
|
||||
allAddresses,
|
||||
serviceConfig,
|
||||
serviceConfigError
|
||||
);
|
||||
},
|
||||
err => {
|
||||
trace(
|
||||
'Resolution error for target ' +
|
||||
this.target +
|
||||
': ' +
|
||||
(err as Error).message
|
||||
);
|
||||
this.pendingResultPromise = null;
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateResolution() {
|
||||
trace('Resolution update requested for target ' + this.target);
|
||||
if (this.pendingResultPromise === null) {
|
||||
this.startResolution();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default authority for the given target. For IP targets, that is
|
||||
* the IP address. For DNS targets, it is the hostname.
|
||||
* @param target
|
||||
*/
|
||||
static getDefaultAuthority(target: string): string {
|
||||
const ipMatch =
|
||||
IPV4_REGEX.exec(target) ||
|
||||
IPV6_REGEX.exec(target) ||
|
||||
IPV6_BRACKET_REGEX.exec(target);
|
||||
if (ipMatch) {
|
||||
return ipMatch[1];
|
||||
}
|
||||
const dnsMatch = DNS_REGEX.exec(target);
|
||||
if (dnsMatch) {
|
||||
return dnsMatch[1];
|
||||
}
|
||||
throw new Error(`Failed to parse target ${target}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the DNS resolver class by registering it as the handler for the
|
||||
* "dns:" prefix and as the default resolver.
|
||||
*/
|
||||
export function setup(): void {
|
||||
registerResolver('dns:', DnsResolver);
|
||||
registerDefaultResolver(DnsResolver);
|
||||
}
|
57
node_modules/@grpc/grpc-js/src/resolver-uds.ts
generated
vendored
Normal file
57
node_modules/@grpc/grpc-js/src/resolver-uds.ts
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Resolver,
|
||||
ResolverListener,
|
||||
registerResolver,
|
||||
registerDefaultResolver,
|
||||
} from './resolver';
|
||||
|
||||
function getUdsName(target: string): string {
|
||||
/* Due to how this resolver is registered, it should only be constructed
|
||||
* with strings that start with 'unix:'. Other strings may result in
|
||||
* nonsensical output. If the string starts with 'unix://' that entire
|
||||
* prefix needs to be ignored */
|
||||
if (target.startsWith('unix://')) {
|
||||
return target.substring(7);
|
||||
} else {
|
||||
return target.substring(5);
|
||||
}
|
||||
}
|
||||
|
||||
class UdsResolver implements Resolver {
|
||||
private addresses: string[] = [];
|
||||
constructor(target: string, private listener: ResolverListener) {
|
||||
this.addresses = [getUdsName(target)];
|
||||
}
|
||||
updateResolution(): void {
|
||||
process.nextTick(
|
||||
this.listener.onSuccessfulResolution,
|
||||
this.addresses,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
static getDefaultAuthority(target: string): string {
|
||||
return 'localhost';
|
||||
}
|
||||
}
|
||||
|
||||
export function setup() {
|
||||
registerResolver('unix:', UdsResolver);
|
||||
}
|
142
node_modules/@grpc/grpc-js/src/resolver.ts
generated
vendored
Normal file
142
node_modules/@grpc/grpc-js/src/resolver.ts
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { ServiceError } from './call';
|
||||
import { ServiceConfig } from './service-config';
|
||||
import * as resolver_dns from './resolver-dns';
|
||||
import * as resolver_uds from './resolver-uds';
|
||||
import { StatusObject } from './call-stream';
|
||||
|
||||
/**
|
||||
* A listener object passed to the resolver's constructor that provides name
|
||||
* resolution updates back to the resolver's owner.
|
||||
*/
|
||||
export interface ResolverListener {
|
||||
/**
|
||||
* Called whenever the resolver has new name resolution results to report
|
||||
* @param addressList The new list of backend addresses
|
||||
* @param serviceConfig The new service configuration corresponding to the
|
||||
* `addressList`. Will be `null` if no service configuration was
|
||||
* retrieved or if the service configuration was invalid
|
||||
* @param serviceConfigError If non-`null`, indicates that the retrieved
|
||||
* service configuration was invalid
|
||||
*/
|
||||
onSuccessfulResolution(
|
||||
addressList: string[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
): void;
|
||||
/**
|
||||
* Called whenever a name resolution attempt fails.
|
||||
* @param error Describes how resolution failed
|
||||
*/
|
||||
onError(error: StatusObject): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolver class that handles one or more of the name syntax schemes defined
|
||||
* in the [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md)
|
||||
*/
|
||||
export interface Resolver {
|
||||
/**
|
||||
* Indicates that the caller wants new name resolution data. Calling this
|
||||
* function may eventually result in calling one of the `ResolverListener`
|
||||
* functions, but that is not guaranteed. Those functions will never be
|
||||
* called synchronously with the constructor or updateResolution.
|
||||
*/
|
||||
updateResolution(): void;
|
||||
}
|
||||
|
||||
export interface ResolverConstructor {
|
||||
new (target: string, listener: ResolverListener): Resolver;
|
||||
/**
|
||||
* Get the default authority for a target. This loosely corresponds to that
|
||||
* target's hostname. Throws an error if this resolver class cannot parse the
|
||||
* `target`.
|
||||
* @param target
|
||||
*/
|
||||
getDefaultAuthority(target: string): string;
|
||||
}
|
||||
|
||||
const registeredResolvers: { [prefix: string]: ResolverConstructor } = {};
|
||||
let defaultResolver: ResolverConstructor | null = null;
|
||||
|
||||
/**
|
||||
* Register a resolver class to handle target names prefixed with the `prefix`
|
||||
* string. This prefix should correspond to a URI scheme name listed in the
|
||||
* [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md)
|
||||
* @param prefix
|
||||
* @param resolverClass
|
||||
*/
|
||||
export function registerResolver(
|
||||
prefix: string,
|
||||
resolverClass: ResolverConstructor
|
||||
) {
|
||||
registeredResolvers[prefix] = resolverClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a default resolver to handle target names that do not start with
|
||||
* any registered prefix.
|
||||
* @param resolverClass
|
||||
*/
|
||||
export function registerDefaultResolver(resolverClass: ResolverConstructor) {
|
||||
defaultResolver = resolverClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a name resolver for the specified target, if possible. Throws an
|
||||
* error if no such name resolver can be created.
|
||||
* @param target
|
||||
* @param listener
|
||||
*/
|
||||
export function createResolver(
|
||||
target: string,
|
||||
listener: ResolverListener
|
||||
): Resolver {
|
||||
for (const prefix of Object.keys(registeredResolvers)) {
|
||||
if (target.startsWith(prefix)) {
|
||||
return new registeredResolvers[prefix](target, listener);
|
||||
}
|
||||
}
|
||||
if (defaultResolver !== null) {
|
||||
return new defaultResolver(target, listener);
|
||||
}
|
||||
throw new Error(`No resolver could be created for target ${target}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default authority for the specified target, if possible. Throws an
|
||||
* error if no registered name resolver can parse that target string.
|
||||
* @param target
|
||||
*/
|
||||
export function getDefaultAuthority(target: string): string {
|
||||
for (const prefix of Object.keys(registeredResolvers)) {
|
||||
if (target.startsWith(prefix)) {
|
||||
return registeredResolvers[prefix].getDefaultAuthority(target);
|
||||
}
|
||||
}
|
||||
if (defaultResolver !== null) {
|
||||
return defaultResolver.getDefaultAuthority(target);
|
||||
}
|
||||
throw new Error(`Invalid target ${target}`);
|
||||
}
|
||||
|
||||
export function registerAll() {
|
||||
resolver_dns.setup();
|
||||
resolver_uds.setup();
|
||||
}
|
447
node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts
generated
vendored
Normal file
447
node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts
generated
vendored
Normal file
@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
ChannelControlHelper,
|
||||
LoadBalancer,
|
||||
isLoadBalancerNameRegistered,
|
||||
createLoadBalancer,
|
||||
} from './load-balancer';
|
||||
import { ServiceConfig } from './service-config';
|
||||
import { ConnectivityState } from './channel';
|
||||
import { createResolver, Resolver } from './resolver';
|
||||
import { ServiceError } from './call';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { Picker, UnavailablePicker, QueuePicker } from './picker';
|
||||
import { LoadBalancingConfig } from './load-balancing-config';
|
||||
import { BackoffTimeout } from './backoff-timeout';
|
||||
import { Status } from './constants';
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Metadata } from './metadata';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
const TRACER_NAME = 'resolving_load_balancer';
|
||||
|
||||
function trace(text: string): void {
|
||||
logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
||||
}
|
||||
|
||||
const DEFAULT_LOAD_BALANCER_NAME = 'pick_first';
|
||||
|
||||
export class ResolvingLoadBalancer implements LoadBalancer {
|
||||
/**
|
||||
* The resolver class constructed for the target address.
|
||||
*/
|
||||
private innerResolver: Resolver;
|
||||
/**
|
||||
* Current internal load balancer used for handling calls.
|
||||
* Invariant: innerLoadBalancer === null => pendingReplacementLoadBalancer === null.
|
||||
*/
|
||||
private innerLoadBalancer: LoadBalancer | null = null;
|
||||
/**
|
||||
* The load balancer instance that will be used in place of the current
|
||||
* `innerLoadBalancer` once either that load balancer loses its connection
|
||||
* or this one establishes a connection. For use when a new name resolution
|
||||
* result comes in with a different load balancing configuration, and the
|
||||
* current `innerLoadBalancer` is still connected.
|
||||
*/
|
||||
private pendingReplacementLoadBalancer: LoadBalancer | null = null;
|
||||
/**
|
||||
* This resolving load balancer's current connectivity state.
|
||||
*/
|
||||
private currentState: ConnectivityState = ConnectivityState.IDLE;
|
||||
/**
|
||||
* The service config object from the last successful resolution, if
|
||||
* available. A value of undefined indicates that there has not yet
|
||||
* been a successful resolution. A value of null indicates that the last
|
||||
* successful resolution explicitly provided a null service config.
|
||||
*/
|
||||
private previousServiceConfig: ServiceConfig | null | undefined = undefined;
|
||||
/**
|
||||
* The most recently reported connectivity state of the `innerLoadBalancer`.
|
||||
*/
|
||||
private innerBalancerState: ConnectivityState = ConnectivityState.IDLE;
|
||||
|
||||
private innerBalancerPicker: Picker = new UnavailablePicker();
|
||||
|
||||
/**
|
||||
* The most recent reported state of the pendingReplacementLoadBalancer.
|
||||
* Starts at IDLE for type simplicity. This should get updated as soon as the
|
||||
* pendingReplacementLoadBalancer gets constructed.
|
||||
*/
|
||||
private replacementBalancerState: ConnectivityState = ConnectivityState.IDLE;
|
||||
/**
|
||||
* The picker associated with the replacementBalancerState. Starts as an
|
||||
* UnavailablePicker for type simplicity. This should get updated as soon as
|
||||
* the pendingReplacementLoadBalancer gets constructed.
|
||||
*/
|
||||
private replacementBalancerPicker: Picker = new UnavailablePicker();
|
||||
|
||||
/**
|
||||
* ChannelControlHelper for the innerLoadBalancer.
|
||||
*/
|
||||
private readonly innerChannelControlHelper: ChannelControlHelper;
|
||||
/**
|
||||
* ChannelControlHelper for the pendingReplacementLoadBalancer.
|
||||
*/
|
||||
private readonly replacementChannelControlHelper: ChannelControlHelper;
|
||||
|
||||
/**
|
||||
* The backoff timer for handling name resolution failures.
|
||||
*/
|
||||
private readonly backoffTimeout: BackoffTimeout;
|
||||
|
||||
/**
|
||||
* Indicates whether we should attempt to resolve again after the backoff
|
||||
* timer runs out.
|
||||
*/
|
||||
private continueResolving = false;
|
||||
|
||||
/**
|
||||
* Wrapper class that behaves like a `LoadBalancer` and also handles name
|
||||
* resolution internally.
|
||||
* @param target The address of the backend to connect to.
|
||||
* @param channelControlHelper `ChannelControlHelper` instance provided by
|
||||
* this load balancer's owner.
|
||||
* @param defaultServiceConfig The default service configuration to be used
|
||||
* if none is provided by the name resolver. A `null` value indicates
|
||||
* that the default behavior should be the default unconfigured behavior.
|
||||
* In practice, that means using the "pick first" load balancer
|
||||
* implmentation
|
||||
*/
|
||||
constructor(
|
||||
private target: string,
|
||||
private channelControlHelper: ChannelControlHelper,
|
||||
private defaultServiceConfig: ServiceConfig | null
|
||||
) {
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
this.innerResolver = createResolver(target, {
|
||||
onSuccessfulResolution: (
|
||||
addressList: string[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: ServiceError | null
|
||||
) => {
|
||||
let workingServiceConfig: ServiceConfig | null = null;
|
||||
/* This first group of conditionals implements the algorithm described
|
||||
* in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
|
||||
* in the section called "Behavior on receiving a new gRPC Config".
|
||||
*/
|
||||
if (serviceConfig === null) {
|
||||
// Step 4 and 5
|
||||
if (serviceConfigError === null) {
|
||||
// Step 5
|
||||
this.previousServiceConfig = serviceConfig;
|
||||
workingServiceConfig = this.defaultServiceConfig;
|
||||
} else {
|
||||
// Step 4
|
||||
if (this.previousServiceConfig === undefined) {
|
||||
// Step 4.ii
|
||||
if (this.defaultServiceConfig === null) {
|
||||
// Step 4.ii.b
|
||||
this.handleResolutionFailure(serviceConfigError);
|
||||
} else {
|
||||
// Step 4.ii.a
|
||||
workingServiceConfig = this.defaultServiceConfig;
|
||||
}
|
||||
} else {
|
||||
// Step 4.i
|
||||
workingServiceConfig = this.previousServiceConfig;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Step 3
|
||||
workingServiceConfig = serviceConfig;
|
||||
this.previousServiceConfig = serviceConfig;
|
||||
}
|
||||
let loadBalancerName: string | null = null;
|
||||
let loadBalancingConfig: LoadBalancingConfig | null = null;
|
||||
if (
|
||||
workingServiceConfig === null ||
|
||||
workingServiceConfig.loadBalancingConfig.length === 0
|
||||
) {
|
||||
loadBalancerName = DEFAULT_LOAD_BALANCER_NAME;
|
||||
} else {
|
||||
for (const lbConfig of workingServiceConfig.loadBalancingConfig) {
|
||||
// Iterating through a oneof looking for whichever one is populated
|
||||
for (const key in lbConfig) {
|
||||
if (Object.prototype.hasOwnProperty.call(lbConfig, key)) {
|
||||
if (isLoadBalancerNameRegistered(key)) {
|
||||
loadBalancerName = key;
|
||||
loadBalancingConfig = lbConfig;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (loadBalancerName !== null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (loadBalancerName === null) {
|
||||
// There were load balancing configs but none are supported. This counts as a resolution failure
|
||||
this.handleResolutionFailure({
|
||||
code: Status.UNAVAILABLE,
|
||||
details:
|
||||
'All load balancer options in service config are not compatible',
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.innerLoadBalancer === null) {
|
||||
this.innerLoadBalancer = createLoadBalancer(
|
||||
loadBalancerName,
|
||||
this.innerChannelControlHelper
|
||||
)!;
|
||||
this.innerLoadBalancer.updateAddressList(
|
||||
addressList,
|
||||
loadBalancingConfig
|
||||
);
|
||||
} else if (this.innerLoadBalancer.getTypeName() === loadBalancerName) {
|
||||
this.innerLoadBalancer.updateAddressList(
|
||||
addressList,
|
||||
loadBalancingConfig
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
this.pendingReplacementLoadBalancer === null ||
|
||||
this.pendingReplacementLoadBalancer.getTypeName() !==
|
||||
loadBalancerName
|
||||
) {
|
||||
if (this.pendingReplacementLoadBalancer !== null) {
|
||||
this.pendingReplacementLoadBalancer.destroy();
|
||||
}
|
||||
this.pendingReplacementLoadBalancer = createLoadBalancer(
|
||||
loadBalancerName,
|
||||
this.replacementChannelControlHelper
|
||||
)!;
|
||||
}
|
||||
this.pendingReplacementLoadBalancer.updateAddressList(
|
||||
addressList,
|
||||
loadBalancingConfig
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
this.handleResolutionFailure(error);
|
||||
},
|
||||
});
|
||||
|
||||
this.innerChannelControlHelper = {
|
||||
createSubchannel: (
|
||||
subchannelAddress: string,
|
||||
subchannelArgs: ChannelOptions
|
||||
) => {
|
||||
return this.channelControlHelper.createSubchannel(
|
||||
subchannelAddress,
|
||||
subchannelArgs
|
||||
);
|
||||
},
|
||||
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
|
||||
this.innerBalancerState = connectivityState;
|
||||
if (connectivityState === ConnectivityState.IDLE) {
|
||||
picker = new QueuePicker(this);
|
||||
}
|
||||
this.innerBalancerPicker = picker;
|
||||
if (
|
||||
connectivityState !== ConnectivityState.READY &&
|
||||
this.pendingReplacementLoadBalancer !== null
|
||||
) {
|
||||
this.switchOverReplacementBalancer();
|
||||
} else {
|
||||
if (connectivityState === ConnectivityState.IDLE) {
|
||||
if (this.innerLoadBalancer) {
|
||||
this.innerLoadBalancer.destroy();
|
||||
this.innerLoadBalancer = null;
|
||||
}
|
||||
}
|
||||
this.updateState(connectivityState, picker);
|
||||
}
|
||||
},
|
||||
requestReresolution: () => {
|
||||
if (this.pendingReplacementLoadBalancer === null) {
|
||||
/* If the backoffTimeout is running, we're still backing off from
|
||||
* making resolve requests, so we shouldn't make another one here.
|
||||
* In that case, the backoff timer callback will call
|
||||
* updateResolution */
|
||||
if (this.backoffTimeout.isRunning()) {
|
||||
this.continueResolving = true;
|
||||
} else {
|
||||
this.updateResolution();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.replacementChannelControlHelper = {
|
||||
createSubchannel: (
|
||||
subchannelAddress: string,
|
||||
subchannelArgs: ChannelOptions
|
||||
) => {
|
||||
return this.channelControlHelper.createSubchannel(
|
||||
subchannelAddress,
|
||||
subchannelArgs
|
||||
);
|
||||
},
|
||||
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
|
||||
if (connectivityState === ConnectivityState.IDLE) {
|
||||
picker = new QueuePicker(this);
|
||||
}
|
||||
this.replacementBalancerState = connectivityState;
|
||||
this.replacementBalancerPicker = picker;
|
||||
if (connectivityState === ConnectivityState.READY) {
|
||||
this.switchOverReplacementBalancer();
|
||||
} else if (connectivityState === ConnectivityState.IDLE) {
|
||||
if (this.pendingReplacementLoadBalancer) {
|
||||
this.pendingReplacementLoadBalancer.destroy();
|
||||
this.pendingReplacementLoadBalancer = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
requestReresolution: () => {
|
||||
/* If the backoffTimeout is running, we're still backing off from
|
||||
* making resolve requests, so we shouldn't make another one here.
|
||||
* In that case, the backoff timer callback will call
|
||||
* updateResolution */
|
||||
if (this.backoffTimeout.isRunning()) {
|
||||
this.continueResolving = true;
|
||||
} else {
|
||||
this.updateResolution();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.backoffTimeout = new BackoffTimeout(() => {
|
||||
if (this.continueResolving) {
|
||||
this.updateResolution();
|
||||
this.continueResolving = false;
|
||||
} else {
|
||||
if (this.innerLoadBalancer === null) {
|
||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||
} else {
|
||||
this.updateState(this.innerBalancerState, this.innerBalancerPicker);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateResolution() {
|
||||
this.innerResolver.updateResolution();
|
||||
if (
|
||||
this.innerLoadBalancer === null ||
|
||||
this.innerBalancerState === ConnectivityState.IDLE
|
||||
) {
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
|
||||
private updateState(connectivitystate: ConnectivityState, picker: Picker) {
|
||||
trace(
|
||||
this.target +
|
||||
' ' +
|
||||
ConnectivityState[this.currentState] +
|
||||
' -> ' +
|
||||
ConnectivityState[connectivitystate]
|
||||
);
|
||||
this.currentState = connectivitystate;
|
||||
this.channelControlHelper.updateState(connectivitystate, picker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop using the current innerLoadBalancer and replace it with the
|
||||
* pendingReplacementLoadBalancer. Must only be called if both of
|
||||
* those are currently not null.
|
||||
*/
|
||||
private switchOverReplacementBalancer() {
|
||||
this.innerLoadBalancer!.destroy();
|
||||
this.innerLoadBalancer = this.pendingReplacementLoadBalancer!;
|
||||
this.innerLoadBalancer.replaceChannelControlHelper(
|
||||
this.innerChannelControlHelper
|
||||
);
|
||||
this.pendingReplacementLoadBalancer = null;
|
||||
this.innerBalancerState = this.replacementBalancerState;
|
||||
this.innerBalancerPicker = this.replacementBalancerPicker;
|
||||
this.updateState(
|
||||
this.replacementBalancerState,
|
||||
this.replacementBalancerPicker
|
||||
);
|
||||
}
|
||||
|
||||
private handleResolutionFailure(error: StatusObject) {
|
||||
if (
|
||||
this.innerLoadBalancer === null ||
|
||||
this.innerBalancerState === ConnectivityState.IDLE
|
||||
) {
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker(error)
|
||||
);
|
||||
}
|
||||
this.backoffTimeout.runOnce();
|
||||
}
|
||||
|
||||
exitIdle() {
|
||||
if (this.innerLoadBalancer !== null) {
|
||||
this.innerLoadBalancer.exitIdle();
|
||||
}
|
||||
if (this.currentState === ConnectivityState.IDLE) {
|
||||
if (this.backoffTimeout.isRunning()) {
|
||||
this.continueResolving = true;
|
||||
} else {
|
||||
this.updateResolution();
|
||||
}
|
||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressList(
|
||||
addressList: string[],
|
||||
lbConfig: LoadBalancingConfig | null
|
||||
) {
|
||||
throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
|
||||
}
|
||||
|
||||
resetBackoff() {
|
||||
this.backoffTimeout.reset();
|
||||
if (this.innerLoadBalancer !== null) {
|
||||
this.innerLoadBalancer.resetBackoff();
|
||||
}
|
||||
if (this.pendingReplacementLoadBalancer !== null) {
|
||||
this.pendingReplacementLoadBalancer.resetBackoff();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.innerLoadBalancer !== null) {
|
||||
this.innerLoadBalancer.destroy();
|
||||
this.innerLoadBalancer = null;
|
||||
}
|
||||
if (this.pendingReplacementLoadBalancer !== null) {
|
||||
this.pendingReplacementLoadBalancer.destroy();
|
||||
this.pendingReplacementLoadBalancer = null;
|
||||
}
|
||||
this.updateState(ConnectivityState.SHUTDOWN, new UnavailablePicker());
|
||||
}
|
||||
|
||||
getTypeName() {
|
||||
return 'resolving_load_balancer';
|
||||
}
|
||||
|
||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper) {
|
||||
this.channelControlHelper = channelControlHelper;
|
||||
}
|
||||
}
|
655
node_modules/@grpc/grpc-js/src/server-call.ts
generated
vendored
Normal file
655
node_modules/@grpc/grpc-js/src/server-call.ts
generated
vendored
Normal file
@ -0,0 +1,655 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as http2 from 'http2';
|
||||
import { Duplex, Readable, Writable } from 'stream';
|
||||
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Status } from './constants';
|
||||
import { Deserialize, Serialize } from './make-client';
|
||||
import { Metadata } from './metadata';
|
||||
import { StreamDecoder } from './stream-decoder';
|
||||
import { ObjectReadable, ObjectWritable } from './object-stream';
|
||||
|
||||
interface DeadlineUnitIndexSignature {
|
||||
[name: string]: number;
|
||||
}
|
||||
|
||||
const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding';
|
||||
const GRPC_ENCODING_HEADER = 'grpc-encoding';
|
||||
const GRPC_MESSAGE_HEADER = 'grpc-message';
|
||||
const GRPC_STATUS_HEADER = 'grpc-status';
|
||||
const GRPC_TIMEOUT_HEADER = 'grpc-timeout';
|
||||
const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/;
|
||||
const deadlineUnitsToMs: DeadlineUnitIndexSignature = {
|
||||
H: 3600000,
|
||||
M: 60000,
|
||||
S: 1000,
|
||||
m: 1,
|
||||
u: 0.001,
|
||||
n: 0.000001,
|
||||
};
|
||||
const defaultResponseHeaders = {
|
||||
// TODO(cjihrig): Remove these encoding headers from the default response
|
||||
// once compression is integrated.
|
||||
[GRPC_ACCEPT_ENCODING_HEADER]: 'identity',
|
||||
[GRPC_ENCODING_HEADER]: 'identity',
|
||||
[http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK,
|
||||
[http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto',
|
||||
};
|
||||
const defaultResponseOptions = {
|
||||
waitForTrailers: true,
|
||||
} as http2.ServerStreamResponseOptions;
|
||||
|
||||
export type ServerStatusResponse = Partial<StatusObject>;
|
||||
|
||||
export type ServerErrorResponse = ServerStatusResponse & Error;
|
||||
|
||||
export type ServerSurfaceCall = {
|
||||
cancelled: boolean;
|
||||
getPeer(): string;
|
||||
sendMetadata(responseMetadata: Metadata): void;
|
||||
} & EventEmitter;
|
||||
|
||||
export type ServerUnaryCall<RequestType, ResponseType> = ServerSurfaceCall & {
|
||||
request: RequestType | null;
|
||||
};
|
||||
export type ServerReadableStream<
|
||||
RequestType,
|
||||
ResponseType
|
||||
> = ServerSurfaceCall & ObjectReadable<RequestType>;
|
||||
export type ServerWritableStream<
|
||||
RequestType,
|
||||
ResponseType
|
||||
> = ServerSurfaceCall &
|
||||
ObjectWritable<ResponseType> & { request: RequestType | null };
|
||||
export type ServerDuplexStream<RequestType, ResponseType> = ServerSurfaceCall &
|
||||
ObjectReadable<RequestType> &
|
||||
ObjectWritable<ResponseType>;
|
||||
|
||||
export class ServerUnaryCallImpl<RequestType, ResponseType> extends EventEmitter
|
||||
implements ServerUnaryCall<RequestType, ResponseType> {
|
||||
cancelled: boolean;
|
||||
request: RequestType | null;
|
||||
|
||||
constructor(
|
||||
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
public metadata: Metadata
|
||||
) {
|
||||
super();
|
||||
this.cancelled = false;
|
||||
this.request = null;
|
||||
this.call.setupSurfaceCall(this);
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
this.call.sendMetadata(responseMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerReadableStreamImpl<RequestType, ResponseType>
|
||||
extends Readable
|
||||
implements ServerReadableStream<RequestType, ResponseType> {
|
||||
cancelled: boolean;
|
||||
|
||||
constructor(
|
||||
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
public metadata: Metadata,
|
||||
public deserialize: Deserialize<RequestType>
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
this.cancelled = false;
|
||||
this.call.setupSurfaceCall(this);
|
||||
this.call.setupReadable(this);
|
||||
}
|
||||
|
||||
_read(size: number) {
|
||||
if (!this.call.consumeUnpushedMessages(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.call.resume();
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
this.call.sendMetadata(responseMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerWritableStreamImpl<RequestType, ResponseType>
|
||||
extends Writable
|
||||
implements ServerWritableStream<RequestType, ResponseType> {
|
||||
cancelled: boolean;
|
||||
request: RequestType | null;
|
||||
private trailingMetadata: Metadata;
|
||||
|
||||
constructor(
|
||||
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
public metadata: Metadata,
|
||||
public serialize: Serialize<ResponseType>
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
this.cancelled = false;
|
||||
this.request = null;
|
||||
this.trailingMetadata = new Metadata();
|
||||
this.call.setupSurfaceCall(this);
|
||||
|
||||
this.on('error', err => {
|
||||
this.call.sendError(err);
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
this.call.sendMetadata(responseMetadata);
|
||||
}
|
||||
|
||||
async _write(
|
||||
chunk: ResponseType,
|
||||
encoding: string,
|
||||
// tslint:disable-next-line:no-any
|
||||
callback: (...args: any[]) => void
|
||||
) {
|
||||
try {
|
||||
const response = await this.call.serializeMessage(chunk);
|
||||
|
||||
if (!this.call.write(response)) {
|
||||
this.call.once('drain', callback);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
err.code = Status.INTERNAL;
|
||||
this.emit('error', err);
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
_final(callback: Function): void {
|
||||
this.call.sendStatus({
|
||||
code: Status.OK,
|
||||
details: 'OK',
|
||||
metadata: this.trailingMetadata,
|
||||
});
|
||||
callback(null);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
end(metadata?: any) {
|
||||
if (metadata) {
|
||||
this.trailingMetadata = metadata;
|
||||
}
|
||||
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerDuplexStreamImpl<RequestType, ResponseType> extends Duplex
|
||||
implements ServerDuplexStream<RequestType, ResponseType> {
|
||||
cancelled: boolean;
|
||||
private trailingMetadata: Metadata;
|
||||
|
||||
constructor(
|
||||
private call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
public metadata: Metadata,
|
||||
public serialize: Serialize<ResponseType>,
|
||||
public deserialize: Deserialize<RequestType>
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
this.cancelled = false;
|
||||
this.trailingMetadata = new Metadata();
|
||||
this.call.setupSurfaceCall(this);
|
||||
this.call.setupReadable(this);
|
||||
|
||||
this.on('error', err => {
|
||||
this.call.sendError(err);
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
this.call.sendMetadata(responseMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
ServerDuplexStreamImpl.prototype._read =
|
||||
ServerReadableStreamImpl.prototype._read;
|
||||
ServerDuplexStreamImpl.prototype._write =
|
||||
ServerWritableStreamImpl.prototype._write;
|
||||
ServerDuplexStreamImpl.prototype._final =
|
||||
ServerWritableStreamImpl.prototype._final;
|
||||
ServerDuplexStreamImpl.prototype.end = ServerWritableStreamImpl.prototype.end;
|
||||
|
||||
// Unary response callback signature.
|
||||
export type sendUnaryData<ResponseType> = (
|
||||
error: ServerErrorResponse | ServerStatusResponse | null,
|
||||
value: ResponseType | null,
|
||||
trailer?: Metadata,
|
||||
flags?: number
|
||||
) => void;
|
||||
|
||||
// User provided handler for unary calls.
|
||||
export type handleUnaryCall<RequestType, ResponseType> = (
|
||||
call: ServerUnaryCall<RequestType, ResponseType>,
|
||||
callback: sendUnaryData<ResponseType>
|
||||
) => void;
|
||||
|
||||
// User provided handler for client streaming calls.
|
||||
export type handleClientStreamingCall<RequestType, ResponseType> = (
|
||||
call: ServerReadableStream<RequestType, ResponseType>,
|
||||
callback: sendUnaryData<ResponseType>
|
||||
) => void;
|
||||
|
||||
// User provided handler for server streaming calls.
|
||||
export type handleServerStreamingCall<RequestType, ResponseType> = (
|
||||
call: ServerWritableStream<RequestType, ResponseType>
|
||||
) => void;
|
||||
|
||||
// User provided handler for bidirectional streaming calls.
|
||||
export type handleBidiStreamingCall<RequestType, ResponseType> = (
|
||||
call: ServerDuplexStream<RequestType, ResponseType>
|
||||
) => void;
|
||||
|
||||
export type HandleCall<RequestType, ResponseType> =
|
||||
| handleUnaryCall<RequestType, ResponseType>
|
||||
| handleClientStreamingCall<RequestType, ResponseType>
|
||||
| handleServerStreamingCall<RequestType, ResponseType>
|
||||
| handleBidiStreamingCall<RequestType, ResponseType>;
|
||||
|
||||
export interface UnaryHandler<RequestType, ResponseType> {
|
||||
func: handleUnaryCall<RequestType, ResponseType>;
|
||||
serialize: Serialize<ResponseType>;
|
||||
deserialize: Deserialize<RequestType>;
|
||||
type: HandlerType;
|
||||
}
|
||||
|
||||
export interface ClientStreamingHandler<RequestType, ResponseType> {
|
||||
func: handleClientStreamingCall<RequestType, ResponseType>;
|
||||
serialize: Serialize<ResponseType>;
|
||||
deserialize: Deserialize<RequestType>;
|
||||
type: HandlerType;
|
||||
}
|
||||
|
||||
export interface ServerStreamingHandler<RequestType, ResponseType> {
|
||||
func: handleServerStreamingCall<RequestType, ResponseType>;
|
||||
serialize: Serialize<ResponseType>;
|
||||
deserialize: Deserialize<RequestType>;
|
||||
type: HandlerType;
|
||||
}
|
||||
|
||||
export interface BidiStreamingHandler<RequestType, ResponseType> {
|
||||
func: handleBidiStreamingCall<RequestType, ResponseType>;
|
||||
serialize: Serialize<ResponseType>;
|
||||
deserialize: Deserialize<RequestType>;
|
||||
type: HandlerType;
|
||||
}
|
||||
|
||||
export type Handler<RequestType, ResponseType> =
|
||||
| UnaryHandler<RequestType, ResponseType>
|
||||
| ClientStreamingHandler<RequestType, ResponseType>
|
||||
| ServerStreamingHandler<RequestType, ResponseType>
|
||||
| BidiStreamingHandler<RequestType, ResponseType>;
|
||||
|
||||
export type HandlerType = 'bidi' | 'clientStream' | 'serverStream' | 'unary';
|
||||
|
||||
const noopTimer: NodeJS.Timer = setTimeout(() => {}, 0);
|
||||
|
||||
// Internal class that wraps the HTTP2 request.
|
||||
export class Http2ServerCallStream<
|
||||
RequestType,
|
||||
ResponseType
|
||||
> extends EventEmitter {
|
||||
cancelled = false;
|
||||
deadline: NodeJS.Timer = noopTimer;
|
||||
private wantTrailers = false;
|
||||
private metadataSent = false;
|
||||
private canPush = false;
|
||||
private isPushPending = false;
|
||||
private bufferedMessages: Array<Buffer | null> = [];
|
||||
private messagesToPush: Array<RequestType | null> = [];
|
||||
|
||||
constructor(
|
||||
private stream: http2.ServerHttp2Stream,
|
||||
private handler: Handler<RequestType, ResponseType>
|
||||
) {
|
||||
super();
|
||||
|
||||
this.stream.once('error', (err: ServerErrorResponse) => {
|
||||
err.code = Status.INTERNAL;
|
||||
this.sendError(err);
|
||||
});
|
||||
|
||||
this.stream.once('close', () => {
|
||||
if (this.stream.rstCode === http2.constants.NGHTTP2_CANCEL) {
|
||||
this.cancelled = true;
|
||||
this.emit('cancelled', 'cancelled');
|
||||
}
|
||||
});
|
||||
|
||||
this.stream.on('drain', () => {
|
||||
this.emit('drain');
|
||||
});
|
||||
}
|
||||
|
||||
sendMetadata(customMetadata?: Metadata) {
|
||||
if (this.metadataSent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.metadataSent = true;
|
||||
const custom = customMetadata ? customMetadata.toHttp2Headers() : null;
|
||||
// TODO(cjihrig): Include compression headers.
|
||||
const headers = Object.assign(defaultResponseHeaders, custom);
|
||||
this.stream.respond(headers, defaultResponseOptions);
|
||||
}
|
||||
|
||||
receiveMetadata(headers: http2.IncomingHttpHeaders) {
|
||||
const metadata = Metadata.fromHttp2Headers(headers);
|
||||
|
||||
// TODO(cjihrig): Receive compression metadata.
|
||||
|
||||
const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER);
|
||||
|
||||
if (timeoutHeader.length > 0) {
|
||||
const match = timeoutHeader[0].toString().match(DEADLINE_REGEX);
|
||||
|
||||
if (match === null) {
|
||||
const err = new Error('Invalid deadline') as ServerErrorResponse;
|
||||
err.code = Status.OUT_OF_RANGE;
|
||||
this.sendError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0;
|
||||
|
||||
this.deadline = setTimeout(handleExpiredDeadline, timeout, this);
|
||||
metadata.remove(GRPC_TIMEOUT_HEADER);
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
receiveUnaryMessage(): Promise<RequestType> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = this.stream;
|
||||
const chunks: Buffer[] = [];
|
||||
let totalLength = 0;
|
||||
|
||||
stream.on('data', (data: Buffer) => {
|
||||
chunks.push(data);
|
||||
totalLength += data.byteLength;
|
||||
});
|
||||
|
||||
stream.once('end', async () => {
|
||||
try {
|
||||
const requestBytes = Buffer.concat(chunks, totalLength);
|
||||
|
||||
resolve(await this.deserializeMessage(requestBytes));
|
||||
} catch (err) {
|
||||
err.code = Status.INTERNAL;
|
||||
this.sendError(err);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
serializeMessage(value: ResponseType) {
|
||||
const messageBuffer = this.handler.serialize(value);
|
||||
|
||||
// TODO(cjihrig): Call compression aware serializeMessage().
|
||||
const byteLength = messageBuffer.byteLength;
|
||||
const output = Buffer.allocUnsafe(byteLength + 5);
|
||||
output.writeUInt8(0, 0);
|
||||
output.writeUInt32BE(byteLength, 1);
|
||||
messageBuffer.copy(output, 5);
|
||||
return output;
|
||||
}
|
||||
|
||||
async deserializeMessage(bytes: Buffer) {
|
||||
// TODO(cjihrig): Call compression aware deserializeMessage().
|
||||
const receivedMessage = bytes.slice(5);
|
||||
|
||||
return this.handler.deserialize(receivedMessage);
|
||||
}
|
||||
|
||||
async sendUnaryMessage(
|
||||
err: ServerErrorResponse | ServerStatusResponse | null,
|
||||
value: ResponseType | null,
|
||||
metadata?: Metadata,
|
||||
flags?: number
|
||||
) {
|
||||
if (!metadata) {
|
||||
metadata = new Metadata();
|
||||
}
|
||||
|
||||
if (err) {
|
||||
err.metadata = metadata;
|
||||
this.sendError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.serializeMessage(value!);
|
||||
|
||||
this.write(response);
|
||||
this.sendStatus({ code: Status.OK, details: 'OK', metadata });
|
||||
} catch (err) {
|
||||
err.code = Status.INTERNAL;
|
||||
this.sendError(err);
|
||||
}
|
||||
}
|
||||
|
||||
sendStatus(statusObj: StatusObject) {
|
||||
if (this.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.deadline);
|
||||
|
||||
if (!this.wantTrailers) {
|
||||
this.wantTrailers = true;
|
||||
this.stream.once('wantTrailers', () => {
|
||||
const trailersToSend = Object.assign(
|
||||
{
|
||||
[GRPC_STATUS_HEADER]: statusObj.code,
|
||||
[GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string),
|
||||
},
|
||||
statusObj.metadata.toHttp2Headers()
|
||||
);
|
||||
|
||||
this.stream.sendTrailers(trailersToSend);
|
||||
});
|
||||
this.sendMetadata();
|
||||
this.stream.end();
|
||||
}
|
||||
}
|
||||
|
||||
sendError(error: ServerErrorResponse | ServerStatusResponse) {
|
||||
const status: StatusObject = {
|
||||
code: Status.UNKNOWN,
|
||||
details: 'message' in error ? error.message : 'Unknown Error',
|
||||
metadata:
|
||||
'metadata' in error && error.metadata !== undefined
|
||||
? error.metadata
|
||||
: new Metadata(),
|
||||
};
|
||||
|
||||
if (
|
||||
'code' in error &&
|
||||
typeof error.code === 'number' &&
|
||||
Number.isInteger(error.code)
|
||||
) {
|
||||
status.code = error.code;
|
||||
|
||||
if ('details' in error && typeof error.details === 'string') {
|
||||
status.details = error.details!;
|
||||
}
|
||||
}
|
||||
|
||||
this.sendStatus(status);
|
||||
}
|
||||
|
||||
write(chunk: Buffer) {
|
||||
if (this.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMetadata();
|
||||
return this.stream.write(chunk);
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.stream.resume();
|
||||
}
|
||||
|
||||
setupSurfaceCall(call: ServerSurfaceCall) {
|
||||
this.once('cancelled', reason => {
|
||||
call.cancelled = true;
|
||||
call.emit('cancelled', reason);
|
||||
});
|
||||
}
|
||||
|
||||
setupReadable(
|
||||
readable:
|
||||
| ServerReadableStream<RequestType, ResponseType>
|
||||
| ServerDuplexStream<RequestType, ResponseType>
|
||||
) {
|
||||
const decoder = new StreamDecoder();
|
||||
|
||||
this.stream.on('data', async (data: Buffer) => {
|
||||
const messages = decoder.write(data);
|
||||
|
||||
for (const message of messages) {
|
||||
this.pushOrBufferMessage(readable, message);
|
||||
}
|
||||
});
|
||||
|
||||
this.stream.once('end', () => {
|
||||
this.pushOrBufferMessage(readable, null);
|
||||
});
|
||||
}
|
||||
|
||||
consumeUnpushedMessages(
|
||||
readable:
|
||||
| ServerReadableStream<RequestType, ResponseType>
|
||||
| ServerDuplexStream<RequestType, ResponseType>
|
||||
): boolean {
|
||||
this.canPush = true;
|
||||
|
||||
while (this.messagesToPush.length > 0) {
|
||||
const nextMessage = this.messagesToPush.shift();
|
||||
const canPush = readable.push(nextMessage);
|
||||
|
||||
if (nextMessage === null || canPush === false) {
|
||||
this.canPush = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this.canPush;
|
||||
}
|
||||
|
||||
private pushOrBufferMessage(
|
||||
readable:
|
||||
| ServerReadableStream<RequestType, ResponseType>
|
||||
| ServerDuplexStream<RequestType, ResponseType>,
|
||||
messageBytes: Buffer | null
|
||||
): void {
|
||||
if (this.isPushPending) {
|
||||
this.bufferedMessages.push(messageBytes);
|
||||
} else {
|
||||
this.pushMessage(readable, messageBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private async pushMessage(
|
||||
readable:
|
||||
| ServerReadableStream<RequestType, ResponseType>
|
||||
| ServerDuplexStream<RequestType, ResponseType>,
|
||||
messageBytes: Buffer | null
|
||||
) {
|
||||
if (messageBytes === null) {
|
||||
if (this.canPush) {
|
||||
readable.push(null);
|
||||
} else {
|
||||
this.messagesToPush.push(null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPushPending = true;
|
||||
|
||||
try {
|
||||
const deserialized = await this.deserializeMessage(messageBytes);
|
||||
|
||||
if (this.canPush) {
|
||||
if (!readable.push(deserialized)) {
|
||||
this.canPush = false;
|
||||
this.stream.pause();
|
||||
}
|
||||
} else {
|
||||
this.messagesToPush.push(deserialized);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore any remaining messages when errors occur.
|
||||
this.bufferedMessages.length = 0;
|
||||
|
||||
err.code = Status.INTERNAL;
|
||||
readable.emit('error', err);
|
||||
}
|
||||
|
||||
this.isPushPending = false;
|
||||
|
||||
if (this.bufferedMessages.length > 0) {
|
||||
this.pushMessage(
|
||||
readable,
|
||||
this.bufferedMessages.shift() as Buffer | null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable:no-any
|
||||
type UntypedServerCall = Http2ServerCallStream<any, any>;
|
||||
|
||||
function handleExpiredDeadline(call: UntypedServerCall) {
|
||||
const err = new Error('Deadline exceeded') as ServerErrorResponse;
|
||||
err.code = Status.DEADLINE_EXCEEDED;
|
||||
|
||||
call.sendError(err);
|
||||
call.cancelled = true;
|
||||
call.emit('cancelled', 'deadline');
|
||||
}
|
108
node_modules/@grpc/grpc-js/src/server-credentials.ts
generated
vendored
Normal file
108
node_modules/@grpc/grpc-js/src/server-credentials.ts
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { SecureServerOptions } from 'http2';
|
||||
import {CIPHER_SUITES, getDefaultRootsData} from './tls-helpers';
|
||||
|
||||
export interface KeyCertPair {
|
||||
private_key: Buffer;
|
||||
cert_chain: Buffer;
|
||||
}
|
||||
|
||||
export abstract class ServerCredentials {
|
||||
abstract _isSecure(): boolean;
|
||||
abstract _getSettings(): SecureServerOptions | null;
|
||||
|
||||
static createInsecure(): ServerCredentials {
|
||||
return new InsecureServerCredentials();
|
||||
}
|
||||
|
||||
static createSsl(
|
||||
rootCerts: Buffer | null,
|
||||
keyCertPairs: KeyCertPair[],
|
||||
checkClientCertificate = false
|
||||
): ServerCredentials {
|
||||
if (rootCerts !== null && !Buffer.isBuffer(rootCerts)) {
|
||||
throw new TypeError('rootCerts must be null or a Buffer');
|
||||
}
|
||||
|
||||
if (!Array.isArray(keyCertPairs)) {
|
||||
throw new TypeError('keyCertPairs must be an array');
|
||||
}
|
||||
|
||||
if (typeof checkClientCertificate !== 'boolean') {
|
||||
throw new TypeError('checkClientCertificate must be a boolean');
|
||||
}
|
||||
|
||||
const cert = [];
|
||||
const key = [];
|
||||
|
||||
for (let i = 0; i < keyCertPairs.length; i++) {
|
||||
const pair = keyCertPairs[i];
|
||||
|
||||
if (pair === null || typeof pair !== 'object') {
|
||||
throw new TypeError(`keyCertPair[${i}] must be an object`);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(pair.private_key)) {
|
||||
throw new TypeError(`keyCertPair[${i}].private_key must be a Buffer`);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(pair.cert_chain)) {
|
||||
throw new TypeError(`keyCertPair[${i}].cert_chain must be a Buffer`);
|
||||
}
|
||||
|
||||
cert.push(pair.cert_chain);
|
||||
key.push(pair.private_key);
|
||||
}
|
||||
|
||||
return new SecureServerCredentials({
|
||||
ca: rootCerts || getDefaultRootsData() || undefined,
|
||||
cert,
|
||||
key,
|
||||
requestCert: checkClientCertificate,
|
||||
ciphers: CIPHER_SUITES
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class InsecureServerCredentials extends ServerCredentials {
|
||||
_isSecure(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
_getSettings(): null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class SecureServerCredentials extends ServerCredentials {
|
||||
private options: SecureServerOptions;
|
||||
|
||||
constructor(options: SecureServerOptions) {
|
||||
super();
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
_isSecure(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
_getSettings(): SecureServerOptions {
|
||||
return this.options;
|
||||
}
|
||||
}
|
504
node_modules/@grpc/grpc-js/src/server.ts
generated
vendored
Normal file
504
node_modules/@grpc/grpc-js/src/server.ts
generated
vendored
Normal file
@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as http2 from 'http2';
|
||||
import { AddressInfo, ListenOptions } from 'net';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { ServiceError } from './call';
|
||||
import { Status } from './constants';
|
||||
import { Deserialize, Serialize, ServiceDefinition } from './make-client';
|
||||
import { Metadata } from './metadata';
|
||||
import {
|
||||
BidiStreamingHandler,
|
||||
ClientStreamingHandler,
|
||||
HandleCall,
|
||||
Handler,
|
||||
HandlerType,
|
||||
Http2ServerCallStream,
|
||||
sendUnaryData,
|
||||
ServerDuplexStream,
|
||||
ServerDuplexStreamImpl,
|
||||
ServerReadableStream,
|
||||
ServerReadableStreamImpl,
|
||||
ServerStreamingHandler,
|
||||
ServerUnaryCall,
|
||||
ServerUnaryCallImpl,
|
||||
ServerWritableStream,
|
||||
ServerWritableStreamImpl,
|
||||
UnaryHandler,
|
||||
ServerErrorResponse,
|
||||
ServerStatusResponse,
|
||||
} from './server-call';
|
||||
import { ServerCredentials } from './server-credentials';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
const unimplementedStatusResponse: Partial<ServiceError> = {
|
||||
code: Status.UNIMPLEMENTED,
|
||||
details: 'The server does not implement this method',
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
|
||||
// tslint:disable:no-any
|
||||
type UntypedUnaryHandler = UnaryHandler<any, any>;
|
||||
type UntypedClientStreamingHandler = ClientStreamingHandler<any, any>;
|
||||
type UntypedServerStreamingHandler = ServerStreamingHandler<any, any>;
|
||||
type UntypedBidiStreamingHandler = BidiStreamingHandler<any, any>;
|
||||
export type UntypedHandleCall = HandleCall<any, any>;
|
||||
type UntypedHandler = Handler<any, any>;
|
||||
export interface UntypedServiceImplementation {
|
||||
[name: string]: UntypedHandleCall;
|
||||
}
|
||||
|
||||
const defaultHandler = {
|
||||
unary(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>): void {
|
||||
callback(unimplementedStatusResponse as ServiceError, null);
|
||||
},
|
||||
clientStream(
|
||||
call: ServerReadableStream<any, any>,
|
||||
callback: sendUnaryData<any>
|
||||
): void {
|
||||
callback(unimplementedStatusResponse as ServiceError, null);
|
||||
},
|
||||
serverStream(call: ServerWritableStream<any, any>): void {
|
||||
call.emit('error', unimplementedStatusResponse);
|
||||
},
|
||||
bidi(call: ServerDuplexStream<any, any>): void {
|
||||
call.emit('error', unimplementedStatusResponse);
|
||||
},
|
||||
};
|
||||
// tslint:enable:no-any
|
||||
|
||||
export class Server {
|
||||
private http2Server:
|
||||
| http2.Http2Server
|
||||
| http2.Http2SecureServer
|
||||
| null = null;
|
||||
private handlers: Map<string, UntypedHandler> = new Map<
|
||||
string,
|
||||
UntypedHandler
|
||||
>();
|
||||
private sessions = new Set<http2.ServerHttp2Session>();
|
||||
private started = false;
|
||||
private options: ChannelOptions;
|
||||
|
||||
constructor(options?: ChannelOptions) {
|
||||
this.options = options ?? {};
|
||||
}
|
||||
|
||||
addProtoService(): void {
|
||||
throw new Error('Not implemented. Use addService() instead');
|
||||
}
|
||||
|
||||
addService(
|
||||
service: ServiceDefinition,
|
||||
implementation: UntypedServiceImplementation
|
||||
): void {
|
||||
if (this.started === true) {
|
||||
throw new Error("Can't add a service to a started server.");
|
||||
}
|
||||
|
||||
if (
|
||||
service === null ||
|
||||
typeof service !== 'object' ||
|
||||
implementation === null ||
|
||||
typeof implementation !== 'object'
|
||||
) {
|
||||
throw new Error('addService() requires two objects as arguments');
|
||||
}
|
||||
|
||||
const serviceKeys = Object.keys(service);
|
||||
|
||||
if (serviceKeys.length === 0) {
|
||||
throw new Error('Cannot add an empty service to a server');
|
||||
}
|
||||
|
||||
serviceKeys.forEach(name => {
|
||||
const attrs = service[name];
|
||||
let methodType: HandlerType;
|
||||
|
||||
if (attrs.requestStream) {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'bidi';
|
||||
} else {
|
||||
methodType = 'clientStream';
|
||||
}
|
||||
} else {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'serverStream';
|
||||
} else {
|
||||
methodType = 'unary';
|
||||
}
|
||||
}
|
||||
|
||||
let implFn = implementation[name];
|
||||
let impl;
|
||||
|
||||
if (implFn === undefined && typeof attrs.originalName === 'string') {
|
||||
implFn = implementation[attrs.originalName];
|
||||
}
|
||||
|
||||
if (implFn !== undefined) {
|
||||
impl = implFn.bind(implementation);
|
||||
} else {
|
||||
impl = defaultHandler[methodType];
|
||||
}
|
||||
|
||||
const success = this.register(
|
||||
attrs.path,
|
||||
impl as UntypedHandleCall,
|
||||
attrs.responseSerialize,
|
||||
attrs.requestDeserialize,
|
||||
methodType
|
||||
);
|
||||
|
||||
if (success === false) {
|
||||
throw new Error(`Method handler for ${attrs.path} already provided.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind(port: string, creds: ServerCredentials): void {
|
||||
throw new Error('Not implemented. Use bindAsync() instead');
|
||||
}
|
||||
|
||||
bindAsync(
|
||||
port: string,
|
||||
creds: ServerCredentials,
|
||||
callback: (error: Error | null, port: number) => void
|
||||
): void {
|
||||
if (this.started === true) {
|
||||
throw new Error('server is already started');
|
||||
}
|
||||
|
||||
if (typeof port !== 'string') {
|
||||
throw new TypeError('port must be a string');
|
||||
}
|
||||
|
||||
if (creds === null || typeof creds !== 'object') {
|
||||
throw new TypeError('creds must be an object');
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new TypeError('callback must be a function');
|
||||
}
|
||||
|
||||
const url = new URL(`http://${port}`);
|
||||
const options: ListenOptions = { host: url.hostname, port: +url.port };
|
||||
const serverOptions: http2.ServerOptions = {};
|
||||
if ('grpc.max_concurrent_streams' in this.options) {
|
||||
serverOptions.settings = {maxConcurrentStreams: this.options['grpc.max_concurrent_streams']};
|
||||
}
|
||||
|
||||
if (creds._isSecure()) {
|
||||
const secureServerOptions = Object.assign(serverOptions, creds._getSettings()!);
|
||||
this.http2Server = http2.createSecureServer(secureServerOptions);
|
||||
} else {
|
||||
this.http2Server = http2.createServer(serverOptions);
|
||||
}
|
||||
|
||||
this.http2Server.setTimeout(0, noop);
|
||||
this._setupHandlers();
|
||||
|
||||
function onError(err: Error): void {
|
||||
callback(err, -1);
|
||||
}
|
||||
|
||||
this.http2Server.once('error', onError);
|
||||
|
||||
this.http2Server.listen(options, () => {
|
||||
const server = this.http2Server as
|
||||
| http2.Http2Server
|
||||
| http2.Http2SecureServer;
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
|
||||
server.removeListener('error', onError);
|
||||
callback(null, port);
|
||||
});
|
||||
}
|
||||
|
||||
forceShutdown(): void {
|
||||
// Close the server if it is still running.
|
||||
if (this.http2Server && this.http2Server.listening) {
|
||||
this.http2Server.close();
|
||||
}
|
||||
|
||||
this.started = false;
|
||||
|
||||
// Always destroy any available sessions. It's possible that one or more
|
||||
// tryShutdown() calls are in progress. Don't wait on them to finish.
|
||||
this.sessions.forEach(session => {
|
||||
// Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to
|
||||
// recognize destroy(code) as a valid signature.
|
||||
// tslint:disable-next-line:no-any
|
||||
session.destroy(http2.constants.NGHTTP2_CANCEL as any);
|
||||
});
|
||||
this.sessions.clear();
|
||||
}
|
||||
|
||||
register<RequestType, ResponseType>(
|
||||
name: string,
|
||||
handler: HandleCall<RequestType, ResponseType>,
|
||||
serialize: Serialize<ResponseType>,
|
||||
deserialize: Deserialize<RequestType>,
|
||||
type: string
|
||||
): boolean {
|
||||
if (this.handlers.has(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.handlers.set(name, {
|
||||
func: handler,
|
||||
serialize,
|
||||
deserialize,
|
||||
type,
|
||||
} as UntypedHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
if (this.http2Server === null || this.http2Server.listening !== true) {
|
||||
throw new Error('server must be bound in order to start');
|
||||
}
|
||||
|
||||
if (this.started === true) {
|
||||
throw new Error('server is already started');
|
||||
}
|
||||
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
tryShutdown(callback: (error?: Error) => void): void {
|
||||
let pendingChecks = 0;
|
||||
|
||||
function maybeCallback(): void {
|
||||
pendingChecks--;
|
||||
|
||||
if (pendingChecks === 0) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// Close the server if necessary.
|
||||
this.started = false;
|
||||
|
||||
if (this.http2Server && this.http2Server.listening) {
|
||||
pendingChecks++;
|
||||
this.http2Server.close(maybeCallback);
|
||||
}
|
||||
|
||||
// If any sessions are active, close them gracefully.
|
||||
pendingChecks += this.sessions.size;
|
||||
this.sessions.forEach(session => {
|
||||
session.close(maybeCallback);
|
||||
});
|
||||
|
||||
// If the server is closed and there are no active sessions, just call back.
|
||||
if (pendingChecks === 0) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
addHttp2Port(): void {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
|
||||
private _setupHandlers(): void {
|
||||
if (this.http2Server === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.http2Server.on(
|
||||
'stream',
|
||||
(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) => {
|
||||
const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE];
|
||||
|
||||
if (
|
||||
typeof contentType !== 'string' ||
|
||||
!contentType.startsWith('application/grpc')
|
||||
) {
|
||||
stream.respond(
|
||||
{
|
||||
[http2.constants.HTTP2_HEADER_STATUS]:
|
||||
http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE,
|
||||
},
|
||||
{ endStream: true }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const path = headers[http2.constants.HTTP2_HEADER_PATH] as string;
|
||||
const handler = this.handlers.get(path);
|
||||
|
||||
if (handler === undefined) {
|
||||
throw unimplementedStatusResponse;
|
||||
}
|
||||
|
||||
const call = new Http2ServerCallStream(stream, handler);
|
||||
const metadata: Metadata = call.receiveMetadata(headers) as Metadata;
|
||||
|
||||
switch (handler.type) {
|
||||
case 'unary':
|
||||
handleUnary(call, handler as UntypedUnaryHandler, metadata);
|
||||
break;
|
||||
case 'clientStream':
|
||||
handleClientStreaming(
|
||||
call,
|
||||
handler as UntypedClientStreamingHandler,
|
||||
metadata
|
||||
);
|
||||
break;
|
||||
case 'serverStream':
|
||||
handleServerStreaming(
|
||||
call,
|
||||
handler as UntypedServerStreamingHandler,
|
||||
metadata
|
||||
);
|
||||
break;
|
||||
case 'bidi':
|
||||
handleBidiStreaming(
|
||||
call,
|
||||
handler as UntypedBidiStreamingHandler,
|
||||
metadata
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown handler type: ${handler.type}`);
|
||||
}
|
||||
} catch (err) {
|
||||
const call = new Http2ServerCallStream(stream, null!);
|
||||
|
||||
if (err.code === undefined) {
|
||||
err.code = Status.INTERNAL;
|
||||
}
|
||||
|
||||
call.sendError(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.http2Server.on('session', session => {
|
||||
if (!this.started) {
|
||||
session.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sessions.add(session);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUnary<RequestType, ResponseType>(
|
||||
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
handler: UnaryHandler<RequestType, ResponseType>,
|
||||
metadata: Metadata
|
||||
): Promise<void> {
|
||||
const emitter = new ServerUnaryCallImpl<RequestType, ResponseType>(
|
||||
call,
|
||||
metadata
|
||||
);
|
||||
const request = await call.receiveUnaryMessage();
|
||||
|
||||
if (request === undefined || call.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
emitter.request = request;
|
||||
handler.func(
|
||||
emitter,
|
||||
(
|
||||
err: ServerErrorResponse | ServerStatusResponse | null,
|
||||
value: ResponseType | null,
|
||||
trailer?: Metadata,
|
||||
flags?: number
|
||||
) => {
|
||||
call.sendUnaryMessage(err, value, trailer, flags);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleClientStreaming<RequestType, ResponseType>(
|
||||
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
handler: ClientStreamingHandler<RequestType, ResponseType>,
|
||||
metadata: Metadata
|
||||
): void {
|
||||
const stream = new ServerReadableStreamImpl<RequestType, ResponseType>(
|
||||
call,
|
||||
metadata,
|
||||
handler.deserialize
|
||||
);
|
||||
|
||||
function respond(
|
||||
err: ServerErrorResponse | ServerStatusResponse | null,
|
||||
value: ResponseType | null,
|
||||
trailer?: Metadata,
|
||||
flags?: number
|
||||
) {
|
||||
stream.destroy();
|
||||
call.sendUnaryMessage(err, value, trailer, flags);
|
||||
}
|
||||
|
||||
if (call.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream.on('error', respond);
|
||||
handler.func(stream, respond);
|
||||
}
|
||||
|
||||
async function handleServerStreaming<RequestType, ResponseType>(
|
||||
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
handler: ServerStreamingHandler<RequestType, ResponseType>,
|
||||
metadata: Metadata
|
||||
): Promise<void> {
|
||||
const request = await call.receiveUnaryMessage();
|
||||
|
||||
if (request === undefined || call.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = new ServerWritableStreamImpl<RequestType, ResponseType>(
|
||||
call,
|
||||
metadata,
|
||||
handler.serialize
|
||||
);
|
||||
|
||||
stream.request = request;
|
||||
handler.func(stream);
|
||||
}
|
||||
|
||||
function handleBidiStreaming<RequestType, ResponseType>(
|
||||
call: Http2ServerCallStream<RequestType, ResponseType>,
|
||||
handler: BidiStreamingHandler<RequestType, ResponseType>,
|
||||
metadata: Metadata
|
||||
): void {
|
||||
const stream = new ServerDuplexStreamImpl<RequestType, ResponseType>(
|
||||
call,
|
||||
metadata,
|
||||
handler.serialize,
|
||||
handler.deserialize
|
||||
);
|
||||
|
||||
if (call.cancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler.func(stream);
|
||||
}
|
309
node_modules/@grpc/grpc-js/src/service-config.ts
generated
vendored
Normal file
309
node_modules/@grpc/grpc-js/src/service-config.ts
generated
vendored
Normal file
@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/* This file implements gRFC A2 and the service config spec:
|
||||
* https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
|
||||
* https://github.com/grpc/grpc/blob/master/doc/service_config.md. Each
|
||||
* function here takes an object with unknown structure and returns its
|
||||
* specific object type if the input has the right structure, and throws an
|
||||
* error otherwise. */
|
||||
|
||||
/* The any type is purposely used here. All functions validate their input at
|
||||
* runtime */
|
||||
/* tslint:disable:no-any */
|
||||
|
||||
import * as lbconfig from './load-balancing-config';
|
||||
import * as os from 'os';
|
||||
|
||||
export interface MethodConfigName {
|
||||
service: string;
|
||||
method?: string;
|
||||
}
|
||||
|
||||
export interface MethodConfig {
|
||||
name: MethodConfigName[];
|
||||
waitForReady?: boolean;
|
||||
timeout?: string;
|
||||
maxRequestBytes?: number;
|
||||
maxResponseBytes?: number;
|
||||
}
|
||||
|
||||
export interface ServiceConfig {
|
||||
loadBalancingPolicy?: string;
|
||||
loadBalancingConfig: lbconfig.LoadBalancingConfig[];
|
||||
methodConfig: MethodConfig[];
|
||||
}
|
||||
|
||||
export interface ServiceConfigCanaryConfig {
|
||||
clientLanguage?: string[];
|
||||
percentage?: number;
|
||||
clientHostname?: string[];
|
||||
serviceConfig: ServiceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognizes a number with up to 9 digits after the decimal point, followed by
|
||||
* an "s", representing a number of seconds.
|
||||
*/
|
||||
const TIMEOUT_REGEX = /^\d+(\.\d{1,9})?s$/;
|
||||
|
||||
/**
|
||||
* Client language name used for determining whether this client matches a
|
||||
* `ServiceConfigCanaryConfig`'s `clientLanguage` list.
|
||||
*/
|
||||
const CLIENT_LANGUAGE_STRING = 'node';
|
||||
|
||||
function validateName(obj: any): MethodConfigName {
|
||||
if (!('service' in obj) || typeof obj.service !== 'string') {
|
||||
throw new Error('Invalid method config name: invalid service');
|
||||
}
|
||||
const result: MethodConfigName = {
|
||||
service: obj.service,
|
||||
};
|
||||
if ('method' in obj) {
|
||||
if (typeof obj.method === 'string') {
|
||||
result.method = obj.method;
|
||||
} else {
|
||||
throw new Error('Invalid method config name: invalid method');
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateMethodConfig(obj: any): MethodConfig {
|
||||
const result: MethodConfig = {
|
||||
name: [],
|
||||
};
|
||||
if (!('name' in obj) || !Array.isArray(obj.name)) {
|
||||
throw new Error('Invalid method config: invalid name array');
|
||||
}
|
||||
for (const name of obj.name) {
|
||||
result.name.push(validateName(name));
|
||||
}
|
||||
if ('waitForReady' in obj) {
|
||||
if (typeof obj.waitForReady !== 'boolean') {
|
||||
throw new Error('Invalid method config: invalid waitForReady');
|
||||
}
|
||||
result.waitForReady = obj.waitForReady;
|
||||
}
|
||||
if ('timeout' in obj) {
|
||||
if (
|
||||
!(typeof obj.timeout === 'string') ||
|
||||
!TIMEOUT_REGEX.test(obj.timeout)
|
||||
) {
|
||||
throw new Error('Invalid method config: invalid timeout');
|
||||
}
|
||||
result.timeout = obj.timeout;
|
||||
}
|
||||
if ('maxRequestBytes' in obj) {
|
||||
if (typeof obj.maxRequestBytes !== 'number') {
|
||||
throw new Error('Invalid method config: invalid maxRequestBytes');
|
||||
}
|
||||
result.maxRequestBytes = obj.maxRequestBytes;
|
||||
}
|
||||
if ('maxResponseBytes' in obj) {
|
||||
if (typeof obj.maxResponseBytes !== 'number') {
|
||||
throw new Error('Invalid method config: invalid maxRequestBytes');
|
||||
}
|
||||
result.maxResponseBytes = obj.maxResponseBytes;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function validateServiceConfig(obj: any): ServiceConfig {
|
||||
const result: ServiceConfig = {
|
||||
loadBalancingConfig: [],
|
||||
methodConfig: [],
|
||||
};
|
||||
if ('loadBalancingPolicy' in obj) {
|
||||
if (typeof obj.loadBalancingPolicy === 'string') {
|
||||
result.loadBalancingPolicy = obj.loadBalancingPolicy;
|
||||
} else {
|
||||
throw new Error('Invalid service config: invalid loadBalancingPolicy');
|
||||
}
|
||||
}
|
||||
if ('loadBalancingConfig' in obj) {
|
||||
if (Array.isArray(obj.loadBalancingConfig)) {
|
||||
for (const config of obj.loadBalancingConfig) {
|
||||
result.loadBalancingConfig.push(lbconfig.validateConfig(config));
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid service config: invalid loadBalancingConfig');
|
||||
}
|
||||
}
|
||||
if ('methodConfig' in obj) {
|
||||
if (Array.isArray(obj.methodConfig)) {
|
||||
for (const methodConfig of obj.methodConfig) {
|
||||
result.methodConfig.push(validateMethodConfig(methodConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Validate method name uniqueness
|
||||
const seenMethodNames: MethodConfigName[] = [];
|
||||
for (const methodConfig of result.methodConfig) {
|
||||
for (const name of methodConfig.name) {
|
||||
for (const seenName of seenMethodNames) {
|
||||
if (
|
||||
name.service === seenName.service &&
|
||||
name.method === seenName.method
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid service config: duplicate name ${name.service}/${name.method}`
|
||||
);
|
||||
}
|
||||
}
|
||||
seenMethodNames.push(name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateCanaryConfig(obj: any): ServiceConfigCanaryConfig {
|
||||
if (!('serviceConfig' in obj)) {
|
||||
throw new Error('Invalid service config choice: missing service config');
|
||||
}
|
||||
const result: ServiceConfigCanaryConfig = {
|
||||
serviceConfig: validateServiceConfig(obj.serviceConfig),
|
||||
};
|
||||
if ('clientLanguage' in obj) {
|
||||
if (Array.isArray(obj.clientLanguage)) {
|
||||
result.clientLanguage = [];
|
||||
for (const lang of obj.clientLanguage) {
|
||||
if (typeof lang === 'string') {
|
||||
result.clientLanguage.push(lang);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Invalid service config choice: invalid clientLanguage'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid service config choice: invalid clientLanguage');
|
||||
}
|
||||
}
|
||||
if ('clientHostname' in obj) {
|
||||
if (Array.isArray(obj.clientHostname)) {
|
||||
result.clientHostname = [];
|
||||
for (const lang of obj.clientHostname) {
|
||||
if (typeof lang === 'string') {
|
||||
result.clientHostname.push(lang);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Invalid service config choice: invalid clientHostname'
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid service config choice: invalid clientHostname');
|
||||
}
|
||||
}
|
||||
if ('percentage' in obj) {
|
||||
if (
|
||||
typeof obj.percentage === 'number' &&
|
||||
0 <= obj.percentage &&
|
||||
obj.percentage <= 100
|
||||
) {
|
||||
result.percentage = obj.percentage;
|
||||
} else {
|
||||
throw new Error('Invalid service config choice: invalid percentage');
|
||||
}
|
||||
}
|
||||
// Validate that no unexpected fields are present
|
||||
const allowedFields = [
|
||||
'clientLanguage',
|
||||
'percentage',
|
||||
'clientHostname',
|
||||
'serviceConfig',
|
||||
];
|
||||
for (const field in obj) {
|
||||
if (!allowedFields.includes(field)) {
|
||||
throw new Error(
|
||||
`Invalid service config choice: unexpected field ${field}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateAndSelectCanaryConfig(
|
||||
obj: any,
|
||||
percentage: number
|
||||
): ServiceConfig {
|
||||
if (!Array.isArray(obj)) {
|
||||
throw new Error('Invalid service config list');
|
||||
}
|
||||
for (const config of obj) {
|
||||
const validatedConfig = validateCanaryConfig(config);
|
||||
/* For each field, we check if it is present, then only discard the
|
||||
* config if the field value does not match the current client */
|
||||
if (
|
||||
typeof validatedConfig.percentage === 'number' &&
|
||||
percentage > validatedConfig.percentage
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(validatedConfig.clientHostname)) {
|
||||
let hostnameMatched = false;
|
||||
for (const hostname of validatedConfig.clientHostname) {
|
||||
if (hostname === os.hostname()) {
|
||||
hostnameMatched = true;
|
||||
}
|
||||
}
|
||||
if (!hostnameMatched) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(validatedConfig.clientLanguage)) {
|
||||
let languageMatched = false;
|
||||
for (const language of validatedConfig.clientLanguage) {
|
||||
if (language === CLIENT_LANGUAGE_STRING) {
|
||||
languageMatched = true;
|
||||
}
|
||||
}
|
||||
if (!languageMatched) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return validatedConfig.serviceConfig;
|
||||
}
|
||||
throw new Error('No matching service config found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the "grpc_config" record among the TXT records, parse its value as JSON, validate its contents,
|
||||
* and select a service config with selection fields that all match this client. Most of these steps
|
||||
* can fail with an error; the caller must handle any errors thrown this way.
|
||||
* @param txtRecord The TXT record array that is output from a successful call to dns.resolveTxt
|
||||
* @param percentage A number chosen from the range [0, 100) that is used to select which config to use
|
||||
* @return The service configuration to use, given the percentage value, or null if the service config
|
||||
* data has a valid format but none of the options match the current client.
|
||||
*/
|
||||
export function extractAndSelectServiceConfig(
|
||||
txtRecord: string[][],
|
||||
percentage: number
|
||||
): ServiceConfig | null {
|
||||
for (const record of txtRecord) {
|
||||
if (record.length > 0 && record[0].startsWith('grpc_config=')) {
|
||||
/* Treat the list of strings in this record as a single string and remove
|
||||
* "grpc_config=" from the beginning. The rest should be a JSON string */
|
||||
const recordString = record.join('').substring('grpc_config='.length);
|
||||
const recordJson: any = JSON.parse(recordString);
|
||||
return validateAndSelectCanaryConfig(recordJson, percentage);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
80
node_modules/@grpc/grpc-js/src/status-builder.ts
generated
vendored
Normal file
80
node_modules/@grpc/grpc-js/src/status-builder.ts
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { StatusObject } from './call-stream';
|
||||
import { Status } from './constants';
|
||||
import { Metadata } from './metadata';
|
||||
|
||||
/**
|
||||
* A builder for gRPC status objects.
|
||||
*/
|
||||
export class StatusBuilder {
|
||||
private code: Status | null;
|
||||
private details: string | null;
|
||||
private metadata: Metadata | null;
|
||||
|
||||
constructor() {
|
||||
this.code = null;
|
||||
this.details = null;
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a status code to the builder.
|
||||
*/
|
||||
withCode(code: Status): this {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds details to the builder.
|
||||
*/
|
||||
withDetails(details: string): this {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds metadata to the builder.
|
||||
*/
|
||||
withMetadata(metadata: Metadata): this {
|
||||
this.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the status object.
|
||||
*/
|
||||
build(): Partial<StatusObject> {
|
||||
const status: Partial<StatusObject> = {};
|
||||
|
||||
if (this.code !== null) {
|
||||
status.code = this.code;
|
||||
}
|
||||
|
||||
if (this.details !== null) {
|
||||
status.details = this.details;
|
||||
}
|
||||
|
||||
if (this.metadata !== null) {
|
||||
status.metadata = this.metadata;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
105
node_modules/@grpc/grpc-js/src/stream-decoder.ts
generated
vendored
Normal file
105
node_modules/@grpc/grpc-js/src/stream-decoder.ts
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
enum ReadState {
|
||||
NO_DATA,
|
||||
READING_SIZE,
|
||||
READING_MESSAGE,
|
||||
}
|
||||
|
||||
export class StreamDecoder {
|
||||
private readState: ReadState = ReadState.NO_DATA;
|
||||
private readCompressFlag: Buffer = Buffer.alloc(1);
|
||||
private readPartialSize: Buffer = Buffer.alloc(4);
|
||||
private readSizeRemaining = 4;
|
||||
private readMessageSize = 0;
|
||||
private readPartialMessage: Buffer[] = [];
|
||||
private readMessageRemaining = 0;
|
||||
|
||||
write(data: Buffer): Buffer[] {
|
||||
let readHead = 0;
|
||||
let toRead: number;
|
||||
const result: Buffer[] = [];
|
||||
|
||||
while (readHead < data.length) {
|
||||
switch (this.readState) {
|
||||
case ReadState.NO_DATA:
|
||||
this.readCompressFlag = data.slice(readHead, readHead + 1);
|
||||
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;
|
||||
if (this.readMessageRemaining > 0) {
|
||||
this.readState = ReadState.READING_MESSAGE;
|
||||
} else {
|
||||
const message = Buffer.concat(
|
||||
[this.readCompressFlag, this.readPartialSize],
|
||||
5
|
||||
);
|
||||
|
||||
this.readState = ReadState.NO_DATA;
|
||||
result.push(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 framedMessageBuffers = [
|
||||
this.readCompressFlag,
|
||||
this.readPartialSize,
|
||||
].concat(this.readPartialMessage);
|
||||
const framedMessage = Buffer.concat(
|
||||
framedMessageBuffers,
|
||||
this.readMessageSize + 5
|
||||
);
|
||||
|
||||
this.readState = ReadState.NO_DATA;
|
||||
result.push(framedMessage);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected read state');
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
176
node_modules/@grpc/grpc-js/src/subchannel-pool.ts
generated
vendored
Normal file
176
node_modules/@grpc/grpc-js/src/subchannel-pool.ts
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { ChannelOptions, channelOptionsEqual } from './channel-options';
|
||||
import { Subchannel } from './subchannel';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
|
||||
// 10 seconds in milliseconds. This value is arbitrary.
|
||||
/**
|
||||
* The amount of time in between checks for dropping subchannels that have no
|
||||
* other references
|
||||
*/
|
||||
const REF_CHECK_INTERVAL = 10_000;
|
||||
|
||||
export class SubchannelPool {
|
||||
private pool: {
|
||||
[channelTarget: string]: {
|
||||
[subchannelTarget: string]: Array<{
|
||||
channelArguments: ChannelOptions;
|
||||
channelCredentials: ChannelCredentials;
|
||||
subchannel: Subchannel;
|
||||
}>;
|
||||
};
|
||||
} = Object.create(null);
|
||||
|
||||
/**
|
||||
* A timer of a task performing a periodic subchannel cleanup.
|
||||
*/
|
||||
private cleanupTimer: NodeJS.Timer | null = null;
|
||||
|
||||
/**
|
||||
* A pool of subchannels use for making connections. Subchannels with the
|
||||
* exact same parameters will be reused.
|
||||
* @param global If true, this is the global subchannel pool. Otherwise, it
|
||||
* is the pool for a single channel.
|
||||
*/
|
||||
constructor(private global: boolean) {}
|
||||
|
||||
/**
|
||||
* Unrefs all unused subchannels and cancels the cleanup task if all
|
||||
* subchannels have been unrefed.
|
||||
*/
|
||||
unrefUnusedSubchannels(): void {
|
||||
let allSubchannelsUnrefed = true;
|
||||
|
||||
/* These objects are created with Object.create(null), so they do not
|
||||
* have a prototype, which means that for (... in ...) loops over them
|
||||
* do not need to be filtered */
|
||||
// tslint:disable-next-line:forin
|
||||
for (const channelTarget in this.pool) {
|
||||
// tslint:disable-next-line:forin
|
||||
for (const subchannelTarget in this.pool[channelTarget]) {
|
||||
const subchannelObjArray = this.pool[channelTarget][subchannelTarget];
|
||||
|
||||
const refedSubchannels = subchannelObjArray.filter(
|
||||
value => !value.subchannel.unrefIfOneRef()
|
||||
);
|
||||
|
||||
if (refedSubchannels.length > 0) {
|
||||
allSubchannelsUnrefed = false;
|
||||
}
|
||||
|
||||
/* For each subchannel in the pool, try to unref it if it has
|
||||
* exactly one ref (which is the ref from the pool itself). If that
|
||||
* does happen, remove the subchannel from the pool */
|
||||
this.pool[channelTarget][subchannelTarget] = refedSubchannels;
|
||||
}
|
||||
}
|
||||
/* Currently we do not delete keys with empty values. If that results
|
||||
* in significant memory usage we should change it. */
|
||||
|
||||
// Cancel the cleanup task if all subchannels have been unrefed.
|
||||
if (allSubchannelsUnrefed && this.cleanupTimer !== null) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the cleanup task is spawned.
|
||||
*/
|
||||
ensureCleanupTask(): void {
|
||||
if (this.global && this.cleanupTimer === null) {
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.unrefUnusedSubchannels();
|
||||
}, REF_CHECK_INTERVAL);
|
||||
|
||||
// Unref because this timer should not keep the event loop running.
|
||||
this.cleanupTimer.unref();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a subchannel if one already exists with exactly matching parameters.
|
||||
* Otherwise, create and save a subchannel with those parameters.
|
||||
* @param channelTarget
|
||||
* @param subchannelTarget
|
||||
* @param channelArguments
|
||||
* @param channelCredentials
|
||||
*/
|
||||
getOrCreateSubchannel(
|
||||
channelTarget: string,
|
||||
subchannelTarget: string,
|
||||
channelArguments: ChannelOptions,
|
||||
channelCredentials: ChannelCredentials
|
||||
): Subchannel {
|
||||
this.ensureCleanupTask();
|
||||
|
||||
if (channelTarget in this.pool) {
|
||||
if (subchannelTarget in this.pool[channelTarget]) {
|
||||
const subchannelObjArray = this.pool[channelTarget][subchannelTarget];
|
||||
for (const subchannelObj of subchannelObjArray) {
|
||||
if (
|
||||
channelOptionsEqual(
|
||||
channelArguments,
|
||||
subchannelObj.channelArguments
|
||||
) &&
|
||||
channelCredentials._equals(subchannelObj.channelCredentials)
|
||||
) {
|
||||
return subchannelObj.subchannel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we get here, no matching subchannel was found
|
||||
const subchannel = new Subchannel(
|
||||
channelTarget,
|
||||
subchannelTarget,
|
||||
channelArguments,
|
||||
channelCredentials
|
||||
);
|
||||
if (!(channelTarget in this.pool)) {
|
||||
this.pool[channelTarget] = Object.create(null);
|
||||
}
|
||||
if (!(subchannelTarget in this.pool[channelTarget])) {
|
||||
this.pool[channelTarget][subchannelTarget] = [];
|
||||
}
|
||||
this.pool[channelTarget][subchannelTarget].push({
|
||||
channelArguments,
|
||||
channelCredentials,
|
||||
subchannel,
|
||||
});
|
||||
if (this.global) {
|
||||
subchannel.ref();
|
||||
}
|
||||
return subchannel;
|
||||
}
|
||||
}
|
||||
|
||||
const globalSubchannelPool = new SubchannelPool(true);
|
||||
|
||||
/**
|
||||
* Get either the global subchannel pool, or a new subchannel pool.
|
||||
* @param global
|
||||
*/
|
||||
export function getSubchannelPool(global: boolean): SubchannelPool {
|
||||
if (global) {
|
||||
return globalSubchannelPool;
|
||||
} else {
|
||||
return new SubchannelPool(false);
|
||||
}
|
||||
}
|
562
node_modules/@grpc/grpc-js/src/subchannel.ts
generated
vendored
Normal file
562
node_modules/@grpc/grpc-js/src/subchannel.ts
generated
vendored
Normal file
@ -0,0 +1,562 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as http2 from 'http2';
|
||||
import { ChannelCredentials } from './channel-credentials';
|
||||
import { Metadata } from './metadata';
|
||||
import { Http2CallStream } from './call-stream';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { PeerCertificate, checkServerIdentity } from 'tls';
|
||||
import { ConnectivityState } from './channel';
|
||||
import { BackoffTimeout, BackoffOptions } from './backoff-timeout';
|
||||
import { getDefaultAuthority } from './resolver';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
||||
const { version: clientVersion } = require('../../package.json');
|
||||
|
||||
const TRACER_NAME = 'subchannel';
|
||||
|
||||
function trace(text: string): void {
|
||||
logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
||||
}
|
||||
|
||||
const MIN_CONNECT_TIMEOUT_MS = 20000;
|
||||
const INITIAL_BACKOFF_MS = 1000;
|
||||
const BACKOFF_MULTIPLIER = 1.6;
|
||||
const MAX_BACKOFF_MS = 120000;
|
||||
const BACKOFF_JITTER = 0.2;
|
||||
|
||||
/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't
|
||||
* have a constant for the max signed 32 bit integer, so this is a simple way
|
||||
* to calculate it */
|
||||
const KEEPALIVE_MAX_TIME_MS = ~(1 << 31);
|
||||
const KEEPALIVE_TIMEOUT_MS = 20000;
|
||||
|
||||
export type ConnectivityStateListener = (
|
||||
subchannel: Subchannel,
|
||||
previousState: ConnectivityState,
|
||||
newState: ConnectivityState
|
||||
) => void;
|
||||
|
||||
const {
|
||||
HTTP2_HEADER_AUTHORITY,
|
||||
HTTP2_HEADER_CONTENT_TYPE,
|
||||
HTTP2_HEADER_METHOD,
|
||||
HTTP2_HEADER_PATH,
|
||||
HTTP2_HEADER_TE,
|
||||
HTTP2_HEADER_USER_AGENT,
|
||||
} = http2.constants;
|
||||
|
||||
/**
|
||||
* Get a number uniformly at random in the range [min, max)
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
function uniformRandom(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii');
|
||||
|
||||
export class Subchannel {
|
||||
/**
|
||||
* The subchannel's current connectivity state. Invariant: `session` === `null`
|
||||
* if and only if `connectivityState` is IDLE or TRANSIENT_FAILURE.
|
||||
*/
|
||||
private connectivityState: ConnectivityState = ConnectivityState.IDLE;
|
||||
/**
|
||||
* The underlying http2 session used to make requests.
|
||||
*/
|
||||
private session: http2.ClientHttp2Session | null = null;
|
||||
/**
|
||||
* Indicates that the subchannel should transition from TRANSIENT_FAILURE to
|
||||
* CONNECTING instead of IDLE when the backoff timeout ends.
|
||||
*/
|
||||
private continueConnecting = false;
|
||||
/**
|
||||
* A list of listener functions that will be called whenever the connectivity
|
||||
* state changes. Will be modified by `addConnectivityStateListener` and
|
||||
* `removeConnectivityStateListener`
|
||||
*/
|
||||
private stateListeners: ConnectivityStateListener[] = [];
|
||||
|
||||
/**
|
||||
* A list of listener functions that will be called when the underlying
|
||||
* socket disconnects. Used for ending active calls with an UNAVAILABLE
|
||||
* status.
|
||||
*/
|
||||
private disconnectListeners: Array<() => void> = [];
|
||||
|
||||
private backoffTimeout: BackoffTimeout;
|
||||
|
||||
/**
|
||||
* The complete user agent string constructed using channel args.
|
||||
*/
|
||||
private userAgent: string;
|
||||
|
||||
/**
|
||||
* The amount of time in between sending pings
|
||||
*/
|
||||
private keepaliveTimeMs: number = KEEPALIVE_MAX_TIME_MS;
|
||||
/**
|
||||
* The amount of time to wait for an acknowledgement after sending a ping
|
||||
*/
|
||||
private keepaliveTimeoutMs: number = KEEPALIVE_TIMEOUT_MS;
|
||||
/**
|
||||
* Timer reference for timeout that indicates when to send the next ping
|
||||
*/
|
||||
private keepaliveIntervalId: NodeJS.Timer;
|
||||
/**
|
||||
* Timer reference tracking when the most recent ping will be considered lost
|
||||
*/
|
||||
private keepaliveTimeoutId: NodeJS.Timer;
|
||||
|
||||
/**
|
||||
* Tracks calls with references to this subchannel
|
||||
*/
|
||||
private callRefcount = 0;
|
||||
/**
|
||||
* Tracks channels and subchannel pools with references to this subchannel
|
||||
*/
|
||||
private refcount = 0;
|
||||
|
||||
/**
|
||||
* A class representing a connection to a single backend.
|
||||
* @param channelTarget The target string for the channel as a whole
|
||||
* @param subchannelAddress The address for the backend that this subchannel
|
||||
* will connect to
|
||||
* @param options The channel options, plus any specific subchannel options
|
||||
* for this subchannel
|
||||
* @param credentials The channel credentials used to establish this
|
||||
* connection
|
||||
*/
|
||||
constructor(
|
||||
private channelTarget: string,
|
||||
private subchannelAddress: string,
|
||||
private options: ChannelOptions,
|
||||
private credentials: ChannelCredentials
|
||||
) {
|
||||
// Build user-agent string.
|
||||
this.userAgent = [
|
||||
options['grpc.primary_user_agent'],
|
||||
`grpc-node-js/${clientVersion}`,
|
||||
options['grpc.secondary_user_agent'],
|
||||
]
|
||||
.filter(e => e)
|
||||
.join(' '); // remove falsey values first
|
||||
|
||||
if ('grpc.keepalive_time_ms' in options) {
|
||||
this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!;
|
||||
}
|
||||
if ('grpc.keepalive_timeout_ms' in options) {
|
||||
this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms']!;
|
||||
}
|
||||
this.keepaliveIntervalId = setTimeout(() => {}, 0);
|
||||
clearTimeout(this.keepaliveIntervalId);
|
||||
this.keepaliveTimeoutId = setTimeout(() => {}, 0);
|
||||
clearTimeout(this.keepaliveTimeoutId);
|
||||
const backoffOptions: BackoffOptions = {
|
||||
initialDelay: options['grpc.initial_reconnect_backoff_ms'],
|
||||
maxDelay: options['grpc.max_reconnect_backoff_ms']
|
||||
};
|
||||
this.backoffTimeout = new BackoffTimeout(() => {
|
||||
if (this.continueConnecting) {
|
||||
this.transitionToState(
|
||||
[ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.CONNECTING],
|
||||
ConnectivityState.CONNECTING
|
||||
);
|
||||
} else {
|
||||
this.transitionToState(
|
||||
[ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.CONNECTING],
|
||||
ConnectivityState.IDLE
|
||||
);
|
||||
}
|
||||
}, backoffOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a backoff timer with the current nextBackoff timeout
|
||||
*/
|
||||
private startBackoff() {
|
||||
this.backoffTimeout.runOnce();
|
||||
}
|
||||
|
||||
private stopBackoff() {
|
||||
this.backoffTimeout.stop();
|
||||
this.backoffTimeout.reset();
|
||||
}
|
||||
|
||||
private sendPing() {
|
||||
this.keepaliveTimeoutId = setTimeout(() => {
|
||||
this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE);
|
||||
}, this.keepaliveTimeoutMs);
|
||||
this.session!.ping(
|
||||
(err: Error | null, duration: number, payload: Buffer) => {
|
||||
clearTimeout(this.keepaliveTimeoutId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private startKeepalivePings() {
|
||||
this.keepaliveIntervalId = setInterval(() => {
|
||||
this.sendPing();
|
||||
}, this.keepaliveTimeMs);
|
||||
this.sendPing();
|
||||
}
|
||||
|
||||
private stopKeepalivePings() {
|
||||
clearInterval(this.keepaliveIntervalId);
|
||||
clearTimeout(this.keepaliveTimeoutId);
|
||||
}
|
||||
|
||||
private startConnectingInternal() {
|
||||
const connectionOptions: http2.SecureClientSessionOptions =
|
||||
this.credentials._getConnectionOptions() || {};
|
||||
let addressScheme = 'http://';
|
||||
if ('secureContext' in connectionOptions) {
|
||||
addressScheme = 'https://';
|
||||
// If provided, the value of grpc.ssl_target_name_override should be used
|
||||
// to override the target hostname when checking server identity.
|
||||
// This option is used for testing only.
|
||||
if (this.options['grpc.ssl_target_name_override']) {
|
||||
const sslTargetNameOverride = this.options[
|
||||
'grpc.ssl_target_name_override'
|
||||
]!;
|
||||
connectionOptions.checkServerIdentity = (
|
||||
host: string,
|
||||
cert: PeerCertificate
|
||||
): Error | undefined => {
|
||||
return checkServerIdentity(sslTargetNameOverride, cert);
|
||||
};
|
||||
connectionOptions.servername = sslTargetNameOverride;
|
||||
} else {
|
||||
connectionOptions.servername = getDefaultAuthority(this.channelTarget);
|
||||
}
|
||||
}
|
||||
const session = http2.connect(
|
||||
addressScheme + this.subchannelAddress,
|
||||
connectionOptions
|
||||
);
|
||||
this.session = session;
|
||||
session.unref();
|
||||
/* For all of these events, check if the session at the time of the event
|
||||
* is the same one currently attached to this subchannel, to ensure that
|
||||
* old events from previous connection attempts cannot cause invalid state
|
||||
* transitions. */
|
||||
session.once('connect', () => {
|
||||
if (this.session === session) {
|
||||
this.transitionToState(
|
||||
[ConnectivityState.CONNECTING],
|
||||
ConnectivityState.READY
|
||||
);
|
||||
}
|
||||
});
|
||||
session.once('close', () => {
|
||||
if (this.session === session) {
|
||||
this.transitionToState(
|
||||
[ConnectivityState.CONNECTING],
|
||||
ConnectivityState.TRANSIENT_FAILURE
|
||||
);
|
||||
/* Transitioning directly to IDLE here should be OK because we are not
|
||||
* doing any backoff, because a connection was established at some
|
||||
* point */
|
||||
this.transitionToState(
|
||||
[ConnectivityState.READY],
|
||||
ConnectivityState.IDLE
|
||||
);
|
||||
}
|
||||
});
|
||||
session.once(
|
||||
'goaway',
|
||||
(errorCode: number, lastStreamID: number, opaqueData: Buffer) => {
|
||||
if (this.session === session) {
|
||||
/* See the last paragraph of
|
||||
* https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */
|
||||
if (
|
||||
errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM &&
|
||||
opaqueData.equals(tooManyPingsData)
|
||||
) {
|
||||
logging.log(
|
||||
LogVerbosity.ERROR,
|
||||
`Connection to ${this.channelTarget} rejected by server because of excess pings`
|
||||
);
|
||||
this.keepaliveTimeMs = Math.min(
|
||||
2 * this.keepaliveTimeMs,
|
||||
KEEPALIVE_MAX_TIME_MS
|
||||
);
|
||||
}
|
||||
this.transitionToState(
|
||||
[ConnectivityState.CONNECTING, ConnectivityState.READY],
|
||||
ConnectivityState.IDLE
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
session.once('error', error => {
|
||||
/* Do nothing here. Any error should also trigger a close event, which is
|
||||
* where we want to handle that. */
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a state transition from any element of oldStates to the new
|
||||
* state. If the current connectivityState is not in oldStates, do nothing.
|
||||
* @param oldStates The set of states to transition from
|
||||
* @param newState The state to transition to
|
||||
* @returns True if the state changed, false otherwise
|
||||
*/
|
||||
private transitionToState(
|
||||
oldStates: ConnectivityState[],
|
||||
newState: ConnectivityState
|
||||
): boolean {
|
||||
if (oldStates.indexOf(this.connectivityState) === -1) {
|
||||
return false;
|
||||
}
|
||||
trace(
|
||||
this.subchannelAddress +
|
||||
' ' +
|
||||
ConnectivityState[this.connectivityState] +
|
||||
' -> ' +
|
||||
ConnectivityState[newState]
|
||||
);
|
||||
const previousState = this.connectivityState;
|
||||
this.connectivityState = newState;
|
||||
switch (newState) {
|
||||
case ConnectivityState.READY:
|
||||
this.stopBackoff();
|
||||
this.session!.socket.once('close', () => {
|
||||
for (const listener of this.disconnectListeners) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case ConnectivityState.CONNECTING:
|
||||
this.startBackoff();
|
||||
this.startConnectingInternal();
|
||||
this.continueConnecting = false;
|
||||
break;
|
||||
case ConnectivityState.TRANSIENT_FAILURE:
|
||||
if (this.session) {
|
||||
this.session.close();
|
||||
}
|
||||
this.session = null;
|
||||
this.stopKeepalivePings();
|
||||
break;
|
||||
case ConnectivityState.IDLE:
|
||||
/* Stopping the backoff timer here is probably redundant because we
|
||||
* should only transition to the IDLE state as a result of the timer
|
||||
* ending, but we still want to reset the backoff timeout. */
|
||||
this.stopBackoff();
|
||||
if (this.session) {
|
||||
this.session.close();
|
||||
}
|
||||
this.session = null;
|
||||
this.stopKeepalivePings();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
|
||||
}
|
||||
/* We use a shallow copy of the stateListeners array in case a listener
|
||||
* is removed during this iteration */
|
||||
for (const listener of [...this.stateListeners]) {
|
||||
listener(this, previousState, newState);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the subchannel associated with zero calls and with zero channels.
|
||||
* If so, shut it down.
|
||||
*/
|
||||
private checkBothRefcounts() {
|
||||
/* If no calls, channels, or subchannel pools have any more references to
|
||||
* this subchannel, we can be sure it will never be used again. */
|
||||
if (this.callRefcount === 0 && this.refcount === 0) {
|
||||
this.transitionToState(
|
||||
[
|
||||
ConnectivityState.CONNECTING,
|
||||
ConnectivityState.IDLE,
|
||||
ConnectivityState.READY,
|
||||
],
|
||||
ConnectivityState.TRANSIENT_FAILURE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
callRef() {
|
||||
trace(
|
||||
this.subchannelAddress +
|
||||
' callRefcount ' +
|
||||
this.callRefcount +
|
||||
' -> ' +
|
||||
(this.callRefcount + 1)
|
||||
);
|
||||
if (this.callRefcount === 0) {
|
||||
if (this.session) {
|
||||
this.session.ref();
|
||||
}
|
||||
this.startKeepalivePings();
|
||||
}
|
||||
this.callRefcount += 1;
|
||||
}
|
||||
|
||||
callUnref() {
|
||||
trace(
|
||||
this.subchannelAddress +
|
||||
' callRefcount ' +
|
||||
this.callRefcount +
|
||||
' -> ' +
|
||||
(this.callRefcount - 1)
|
||||
);
|
||||
this.callRefcount -= 1;
|
||||
if (this.callRefcount === 0) {
|
||||
if (this.session) {
|
||||
this.session.unref();
|
||||
}
|
||||
this.stopKeepalivePings();
|
||||
this.checkBothRefcounts();
|
||||
}
|
||||
}
|
||||
|
||||
ref() {
|
||||
trace(
|
||||
this.subchannelAddress +
|
||||
' callRefcount ' +
|
||||
this.refcount +
|
||||
' -> ' +
|
||||
(this.refcount + 1)
|
||||
);
|
||||
this.refcount += 1;
|
||||
}
|
||||
|
||||
unref() {
|
||||
trace(
|
||||
this.subchannelAddress +
|
||||
' callRefcount ' +
|
||||
this.refcount +
|
||||
' -> ' +
|
||||
(this.refcount - 1)
|
||||
);
|
||||
this.refcount -= 1;
|
||||
this.checkBothRefcounts();
|
||||
}
|
||||
|
||||
unrefIfOneRef(): boolean {
|
||||
if (this.refcount === 1) {
|
||||
this.unref();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a stream on the current session with the given `metadata` as headers
|
||||
* and then attach it to the `callStream`. Must only be called if the
|
||||
* subchannel's current connectivity state is READY.
|
||||
* @param metadata
|
||||
* @param callStream
|
||||
*/
|
||||
startCallStream(metadata: Metadata, callStream: Http2CallStream) {
|
||||
const headers = metadata.toHttp2Headers();
|
||||
headers[HTTP2_HEADER_AUTHORITY] = callStream.getHost();
|
||||
headers[HTTP2_HEADER_USER_AGENT] = this.userAgent;
|
||||
headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
|
||||
headers[HTTP2_HEADER_METHOD] = 'POST';
|
||||
headers[HTTP2_HEADER_PATH] = callStream.getMethod();
|
||||
headers[HTTP2_HEADER_TE] = 'trailers';
|
||||
const http2Stream = this.session!.request(headers);
|
||||
callStream.attachHttp2Stream(http2Stream, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the subchannel is currently IDLE, start connecting and switch to the
|
||||
* CONNECTING state. If the subchannel is current in TRANSIENT_FAILURE,
|
||||
* the next time it would transition to IDLE, start connecting again instead.
|
||||
* Otherwise, do nothing.
|
||||
*/
|
||||
startConnecting() {
|
||||
/* First, try to transition from IDLE to connecting. If that doesn't happen
|
||||
* because the state is not currently IDLE, check if it is
|
||||
* TRANSIENT_FAILURE, and if so indicate that it should go back to
|
||||
* connecting after the backoff timer ends. Otherwise do nothing */
|
||||
if (
|
||||
!this.transitionToState(
|
||||
[ConnectivityState.IDLE],
|
||||
ConnectivityState.CONNECTING
|
||||
)
|
||||
) {
|
||||
if (this.connectivityState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||
this.continueConnecting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subchannel's current connectivity state.
|
||||
*/
|
||||
getConnectivityState() {
|
||||
return this.connectivityState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener function to be called whenever the subchannel's
|
||||
* connectivity state changes.
|
||||
* @param listener
|
||||
*/
|
||||
addConnectivityStateListener(listener: ConnectivityStateListener) {
|
||||
this.stateListeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener previously added with `addConnectivityStateListener`
|
||||
* @param listener A reference to a function previously passed to
|
||||
* `addConnectivityStateListener`
|
||||
*/
|
||||
removeConnectivityStateListener(listener: ConnectivityStateListener) {
|
||||
const listenerIndex = this.stateListeners.indexOf(listener);
|
||||
if (listenerIndex > -1) {
|
||||
this.stateListeners.splice(listenerIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
addDisconnectListener(listener: () => void) {
|
||||
this.disconnectListeners.push(listener);
|
||||
}
|
||||
|
||||
removeDisconnectListener(listener: () => void) {
|
||||
const listenerIndex = this.disconnectListeners.indexOf(listener);
|
||||
if (listenerIndex > -1) {
|
||||
this.disconnectListeners.splice(listenerIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the backoff timeout, and immediately start connecting if in backoff.
|
||||
*/
|
||||
resetBackoff() {
|
||||
this.backoffTimeout.reset();
|
||||
this.transitionToState(
|
||||
[ConnectivityState.TRANSIENT_FAILURE],
|
||||
ConnectivityState.CONNECTING
|
||||
);
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.subchannelAddress;
|
||||
}
|
||||
}
|
34
node_modules/@grpc/grpc-js/src/tls-helpers.ts
generated
vendored
Normal file
34
node_modules/@grpc/grpc-js/src/tls-helpers.ts
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2019 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const CIPHER_SUITES: string | undefined = process.env.GRPC_SSL_CIPHER_SUITES;
|
||||
|
||||
const DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
|
||||
|
||||
let defaultRootsData: Buffer | null = null;
|
||||
|
||||
export function getDefaultRootsData(): Buffer | null {
|
||||
if (DEFAULT_ROOTS_FILE_PATH) {
|
||||
if (defaultRootsData === null) {
|
||||
defaultRootsData = fs.readFileSync(DEFAULT_ROOTS_FILE_PATH);
|
||||
}
|
||||
return defaultRootsData;
|
||||
}
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user