當(dāng)前的返回格式
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"
}
}
實現(xiàn)方式:
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>
-
阿里云
異常返回示例
接口調(diào)用出錯后,會返回錯誤碼、錯誤信息和請求 ID,我們稱這樣的返回為異常返回。HTTP 狀態(tài)碼為 4xx 或者 5xx。
定義了錯誤代碼,HTTP狀態(tài)碼,錯誤信息 的對應(yīng)關(guān)系
# -------------------XML示例-------------------
<?xml version="1.0" encoding="UTF-8"?><!--結(jié)果的根結(jié)點-->
<Error>
<RequestId>540CFF28-407A-40B5-B6A5-74Bxxxxxxxxx</RequestId> <!--請求 ID-->
<HostId>ecs.aliyuncs.com</HostId> <!--服務(wù)節(jié)點-->
<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", /* 服務(wù)節(jié)點 */
"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": "系統(tǒng)繁忙"
},
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
微信
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
<xml>
<return_code><![CDATA[SUCCESS]]></return_code> # 返回狀態(tài)碼
<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> # 業(yè)務(wù)結(jié)果
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
<trade_type><![CDATA[APP]]></trade_type>
</xml>
Error 設(shè)計方案
Google Cloud 建議的 grpc 錯誤處理方式
https://cloud.google.com/apis/design/errors
應(yīng)該使用 google.golang.org/grpc/codes.Code 中的 Grpc Error Code 定義,message 傳遞用戶可讀消息。
標(biāo)準(zhǔn)錯誤負載:google/rpc/error_details.proto,以下是一些 error_details 負載的示例:
- RetryInfo 描述了當(dāng)客戶端能夠重試請求時,可能返回 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 來實現(xiàn)額外的 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 轉(zhuǎn)為 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)
最后實現(xiàn)方式
直接利用 status.Status 的 Message 部分作為傳遞中介
然后在 customHttpError 層進行解析并設(shè)定 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],
}
}