grpc 支持在 server 端和 client 端发送 metedata,一些验证信息之类的可以放在这个里边
metadata
可以通过 metadata 包来构建
type MD map[string][]string
一个键可以对应多个值
生成 metadata
func New(m map[string]string) MD
func Pairs(kv ...string) MD
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
md = metadata.Pairs(
"key1", "val1",
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2",
)
key 中大写字母会被转化为小写
grpc- 开头的键为 grpc 内部使用,如果再 metadata 中设置这样的键可能会导致一些错误
metadata 中可以存储二进制数据
key 以 -bin 为后缀,这时,值会在传输前后以 base64 进行编解码
md := metadata.Pairs(
"key", "string value",
"key-bin", string([]byte{96, 102}),
)
注意
服务端和客户端接收到的 metadata 如果被修改的话可能会导致 race
通过 Copy() 方法返回的 metadata 可以修改
func (md MD) Copy() MD
有两种发送 metadata 到 server 的方法
推荐的方法是使用 AppendToOutgoingContext
如果 metadata 已存在则会合并,不存在则添加
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context
// create a new context with some metadata
ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")
// later, add some more metadata to the context (e.g. in an interceptor)
ctx := metadata.AppendToOutgoingContext(ctx, "k3", "v4")
而 NewOutgoingContext 则会覆盖 context 中 已有的 metadata
func NewOutgoingContext(ctx context.Context, md MD) context.Context
// create a new context with some metadata
md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
ctx := metadata.NewOutgoingContext(context.Background(), md)
client 和 server 接收 metadata 的方式是不同的,metadata 会被分为 header 和 triler
unary RPC 可以在调用的时候,使用 CallOption Header 和 Trailer 来获取
```go
var header, trailer metadata.MD // variable to store header and trailer
r, err := client.SomeRPC(
ctx,
someRequest,
grpc.Header(&header), // will retrieve header
grpc.Trailer(&trailer), // will retrieve trailer
)
// do something with header and trailer
stream RPC 可以通过 ClientStream 接口的 Header, Trailer 方法来获取
stream, err := client.SomeStreamingRPC(ctx)
// retrieve header
header, err := stream.Header()
// retrieve trailer
trailer := stream.Trailer()
server 端会把 metadata 分为 header 和 trailer 发送给 client
unary RPC 可以通过 CallOption grpc.SendHeader 和 grpc.SetTriler 来发送 header, trailer metadata
SetTriler 可以被调用多次,并且所有 metadata 会被合并
当 RPC 返回的时候, trailer metadata 会被发送
func SendHeader(ctx context.Context, md metadata.MD) error
func SetTrailer(ctx context.Context, md metadata.MD) error
func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
// create and send header
header := metadata.Pairs("header-key", "val")
grpc.SendHeader(ctx, header)
// create and set trailer
trailer := metadata.Pairs("trailer-key", "val")
grpc.SetTrailer(ctx, trailer)
}
stream RPC 则可以直接使用 ServerStream 接口的方法
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
// create and send header
header := metadata.Pairs("header-key", "val")
stream.SendHeader(header)
// create and set trailer
trailer := metadata.Pairs("trailer-key", "val")
stream.SetTrailer(trailer)
}
grpc.SendHeader 和 grpc.SetHeader 的区别
SendHeader 最多会被调用一次,提供的metadata会通过 SetHeader 来设置
SetHeader 可以被多次调用,所有的 metadata 都会被合并
当遇到以下行为的时候 metadata 会被发送
grpc.SendHeader 被调用
第一个响应被发送
RPC status 被发送
服务端调用 FromIncomingContext 即可从 context 中接收 client 发送的 metadata
func FromIncomingContext(ctx context.Context) (md MD, ok bool)
func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
md, ok := metadata.FromIncomingContext(stream.Context()) // get context from stream
// do something with metadata