当前的返回格式
1. grpc 层 resp, error
返回 nil, error
return nil, errors.New("Instance Not Exists!")
{
"error": "Instance Not Exists!",
"code": 2,
"message": "Instance Not Exists!"
}
# ---------------------Explain------------------
'''
code 2 is `codes.Unknown`,
`in grpc/interceptor.go`:
UnaryHandler defines the handler invoked by UnaryServerInterceptor to complete the normal
execution of a unary RPC. If a UnaryHandler returns an error, it should be produced by the
status package, or else gRPC will use codes.Unknown as the status code and err.Error() as
the status message of the RPC.
'''
return nil, status.Error(codes.NotFound, "Instance Not Exists!")
{
"error": "Instance Not Exists!",
"code": 5,
"message": "Instance Not Exists!"
}
return nil, status.Error(codes.NotFound, "")
{
"code": 5
}
2. grpc 层 resp, error
返回 respWithErr, nil
SetErrResp(resp, reqId, DefaultErrCode, err.Error())
return resp, nil
{
"requestId": "62eec7c5-edcf-498b-b13c-f87a77b57feb",
"err": {
"Code": 400,
"Message": "Count shoud be a positive number"
}
}
实现方式:
xxx.proto
message CreateInstanceResponse {
// instance id list
repeated string instanceId = 1;
// request id
string requestId = 98;
// err message
ErrorbaseResponse err = 99;
}
生成的 xxx.pb.go
type CreateInstanceResponse struct {
// instance id list
InstanceId []string `protobuf:"bytes,1,rep,name=instanceId,proto3" json:"instanceId,omitempty"`
// request id
RequestId string `protobuf:"bytes,98,opt,name=requestId,proto3" json:"requestId,omitempty"`
// err message
Err *ErrorbaseResponse `protobuf:"bytes,99,opt,name=err,proto3" json:"err,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
API 层 xxx.go
grpc 返回
func (i *InstanceService) CreateInstance(ctx context.Context, req *pb.CreateInstanceRequest) (*pb.CreateInstanceResponse, error) {{
...
SetErrResp(resp, reqId, DefaultErrCode, err.Error())
return resp, nil
}
func SetErrResp(obj interface{}, reqId string, code int32, errMsg string) {
...
reqIdV := v.FieldByName("RequestId")
if reqIdV.IsValid() {
reqIdV.Set(reflect.ValueOf(reqId))
}
errV := v.FieldByName("Err")
if errV.IsValid() {
errV.Set(reflect.ValueOf(&pb.ErrorbaseResponse{
Code: code,
Message: errMsg,
}))
}
...
}
old ark 返回方式
service 层返回 errors.go
生成的 ApiError
type ApiError struct {
HttpCode int
Code string
Message string
}
func NewApiError(code string, msg string, httpCode int) *ApiError {
return &ApiError{Code: code, Message: msg, HttpCode: httpCode}
}
# -----------------代码中的用法---------------
return e.ErrorNoVaildHost
return e.ErrorInternalError
// err.go 定义的 common code 示例
ErrorActionNotFound = NewApiError("ActionNotFound", "动作不存在", http.StatusBadRequest)
ErrorMethodNotFound = NewApiError("MethodNotFound", "方法不存在", http.StatusNotFound)
handler 层返回 ResponseMessage,其中嵌套 ResponseMetadata,ResponseMetadata 嵌套 ErrorMessage
type ErrorMessage struct {
Code string
Message string
}
type ResponseMetadata struct {
RequestId string `json:"RequestId,omitempty"`
Action string `json:"Action,omitempty"`
Version string `json:"Version,omitempty"`
Service string `json:"Service,omitempty"`
Region string `json:"Region,omitempty"`
Error *ErrorMessage `json:"Error,omitempty"`
}
type ResponseMessage struct {
ResponseMetadata *ResponseMetadata
Result interface{} `json:"Result,omitempty"`
}
在 handler 层,逻辑判断
- 有错误则返回错误;
- 没错误则返回 InstanceId,并填充 Result
func ResponseWithError(c *gin.Context, httpErr *errors.ApiError) {
res := &ResponseMessage{
ResponseMetadata: getResponseMetadata(c),
}
res.ResponseMetadata.Error = &ErrorMessage{
Code: httpErr.Code,
Message: httpErr.Message,
}
c.AbortWithStatusJSON(httpErr.HttpCode, res)
}
func Response(c *gin.Context, result interface{}) {
res := &ResponseMessage{
ResponseMetadata: getResponseMetadata(c),
}
processHiddenProjectName(reflect.ValueOf(result))
if c.GetBool("OAM") {
res.Result = replaceOamJsonTag(result)
} else {
res.Result = result
}
c.JSON(http.StatusOK, res)
}
其他返回格式
-
亚马逊 AWS
# -------------------Template-------------------
<Response>
<Errors>
<Error>
<Code>Error code text</Code>
<Message>Error message</Message>
</Error>
</Errors>
<RequestID>request ID</RequestID>
</Response>
# -------------------Example-------------------
<Response>
<Errors>
<Error>
<Code>InvalidInstanceID.NotFound</Code>
<Message>The instance ID 'i-1a2b3c4d' does not exist</Message>
</Error>
</Errors>
<RequestID>ea966190-f9aa-478e-9ede-example</RequestID>
</Response>
-
阿里云
异常返回示例
接口调用出错后,会返回错误码、错误信息和请求 ID,我们称这样的返回为异常返回。HTTP 状态码为 4xx 或者 5xx。
定义了错误代码,HTTP状态码,错误信息 的对应关系
# -------------------XML示例-------------------
<?xml version="1.0" encoding="UTF-8"?><!--结果的根结点-->
<Error>
<RequestId>540CFF28-407A-40B5-B6A5-74Bxxxxxxxxx</RequestId> <!--请求 ID-->
<HostId>ecs.aliyuncs.com</HostId> <!--服务节点-->
<Code>MissingParameter.CommandId</Code> <!--错误码-->
<Message>The input parameter “CommandId” that is mandatory for processing this request is not supplied.</Message> <!--错误信息-->
</Error>
# -------------------JSON示例-------------------
{
"RequestId": "540CFF28-407A-40B5-B6A5-74Bxxxxxxxxx", /* 请求 ID */
"HostId": "ecs.aliyuncs.com", /* 服务节点 */
"Code": "MissingParameter.CommandId", /* 错误码 */
"Message": "The input parameter “CommandId” that is mandatory for processing this request is not supplied." /* 错误信息 */
}
-
Microsoft Azure
storageservices/status-and-error-codes2
# -------------------Template-------------------
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>string-value</Code>
<Message>string-value</Message>
</Error>
# -------------------Example-------------------
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>InvalidQueryParameterValue</Code>
<Message>Value for one of the query parameters specified in the request URI is invalid.</Message>
<---- 其他信息 ---->
<QueryParameterName>popreceipt</QueryParameterName>
<QueryParameterValue>33537277-6a52-4a2b-b4eb-0f905051827b</QueryParameterValue>
<Reason>invalid receipt format</Reason>
</Error>
# -------------------JSON Example for the Table Service-------------------
{
"odata.error": {
"code": "ResourceNotFound",
"message": {
"lang": "en-US",
"value": "The specified resource does not exist.\nRequestId:102a2b55-eb35-4254-9daf-854db78a47bd\nTime:2014-06-04T16:18:20.4307735Z"
}
}
}
# ------------------- in Atom Format -------------------
<?xml version="1.0" encoding="utf-8"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<code>ResourceNotFound</code>
<message xml:lang="en-US">The specified resource does not exist.
RequestId:e288ba1e-f5dd-4014-9e09-f1263d223dec
Time:2014-06-04T16:18:20.7088013Z</message>
</error>
-
OpenStack
api-guide/compute/faults
In each REST API request, you can specify the global request ID in X-Openstack-Request-Id header
X-Openstack-Request-Id: req-3dccb8c4-08fe-4706-a91d-e843b8fe9ed2
For a server with status ERROR or DELETED, a GET /servers/{server_id} request will include a fault object in the response body for the server resource.
GET https://10.211.2.122/compute/v2.1/servers/c76a7603-95be-4368-87e9-7b9b89fb1d7e
{
"server": {
"id": "c76a7603-95be-4368-87e9-7b9b89fb1d7e",
"fault": {
"created": "2018-04-10T13:49:40Z",
"message": "No valid host was found.",
"code": 500
},
"status": "ERROR",
...
}
}
支付宝
# -----------------正常返回------------------
{
"alipay_trade_query_response": {
"code": "10000",
"msg": "Success",
...
},
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
# -----------------异常返回------------------
{
"alipay_trade_query_response": {
"code": "20000",
"msg": "Service Currently Unavailable",
"sub_code": "isp.unknow-error",
"sub_msg": "系统繁忙"
},
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
微信
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
<xml>
<return_code><![CDATA[SUCCESS]]></return_code> # 返回状态码
<return_msg><![CDATA[OK]]></return_msg> # 返回信息
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code> # 业务结果
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
<trade_type><![CDATA[APP]]></trade_type>
</xml>
Error 设计方案
Google Cloud 建议的 grpc 错误处理方式
https://cloud.google.com/apis/design/errors
应该使用 google.golang.org/grpc/codes.Code
中的 Grpc Error Code 定义,message 传递用户可读消息。
标准错误负载:google/rpc/error_details.proto,以下是一些 error_details 负载的示例:
- RetryInfo 描述了当客户端能够重试请求时,可能返回 Code.UNAVAILABLE 或 Code.ABORTED
- QuotaFailure 描述了配额检查失败的原因,可能返回 Code.RESOURCE_EXHAUSTED
- BadRequest 描述了客户端的非法请求,可能返回 Code.INVALID_ARGUMENT
package google.rpc;
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by `google.rpc.Code`.
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry delay or a help link.
repeated google.protobuf.Any details = 3;
}
Template——GrpcWebErr
一个基于 proto 的 Error 返回示例 & https://github.com/Megalepozy/grpcweberr
利用 status
package 来定义并返回 Error
利用 errdetails
package 来实现额外的 error metadata
返回样式
return nil, gwe.New(codes.NotFound, http.StatusBadRequest, ErrMsgINE)
{
"error": "Instance Not Exists!",
"code": 5,
"message": "Instance Not Exists!",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"field_violations": [
{
"field": "httpStatus",
"description": "400"
},
{
"field": "messageToUser",
"description": "Instance Not Exists!"
}
]
}
]
}
err := gwe.New(codes.NotFound, http.StatusBadRequest, ErrMsgINE)
err = gwe.AddLogTracingID("1", err)
err = gwe.AddLogTracingID("2", err)
return nil, err
{
"error": "Instance Not Exists!",
"code": 5,
"message": "Instance Not Exists!",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"field_violations": [
{
"field": "httpStatus",
"description": "400"
},
{
"field": "messageToUser",
"description": "Instance Not Exists!"
}
]
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"field_violations": [
{
"field": "httpStatus",
"description": "400"
},
{
"field": "messageToUser",
"description": "Instance Not Exists!"
},
{
"field": "logTracingID",
"description": "1"
}
]
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"field_violations": [
{
"field": "httpStatus",
"description": "400"
},
{
"field": "messageToUser",
"description": "Instance Not Exists!"
},
{
"field": "logTracingID",
"description": "2"
}
]
}
]
}
Grpc to Http Error Return
大佬博客
在利用 proto 返回 error 的时候,会将收到的 grpc Error 转为 http Error(in github.com/grpc-ecosystem/grpc-gateway/runtime/errors.go)
// DefaultHTTPError is the default implementation of HTTPError.
// If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
// If otherwise, it replies with http.StatusInternalServerError.
//
// The response body returned by this function is a JSON object,
// which contains a member whose key is "error" and whose value is err.Error().
func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, _ *http.Request, err error)
最后实现方式
直接利用 status.Status 的 Message 部分作为传递中介
然后在 customHttpError 层进行解析并设定 json
const SPLIT string = "$_$_$"
func New(grpcCode string, desc string, httpCode int) error {
msg := fmt.Sprintf("%s%s%s%s%d", grpcCode, SPLIT, desc, SPLIT, httpCode)
return status.Error(codes.Unknown, msg)
}
func convert(rawMsg string) *arkError {
var hc int64
sRMsg := strings.Split(rawMsg, SPLIT)
fmt.Print(sRMsg)
hc, _ = strconv.ParseInt(sRMsg[2], 10, 32)
return &arkError{
HttpCode: int(hc),
GrpcCode: sRMsg[0],
Desc: sRMsg[1],
}
}