自定义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。