gRPC| 青训营笔记

73 阅读2分钟

METADATA

在http协议中, session_id放在cookie中, 最终在Header中. gRPC中, 采用了HTTP 2.0. METADATA放在Context中 Client

//md := metadata.Pairs("timestamp", time.Now().String()) // 两种方式都可以
md := metadata.New(map[string]string {
	"name": "Bing",
	"password": "pwd",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)

req := proto.HelloRequest{
	Name: "Bing",
}
resp, _ := client.SayHello(ctx, &req)

Server

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		fmt.Println(md.Get("name"), md.Get("password"))
	}
	fmt.Println("---")
	for k, v := range md {
		fmt.Println(k, v)
	}
	fmt.Println("---")
	if name, ok := md["name"]; ok {
		for i, v := range name {
			fmt.Println(i, v)
		}
	}
	return &proto.HelloResponse{Message: "Hello, " + request.Name}, nil
}

拦截器

对所有的请求, 进行统一的拦截处理, 例如: 统一校验, 重试, 监控, 速率限制, 日志

服务端拦截器

unaryInterceptor - 一元拦截器 eg: 统计请求时间

interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	fmt.Println("Receive a new request!")
	beforeHandle := time.Now()
	res, err := handler(ctx, req) // 继续正常处理
	fmt.Println("Request processed! cost", time.Since(beforeHandle))
	return res, err
}
opt := grpc.UnaryInterceptor(interceptor)
g := grpc.NewServer(opt)

客户端拦截器

interceptor := 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.Println("Cost", time.Since(start))
	return err
})

conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), interceptor)

token认证

  1. 拦截器 + metadata
interceptor := grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	start := time.Now()
	md := metadata.New(map[string]string{
		"app_id":  "test_app",
		"app_key": "test_key",
	})
	ctx = metadata.NewOutgoingContext(ctx, md)
	err := invoker(ctx, method, req, reply, cc, opts...)
	if err != nil {
		panic(err)
	}
	fmt.Println("Cost", time.Since(start))
	return err
})
  1. 内置了专门的校验 本质上就是封装了metadata的操作
type customCredential struct{}

func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"app_id":  "test_app",
		"app_key": "test_key",
	}, nil
}

func (c customCredential) RequireTransportSecurity() bool {
	return false
}

opts = append(opts, grpc.WithPerRPCCredentials(customCredential{}))
conn, err := grpc.Dial("127.0.0.1:8080", opts...)

表单验证

  1. 保存validate.proto
  2. 为protobuf添加规则
  3. 生成
protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. --validate_out="lang=go:." .\hello_world.proto
  1. 在Go中使用
// 用到了断言, 看req是否实现了Validator接口(带Validate方法), 这里主要是为了不同Message调用不同的Validate方法
if r, ok := req.(Validator); ok { 
	if err := r.Validate(); err != nil {
		fmt.Println(err)
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
}

异常处理

gRPC自带一些常用的状态码, 实际开发中, 都会自定义状态码. 服务端构造Error

status.Error(codes.InvalidArgument, "参数错误")

客户端解析Error

st, ok := status.FromError(err)
if !ok {
	// Error was not a status error
}
fmt.Println(st.Code(), st.Message())

超时处理

客户端超时处理, 与Context有关

ctx, _ := context.WithTimeout(context.Background(), time.Second*2)
resp, err := client.SayHello(ctx, &req)