mirror of https://github.com/grpc/grpc-web.git
Propagate grpc backend status to frontend
This commit is contained in:
parent
55cfeac15f
commit
cea6f9ae8a
|
@ -16,8 +16,6 @@ goog.require('goog.net.XhrIo');
|
|||
goog.require('goog.net.streams.NodeReadableStream');
|
||||
goog.require('goog.net.streams.createXhrNodeReadableStream');
|
||||
goog.require('jspb.Message');
|
||||
goog.require('proto.google.rpc.Status');
|
||||
goog.require('proto.grpc.gateway.Pair');
|
||||
|
||||
|
||||
/**
|
||||
|
@ -28,10 +26,11 @@ goog.require('proto.grpc.gateway.Pair');
|
|||
* @final
|
||||
* @param {!goog.net.XhrIo} xhr The XhrIo object
|
||||
* @param {function(?):!jspb.Message} deserializeFunc
|
||||
* @param {function(!string):!Object} parseRpcStatusFunc
|
||||
* The deserialize function for the proto
|
||||
*/
|
||||
grpc.web.ClientReadableStream = function(
|
||||
xhr, deserializeFunc) {
|
||||
xhr, deserializeFunc, parseRpcStatusFunc) {
|
||||
/**
|
||||
* @private
|
||||
* @type {?goog.net.streams.NodeReadableStream} The XHR Node Readable
|
||||
|
@ -57,6 +56,12 @@ grpc.web.ClientReadableStream = function(
|
|||
* @type {function(!Object)|null} The trailing metadata callback
|
||||
*/
|
||||
this.onStatusCallback_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(!string):!Object} A function to parse the Rpc Status response
|
||||
*/
|
||||
this.parseRpcStatusFunc_ = parseRpcStatusFunc;
|
||||
};
|
||||
|
||||
|
||||
|
@ -74,33 +79,13 @@ grpc.web.ClientReadableStream.prototype.on = function(
|
|||
if (eventType == 'data') {
|
||||
this.xhrNodeReadableStream_.on('data', function(data) {
|
||||
if ('1' in data) {
|
||||
var base64_message = data['1'];
|
||||
var response = self.deserializeFunc_(base64_message);
|
||||
var response = self.deserializeFunc_(data['1']);
|
||||
callback(response);
|
||||
}
|
||||
if ('2' in data) {
|
||||
var base64_message = data['2'];
|
||||
var _status =
|
||||
proto.google.rpc.Status.deserializeBinary(base64_message);
|
||||
var status = {};
|
||||
var metadata = {};
|
||||
status['code'] = _status.getCode();
|
||||
status['details'] = _status.getMessage();
|
||||
var details = _status.getDetailsList();
|
||||
for (var i = 0; i < details.length; i++) {
|
||||
var pair = proto.grpc.gateway.Pair.deserializeBinary(
|
||||
details[i].getValue());
|
||||
var first = new TextDecoder("utf-8").decode(
|
||||
/** @type {!ArrayBufferView|undefined} */ (pair.getFirst_asU8()));
|
||||
var second = new TextDecoder("utf-8").decode(
|
||||
/** @type {!ArrayBufferView|undefined} */ (pair.getSecond_asU8()));
|
||||
metadata[first] = second;
|
||||
}
|
||||
status['metadata'] = metadata;
|
||||
if (self.onStatusCallback_) {
|
||||
if ('2' in data && self.onStatusCallback_) {
|
||||
var status = self.parseRpcStatusFunc_(data['2']);
|
||||
self.onStatusCallback_(status);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (eventType == 'status') {
|
||||
this.onStatusCallback_ = callback;
|
||||
|
@ -108,6 +93,7 @@ grpc.web.ClientReadableStream.prototype.on = function(
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the stream.
|
||||
*/
|
||||
|
|
|
@ -8,9 +8,13 @@
|
|||
goog.provide('grpc.web.GatewayClientBase');
|
||||
|
||||
|
||||
goog.require('goog.crypt');
|
||||
goog.require('goog.crypt.base64');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('grpc.web.ClientReadableStream');
|
||||
goog.require('grpc.web.Status');
|
||||
goog.require('proto.google.rpc.Status');
|
||||
goog.require('proto.grpc.gateway.Pair');
|
||||
|
||||
|
||||
|
||||
|
@ -40,7 +44,7 @@ grpc.web.GatewayClientBase.prototype.serialize_ = function(request) {
|
|||
* @param {!Object<string, string>} metadata User defined call metadata
|
||||
* @param {function(?):!jspb.Message} deserializeFunc
|
||||
* The deserialize function for the proto
|
||||
* @param {function(?string, ?Object=)} callback A callback
|
||||
* @param {function(?Object=, ?Object=)} callback A callback
|
||||
* function which takes (error, response)
|
||||
* @return {!grpc.web.ClientReadableStream|undefined} The Client Readable
|
||||
* Stream
|
||||
|
@ -55,8 +59,16 @@ grpc.web.GatewayClientBase.prototype.rpcCall = function(
|
|||
var stream = this.getClientReadableStream_(xhr, deserializeFunc);
|
||||
|
||||
stream.on('data', function(response) {
|
||||
var err = null; // TODO: propagate error
|
||||
callback(err, response);
|
||||
callback(null, response);
|
||||
});
|
||||
|
||||
stream.on('status', function(status) {
|
||||
if (status.code != grpc.web.Status.StatusCode.OK) {
|
||||
callback({
|
||||
'code': status.code,
|
||||
'message': status.details
|
||||
}, null);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.headers.set('Content-Type', 'application/x-protobuf');
|
||||
|
@ -110,5 +122,34 @@ grpc.web.GatewayClientBase.prototype.newXhr_ = function() {
|
|||
*/
|
||||
grpc.web.GatewayClientBase.prototype.getClientReadableStream_ = function(
|
||||
xhr, deserializeFunc) {
|
||||
return new grpc.web.ClientReadableStream(xhr, deserializeFunc);
|
||||
return new grpc.web.ClientReadableStream(xhr, deserializeFunc,
|
||||
grpc.web.GatewayClientBase.parseRpcStatusFunc_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @static
|
||||
* @param {!string} data Data returned from underlying stream
|
||||
* @return {!Object} status The Rpc Status details
|
||||
*/
|
||||
grpc.web.GatewayClientBase.parseRpcStatusFunc_ = function(data) {
|
||||
var rpcStatus =
|
||||
proto.google.rpc.Status.deserializeBinary(data);
|
||||
var status = {};
|
||||
var metadata = {};
|
||||
status['code'] = rpcStatus.getCode();
|
||||
status['details'] = rpcStatus.getMessage();
|
||||
var details = rpcStatus.getDetailsList();
|
||||
for (var i = 0; i < details.length; i++) {
|
||||
var pair = proto.grpc.gateway.Pair.deserializeBinary(
|
||||
details[i].getValue());
|
||||
var first = goog.crypt.utf8ByteArrayToString(
|
||||
pair.getFirst_asU8());
|
||||
var second = goog.crypt.utf8ByteArrayToString(
|
||||
pair.getSecond_asU8());
|
||||
metadata[first] = second;
|
||||
}
|
||||
status['metadata'] = metadata;
|
||||
return status;
|
||||
};
|
||||
|
|
|
@ -168,7 +168,7 @@ void PrintUnaryCall(Printer* printer, std::map<string, string> vars) {
|
|||
" * request proto\n"
|
||||
" * @param {!Object<string, string>} metadata User defined\n"
|
||||
" * call metadata\n"
|
||||
" * @param {function(?string, ?Object=)} callback "
|
||||
" * @param {function(?Object=, ?Object=)} callback "
|
||||
"The callback\n"
|
||||
" * function(error, response)\n"
|
||||
" * @return {!grpc.web.ClientReadableStream|undefined} The XHR Node\n"
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* @fileoverview gRPC Web Status codes and mapping.
|
||||
*
|
||||
* gRPC Web Status codes and mapping.
|
||||
*
|
||||
* @author stanleycheung@google.com (Stanley Cheung)
|
||||
*/
|
||||
goog.provide('grpc.web.Status');
|
||||
|
||||
|
||||
/**
|
||||
* gRPC Status Codes
|
||||
* See: https://github.com/grpc/grpc/blob/master/include/grpc%2B%2B/impl/codegen/status_code_enum.h
|
||||
* @enum {int}
|
||||
*/
|
||||
grpc.web.Status.StatusCode = {
|
||||
// Not an error; returned on success.
|
||||
OK: 0,
|
||||
|
||||
// The operation was cancelled (typically by the caller).
|
||||
CANCELLED: 1,
|
||||
|
||||
// Unknown error. An example of where this error may be returned is if a
|
||||
// Status value received from another address space belongs to an error-space
|
||||
// that is not known in this address space. Also errors raised by APIs that
|
||||
// do not return enough error information may be converted to this error.
|
||||
UNKNOWN: 2,
|
||||
|
||||
// Client specified an invalid argument. Note that this differs from
|
||||
// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
|
||||
// problematic regardless of the state of the system (e.g., a malformed file
|
||||
// name).
|
||||
INVALID_ARGUMENT: 3,
|
||||
|
||||
// Deadline expired before operation could complete. For operations that
|
||||
// change the state of the system, this error may be returned even if the
|
||||
// operation has completed successfully. For example, a successful response
|
||||
// from a server could have been delayed long enough for the deadline to
|
||||
// expire.
|
||||
DEADLINE_EXCEEDED: 4,
|
||||
|
||||
// Some requested entity (e.g., file or directory) was not found.
|
||||
NOT_FOUND: 5,
|
||||
|
||||
// Some entity that we attempted to create (e.g., file or directory) already
|
||||
// exists.
|
||||
ALREADY_EXISTS: 6,
|
||||
|
||||
// The caller does not have permission to execute the specified operation.
|
||||
// PERMISSION_DENIED must not be used for rejections caused by exhausting
|
||||
// some resource (use RESOURCE_EXHAUSTED instead for those errors).
|
||||
// PERMISSION_DENIED must not be used if the caller can not be identified
|
||||
// (use UNAUTHENTICATED instead for those errors).
|
||||
PERMISSION_DENIED: 7,
|
||||
|
||||
// The request does not have valid authentication credentials for the
|
||||
// operation.
|
||||
UNAUTHENTICATED: 16,
|
||||
|
||||
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
|
||||
// entire file system is out of space.
|
||||
RESOURCE_EXHAUSTED: 8,
|
||||
|
||||
// Operation was rejected because the system is not in a state required for
|
||||
// the operation's execution. For example, directory to be deleted may be
|
||||
// non-empty, an rmdir operation is applied to a non-directory, etc.
|
||||
//
|
||||
// A litmus test that may help a service implementor in deciding
|
||||
// between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
|
||||
// (a) Use UNAVAILABLE if the client can retry just the failing call.
|
||||
// (b) Use ABORTED if the client should retry at a higher-level
|
||||
// (e.g., restarting a read-modify-write sequence).
|
||||
// (c) Use FAILED_PRECONDITION if the client should not retry until
|
||||
// the system state has been explicitly fixed. E.g., if an "rmdir"
|
||||
// fails because the directory is non-empty, FAILED_PRECONDITION
|
||||
// should be returned since the client should not retry unless
|
||||
// they have first fixed up the directory by deleting files from it.
|
||||
// (d) Use FAILED_PRECONDITION if the client performs conditional
|
||||
// REST Get/Update/Delete on a resource and the resource on the
|
||||
// server does not match the condition. E.g., conflicting
|
||||
// read-modify-write on the same resource.
|
||||
FAILED_PRECONDITION: 9,
|
||||
|
||||
// The operation was aborted, typically due to a concurrency issue like
|
||||
// sequencer check failures, transaction aborts, etc.
|
||||
//
|
||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||
// and UNAVAILABLE.
|
||||
ABORTED: 10,
|
||||
|
||||
// Operation was attempted past the valid range. E.g., seeking or reading
|
||||
// past end of file.
|
||||
//
|
||||
// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
|
||||
// if the system state changes. For example, a 32-bit file system will
|
||||
// generate INVALID_ARGUMENT if asked to read at an offset that is not in the
|
||||
// range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
|
||||
// an offset past the current file size.
|
||||
//
|
||||
// There is a fair bit of overlap between FAILED_PRECONDITION and
|
||||
// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
|
||||
// when it applies so that callers who are iterating through a space can
|
||||
// easily look for an OUT_OF_RANGE error to detect when they are done.
|
||||
OUT_OF_RANGE: 11,
|
||||
|
||||
// Operation is not implemented or not supported/enabled in this service.
|
||||
UNIMPLEMENTED: 12,
|
||||
|
||||
// Internal errors. Means some invariants expected by underlying System has
|
||||
// been broken. If you see one of these errors, Something is very broken.
|
||||
INTERNAL: 13,
|
||||
|
||||
// The service is currently unavailable. This is a most likely a transient
|
||||
// condition and may be corrected by retrying with a backoff.
|
||||
//
|
||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||
// and UNAVAILABLE.
|
||||
UNAVAILABLE: 14,
|
||||
|
||||
// Unrecoverable data loss or corruption.
|
||||
DATA_LOSS: 15,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convert HTTP Status code to gRPC Status code
|
||||
* @param {int} http_status HTTP Status Code
|
||||
* @return {int} gRPC Status Code
|
||||
*/
|
||||
grpc.web.Status.HttpStatusToGrpcStatus = function(http_status) {
|
||||
switch (http_status) {
|
||||
case 200:
|
||||
return grpc.web.Status.StatusCode.OK;
|
||||
case 400:
|
||||
return grpc.web.Status.StatusCode.INVALID_ARGUMENT;
|
||||
case 401:
|
||||
return grpc.web.Status.StatusCode.UNAUTHENTICATED;
|
||||
case 403:
|
||||
return grpc.web.Status.StatusCode.PERMISSION_DENIED;
|
||||
case 404:
|
||||
return grpc.web.Status.StatusCode.NOT_FOUND;
|
||||
case 409:
|
||||
return grpc.web.Status.StatusCode.ABORTED;
|
||||
case 412:
|
||||
return grpc.web.Status.StatusCode.FAILED_PRECONDITION;
|
||||
case 429:
|
||||
return grpc.web.Status.StatusCode.RESOURCE_EXHAUSTED;
|
||||
case 499:
|
||||
return grpc.web.Status.StatusCode.CANCELLED;
|
||||
case 500:
|
||||
return grpc.web.Status.StatusCode.UNKNOWN;
|
||||
case 501:
|
||||
return grpc.web.Status.StatusCode.UNIMPLEMENTED;
|
||||
case 503:
|
||||
return grpc.web.Status.StatusCode.UNAVAILABLE;
|
||||
case 504:
|
||||
return grpc.web.Status.StatusCode.DEADLINE_EXCEEDED;
|
||||
/* everything else is unknown */
|
||||
default:
|
||||
return grpc.web.Status.StatusCode.UNKNOWN;
|
||||
}
|
||||
};
|
|
@ -32,9 +32,13 @@
|
|||
var unaryRequest = new proto.grpc.gateway.testing.EchoRequest();
|
||||
unaryRequest.setMessage(msg);
|
||||
echoService.Echo(unaryRequest, {}, function(err, response) {
|
||||
if (err) {
|
||||
addRightMessage('Error code: '+err.code+' "'+err.message+'"');
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
addRightMessage(response.getMessage());
|
||||
}, INTERVAL);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -51,6 +55,11 @@
|
|||
stream.on('data', function(response) {
|
||||
addRightMessage(response.getMessage());
|
||||
});
|
||||
stream.on('status', function(status) {
|
||||
if (status.code != grpc.web.Status.StatusCode.OK) {
|
||||
addRightMessage('Error code: '+status.code+' "'+status.details+'"');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var send = function(e) {
|
||||
|
|
Loading…
Reference in New Issue