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认证
- 拦截器 + 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
})
- 内置了专门的校验 本质上就是封装了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...)
表单验证
- 保存validate.proto
- 为protobuf添加规则
- 生成
protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. --validate_out="lang=go:." .\hello_world.proto
- 在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)