Skip to content

Commit 82f7f36

Browse files
committed
feat: pass http status code to RpcError
1 parent 6a53370 commit 82f7f36

File tree

4 files changed

+111
-11
lines changed

4 files changed

+111
-11
lines changed

javascript/net/grpc/web/grpcwebclientbase_test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,27 @@ testSuite({
194194
assertEquals(3, error.code);
195195
},
196196

197+
async testRpcErrorWithHttpStatusCode() {
198+
const xhr = new XhrIo();
199+
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
200+
const methodDescriptor = createMethodDescriptor((bytes) => new MockReply());
201+
202+
const error = await new Promise((resolve, reject) => {
203+
client.rpcCall(
204+
'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor,
205+
(error, response) => {
206+
assertNull(response);
207+
resolve(error);
208+
});
209+
// This decodes to "grpc-status: 3"
210+
xhr.simulateResponse(505, '', {'Content-Type': 'text/html'});
211+
});
212+
assertTrue(error instanceof RpcError);
213+
assert('metadata' in error);
214+
assert('httpStatusCode' in error.metadata);
215+
assertEquals(505, error.metadata.httpStatusCode);
216+
},
217+
197218
async testRpcDeserializationError() {
198219
const xhr = new XhrIo();
199220
const client = new GrpcWebClientBase(/* options= */ {}, xhr);

javascript/net/grpc/web/grpcwebclientreadablestream.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,8 @@ class GrpcWebClientReadableStream {
136136

137137
const self = this;
138138
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
139-
let contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
140-
if (!contentType) return;
141-
contentType = contentType.toLowerCase();
139+
const contentType = (self.xhr_.getStreamingResponseHeader('Content-Type') || '').toLowerCase();
140+
if (!contentType.startsWith('application/grpc')) return;
142141

143142
let byteSource;
144143
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
@@ -152,11 +151,8 @@ class GrpcWebClientReadableStream {
152151
} else if (googString.startsWith(contentType, 'application/grpc')) {
153152
byteSource = new Uint8Array(
154153
/** @type {!ArrayBuffer} */ (self.xhr_.getResponse()));
155-
} else {
156-
self.handleError_(
157-
new RpcError(StatusCode.UNKNOWN, 'Unknown Content-type received.'));
158-
return;
159154
}
155+
160156
let messages = null;
161157
try {
162158
messages = self.parser_.parse(byteSource);
@@ -257,11 +253,14 @@ class GrpcWebClientReadableStream {
257253
return;
258254
}
259255
let errorMessage = ErrorCode.getDebugMessage(lastErrorCode);
256+
257+
const errorMetadata = /** @type {!StatusMetadata} */ ({});
260258
if (xhrStatusCode != -1) {
261259
errorMessage += ', http status code: ' + xhrStatusCode;
260+
errorMetadata['httpStatusCode'] = xhrStatusCode;
262261
}
263262

264-
self.handleError_(new RpcError(grpcStatusCode, errorMessage));
263+
self.handleError_(new RpcError(grpcStatusCode, errorMessage, errorMetadata));
265264
return;
266265
}
267266

packages/grpc-web/index.d.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
declare module "grpc-web" {
22

33
export interface Metadata { [s: string]: string; }
4+
export type StatusMetadata = Metadata & {
5+
httpStatusCode?: number
6+
};
47

58
export class AbstractClientBase {
69
thenableCall<REQ, RESP> (
@@ -105,15 +108,15 @@ declare module "grpc-web" {
105108
}
106109

107110
export class RpcError extends Error {
108-
constructor(code: StatusCode, message: string, metadata: Metadata);
111+
constructor(code: StatusCode, message: string, metadata: StatusMetadata);
109112
code: StatusCode;
110-
metadata: Metadata;
113+
metadata: StatusMetadata;
111114
}
112115

113116
export interface Status {
114117
code: number;
115118
details: string;
116-
metadata?: Metadata;
119+
metadata?: StatusMetadata;
117120
}
118121

119122
export enum StatusCode {

packages/grpc-web/test/generated_code_test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,29 @@ describe('grpc-web generated code: promise-based client', function() {
118118
});
119119
});
120120

121+
it('should receive error, on http error - Content-Type not matching application/grpc*', function(done) {
122+
const {EchoServicePromiseClient} = require(genCodePath);
123+
const {EchoRequest} = require(protoGenCodePath);
124+
MockXMLHttpRequest.onSend = function(xhr) {
125+
xhr.respond(
126+
505, {'Content-Type': 'text/html'});
127+
};
128+
var echoService = new EchoServicePromiseClient('MyHostname', null, null);
129+
var request = new EchoRequest();
130+
request.setMessage('aaa');
131+
132+
echoService.echo(request, {})
133+
.then((response) => {
134+
assert.fail('should not receive response');
135+
})
136+
.catch((error) => {
137+
assert('metadata' in error);
138+
assert('httpStatusCode' in error.metadata);
139+
assert.equal(505, error.metadata.httpStatusCode);
140+
done();
141+
});
142+
});
143+
121144
it('should receive error', function(done) {
122145
const {EchoServicePromiseClient} = require(genCodePath);
123146
const {EchoRequest} = require(protoGenCodePath);
@@ -613,6 +636,36 @@ describe('grpc-web generated code: callbacks tests', function() {
613636
});
614637
});
615638

639+
it('should receive error, on http error - Content-Type not matching application/grpc*', function(done) {
640+
done = multiDone(done, 2);
641+
MockXMLHttpRequest.onSend = function(xhr) {
642+
xhr.respond(
643+
505, {'Content-Type': 'text/html'});
644+
};
645+
var call = echoService.echo(
646+
request, {},
647+
function(err, response) {
648+
if (response) {
649+
assert.fail('should not have received response with non-OK status');
650+
} else {
651+
assert('metadata' in err);
652+
assert('httpStatusCode' in err.metadata);
653+
assert.equal(505, err.metadata.httpStatusCode);
654+
}
655+
done();
656+
}
657+
);
658+
call.on('status', (status) => {
659+
assert('metadata' in status);
660+
assert('httpStatusCode' in status.metadata);
661+
assert.equal(505, status.metadata.httpStatusCode);
662+
done();
663+
});
664+
call.on('error', (error) => {
665+
assert.fail('error callback should not be called for unary calls');
666+
});
667+
});
668+
616669
it('should receive error, on http error', function(done) {
617670
done = multiDone(done, 2);
618671
MockXMLHttpRequest.onSend = function(xhr) {
@@ -802,6 +855,30 @@ describe('grpc-web generated code: callbacks tests', function() {
802855
});
803856
});
804857

858+
it('should receive error, on http error (streaming) - Content-Type not matching application/grpc*', function(done) {
859+
done = multiDone(done, 2);
860+
MockXMLHttpRequest.onSend = function(xhr) {
861+
xhr.respond(
862+
505, {'Content-Type': 'text/html'});
863+
};
864+
var call = echoService.serverStreamingEcho(request, {});
865+
call.on('data', (response) => {
866+
assert.fail('should not receive data response');
867+
});
868+
call.on('status', (status) => {
869+
assert('metadata' in status);
870+
assert('httpStatusCode' in status.metadata);
871+
assert.equal(505, status.metadata.httpStatusCode);
872+
done();
873+
});
874+
call.on('error', (error) => {
875+
assert('metadata' in error);
876+
assert('httpStatusCode' in error.metadata);
877+
assert.equal(505, error.metadata.httpStatusCode);
878+
done();
879+
});
880+
});
881+
805882
it('should receive error, on http error (streaming)', function(done) {
806883
done = multiDone(done, 2);
807884
MockXMLHttpRequest.onSend = function(xhr) {

0 commit comments

Comments
 (0)