mirror of https://github.com/grpc/grpc-node.git
grpc-js: make client interceptors tests pass mostly unmodified
This commit is contained in:
parent
17126e4640
commit
33875dce4a
|
@ -152,6 +152,7 @@ export class Http2CallStream implements Call {
|
|||
filterStack: Filter;
|
||||
private http2Stream: http2.ClientHttp2Stream | null = null;
|
||||
private pendingRead = false;
|
||||
private isWriteFilterPending = false;
|
||||
private pendingWrite: Buffer | null = null;
|
||||
private pendingWriteCallback: WriteCallback | null = null;
|
||||
private writesClosed = false;
|
||||
|
@ -160,12 +161,16 @@ export class Http2CallStream implements Call {
|
|||
|
||||
private isReadFilterPending = false;
|
||||
private canPush = false;
|
||||
/**
|
||||
* Indicates that an 'end' event has come from the http2 stream, so there
|
||||
* will be no more data events.
|
||||
*/
|
||||
private readsClosed = false;
|
||||
|
||||
private statusOutput = false;
|
||||
|
||||
private unpushedReadMessages: Array<Buffer | null> = [];
|
||||
private unfilteredReadMessages: Array<Buffer | null> = [];
|
||||
private unpushedReadMessages: Buffer[] = [];
|
||||
private unfilteredReadMessages: Buffer[] = [];
|
||||
|
||||
// Status code mapped from :status. To be used if grpc-status is not received
|
||||
private mappedStatusCode: Status = Status.UNKNOWN;
|
||||
|
@ -200,16 +205,7 @@ export class Http2CallStream implements Call {
|
|||
/* Precondition: this.finalStatus !== null */
|
||||
if (!this.statusOutput) {
|
||||
this.statusOutput = true;
|
||||
/* 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.listener!.onReceiveStatus(this.finalStatus!);
|
||||
});
|
||||
this.listener!.onReceiveStatus(this.finalStatus!);
|
||||
if (this.subchannel) {
|
||||
this.subchannel.callUnref();
|
||||
this.subchannel.removeDisconnectListener(this.disconnectListener);
|
||||
|
@ -227,30 +223,24 @@ export class Http2CallStream implements Call {
|
|||
* deserialization failure), that new status takes priority */
|
||||
if (this.finalStatus === null || this.finalStatus.code === Status.OK) {
|
||||
this.finalStatus = status;
|
||||
/* Then, if an incoming message is still being handled or the status code
|
||||
* is OK, hold off on emitting the status until that is done */
|
||||
if (this.readsClosed || this.finalStatus.code !== Status.OK) {
|
||||
this.maybeOutputStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private maybeOutputStatus() {
|
||||
if (this.finalStatus !== null) {
|
||||
/* The combination check of readsClosed and that the two message buffer
|
||||
* arrays are empty checks that there all incoming data has been fully
|
||||
* processed */
|
||||
if (this.finalStatus.code !== Status.OK || (this.readsClosed && this.unpushedReadMessages.length === 0 && this.unfilteredReadMessages.length === 0 && !this.isReadFilterPending)) {
|
||||
this.outputStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private push(message: Buffer | null): void {
|
||||
if (message === null) {
|
||||
this.readsClosed = true;
|
||||
if (this.finalStatus) {
|
||||
this.outputStatus();
|
||||
}
|
||||
} else {
|
||||
this.listener!.onReceiveMessage(message);
|
||||
/* Don't wait for the upper layer to ask for a read before pushing null
|
||||
* to close out the call, because pushing null doesn't actually push
|
||||
* another message up to the upper layer */
|
||||
if (this.unpushedReadMessages.length > 0 && this.unpushedReadMessages[0] === null) {
|
||||
this.unpushedReadMessages.shift();
|
||||
this.push(null);
|
||||
}
|
||||
}
|
||||
private push(message: Buffer): void {
|
||||
this.listener!.onReceiveMessage(message);
|
||||
this.maybeOutputStatus();
|
||||
}
|
||||
|
||||
private handleFilterError(error: Error) {
|
||||
|
@ -261,7 +251,7 @@ export class Http2CallStream implements Call {
|
|||
/* 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) {
|
||||
this.push(null);
|
||||
this.maybeOutputStatus();
|
||||
return;
|
||||
}
|
||||
this.isReadFilterPending = false;
|
||||
|
@ -275,24 +265,16 @@ export class Http2CallStream implements Call {
|
|||
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;
|
||||
const nextMessage = this.unfilteredReadMessages.shift()!;
|
||||
this.filterReceivedMessage(nextMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private filterReceivedMessage(framedMessage: Buffer | null) {
|
||||
private filterReceivedMessage(framedMessage: 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) {
|
||||
this.push(null);
|
||||
return;
|
||||
}
|
||||
if (framedMessage === null) {
|
||||
if (this.canPush) {
|
||||
this.push(null);
|
||||
} else {
|
||||
this.unpushedReadMessages.push(null);
|
||||
}
|
||||
this.maybeOutputStatus();
|
||||
return;
|
||||
}
|
||||
this.isReadFilterPending = true;
|
||||
|
@ -304,7 +286,7 @@ export class Http2CallStream implements Call {
|
|||
);
|
||||
}
|
||||
|
||||
private tryPush(messageBytes: Buffer | null): void {
|
||||
private tryPush(messageBytes: Buffer): void {
|
||||
if (this.isReadFilterPending) {
|
||||
this.unfilteredReadMessages.push(messageBytes);
|
||||
} else {
|
||||
|
@ -411,12 +393,23 @@ export class Http2CallStream implements Call {
|
|||
}
|
||||
});
|
||||
stream.on('end', () => {
|
||||
this.tryPush(null);
|
||||
this.readsClosed = true;
|
||||
this.maybeOutputStatus();
|
||||
});
|
||||
stream.on('close', async () => {
|
||||
stream.on('close', () => {
|
||||
let code: Status;
|
||||
let details = '';
|
||||
switch (stream.rstCode) {
|
||||
case http2.constants.NGHTTP2_NO_ERROR:
|
||||
/* If we get a NO_ERROR code and we already have a status, the
|
||||
* stream completed properly and we just haven't fully processed
|
||||
* it yet */
|
||||
if (this.finalStatus !== null) {
|
||||
return;
|
||||
}
|
||||
code = Status.INTERNAL;
|
||||
details = `Received RST_STREAM with code ${stream.rstCode}`;
|
||||
break;
|
||||
case http2.constants.NGHTTP2_REFUSED_STREAM:
|
||||
code = Status.UNAVAILABLE;
|
||||
details = 'Stream refused by server';
|
||||
|
@ -435,6 +428,7 @@ export class Http2CallStream implements Call {
|
|||
break;
|
||||
default:
|
||||
code = Status.INTERNAL;
|
||||
details = `Received RST_STREAM with code ${stream.rstCode}`;
|
||||
}
|
||||
// This is a no-op if trailers were received at all.
|
||||
// This is OK, because status codes emitted here correspond to more
|
||||
|
@ -456,9 +450,7 @@ export class Http2CallStream implements Call {
|
|||
}
|
||||
stream.write(this.pendingWrite, this.pendingWriteCallback);
|
||||
}
|
||||
if (this.writesClosed) {
|
||||
stream.end();
|
||||
}
|
||||
this.maybeCloseWrites();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -514,7 +506,7 @@ export class Http2CallStream implements Call {
|
|||
/* 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);
|
||||
this.maybeOutputStatus();
|
||||
return;
|
||||
}
|
||||
this.canPush = true;
|
||||
|
@ -522,7 +514,7 @@ export class Http2CallStream implements Call {
|
|||
this.pendingRead = true;
|
||||
} else {
|
||||
if (this.unpushedReadMessages.length > 0) {
|
||||
const nextMessage: Buffer | null = this.unpushedReadMessages.shift() as Buffer | null;
|
||||
const nextMessage: Buffer = this.unpushedReadMessages.shift()!;
|
||||
this.push(nextMessage);
|
||||
this.canPush = false;
|
||||
return;
|
||||
|
@ -534,26 +526,33 @@ export class Http2CallStream implements Call {
|
|||
}
|
||||
}
|
||||
|
||||
private maybeCloseWrites() {
|
||||
if (this.writesClosed && !this.isWriteFilterPending && this.http2Stream !== null) {
|
||||
this.http2Stream.end();
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageWithContext(context: MessageContext, message: Buffer) {
|
||||
const writeObj: WriteObject = {
|
||||
message: message,
|
||||
flags: context.flags
|
||||
};
|
||||
const cb: WriteCallback = context.callback || (() => {});
|
||||
this.isWriteFilterPending = true;
|
||||
this.filterStack.sendMessage(Promise.resolve(writeObj)).then(message => {
|
||||
this.isWriteFilterPending = false;
|
||||
if (this.http2Stream === null) {
|
||||
this.pendingWrite = message.message;
|
||||
this.pendingWriteCallback = cb;
|
||||
} else {
|
||||
this.http2Stream.write(message.message, cb);
|
||||
this.maybeCloseWrites();
|
||||
}
|
||||
}, this.handleFilterError.bind(this));
|
||||
}
|
||||
|
||||
halfClose() {
|
||||
this.writesClosed = true;
|
||||
if (this.http2Stream !== null) {
|
||||
this.http2Stream.end();
|
||||
}
|
||||
this.maybeCloseWrites();
|
||||
}
|
||||
}
|
|
@ -18,11 +18,12 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { Duplex, Readable, Writable } from 'stream';
|
||||
|
||||
import { Call, StatusObject, WriteObject } from './call-stream';
|
||||
import { StatusObject, MessageContext } from './call-stream';
|
||||
import { Status } from './constants';
|
||||
import { EmitterAugmentation1 } from './events';
|
||||
import { Metadata } from './metadata';
|
||||
import { ObjectReadable, ObjectWritable, WriteCallback } from './object-stream';
|
||||
import { InterceptingCallInterface } from './client-interceptors';
|
||||
|
||||
/**
|
||||
* A type extending the built-in Error object with additional fields.
|
||||
|
@ -81,7 +82,7 @@ export function callErrorFromStatus(status: StatusObject): ServiceError {
|
|||
|
||||
export class ClientUnaryCallImpl extends EventEmitter
|
||||
implements ClientUnaryCall {
|
||||
constructor(private readonly call: Call) {
|
||||
constructor(private readonly call: InterceptingCallInterface) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
@ -97,7 +98,7 @@ export class ClientUnaryCallImpl extends EventEmitter
|
|||
export class ClientReadableStreamImpl<ResponseType> extends Readable
|
||||
implements ClientReadableStream<ResponseType> {
|
||||
constructor(
|
||||
private readonly call: Call,
|
||||
private readonly call: InterceptingCallInterface,
|
||||
readonly deserialize: (chunk: Buffer) => ResponseType
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
|
@ -116,33 +117,10 @@ export class ClientReadableStreamImpl<ResponseType> extends Readable
|
|||
}
|
||||
}
|
||||
|
||||
function tryWrite<RequestType>(
|
||||
call: Call,
|
||||
serialize: (value: RequestType) => Buffer,
|
||||
chunk: RequestType,
|
||||
encoding: string,
|
||||
cb: WriteCallback
|
||||
) {
|
||||
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,
|
||||
private readonly call: InterceptingCallInterface,
|
||||
readonly serialize: (value: RequestType) => Buffer
|
||||
) {
|
||||
super({ objectMode: true });
|
||||
|
@ -157,12 +135,14 @@ export class ClientWritableStreamImpl<RequestType> extends Writable
|
|||
}
|
||||
|
||||
_write(chunk: RequestType, encoding: string, cb: WriteCallback) {
|
||||
const writeObj: WriteObject = { message: chunk };
|
||||
const context: MessageContext = {
|
||||
callback: cb
|
||||
}
|
||||
const flags: number = Number(encoding);
|
||||
if (!Number.isNaN(flags)) {
|
||||
writeObj.flags = flags;
|
||||
context.flags = flags;
|
||||
}
|
||||
this.call.write(writeObj, cb);
|
||||
this.call.sendMessageWithContext(context, chunk);
|
||||
}
|
||||
|
||||
_final(cb: Function) {
|
||||
|
@ -174,7 +154,7 @@ export class ClientWritableStreamImpl<RequestType> extends Writable
|
|||
export class ClientDuplexStreamImpl<RequestType, ResponseType> extends Duplex
|
||||
implements ClientDuplexStream<RequestType, ResponseType> {
|
||||
constructor(
|
||||
private readonly call: Call,
|
||||
private readonly call: InterceptingCallInterface,
|
||||
readonly serialize: (value: RequestType) => Buffer,
|
||||
readonly deserialize: (chunk: Buffer) => ResponseType
|
||||
) {
|
||||
|
@ -194,12 +174,14 @@ export class ClientDuplexStreamImpl<RequestType, ResponseType> extends Duplex
|
|||
}
|
||||
|
||||
_write(chunk: RequestType, encoding: string, cb: WriteCallback) {
|
||||
const writeObj: WriteObject = { message: chunk };
|
||||
const context: MessageContext = {
|
||||
callback: cb
|
||||
}
|
||||
const flags: number = Number(encoding);
|
||||
if (!Number.isNaN(flags)) {
|
||||
writeObj.flags = flags;
|
||||
context.flags = flags;
|
||||
}
|
||||
this.call.write(writeObj, cb);
|
||||
this.call.sendMessageWithContext(context, chunk);
|
||||
}
|
||||
|
||||
_final(cb: Function) {
|
||||
|
|
|
@ -155,8 +155,9 @@ export interface InterceptorOptions extends CallOptions {
|
|||
export interface InterceptingCallInterface {
|
||||
cancelWithStatus(status: Status, details: string): void;
|
||||
getPeer(): string;
|
||||
start(metadata: Metadata, listener: InterceptingListener): void;
|
||||
start(metadata: Metadata, listener?: Partial<InterceptingListener>): void;
|
||||
sendMessageWithContext(context: MessageContext, message: any): void;
|
||||
sendMessage(message: any): void;
|
||||
startRead(): void;
|
||||
halfClose(): void;
|
||||
|
||||
|
@ -194,18 +195,23 @@ export class InterceptingCall implements InterceptingCallInterface {
|
|||
getPeer() {
|
||||
return this.nextCall.getPeer();
|
||||
}
|
||||
start(metadata: Metadata, interceptingListener: InterceptingListener): void {
|
||||
this.requester.start(metadata, interceptingListener, (md, listener) => {
|
||||
start(metadata: Metadata, interceptingListener?: Partial<InterceptingListener>): void {
|
||||
const fullInterceptingListener: InterceptingListener = {
|
||||
onReceiveMetadata: interceptingListener?.onReceiveMetadata?.bind(interceptingListener) ?? (metadata => {}),
|
||||
onReceiveMessage: interceptingListener?.onReceiveMessage?.bind(interceptingListener) ?? (message => {}),
|
||||
onReceiveStatus: interceptingListener?.onReceiveStatus?.bind(interceptingListener) ?? (status => {})
|
||||
}
|
||||
this.requester.start(metadata, fullInterceptingListener, (md, listener) => {
|
||||
let finalInterceptingListener: InterceptingListener;
|
||||
if (isInterceptingListener(listener)) {
|
||||
finalInterceptingListener = listener;
|
||||
} else {
|
||||
const fullListener: FullListener = {
|
||||
onReceiveMetadata: listener.onReceiveMetadata || defaultListener.onReceiveMetadata,
|
||||
onReceiveMessage: listener.onReceiveMessage || defaultListener.onReceiveMessage,
|
||||
onReceiveStatus: listener.onReceiveStatus || defaultListener.onReceiveStatus
|
||||
onReceiveMetadata: listener.onReceiveMetadata ?? defaultListener.onReceiveMetadata,
|
||||
onReceiveMessage: listener.onReceiveMessage ?? defaultListener.onReceiveMessage,
|
||||
onReceiveStatus: listener.onReceiveStatus ?? defaultListener.onReceiveStatus
|
||||
};
|
||||
finalInterceptingListener = new InterceptingListenerImpl(fullListener, interceptingListener);
|
||||
finalInterceptingListener = new InterceptingListenerImpl(fullListener, fullInterceptingListener);
|
||||
}
|
||||
this.nextCall.start(md, finalInterceptingListener);
|
||||
});
|
||||
|
@ -218,7 +224,7 @@ export class InterceptingCall implements InterceptingCallInterface {
|
|||
if (this.pendingHalfClose) {
|
||||
this.nextCall.halfClose();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
sendMessage(message: any): void {
|
||||
this.sendMessageWithContext({}, message);
|
||||
|
@ -308,17 +314,20 @@ class BaseInterceptingCall implements InterceptingCallInterface {
|
|||
this.call.cancelWithStatus(Status.INTERNAL, 'Serialization failure');
|
||||
}
|
||||
}
|
||||
start(metadata: Metadata, listener: InterceptingListener): void {
|
||||
sendMessage(message: any) {
|
||||
this.sendMessageWithContext({}, message);
|
||||
}
|
||||
start(metadata: Metadata, interceptingListener?: Partial<InterceptingListener>): void {
|
||||
let readError: StatusObject | null = null;
|
||||
this.call.start(metadata, {
|
||||
onReceiveMetadata: (metadata) => {
|
||||
listener.onReceiveMetadata(metadata);
|
||||
interceptingListener?.onReceiveMetadata?.(metadata);
|
||||
},
|
||||
onReceiveMessage: (message) => {
|
||||
let deserialized: any;
|
||||
try {
|
||||
deserialized = this.methodDefinition.responseDeserialize(message);
|
||||
listener.onReceiveMessage(deserialized);
|
||||
interceptingListener?.onReceiveMessage?.(deserialized);
|
||||
} catch (e) {
|
||||
readError = {code: Status.INTERNAL, details: 'Failed to parse server response', metadata: new Metadata()};
|
||||
this.call.cancelWithStatus(readError.code, readError.details);
|
||||
|
@ -326,9 +335,9 @@ class BaseInterceptingCall implements InterceptingCallInterface {
|
|||
},
|
||||
onReceiveStatus: (status) => {
|
||||
if (readError) {
|
||||
listener.onReceiveStatus(readError);
|
||||
interceptingListener?.onReceiveStatus?.(readError);
|
||||
} else {
|
||||
listener.onReceiveStatus(status);
|
||||
interceptingListener?.onReceiveStatus?.(status);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -345,8 +354,22 @@ class BaseUnaryInterceptingCall extends BaseInterceptingCall implements Intercep
|
|||
constructor(call: Call, methodDefinition: ClientMethodDefinition<any, any>) {
|
||||
super(call, methodDefinition);
|
||||
}
|
||||
start(metadata: Metadata, listener: InterceptingListener): void {
|
||||
super.start(metadata, listener);
|
||||
start(metadata: Metadata, listener?: Partial<InterceptingListener>): void {
|
||||
let receivedMessage = false;
|
||||
const wrapperListener: InterceptingListener = {
|
||||
onReceiveMetadata: listener?.onReceiveMetadata?.bind(listener) ?? (metadata => {}),
|
||||
onReceiveMessage: (message: any) => {
|
||||
receivedMessage = true;
|
||||
listener?.onReceiveMessage?.(message);
|
||||
},
|
||||
onReceiveStatus: (status: StatusObject) => {
|
||||
if (!receivedMessage) {
|
||||
listener?.onReceiveMessage?.(null);
|
||||
}
|
||||
listener?.onReceiveStatus?.(status);
|
||||
}
|
||||
}
|
||||
super.start(metadata, wrapperListener);
|
||||
this.call.startRead();
|
||||
}
|
||||
}
|
||||
|
@ -416,8 +439,8 @@ export function getInterceptingCall(interceptorArgs: InterceptorArguments, metho
|
|||
* initialValue, which is effectively at the end of the list, is a nextCall
|
||||
* function that invokes getBottomInterceptingCall, which handles
|
||||
* (de)serialization and also gets the underlying call from the channel */
|
||||
const getCall: NextCall = interceptors.reduceRight<NextCall>((previousValue: NextCall, currentValue: Interceptor) => {
|
||||
return currentOptions => currentValue(currentOptions, previousValue);
|
||||
const getCall: NextCall = interceptors.reduceRight<NextCall>((nextCall: NextCall, nextInterceptor: Interceptor) => {
|
||||
return currentOptions => nextInterceptor(currentOptions, nextCall);
|
||||
}, (finalOptions: InterceptorOptions) => getBottomInterceptingCall(channel, methodDefinition.path, finalOptions, methodDefinition));
|
||||
return getCall(interceptorOptions);
|
||||
}
|
|
@ -29,14 +29,14 @@ import {
|
|||
SurfaceCall,
|
||||
} from './call';
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Call, Deadline, StatusObject, WriteObject, InterceptingListener } from './call-stream';
|
||||
import { Deadline, StatusObject, WriteObject, InterceptingListener } 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';
|
||||
import { ClientMethodDefinition } from './make-client';
|
||||
import { getInterceptingCall, Interceptor, InterceptorProvider, InterceptorArguments } from './client-interceptors';
|
||||
import { getInterceptingCall, Interceptor, InterceptorProvider, InterceptorArguments, InterceptingCallInterface } from './client-interceptors';
|
||||
|
||||
const CHANNEL_SYMBOL = Symbol();
|
||||
const INTERCEPTOR_SYMBOL = Symbol();
|
||||
|
@ -231,13 +231,13 @@ export class Client {
|
|||
callInterceptors: options.interceptors || [],
|
||||
callInterceptorProviders: options.interceptor_providers || []
|
||||
};
|
||||
const call: Call = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
const call: InterceptingCallInterface = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
const writeObj: WriteObject = { message: argument };
|
||||
const emitter = new ClientUnaryCallImpl(call);
|
||||
let responseMessage: ResponseType | null = null;
|
||||
let receivedStatus = false;
|
||||
call.start(metadata, {
|
||||
onReceiveMetadata: (metadata) => {
|
||||
emitter.emit('metadata', metadata);
|
||||
|
@ -247,9 +247,12 @@ export class Client {
|
|||
call.cancelWithStatus(Status.INTERNAL, 'Too many responses received');
|
||||
}
|
||||
responseMessage = message;
|
||||
call.startRead();
|
||||
},
|
||||
onReceiveStatus(status: StatusObject) {
|
||||
if (receivedStatus) {
|
||||
return;
|
||||
}
|
||||
receivedStatus = true;
|
||||
if (status.code === Status.OK) {
|
||||
callback!(null, responseMessage!);
|
||||
} else {
|
||||
|
@ -258,8 +261,8 @@ export class Client {
|
|||
emitter.emit('status', status);
|
||||
}
|
||||
});
|
||||
call.write(writeObj, () => {call.halfClose();});
|
||||
call.startRead();
|
||||
call.sendMessage(argument);
|
||||
call.halfClose();
|
||||
return emitter;
|
||||
}
|
||||
|
||||
|
@ -315,12 +318,13 @@ export class Client {
|
|||
callInterceptors: options.interceptors || [],
|
||||
callInterceptorProviders: options.interceptor_providers || []
|
||||
};
|
||||
const call: Call = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
const call: InterceptingCallInterface = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
const emitter = new ClientWritableStreamImpl<RequestType>(call, serialize);
|
||||
let responseMessage: ResponseType | null = null;
|
||||
let receivedStatus = false;
|
||||
call.start(metadata, {
|
||||
onReceiveMetadata: (metadata) => {
|
||||
emitter.emit('metadata', metadata);
|
||||
|
@ -330,9 +334,12 @@ export class Client {
|
|||
call.cancelWithStatus(Status.INTERNAL, 'Too many responses received');
|
||||
}
|
||||
responseMessage = message;
|
||||
call.startRead();
|
||||
},
|
||||
onReceiveStatus(status: StatusObject) {
|
||||
if (receivedStatus) {
|
||||
return;
|
||||
}
|
||||
receivedStatus = true;
|
||||
if (status.code === Status.OK) {
|
||||
callback!(null, responseMessage!);
|
||||
} else {
|
||||
|
@ -341,7 +348,6 @@ export class Client {
|
|||
emitter.emit('status', status);
|
||||
}
|
||||
});
|
||||
call.startRead();
|
||||
return emitter;
|
||||
}
|
||||
|
||||
|
@ -406,12 +412,12 @@ export class Client {
|
|||
callInterceptors: options.interceptors || [],
|
||||
callInterceptorProviders: options.interceptor_providers || []
|
||||
};
|
||||
const call: Call = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
const call: InterceptingCallInterface = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
const writeObj: WriteObject = { message: argument };
|
||||
const stream = new ClientReadableStreamImpl<ResponseType>(call, deserialize);
|
||||
let receivedStatus = false;
|
||||
call.start(metadata, {
|
||||
onReceiveMetadata(metadata: Metadata) {
|
||||
stream.emit('metadata', metadata);
|
||||
|
@ -422,6 +428,10 @@ export class Client {
|
|||
}
|
||||
},
|
||||
onReceiveStatus(status: StatusObject) {
|
||||
if (receivedStatus) {
|
||||
return;
|
||||
}
|
||||
receivedStatus = true;
|
||||
stream.push(null);
|
||||
if (status.code !== Status.OK) {
|
||||
stream.emit('error', callErrorFromStatus(status));
|
||||
|
@ -429,7 +439,8 @@ export class Client {
|
|||
stream.emit('status', status);
|
||||
}
|
||||
});
|
||||
call.write(writeObj, () => {call.halfClose();});
|
||||
call.sendMessage(argument);
|
||||
call.halfClose();
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
@ -467,7 +478,7 @@ export class Client {
|
|||
callInterceptors: options.interceptors || [],
|
||||
callInterceptorProviders: options.interceptor_providers || []
|
||||
};
|
||||
const call: Call = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
const call: InterceptingCallInterface = getInterceptingCall(interceptorArgs, methodDefinition, options, this[CHANNEL_SYMBOL]);
|
||||
if (options.credentials) {
|
||||
call.setCredentials(options.credentials);
|
||||
}
|
||||
|
@ -476,6 +487,7 @@ export class Client {
|
|||
serialize,
|
||||
deserialize
|
||||
);
|
||||
let receivedStatus = false;
|
||||
call.start(metadata, {
|
||||
onReceiveMetadata(metadata: Metadata) {
|
||||
stream.emit('metadata', metadata);
|
||||
|
@ -486,6 +498,10 @@ export class Client {
|
|||
}
|
||||
},
|
||||
onReceiveStatus(status: StatusObject) {
|
||||
if (receivedStatus) {
|
||||
return;
|
||||
}
|
||||
receivedStatus = true;
|
||||
stream.push(null);
|
||||
if (status.code !== Status.OK) {
|
||||
stream.emit('error', callErrorFromStatus(status));
|
||||
|
|
|
@ -351,10 +351,8 @@ export class Http2ServerCallStream<
|
|||
});
|
||||
|
||||
this.stream.once('close', () => {
|
||||
if (this.stream.rstCode === http2.constants.NGHTTP2_CANCEL) {
|
||||
this.cancelled = true;
|
||||
this.emit('cancelled', 'cancelled');
|
||||
}
|
||||
this.cancelled = true;
|
||||
this.emit('cancelled', 'cancelled');
|
||||
});
|
||||
|
||||
this.stream.on('drain', () => {
|
||||
|
@ -362,7 +360,20 @@ export class Http2ServerCallStream<
|
|||
});
|
||||
}
|
||||
|
||||
private checkCancelled(): boolean {
|
||||
/* In some cases the stream can become destroyed before the close event
|
||||
* fires. That creates a race condition that this check works around */
|
||||
if (this.stream.destroyed) {
|
||||
this.cancelled = true;
|
||||
}
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
sendMetadata(customMetadata?: Metadata) {
|
||||
if (this.checkCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.metadataSent) {
|
||||
return;
|
||||
}
|
||||
|
@ -397,6 +408,13 @@ export class Http2ServerCallStream<
|
|||
metadata.remove(GRPC_TIMEOUT_HEADER);
|
||||
}
|
||||
|
||||
// Remove several headers that should not be propagated to the application
|
||||
metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING);
|
||||
metadata.remove(http2.constants.HTTP2_HEADER_TE);
|
||||
metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE);
|
||||
metadata.remove('grpc-encoding');
|
||||
metadata.remove('grpc-accept-encoding');
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
@ -450,6 +468,9 @@ export class Http2ServerCallStream<
|
|||
metadata?: Metadata,
|
||||
flags?: number
|
||||
) {
|
||||
if (this.checkCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (!metadata) {
|
||||
metadata = new Metadata();
|
||||
}
|
||||
|
@ -472,7 +493,7 @@ export class Http2ServerCallStream<
|
|||
}
|
||||
|
||||
sendStatus(statusObj: StatusObject) {
|
||||
if (this.cancelled) {
|
||||
if (this.checkCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -497,6 +518,9 @@ export class Http2ServerCallStream<
|
|||
}
|
||||
|
||||
sendError(error: ServerErrorResponse | ServerStatusResponse) {
|
||||
if (this.checkCancelled()) {
|
||||
return;
|
||||
}
|
||||
const status: StatusObject = {
|
||||
code: Status.UNKNOWN,
|
||||
details: 'message' in error ? error.message : 'Unknown Error',
|
||||
|
@ -522,7 +546,7 @@ export class Http2ServerCallStream<
|
|||
}
|
||||
|
||||
write(chunk: Buffer) {
|
||||
if (this.cancelled) {
|
||||
if (this.checkCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -346,7 +346,6 @@ export class Server {
|
|||
|
||||
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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue