自定义gRPC Gateway拦截器
本文通过实现一个从请求Header中获取自定义字段,将其注入context中的拦截器,介绍如何实现一个自定义的gRPC Gateway拦截器。
grpc标准包中定义了grpc拦截器的格式,如下:
// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info
// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper
// of the service method implementation. It is the responsibility of the interceptor to invoke handler
// to complete the RPC.
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
实现的服务接口中ctx
从interceptor ctx
继承而来,req
为接口Request
载体,info
承载了server
及method
信息,handler
定义了interceptor的处理程序。
info
定义如下:
// UnaryServerInfo consists of various information about a unary RPC on
// server side. All per-rpc information may be mutated by the interceptor.
type UnaryServerInfo struct {
// Server is the service implementation the user provides. This is read-only.
Server interface{}
// FullMethod is the full RPC method string, i.e., /package.service/method.
FullMethod string
}
handler
定义如下:
// UnaryHandler defines the handler invoked by UnaryServerInterceptor to complete the normal
// execution of a unary RPC. If a UnaryHandler returns an error, it should be produced by the
// status package, or else gRPC will use codes.Unknown as the status code and err.Error() as
// the status message of the RPC.
type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)
了解interceptor结构后,就可根据业务功能自定义实现interceptor。下文为一个从header中读取自定义字段,将其值赋给新加字段,从填充到context中的拦截器实现。
// UnaryServerInterceptor customized unary server interceptor
// duplicates a new header field using old one, add it to context and return
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(
ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (resp interface{}, err error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
var value []string
if v, ok := md[string(OldKey)]; ok {
value = v
}
md1 := metadata.Pairs(string(NewKey), value[0])
md2 := metadata.Join(md, md1)
ctx = metadata.NewIncomingContext(ctx, md2)
}
return handler(ctx, req)
}
}
涉及到的相关函数可通过go pkg查看。
UnaryServerInterceptor
函数以gRPC metadata
结构往context中添加新的字段,起类型为metadata
包定义的mdIncomingKey
类型,此类型不可导出。
type mdIncomingKey struct{}
// NewIncomingContext creates a new context with incoming md attached.
func NewIncomingContext(ctx context.Context, md MD) context.Context {
return context.WithValue(ctx, mdIncomingKey{}, md)
}
所以新添加的字段是无法以string
等基本类型直接获取,好的做法是实现统一的提取函数,如下:
// ExtractMetadata extract NewKey from context and return
func ExtractMetadata(ctx context.Context, key KeyType) (interface{}, bool) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
v := md.Get(string(key))
if len(v) > 0 {
return v[0], true
}
return nil, true
}
return nil, false
}
完整代码可参见grpc-helloworld。
当使用gRPC Gateway提供的restful服务时,Header中的自定义字段需以Grpc-metadata-
作为前缀,否则gRPC Gateway网关无法识别。参见github issue。