gRPC 一般不在 message 中定义错误。
毕竟每个 gRPC 服务本身就带一个 error 的返回值,这是用来传输错误的专用通道。
gRPC 中所有的错误返回都应该是 nil 或者 由 status.Status 产生的一个error。这样error可以直接被调用方Client识别。
This article is first published in the medium MPP plan. If you are a medium user, please follow me in medium. Thank you very much.
1. 常规用法
当遇到一个go错误的时候,直接返回是无法被下游client识别的。
恰当的做法是:
- 调用 status.New 方法,并传入一个适当的错误码,生成一个 status.Status 对象
- 调用该 status.Err 方法生成一个能被调用方识别的error,然后返回
1
2
|
st := status.New(codes.NotFound, "some description")
err := st.Err()
|
传入的错误码是 codes.Code 类型。
此外还有更便捷的办法:使用 status.Error。它避免了手动转换的操作。
1
|
err := status.Error(codes.NotFound, "some description")
|
2. 进阶用法
上面的错误有个问题,就是 code.Code 定义的错误码只有固定的几种,无法详尽地表达业务中遇到的错误场景。
gRPC 提供了在错误中补充信息的机制:status.WithDetails 方法
Client 通过将 error 重新转换位 status.Status ,就可以通过 status.Details 方法直接获取其中的内容。
status.Details 返回的是个slice, 是interface{}的slice,然而go已经自动做了类型转换,可以通过断言直接使用。
服务端示例服务端示例
- 生成一个 status.Status 对象
- 填充错误的补充信息
// 生成一个 status.Status
1
2
3
4
5
6
7
8
9
10
11
12
|
func ErrorWithDetails() error {
st := status.Newf(codes.Internal, fmt.Sprintf("something went wrong: %v", "api.Getter"))
v := &errdetails.PreconditionFailure_Violation{ //errDetails
Type: "test",
Subject: "12",
Description: "32",
}
br := &errdetails.PreconditionFailure{}
br.Violations = append(br.Violations, v)
st, _ = st.WithDetails(br)
return st.Err()
}
|
客户端的示例
- 调用RPC错误后,解析错误信息
- 通过断言直接获取错误详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
resp, err := odinApp.CreatePlan(cli.StaffId.AssetId, gentRatePlanMeta(cli.StaffId))
if status.Code(err) != codes.InvalidArgument {
logger.Error("create plan error:%v", err)
} else {
for _, d := range status.Convert(err).Details() {
//
switch info := d.(type) {
case *errdetails.QuotaFailure:
logger.Info("Quota failure: %s", info)
case *errdetails.PreconditionFailure:
detail := d.(*errdetails.PreconditionFailure).Violations
for _, v1 := range detail {
logger.Info(fmt.Sprintf("details: %+v", v1))
}
case *errdetails.ResourceInfo:
logger.Info("ResourceInfo: %s", info)
case *errdetails.BadRequest:
logger.Info("ResourceInfo: %s", info)
default:
logger.Info("Unexpected type: %s", info)
}
}
}
logger.Infof("create plan success,resp=%v", resp)
|
原理
这个错误是如何传递给调用方Client的呢?
是放到 metadata中的,而metadata是放到HTTP的header中的。
metadata是key:value格式的数据。错误的传递中,key是个固定值:grpc-status-details-bin。
而value,是被proto编码过的,是二进制安全的。
目前大多数语言都实现了这个机制。
注意
gRPC对响应头做了限制,上限为8K,所以错误不能太大。