今天来讲一讲 gRPC 错误处理的方式,以及如何自定义错误处理。当发起 gRPC 调用时,客户端会接受成功状态的响应或带有错误信息状态的错误响应。 考虑程序的健壮性,我们需要在编写客户端处理信息时,要处理所有可能的错误。编写服务端代码也要处理错误,并构建合适的错误状态码。
当 gRPC 发生错误时,会返回一个错误码。并附带一条可选的信息, 错误状态信息由一个整形状态码和一条字符串消息组成,适用于不同的语言实现。
下图展示了 gRPC 内置的错误码:
缺陷
gRPC 提供的错误模型非常有限,并且与底层的数据格式无关,最常用的数据格式就是 protocol buffers。 如果使用了 protocol buffers, google.rpc 提供了更丰富的错误模型,但语言兼容性待测试。
错误处理例子
接下来继续沿用前面的代码,来讲解如何运用错误处理。 假如我们需要在订单添加处理中处理非法 ID 请求。 如果我们传了一个不合法的 ID 如 -1,需要返回错误给客户端消费者。
func (s *server) AddOrder(ctx context.Context, orderReq *pb.Order) (*wrappers.StringValue, error) {
if orderReq.Id == "-1" {
log.Printf("Order ID is vaild : %s", orderReq.Id)
errorStatus := status.New(codes.InvalidArgument, "Order ID is not valid")
ds, err := errorStatus.WithDetails(
&epb.BadRequest_FieldViolation{
Field: "ID",
Description: fmt.Sprintf("Order ID received is not valid %s : %s", orderReq.Id, orderReq.Description),
},
)
if err != nil {
return nil, errorStatus.Err()
}
return nil, ds.Err()
}
orderMap[orderReq.Id] = *orderReq
log.Println("Order : ", orderReq.Id, " -> Added")
return &wrapper.StringValue{Value: "Order Added: " + orderReq.Id}, nil
}
通过 status 包可以很方便的创建所需的错误码和相应错误详情。使用 Google API 的相应包可以设置更丰富的错误详情。
客户端处理错误,只需处理相应 RPC 请求返回的错误即可。接下来的代码展示如何处理前面的 AddOrder 方法返回的错误。
order1 := pb.Order{Id: "-1", Items:[]string{"iPhone XS", "Mac Book Pro"}, Destination:"San Jose, CA", Price:2300.00}
res, addOrderError := client.AddOrder(ctx, &order1)
if addOrderError != nil {
errorCode := status.Code(addOrderError)
if errorCode == codes.InvalidArgument {
log.Printf("Invalid Argument Error : %s", errorCode)
errorStatus := status.Convert(addOrderError)
for _, d := range errorStatus.Details() {
switch info := d.(type) {
case *epb.BadRequest_FieldViolation:
log.Printf("Request Field Invalid: %s", info)
default:
log.Printf("Unexpected error type: %s", info)
}
}
} else {
log.Printf("Unhandled error : %s ", errorCode)
}
} else {
log.Print("AddOrder Response -> ", res.Value)
}
在 gRPC 处理中,尽可能使用适当的 gRPC 错误码和丰富的错误模型,这样可以大大的提供系统的健壮性。