在业务开发中,大家是会使用http通用的错误码,还是统一返回HttpCode-200,转而使用业务自己定义的错误码呢?这个问题可能见仁见智,大家都有一些自己的理解和最佳实践。这节我们来看下gRPC中如何对错误进行处理的。
gRPC提供了一组定义良好的专用状态码,举例如下:
案例 | 状态码 |
---|---|
客户端应用程序取消了请求 | GRPC_STATUS_CANCELLED |
截止日期在服务器返回状态之前已过期 | GRPC_STATUS_DEADLINE_EXCEEDED |
在服务器上找不到方法 | GRPC_STATUS_UNIMPLEMENTED |
服务器关闭 | GRPC_STATUS_UNAVAILABLE |
服务器抛出异常(或者除了返回状态码来终止 RPC 之外做了其他事情) | GRPC_STATUS_UNKNOWN |
案例 | 状态码 |
---|---|
在截止日期到期之前不会传输任何数据。也适用于在截止日期到期之前传输了一些数据并且没有检测到其他故障的情况 | GRPC_STATUS_DEADLINE_EXCEEDED |
连接中断前传输的一些数据(例如,请求元数据已写入 TCP 连接) | GRPC_STATUS_UNAVAILABLE |
案例 | 状态码 |
---|---|
无法解压但支持压缩算法 | GRPC_STATUS_INTERNAL |
服务器不支持客户端使用的压缩机制 | GRPC_STATUS_UNIMPLEMENTED |
达到流量控制资源限制 | GRPC_STATUS_RESOURCE_EXHAUSTED |
流量控制协议违规 | GRPC_STATUS_INTERNAL |
解析返回状态时出错 | GRPC_STATUS_UNKNOWN |
未经身份验证:凭据无法获取元数据 | GRPC_STATUS_UNAUTHENTICATED |
权限元数据中的主机集无效 | GRPC_STATUS_UNAUTHENTICATED |
解析响应协议缓冲区时出错 | GRPC_STATUS_INTERNAL |
解析请求协议缓冲区时出错 | GRPC_STATUS_INTERNAL |
这里模拟前端请求推荐列表页的接口,当用户没有登录是UserId=-1,这时候服务端因为没有用户特征所以会拒绝下发数据,转而返回一个gRPC特定错误。现在来看下,Java和go是分别怎么样实现的吧。
在返回错误码的时候,还可以返回绑定的错误详情
这里使用了gRPC自带的包
import (
"context"
"fmt"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"grpc-in-action/part05-errorhanle/go/server/pb"
"log"
)
func (*BlendService) Blend(context context.Context, req *pb.BlendReq) (*pb.BlendRes, error) {
if req.UserId == "-1" {
log.Printf("UserId is invalid! -> Received User ID %s", req.UserId)
errorStatus := status.New(codes.InvalidArgument, "Invalid information received")
ds, err := errorStatus.WithDetails(
&errdetails.BadRequest_FieldViolation{
Field:"ID",
Description: fmt.Sprintf("UserId received is not valid :%s ", req.UserId),
},
)
if err != nil {
return nil, errorStatus.Err()
}
return nil, ds.Err()
}else {
nids :=[]string{"1","2","3"}
return &pb.BlendRes{
Nid:nids,
},nil
}
}
客户端的行为就是拿到服务端返回的错误码和错误详情,进行对应的处理即可
package main
import (
"context"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"grpc-in-action/part05-errorhanle/go/client/pb"
"log"
)
func main() {
dial, err := grpc.Dial("localhost:10085", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client := pb.NewBlendServiceClient(dial)
blend, err := client.Blend(context.Background(), &pb.BlendReq{
UserId: "-1",
})
if err != nil {
log.Printf("err is :%+v",err)
errorCode := status.Code(err)
if errorCode == codes.InvalidArgument {
log.Printf("Invalid Argument Error : %s", errorCode)
errorStatus := status.Convert(err)
for _, d := range errorStatus.Details() {
switch info := d.(type) {
case *errdetails.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.Printf("blend:%v",blend.Nid)
}
}