Golang處理gRPC請求/響應元數據的示例代碼
元數據
gRPC的元數據(metadata)是基于HTTP/2頭部實現的鍵值對數據,它通常用來實現gRPC的鑒權、鏈路跟蹤以及自定義頭部數據等功能。
gRPC的元數據分為兩種類型,分別是Header及Trailer。Header可以由客戶端或服務端發(fā)送,它在客戶端請求數據或服務器響應數據前發(fā)送。Trailer是一種特殊的頭部信息,它僅可由服務端發(fā)送,且位于發(fā)送的數據之后。
客戶端處理
在gRPC客戶端中,無論是一元調用還是流調用,可以比較簡單地通過google.golang.org/grpc/metadata包提供的AppendToOutgoingContext或NewOutgoingContext方法向請求中加入頭部元數據,例如以下幾種方式:
// 通過metadata創(chuàng)建新的context
md := metadata.Pairs("k1", "v1", "k2", "v2")
ctx := metadata.NewOutgoingContext(ctx, md)
// 或是向context中添加元數據
ctx = metadata.AppendToOutgoingContext(ctx, "k3", "v3")
// ... 通過ctx進行RPC調用
對于服務端返回的響應中的元數據,一元調用與流調用的處理方式就較為不同。對于一元調用,需要提前定義好用于存儲元數據的變量,然后在調用時通過grpc.Header或grpc.Trailer增加調用的選項:
var header, trailer metadata.MD resp, err := cli.UnaryCall(ctx, req, grpc.Header(&header), grpc.Trailer(&trailer)) // 處理header或trailer
而對于任意方式的流調用,都可以簡單地通過流調用返回流的Header或Trailer方法獲得元數據:
stream, err := cli.StreamCall(ctx) header, err := stream.Header() trailer, err := stream.Trailer()
服務端處理
對于服務端,請求的元數據需要通過metadata.FromIncomingContext從context中獲?。?/p>
// 一元調用 md, ok := metadata.FromIncomingContext(ctx) // 流調用 ctx := stream.Context() // 需要先從流中得到context md, ok := metadata.FromIncomingContext(ctx)
同樣,在服務端發(fā)送元數據需要根據一元調用與流調用使用不同的方式。對于一元調用,可以通過grpc.SendHeader、grpc.SetHeader以及grpc.SetTrailer方法設置發(fā)送的元數據,例如:
header := metadata.Pairs("header-key", "header-val")
grpc.SendHeader(ctx, header)
trailer := metadata.Pairs("trailer-key", "trailer-val")
grpc.SetTrailer(ctx, trailer)
對于上述的SendHeader及SetHeader方法,其區(qū)別為SendHeader方法只能調用一次,而SetHeader方法將會對所有調用的元數據進行合并發(fā)送。
對于流調用,服務端發(fā)送元數據則是通過流對象中的上述方法:
header := metadata.Pairs("header-key", "header-val")
stream.SendHeader(,header)
trailer := metadata.Pairs("trailer-key", "trailer-val")
stream.SetTrailer(trailer)
服務器攔截器處理
對于gRPC服務端一元調用及流調用攔截器,請求元數據的讀取與響應元數據的發(fā)送與上一節(jié)中的實現相同,便不再贅述。下面我們將討論一下在攔截器中更新請求元數據,以及讀取響應的元數據。
一元調用攔截器更新請求元數據
在服務端攔截器中更新請求的元數據,其實現的方式與客戶端發(fā)送元數據類似,即需要通過更新后的元數據創(chuàng)建新的context。對于一元調用攔截器,其簡單實現如下所示:
md, ok := metadata.FromIncomingContext(ctx)
md.Append("new-key", "new-value")
ctx = metadata.NewIncomingContext(ctx, md)
resp, err := handler(ctx, req) // 傳遞context至handler中
一元調用攔截器讀取響應元數據
對于一元調用響應的元數據,gRPC未提供直接訪問的方法響應的元數據。為了在攔截器中能讀取到響應的元數據,我們可以通過覆蓋原始grpc.ServerTransportStream并對設置的元數據進行備份的方式進行實現。
type WrappedServerTransportStream struct {
grpc.ServerTransportStream
header metadata.MD
trailer metadata.MD
}
func (s *WrappedServerTransportStream) SendHeader(md metadata.MD) error {
if err := s.ServerTransportStream.SendHeader(md); err != nil {
return err
}
s.header = md
return nil
}
// 在需要的情況下繼續(xù)實現下面的幾個方法:
// func (s *WrappedServerTransportStream) SetHeader(metadata.MD) error
// func (s *WrappedServerTransportStream) SetTrailer(metadata.MD) error
在定義帶有元數據副本的ServerTransportStream實現后,我們需要通過grpc.ServerTransportStreamFromContext獲取到一元調用的原始流,在對其進行封裝后,調用grpc.NewContextWithServerTransportStream創(chuàng)建新的context。
stream := grpc.ServerTransportStreamFromContext(ctx)
wrappedStream := &WrappedServerTransportStream{
ServerTransportStream: stream,
}
ctx = grpc.NewContextWithServerTransportStream(ctx, wrappedStream)
resp, err := handler(ctx, req)
// 通過wrappedStream.header、wrappedStream.trailer讀取響應的元數據
需要注意,grpc.ServerTransportStream接口是一個實驗性的接口,在后續(xù)版本中可能會被移除,所以本節(jié)中描述的方法在后續(xù)版本中可能不再可用。
流調用攔截器更新請求元數據
而對于流調用,gRPC沒有提供修改其context的方法,為了實現修改流調用請求元數據,就需要實現grpc.ServerStream接口并加入帶有修改后元數據的context。以下是一個簡單的實現:
type WrappedStream struct {
grpc.ServerStream
ctx context.Context
}
func (s *WrappedStream) Context() context.Context {
return s.ctx
}
func ExampleStreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
md, ok := metadata.FromIncomingContext(ss.Context())
md.append("new-key", "new-value")
ctx := metadata.NewIncomingContext(ss.Context(), md)
return handler(srv, &WrappedStream{ss, ctx})
}
流調用攔截器讀取響應元數據
與在一元調用攔截器中相同,若需要在流調用攔截器中讀取響應的元數據,我們可以實現grpc.ServerStream接口,并在其中保存元數據的副本。例如我們可以在上節(jié)的WrappedStream的基礎上,對其進行一定修改:
type WrappedStream struct {
grpc.ServerStream
header metadata.MD
trailer metadata.MD
}
func (s *WrappedStream) SendHeader(md metadata.MD) error {
if err := s.ServerStream.SendHeader(md); err != nil {
return err
}
s.header = md
return nil
}
// 繼續(xù)實現SetHeader、SetTrailer等方法
func ExampleStreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
stream := &WrappedStream{ServerStream: ss}
err := handler(srv, stream)
// 通過stream.header、stream.trailer讀取響應元數據
return err
}
以上就是Golang處理gRPC請求/響應元數據的示例代碼的詳細內容,更多關于Golang處理gRPC請求/響應的資料請關注腳本之家其它相關文章!
相關文章
golang gin 監(jiān)聽rabbitmq隊列無限消費的案例代碼
這篇文章主要介紹了golang gin 監(jiān)聽rabbitmq隊列無限消費,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
golang goquery selector選擇器使用示例大全
這篇文章主要為大家介紹了golang goquery selector選擇器使用示例大全,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09

