传送门:
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)
}