调研 Err Msg 返回格式

当前的返回格式

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,其中嵌套 ResponseMetadataResponseMetadata 嵌套 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>
  • 阿里云

异常返回示例
接口调用出错后,会返回错误码、错误信息和请求 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",
      ...
   }
}

支付宝

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": "系统繁忙"
    },
    "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],
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容