GrpcWebClientReadableStream: keep falsy data (#1230)

This commit is contained in:
pro-wh 2022-10-25 00:49:54 -07:00 committed by GitHub
parent b0ea9c7c45
commit e11903b337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 7 deletions

View File

@ -123,10 +123,10 @@ class GrpcWebClientBase {
let unaryStatus; let unaryStatus;
let unaryMsg; let unaryMsg;
GrpcWebClientBase.setCallback_( GrpcWebClientBase.setCallback_(
stream, (error, response, status, metadata) => { stream, (error, response, status, metadata, unaryResponseReceived) => {
if (error) { if (error) {
reject(error); reject(error);
} else if (response) { } else if (unaryResponseReceived) {
unaryMsg = response; unaryMsg = response;
} else if (status) { } else if (status) {
unaryStatus = status; unaryStatus = status;
@ -222,9 +222,16 @@ class GrpcWebClientBase {
* @static * @static
* @template RESPONSE * @template RESPONSE
* @param {!ClientReadableStream<RESPONSE>} stream * @param {!ClientReadableStream<RESPONSE>} stream
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=)| * @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
* function(?RpcError,?RESPONSE)} callback * function(?RpcError,?RESPONSE)} callback
* @param {boolean} useUnaryResponse * @param {boolean} useUnaryResponse Pass true to have the client make
* multiple calls to the callback, using (error, response, status,
* metadata, unaryResponseReceived) arguments. One of error, status,
* metadata, or unaryResponseReceived will be truthy to indicate which piece
* of information the client is providing in that call. After the stream
* ends, it will call the callback an additional time with all falsy
* arguments. Pass false to have the client make one call to the callback
* using (error, response) arguments.
*/ */
static setCallback_(stream, callback, useUnaryResponse) { static setCallback_(stream, callback, useUnaryResponse) {
let isResponseReceived = false; let isResponseReceived = false;
@ -272,11 +279,11 @@ class GrpcWebClientBase {
message: 'Incomplete response', message: 'Incomplete response',
}); });
} else { } else {
callback(null, responseReceived); callback(null, responseReceived, null, null, /* unaryResponseReceived= */ true);
} }
} }
if (useUnaryResponse) { if (useUnaryResponse) {
callback(null, null); // trigger unaryResponse callback(null, null);
} }
}); });
} }

View File

@ -75,6 +75,77 @@ testSuite({
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers)); assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
}, },
async testRpcFalsyResponse_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const response = await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
resolve(response);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcResponseThenableCall() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals('value', response.data);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcFalsyResponseThenableCall_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testDeadline() { async testDeadline() {
const xhr = new XhrIo(); const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr); const client = new GrpcWebClientBase(/* options= */ {}, xhr);

View File

@ -170,16 +170,18 @@ class GrpcWebClientReadableStream {
if (FrameType.DATA in messages[i]) { if (FrameType.DATA in messages[i]) {
const data = messages[i][FrameType.DATA]; const data = messages[i][FrameType.DATA];
if (data) { if (data) {
let isResponseDeserialized = false;
let response; let response;
try { try {
response = self.responseDeserializeFn_(data); response = self.responseDeserializeFn_(data);
isResponseDeserialized = true;
} catch (err) { } catch (err) {
self.handleError_(new RpcError( self.handleError_(new RpcError(
StatusCode.INTERNAL, StatusCode.INTERNAL,
`Error when deserializing response data; error: ${err}` + `Error when deserializing response data; error: ${err}` +
`, response: ${response}`)); `, response: ${response}`));
} }
if (response) { if (isResponseDeserialized) {
self.sendDataCallbacks_(response); self.sendDataCallbacks_(response);
} }
} }