GRPC攔截器和Metadata的使用

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類型,keystringvaluestring 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

有兩種方法可以將元數據發送到服務器。推薦的方法是使用 AppendToOutgoingContextkv 對附加到上下文中。這可以與上下文中的現有元數據一起使用,也可以不使用。當沒有先前的元數據時,將添加元數據;當上下文中已存在元數據時,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{})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章