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{})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章