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.NodeReadableStream');
|
||||||
goog.require('goog.net.streams.createXhrNodeReadableStream');
|
goog.require('goog.net.streams.createXhrNodeReadableStream');
|
||||||
goog.require('jspb.Message');
|
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
|
* @final
|
||||||
* @param {!goog.net.XhrIo} xhr The XhrIo object
|
* @param {!goog.net.XhrIo} xhr The XhrIo object
|
||||||
* @param {function(?):!jspb.Message} deserializeFunc
|
* @param {function(?):!jspb.Message} deserializeFunc
|
||||||
|
* @param {function(!string):!Object} parseRpcStatusFunc
|
||||||
* The deserialize function for the proto
|
* The deserialize function for the proto
|
||||||
*/
|
*/
|
||||||
grpc.web.ClientReadableStream = function(
|
grpc.web.ClientReadableStream = function(
|
||||||
xhr, deserializeFunc) {
|
xhr, deserializeFunc, parseRpcStatusFunc) {
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {?goog.net.streams.NodeReadableStream} The XHR Node Readable
|
* @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
|
* @type {function(!Object)|null} The trailing metadata callback
|
||||||
*/
|
*/
|
||||||
this.onStatusCallback_ = null;
|
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') {
|
if (eventType == 'data') {
|
||||||
this.xhrNodeReadableStream_.on('data', function(data) {
|
this.xhrNodeReadableStream_.on('data', function(data) {
|
||||||
if ('1' in data) {
|
if ('1' in data) {
|
||||||
var base64_message = data['1'];
|
var response = self.deserializeFunc_(data['1']);
|
||||||
var response = self.deserializeFunc_(base64_message);
|
|
||||||
callback(response);
|
callback(response);
|
||||||
}
|
}
|
||||||
if ('2' in data) {
|
if ('2' in data && self.onStatusCallback_) {
|
||||||
var base64_message = data['2'];
|
var status = self.parseRpcStatusFunc_(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_) {
|
|
||||||
self.onStatusCallback_(status);
|
self.onStatusCallback_(status);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else if (eventType == 'status') {
|
} else if (eventType == 'status') {
|
||||||
this.onStatusCallback_ = callback;
|
this.onStatusCallback_ = callback;
|
||||||
|
@ -108,6 +93,7 @@ grpc.web.ClientReadableStream.prototype.on = function(
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the stream.
|
* Close the stream.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,9 +8,13 @@
|
||||||
goog.provide('grpc.web.GatewayClientBase');
|
goog.provide('grpc.web.GatewayClientBase');
|
||||||
|
|
||||||
|
|
||||||
|
goog.require('goog.crypt');
|
||||||
goog.require('goog.crypt.base64');
|
goog.require('goog.crypt.base64');
|
||||||
goog.require('goog.net.XhrIo');
|
goog.require('goog.net.XhrIo');
|
||||||
goog.require('grpc.web.ClientReadableStream');
|
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 {!Object<string, string>} metadata User defined call metadata
|
||||||
* @param {function(?):!jspb.Message} deserializeFunc
|
* @param {function(?):!jspb.Message} deserializeFunc
|
||||||
* The deserialize function for the proto
|
* 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)
|
* function which takes (error, response)
|
||||||
* @return {!grpc.web.ClientReadableStream|undefined} The Client Readable
|
* @return {!grpc.web.ClientReadableStream|undefined} The Client Readable
|
||||||
* Stream
|
* Stream
|
||||||
|
@ -55,8 +59,16 @@ grpc.web.GatewayClientBase.prototype.rpcCall = function(
|
||||||
var stream = this.getClientReadableStream_(xhr, deserializeFunc);
|
var stream = this.getClientReadableStream_(xhr, deserializeFunc);
|
||||||
|
|
||||||
stream.on('data', function(response) {
|
stream.on('data', function(response) {
|
||||||
var err = null; // TODO: propagate error
|
callback(null, response);
|
||||||
callback(err, 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');
|
xhr.headers.set('Content-Type', 'application/x-protobuf');
|
||||||
|
@ -110,5 +122,34 @@ grpc.web.GatewayClientBase.prototype.newXhr_ = function() {
|
||||||
*/
|
*/
|
||||||
grpc.web.GatewayClientBase.prototype.getClientReadableStream_ = function(
|
grpc.web.GatewayClientBase.prototype.getClientReadableStream_ = function(
|
||||||
xhr, deserializeFunc) {
|
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"
|
" * request proto\n"
|
||||||
" * @param {!Object<string, string>} metadata User defined\n"
|
" * @param {!Object<string, string>} metadata User defined\n"
|
||||||
" * call metadata\n"
|
" * call metadata\n"
|
||||||
" * @param {function(?string, ?Object=)} callback "
|
" * @param {function(?Object=, ?Object=)} callback "
|
||||||
"The callback\n"
|
"The callback\n"
|
||||||
" * function(error, response)\n"
|
" * function(error, response)\n"
|
||||||
" * @return {!grpc.web.ClientReadableStream|undefined} The XHR Node\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();
|
var unaryRequest = new proto.grpc.gateway.testing.EchoRequest();
|
||||||
unaryRequest.setMessage(msg);
|
unaryRequest.setMessage(msg);
|
||||||
echoService.Echo(unaryRequest, {}, function(err, response) {
|
echoService.Echo(unaryRequest, {}, function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
addRightMessage('Error code: '+err.code+' "'+err.message+'"');
|
||||||
|
} else {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
addRightMessage(response.getMessage());
|
addRightMessage(response.getMessage());
|
||||||
}, INTERVAL);
|
}, INTERVAL);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,6 +55,11 @@
|
||||||
stream.on('data', function(response) {
|
stream.on('data', function(response) {
|
||||||
addRightMessage(response.getMessage());
|
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) {
|
var send = function(e) {
|
||||||
|
|
Loading…
Reference in New Issue