From 5276b1e36deabb64776c9af6bfe6be5f83fa1ba2 Mon Sep 17 00:00:00 2001 From: Stanley Cheung Date: Thu, 16 Jul 2020 23:29:47 -0700 Subject: [PATCH] Add error handling to a few error conditions --- javascript/net/grpc/web/grpcwebclientbase.js | 9 +- .../grpc/web/grpcwebclientreadablestream.js | 25 ++++- packages/grpc-web/test/generated_code_test.js | 91 +++++++++++++++++++ 3 files changed, 120 insertions(+), 5 deletions(-) diff --git a/javascript/net/grpc/web/grpcwebclientbase.js b/javascript/net/grpc/web/grpcwebclientbase.js index 2ac1d6f..0b41b23 100644 --- a/javascript/net/grpc/web/grpcwebclientbase.js +++ b/javascript/net/grpc/web/grpcwebclientbase.js @@ -300,7 +300,14 @@ class GrpcWebClientBase { stream.on('end', function() { if (!errorEmitted) { - callback(null, responseReceived); + if (responseReceived == null) { + callback({ + code: StatusCode.UNKNOWN, + message: 'Incomplete response', + }); + } else { + callback(null, responseReceived); + } } if (useUnaryResponse) { callback(null, null); // trigger unaryResponse diff --git a/javascript/net/grpc/web/grpcwebclientreadablestream.js b/javascript/net/grpc/web/grpcwebclientreadablestream.js index eee28bb..64e6ffb 100644 --- a/javascript/net/grpc/web/grpcwebclientreadablestream.js +++ b/javascript/net/grpc/web/grpcwebclientreadablestream.js @@ -158,16 +158,33 @@ class GrpcWebClientReadableStream { }); return; } - var messages = self.parser_.parse(byteSource); + var messages = null; + try { + messages = self.parser_.parse(byteSource); + } catch (err) { + self.handleError_({ + code: StatusCode.UNKNOWN, + message: 'Error in parsing response body', + metadata: {}, + }); + } if (messages) { var FrameType = GrpcWebStreamParser.FrameType; for (var i = 0; i < messages.length; i++) { if (FrameType.DATA in messages[i]) { var data = messages[i][FrameType.DATA]; if (data) { - var response = self.responseDeserializeFn_(data); - if (response) { - self.sendDataCallbacks_(response); + try { + var response = self.responseDeserializeFn_(data); + if (response) { + self.sendDataCallbacks_(response); + } + } catch (err) { + self.handleError_({ + code: StatusCode.UNKNOWN, + message: 'Error in response deserializer function.', + metadata: {}, + }); } } } diff --git a/packages/grpc-web/test/generated_code_test.js b/packages/grpc-web/test/generated_code_test.js index 5e65b0e..0b7a59f 100644 --- a/packages/grpc-web/test/generated_code_test.js +++ b/packages/grpc-web/test/generated_code_test.js @@ -328,6 +328,97 @@ describe('grpc-web generated code (commonjs+grpcwebtext)', function() { }); }); + it('should error out on incomplete response', function(done) { + execSync(genCodeCmd); + const {EchoServiceClient} = require(genCodePath); + const {EchoRequest} = require(protoGenCodePath); + var echoService = new EchoServiceClient('MyHostname', null, null); + var request = new EchoRequest(); + MockXMLHttpRequest.onSend = function(xhr) { + xhr.respond(200, {'Content-Type': 'application/grpc-web-text'}, + // An incomplete response. The frame length indicates + // 26 bytes, but the rest of the frame only contains + // 18 bytes. + 'AAAAABoKCwgBEgdGaWN0aW9uCgsIAhI'); + }; + var call = echoService.echo( + request, {}, function(err, response) { + if (response) { + assert.fail('should not receive response'); + } + assert.equal(2, err.code); + assert.equal(true, err.message.toLowerCase().includes( + 'incomplete response')); + done(); + }); + call.on('data', (response) => { + assert.fail('should not receive response this way'); + }); + call.on('error', (error) => { + assert.fail('should not receive error this way'); + }); + }); + + it('should error out on invalid proto response', function(done) { + execSync(genCodeCmd); + const {EchoServiceClient} = require(genCodePath); + const {EchoRequest} = require(protoGenCodePath); + var echoService = new EchoServiceClient('MyHostname', null, null); + var request = new EchoRequest(); + MockXMLHttpRequest.onSend = function(xhr) { + xhr.respond(200, {'Content-Type': 'application/grpc-web-text'}, + // A valid grpc-web frame, but contains an invalid + // protobuf payload. + 'AAAAAAUKCgoKCg=='); + }; + var call = echoService.echo( + request, {}, function(err, response) { + if (response) { + assert.fail('should not receive response'); + } + assert.equal(2, err.code); + assert.equal(true, err.message.toLowerCase().includes('deserialize')); + assert.equal(true, err.message.toLowerCase().includes('error')); + done(); + }); + call.on('data', (response) => { + assert.fail('should not receive response this way'); + }); + call.on('error', (error) => { + assert.fail('should not receive error this way'); + }); + }); + + it('should error out on invalid response body', function(done) { + execSync(genCodeCmd); + const {EchoServiceClient} = require(genCodePath); + const {EchoRequest} = require(protoGenCodePath); + var echoService = new EchoServiceClient('MyHostname', null, null); + var request = new EchoRequest(); + MockXMLHttpRequest.onSend = function(xhr) { + xhr.respond(200, {'Content-Type': 'application/grpc-web-text'}, + // An invalid response body. Should trip up in the + // stream parser. + 'ZZZZZ'); + }; + var call = echoService.echo( + request, {}, function(err, response) { + if (response) { + assert.fail('should not receive response'); + } + assert.equal(2, err.code); + assert.equal(true, err.message.toLowerCase().includes( + 'error in parsing response body')); + done(); + }); + call.on('data', (response) => { + assert.fail('should not receive response this way'); + }); + call.on('error', (error) => { + assert.fail('should not receive error this way'); + }); + }); + it('should not receive response on non-ok status', function(done) { done = multiDone(done, 2); execSync(genCodeCmd);