grpc 開發進階 - 傳遞 metadata

現在網上大部分都是 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 會被分爲 headertriler

unary RPC 可以在調用的時候,使用 CallOption HeaderTrailer 來獲取

```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.SendHeadergrpc.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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章