Skip to content

Commit e11903b

Browse files
authored
GrpcWebClientReadableStream: keep falsy data (#1230)
1 parent b0ea9c7 commit e11903b

File tree

3 files changed

+87
-7
lines changed

3 files changed

+87
-7
lines changed

javascript/net/grpc/web/grpcwebclientbase.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ class GrpcWebClientBase {
123123
let unaryStatus;
124124
let unaryMsg;
125125
GrpcWebClientBase.setCallback_(
126-
stream, (error, response, status, metadata) => {
126+
stream, (error, response, status, metadata, unaryResponseReceived) => {
127127
if (error) {
128128
reject(error);
129-
} else if (response) {
129+
} else if (unaryResponseReceived) {
130130
unaryMsg = response;
131131
} else if (status) {
132132
unaryStatus = status;
@@ -222,9 +222,16 @@ class GrpcWebClientBase {
222222
* @static
223223
* @template RESPONSE
224224
* @param {!ClientReadableStream<RESPONSE>} stream
225-
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=)|
225+
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
226226
* function(?RpcError,?RESPONSE)} callback
227-
* @param {boolean} useUnaryResponse
227+
* @param {boolean} useUnaryResponse Pass true to have the client make
228+
* multiple calls to the callback, using (error, response, status,
229+
* metadata, unaryResponseReceived) arguments. One of error, status,
230+
* metadata, or unaryResponseReceived will be truthy to indicate which piece
231+
* of information the client is providing in that call. After the stream
232+
* ends, it will call the callback an additional time with all falsy
233+
* arguments. Pass false to have the client make one call to the callback
234+
* using (error, response) arguments.
228235
*/
229236
static setCallback_(stream, callback, useUnaryResponse) {
230237
let isResponseReceived = false;
@@ -272,11 +279,11 @@ class GrpcWebClientBase {
272279
message: 'Incomplete response',
273280
});
274281
} else {
275-
callback(null, responseReceived);
282+
callback(null, responseReceived, null, null, /* unaryResponseReceived= */ true);
276283
}
277284
}
278285
if (useUnaryResponse) {
279-
callback(null, null); // trigger unaryResponse
286+
callback(null, null);
280287
}
281288
});
282289
}

javascript/net/grpc/web/grpcwebclientbase_test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,77 @@ testSuite({
7575
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
7676
},
7777

78+
async testRpcFalsyResponse_ForNonProtobufDescriptor() {
79+
const xhr = new XhrIo();
80+
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
81+
const methodDescriptor = createMethodDescriptor((bytes) => {
82+
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
83+
return 0;
84+
});
85+
86+
const response = await new Promise((resolve, reject) => {
87+
client.rpcCall(
88+
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
89+
(error, response) => {
90+
assertNull(error);
91+
resolve(response);
92+
});
93+
xhr.simulatePartialResponse(
94+
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
95+
DEFAULT_RESPONSE_HEADERS);
96+
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
97+
});
98+
99+
assertEquals(0, response);
100+
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
101+
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
102+
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
103+
},
104+
105+
async testRpcResponseThenableCall() {
106+
const xhr = new XhrIo();
107+
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
108+
const methodDescriptor = createMethodDescriptor((bytes) => {
109+
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
110+
return new MockReply('value');
111+
});
112+
113+
const responsePromise = client.thenableCall(
114+
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
115+
xhr.simulatePartialResponse(
116+
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
117+
DEFAULT_RESPONSE_HEADERS);
118+
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
119+
const response = await responsePromise;
120+
121+
assertEquals('value', response.data);
122+
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
123+
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
124+
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
125+
},
126+
127+
async testRpcFalsyResponseThenableCall_ForNonProtobufDescriptor() {
128+
const xhr = new XhrIo();
129+
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
130+
const methodDescriptor = createMethodDescriptor((bytes) => {
131+
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
132+
return 0;
133+
});
134+
135+
const responsePromise = client.thenableCall(
136+
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
137+
xhr.simulatePartialResponse(
138+
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
139+
DEFAULT_RESPONSE_HEADERS);
140+
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
141+
const response = await responsePromise;
142+
143+
assertEquals(0, response);
144+
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
145+
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
146+
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
147+
},
148+
78149
async testDeadline() {
79150
const xhr = new XhrIo();
80151
const client = new GrpcWebClientBase(/* options= */ {}, xhr);

javascript/net/grpc/web/grpcwebclientreadablestream.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,18 @@ class GrpcWebClientReadableStream {
170170
if (FrameType.DATA in messages[i]) {
171171
const data = messages[i][FrameType.DATA];
172172
if (data) {
173+
let isResponseDeserialized = false;
173174
let response;
174175
try {
175176
response = self.responseDeserializeFn_(data);
177+
isResponseDeserialized = true;
176178
} catch (err) {
177179
self.handleError_(new RpcError(
178180
StatusCode.INTERNAL,
179181
`Error when deserializing response data; error: ${err}` +
180182
`, response: ${response}`));
181183
}
182-
if (response) {
184+
if (isResponseDeserialized) {
183185
self.sendDataCallbacks_(response);
184186
}
185187
}

0 commit comments

Comments
 (0)