调研 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],
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容