現在網上大部分都是 grpc 相關的介紹,真正涉及到 grpc 的配置使用的文章還是比較少的
所以本系列着重介紹 grpc 開發時可以能會用到的一些配置
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
客戶端
發送
有兩種發送 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
}
example: metadata