調(diào)研 Err Msg 返回格式

當(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

Error Codes

# -------------------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",
      ...
   }
}

支付寶

apis/api_1/alipay.trade.query

# -----------------正常返回------------------
{
    "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],
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容