GO GRPC攔截器和Metadata的使用
標籤(空格分隔): go,grpc
metadata:https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md
什麼是metadata
gRPC 支持在客戶端和服務器之間發送元數據(metadata)。本文檔介紹如何在 gRPC-go 中發送和接收元數據(metadata)
metadata的構造
可以使用包元數據創建元數據。MD 類型實際上是從字符串到字符串列表的映射
type MD map[string][]string
實際就是map
類型,key
是 string
, value
是 string slice
創建metadata
可以使用函數 New 從 map[string]string 創建元數據
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", )
注意:所有鍵將自動轉換爲小寫,因此“key1”和“kEy1”將是相同的鍵,它們的值將合併到同一列表中。新和成對都會發生這種情況
metadata存儲二進制
在元數據中,鍵始終是字符串。但值可以是字符串或二進制數據。要在元數據中存儲二進制數據值,只需在鍵中添加“-bin”後綴即可。創建元數據時,將編碼帶有“-bin”後綴鍵的值
md := metadata.Pairs( "key", "string value", "key-bin", string([]byte{96, 102}), // this binary data will be encoded (base64) before sending // and will be decoded after being transferred. )
從上下文中檢索元數據 (在context中獲取metadata數據 一般在服務端)
可以使用以下命令從上下文中檢索元數據 FromIncomingContext
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) { md, ok := metadata.FromIncomingContext(ctx) // do something with metadata }
發送metadata
有兩種方法可以將元數據發送到服務器。推薦的方法是使用 AppendToOutgoingContext
將 kv
對附加到上下文中。這可以與上下文中的現有元數據一起使用,也可以不使用。當沒有先前的元數據時,將添加元數據;當上下文中已存在元數據時,KV 對將合併到
// 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")
// make unary RPC
response, err := client.SomeRPC(ctx, someRequest)
// or make streaming RPC
stream, err := client.SomeStreamingRPC(ctx)
或者,可以使用NewOutgoingContext
將元數據附加到上下文中。但是,這會替換上下文中的任何現有元數據,因此如果需要,必須注意保留現有元數據。這比使用 AppendToOutgoingContext
慢。下面是一個示例
// create a new context with some metadata
md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
ctx := metadata.NewOutgoingContext(context.Background(), md)
// later, add some more metadata to the context (e.g. in an interceptor)
send, _ := metadata.FromOutgoingContext(ctx)
newMD := metadata.Pairs("k3", "v3")
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(send, newMD))
// make unary RPC
response, err := client.SomeRPC(ctx, someRequest)
// or make streaming RPC
stream, err := client.SomeStreamingRPC(ctx)
流模式metadata
// 獲取md
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
md, ok := metadata.FromIncomingContext(stream.Context()) // get context from stream
// do something with metadata
}
// 發送md
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攔截器
GRPC客戶端攔截器 grpc.WithUnaryInterceptor
, invoker
就是要執行的方法
// grpc 客戶端攔截器
var opts []grpc.DialOption
opts = append(
opts,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
fmt.Printf("耗時: %s\n", time.Since(start))
return err
}),
)
dial, err := grpc.Dial("127.0.0.1:6664", opts...)
.....
GRPC服務端攔截器 grpc.UnaryInterceptor
, handler
就是要執行的方法
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("前置中間件")
r, e := handler(ctx, req)
fmt.Println("後置中間件")
return r, e
}))
server := grpc.NewServer(opts...)
mate_data_pb.RegisterUserServiceServer(server, &Service{})
GRPC攔截器使用場景
參考第三方包地址:https://github.com/grpc-ecosystem/go-grpc-middleware
auth(認證)
Observability(日誌、監控)
Client(重試、超時)
Server(限流、驗證、異常捕獲)
通過攔截器和metadata實現grpc的auth認證
客戶端實現
// MyCredentials 自定義認證 實現 PerRPCCredentials Interface
type MyCredentials struct {
}
// GetRequestMetadata 第二種方法 只需要返回MD類型的數據即可
func (m MyCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"token":"test_token",
}, nil
}
// RequireTransportSecurity 是否需要數據傳輸安全
func (m MyCredentials) RequireTransportSecurity() bool {
return false
}
func main() {
var opts []grpc.DialOption
opts = append(
opts,
grpc.WithTransportCredentials(insecure.NewCredentials()),
//grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// start := time.Now()
// // 第一種方法 在context中添加md
// ctx = metadata.AppendToOutgoingContext(ctx, "token", "test_token")
// err := invoker(ctx, method, req, reply, cc, opts...)
// fmt.Printf("耗時: %s\n", time.Since(start))
// return err
//}),
// 第二種方法
grpc.WithPerRPCCredentials(MyCredentials{}),
)
dial, err := grpc.Dial("127.0.0.1:6664", opts...)
.......
服務端實現:
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("前置中間件")
// 獲取md
md, b := metadata.FromIncomingContext(ctx)
fmt.Printf("md: %+v\n", md)
if !b {
return resp, status.Errorf(codes.Unauthenticated, "認證未通過")
}
tokenSlice, ok := md["token"]
if !ok {
return resp, status.Errorf(codes.Unauthenticated, "認證未通過")
}
if tokenSlice[0] != "test_token" {
return resp, status.Errorf(codes.Unauthenticated, "認證未通過")
}
r, e := handler(ctx, req)
fmt.Println("後置中間件")
return r, e
}))
server := grpc.NewServer(opts...)
mate_data_pb.RegisterUserServiceServer(server, &Service{})