mirror of https://github.com/grpc/grpc-web.git
feat: pass http status code to RpcError
This commit is contained in:
parent
b5ff5d303d
commit
b84dc9c1bc
|
@ -194,6 +194,27 @@ testSuite({
|
||||||
assertEquals(3, error.code);
|
assertEquals(3, error.code);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async testRpcErrorWithHttpStatusCode() {
|
||||||
|
const xhr = new XhrIo();
|
||||||
|
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
||||||
|
const methodDescriptor = createMethodDescriptor((bytes) => new MockReply());
|
||||||
|
|
||||||
|
const error = await new Promise((resolve, reject) => {
|
||||||
|
client.rpcCall(
|
||||||
|
'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
||||||
|
(error, response) => {
|
||||||
|
assertNull(response);
|
||||||
|
resolve(error);
|
||||||
|
});
|
||||||
|
// This decodes to "grpc-status: 3"
|
||||||
|
xhr.simulateResponse(505, '', {'Content-Type': 'text/html'});
|
||||||
|
});
|
||||||
|
assertTrue(error instanceof RpcError);
|
||||||
|
assert('metadata' in error);
|
||||||
|
assert('httpStatusCode' in error.metadata);
|
||||||
|
assertEquals(505, error.metadata.httpStatusCode);
|
||||||
|
},
|
||||||
|
|
||||||
async testRpcDeserializationError() {
|
async testRpcDeserializationError() {
|
||||||
const xhr = new XhrIo();
|
const xhr = new XhrIo();
|
||||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
||||||
|
|
|
@ -136,9 +136,8 @@ class GrpcWebClientReadableStream {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
|
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
|
||||||
let contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
|
const contentType = (self.xhr_.getStreamingResponseHeader('Content-Type') || '').toLowerCase();
|
||||||
if (!contentType) return;
|
if (!contentType.startsWith('application/grpc')) return;
|
||||||
contentType = contentType.toLowerCase();
|
|
||||||
|
|
||||||
let byteSource;
|
let byteSource;
|
||||||
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
||||||
|
@ -152,11 +151,8 @@ class GrpcWebClientReadableStream {
|
||||||
} else if (googString.startsWith(contentType, 'application/grpc')) {
|
} else if (googString.startsWith(contentType, 'application/grpc')) {
|
||||||
byteSource = new Uint8Array(
|
byteSource = new Uint8Array(
|
||||||
/** @type {!ArrayBuffer} */ (self.xhr_.getResponse()));
|
/** @type {!ArrayBuffer} */ (self.xhr_.getResponse()));
|
||||||
} else {
|
|
||||||
self.handleError_(
|
|
||||||
new RpcError(StatusCode.UNKNOWN, 'Unknown Content-type received.'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let messages = null;
|
let messages = null;
|
||||||
try {
|
try {
|
||||||
messages = self.parser_.parse(byteSource);
|
messages = self.parser_.parse(byteSource);
|
||||||
|
@ -257,11 +253,14 @@ class GrpcWebClientReadableStream {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let errorMessage = ErrorCode.getDebugMessage(lastErrorCode);
|
let errorMessage = ErrorCode.getDebugMessage(lastErrorCode);
|
||||||
|
|
||||||
|
const errorMetadata = /** @type {!StatusMetadata} */ ({});
|
||||||
if (xhrStatusCode != -1) {
|
if (xhrStatusCode != -1) {
|
||||||
errorMessage += ', http status code: ' + xhrStatusCode;
|
errorMessage += ', http status code: ' + xhrStatusCode;
|
||||||
|
errorMetadata['httpStatusCode'] = xhrStatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handleError_(new RpcError(grpcStatusCode, errorMessage));
|
self.handleError_(new RpcError(grpcStatusCode, errorMessage, errorMetadata));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
declare module "grpc-web" {
|
declare module "grpc-web" {
|
||||||
|
|
||||||
export interface Metadata { [s: string]: string; }
|
export interface Metadata { [s: string]: string; }
|
||||||
|
export type StatusMetadata = Metadata & {
|
||||||
|
httpStatusCode?: number
|
||||||
|
};
|
||||||
|
|
||||||
export class AbstractClientBase {
|
export class AbstractClientBase {
|
||||||
thenableCall<REQ, RESP> (
|
thenableCall<REQ, RESP> (
|
||||||
|
@ -105,15 +108,15 @@ declare module "grpc-web" {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RpcError extends Error {
|
export class RpcError extends Error {
|
||||||
constructor(code: StatusCode, message: string, metadata: Metadata);
|
constructor(code: StatusCode, message: string, metadata: StatusMetadata);
|
||||||
code: StatusCode;
|
code: StatusCode;
|
||||||
metadata: Metadata;
|
metadata: StatusMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Status {
|
export interface Status {
|
||||||
code: number;
|
code: number;
|
||||||
details: string;
|
details: string;
|
||||||
metadata?: Metadata;
|
metadata?: StatusMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StatusCode {
|
export enum StatusCode {
|
||||||
|
|
|
@ -118,6 +118,29 @@ describe('grpc-web generated code: promise-based client', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should receive error, on http error - Content-Type not matching application/grpc*', function(done) {
|
||||||
|
const {EchoServicePromiseClient} = require(genCodePath);
|
||||||
|
const {EchoRequest} = require(protoGenCodePath);
|
||||||
|
MockXMLHttpRequest.onSend = function(xhr) {
|
||||||
|
xhr.respond(
|
||||||
|
505, {'Content-Type': 'text/html'});
|
||||||
|
};
|
||||||
|
var echoService = new EchoServicePromiseClient('MyHostname', null, null);
|
||||||
|
var request = new EchoRequest();
|
||||||
|
request.setMessage('aaa');
|
||||||
|
|
||||||
|
echoService.echo(request, {})
|
||||||
|
.then((response) => {
|
||||||
|
assert.fail('should not receive response');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
assert('metadata' in error);
|
||||||
|
assert('httpStatusCode' in error.metadata);
|
||||||
|
assert.equal(505, error.metadata.httpStatusCode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should receive error', function(done) {
|
it('should receive error', function(done) {
|
||||||
const {EchoServicePromiseClient} = require(genCodePath);
|
const {EchoServicePromiseClient} = require(genCodePath);
|
||||||
const {EchoRequest} = require(protoGenCodePath);
|
const {EchoRequest} = require(protoGenCodePath);
|
||||||
|
@ -613,6 +636,36 @@ describe('grpc-web generated code: callbacks tests', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should receive error, on http error - Content-Type not matching application/grpc*', function(done) {
|
||||||
|
done = multiDone(done, 2);
|
||||||
|
MockXMLHttpRequest.onSend = function(xhr) {
|
||||||
|
xhr.respond(
|
||||||
|
505, {'Content-Type': 'text/html'});
|
||||||
|
};
|
||||||
|
var call = echoService.echo(
|
||||||
|
request, {},
|
||||||
|
function(err, response) {
|
||||||
|
if (response) {
|
||||||
|
assert.fail('should not have received response with non-OK status');
|
||||||
|
} else {
|
||||||
|
assert('metadata' in err);
|
||||||
|
assert('httpStatusCode' in err.metadata);
|
||||||
|
assert.equal(505, err.metadata.httpStatusCode);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
call.on('status', (status) => {
|
||||||
|
assert('metadata' in status);
|
||||||
|
assert('httpStatusCode' in status.metadata);
|
||||||
|
assert.equal(505, status.metadata.httpStatusCode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
call.on('error', (error) => {
|
||||||
|
assert.fail('error callback should not be called for unary calls');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should receive error, on http error', function(done) {
|
it('should receive error, on http error', function(done) {
|
||||||
done = multiDone(done, 2);
|
done = multiDone(done, 2);
|
||||||
MockXMLHttpRequest.onSend = function(xhr) {
|
MockXMLHttpRequest.onSend = function(xhr) {
|
||||||
|
@ -802,6 +855,30 @@ describe('grpc-web generated code: callbacks tests', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should receive error, on http error (streaming) - Content-Type not matching application/grpc*', function(done) {
|
||||||
|
done = multiDone(done, 2);
|
||||||
|
MockXMLHttpRequest.onSend = function(xhr) {
|
||||||
|
xhr.respond(
|
||||||
|
505, {'Content-Type': 'text/html'});
|
||||||
|
};
|
||||||
|
var call = echoService.serverStreamingEcho(request, {});
|
||||||
|
call.on('data', (response) => {
|
||||||
|
assert.fail('should not receive data response');
|
||||||
|
});
|
||||||
|
call.on('status', (status) => {
|
||||||
|
assert('metadata' in status);
|
||||||
|
assert('httpStatusCode' in status.metadata);
|
||||||
|
assert.equal(505, status.metadata.httpStatusCode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
call.on('error', (error) => {
|
||||||
|
assert('metadata' in error);
|
||||||
|
assert('httpStatusCode' in error.metadata);
|
||||||
|
assert.equal(505, error.metadata.httpStatusCode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should receive error, on http error (streaming)', function(done) {
|
it('should receive error, on http error (streaming)', function(done) {
|
||||||
done = multiDone(done, 2);
|
done = multiDone(done, 2);
|
||||||
MockXMLHttpRequest.onSend = function(xhr) {
|
MockXMLHttpRequest.onSend = function(xhr) {
|
||||||
|
|
Loading…
Reference in New Issue