传送门:
Go微服务(三)——gRPC详细入门_小象裤衩的博客-CSDN博客
一般接口都会定义统一的错误返回格式,如果在proto文件中的每个message消息体内硬是增加一个错误消息结构,十分的不优雅,go 的 grpc 包提供了一个 status 功能,可以通过metadata(下面再介绍metadata)在header中返回给客户端,这样就不用修改每个接口的message消息体了
常规用法:
- st := status.New(codes.NotFound, "some description")
- err := st.Err()
-
- // 等同于 status.Error(codes.NotFound, "some description")
进阶用法:
status.New只能声明一个错误code(还不能自定义)以及一段msg文本,如果需要更丰富的报错,则需要使用 WithDetails 功能自定义错误结构体
服务端示例:
- // 生成一个 status.Status
- st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
- // 填充错误的补充信息 WithDetails
- ds, err := st.WithDetails(
- &pb.CustomError{ // CustomError 需要在对应的 proto 文件中定义成 message
- xxx: "xxx",
- xxx: "xxx",
- ......
- },
- )
- if err != nil {
- return nil, st.Err()
- }
- return nil, ds.Err()
客户端示例:
- r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
- // 调用 RPC 如果遇到错误就对错误处理
- if err != nil {
- // 转换错误
- s := status.Convert(err)
- // 解析错误信息
- for _, d := range s.Details() {
- // 通过断言直接使用
- switch info := d.(type) {
- case *pb.CustomError:
- log.Printf("Custom error: %s", info)
- default:
- log.Printf("Unexpected type: %s", info)
- }
- }
- }
原理:
这个错误是如何传递给调用方Client的呢?
是放到 metadata中的,而metadata是放到HTTP的header中的。
metadata是key:value格式的数据。错误的传递中,key是个固定值:grpc-status-details-bin。
而value,是被proto编码过的,是二进制安全的。
目前大多数语言都实现了这个机制
metadata,简称MD,一般用来传递消息以外的额外数据(挂载数据):
gRPC是基于HTTP2的,HTTP2中除了 header 还有 trailer。
metadata就是放在header和trailer中传输的。
客户端发起请求的时候可以带,服务端返回数据的时候也可以带。
metadata是一个key-value结构的数据(map),但是value是string类型的切片
type MD map[string][]string
客户端发送请求的MD都会放到header中。
服务端响应的MD可以选择放到header,也可以选择放到trailer。
对于Unary模式的调用,因为是一次请求一次响应,所以放在哪里无所谓
构造metadata
- // key1 的值将会是一个slice,有两个值: []string{"val1", "val1-2"}
- md := metadata.Pairs(
- "key1", "val1",
- "key1", "val1-2",
- "key2", "val2",
- )
客户端发起/接收metadata
- //方式1:创建一个带md的context
- md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
- ctx := metadata.NewOutgoingContext(context.Background(), md)
-
- //方式2:对原有的 context 追加,AppendToOutgoingContext
- ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")
-
- //方式3:对原有的 context 追加参数,metadata.Join
- send, _ := metadata.FromOutgoingContext(ctx)
- newMD := metadata.Pairs("k3", "v3")
- ctx = metadata.NewOutgoingContext(ctx, metadata.Join(send, newMD))
- // 提前声明用于接收的变量
- var header, trailer metadata.MD
- r, err := client.SomeRPC(
- ctx,
- someRequest,
- grpc.Header(&header), // 接收的header放在这里
- grpc.Trailer(&trailer), // 接收的trailer放这里
- )
-
- fmt.Println(header.Get("key")) // 打印从服务端这边得到的md中定义的key
服务端发起/接收metadata
- //unary 模式
- md, ok := metadata.FromIncomingContext(ctx)
- func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
- // 创建并设置 header
- header := metadata.Pairs("header-key", "val")
- grpc.SendHeader(ctx, header)
- // 创建并设置 trailer
- trailer := metadata.Pairs("trailer-key", "val")
- grpc.SetTrailer(ctx, trailer)
- }