mirror of https://github.com/grpc/grpc-web.git
				
				
				
			GrpcWebClientReadableStream: keep falsy data (#1230)
This commit is contained in:
		
							parent
							
								
									b0ea9c7c45
								
							
						
					
					
						commit
						e11903b337
					
				|  | @ -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); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue