> 现在网上大部分都是 grpc 相关的介绍,真正涉及到 grpc 的配置使用的文章还是比较少的
> 所以本系列着重介绍 grpc 开发时可以能会用到的一些配置
grpc 支持在 server 端和 client 端发送 metedata,一些验证信息之类的可以放在这个里边
## metadata
可以通过 [metadata](https://godoc.org/google.golang.org/grpc/metadata) 包来构建
```go
type MD map[string][]string
```
一个键可以对应多个值
##### 生成 metadata
```go
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 进行编解码
```go
md := metadata.Pairs(
"key", "string value",
"key-bin", string([]byte{96, 102}),
)
```
##### 注意
服务端和客户端接收到的 metadata 如果被修改的话可能会导致 race
**通过 Copy() 方法返回的 metadata 可以修改**
```go
func (md MD) Copy() MD
```
---
## 发送和接收 metadata
### 客户端
#### 发送
有两种发送 metadata 到 server 的方法
推荐的方法是使用 `AppendToOutgoingContext`
如果 metadata 已存在则会合并,不存在则添加
```go
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
```go
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](https://godoc.org/google.golang.org/grpc#Header) 和 [Trailer](https://godoc.org/google.golang.org/grpc#Trailer) 来获取
```go
```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 方法来获取
```go
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](https://godoc.org/google.golang.org/grpc#SendHeader) 和 [grpc.SetTriler](https://godoc.org/google.golang.org/grpc#SetTrailer) 来发送 `header`, `trailer` metadata
SetTriler 可以被调用多次,并且所有 metadata 会被合并
**当 RPC 返回的时候, trailer metadata 会被发送**
```go
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 接口的方法
```go
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
```go
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
}
```
**example:** [metadata](https://github.com/grpc/grpc-go/tree/master/examples/features/metadata)
grpc 开发进阶 - 传递 metadata