gRPC 的 go 拦截器

grpc interceptor with go

Interceptor

通过 ServerOption 给 grpc.Server 增加 interceptor 时有限制:Only one unary interceptor can be installed.。为了安装多个拦截器,我们需要一种可以将多个 interceptor 重新组合的方式。

目前常用的方式是:

        package : github.com/grpc-ecosystem/go-grpc-middleware:
            func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor
            func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor

使用示例见:enceladus.wgrpc.server

下边我们来看看它的实现:

        // Execution is done in left-to-right order, including passing of context.
        func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
            n := len(interceptors)

            if n > 1 {
                lastI := n - 1
                return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
                    var (
                        chainHandler grpc.UnaryHandler
                        curI         int
                    )

                    chainHandler = func(currentCtx context.Context, currentReq interface{}) (interface{}, error) {
                        if curI == lastI {
                            return handler(currentCtx, currentReq)
                        }
                        curI++
                        resp, err := interceptors[curI](currentCtx, currentReq, info, chainHandler)
                        curI--
                        return resp, err
                    }

                    return interceptors[0](ctx, req, info, chainHandler)
                }
            }

            if n == 1 {
                return interceptors[0]
            }

            // n == 0; Dummy interceptor maintained for backward compatibility to avoid returning nil.
            return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
                return handler(ctx, req)
            }
        }

其实,它的实现的本质就是:利用闭包来引用栈中数据达到延迟使用的效果

通过这种方式,我们就可以尽情的实现自己的各种 interceptor:UnaryServerInterceptorAccessLog、grpc_recovery.UnaryServerInterceptor(package: github.com/grpc-ecosystem/go-grpc-middleware/recovery) 等等。

grpc.stats

grpc.Server 可以通过安装 func WithStatsHandler(h stats.Handler) DialOption 生成的 ServerOption 实现 This package is for monitoring purpose only. All fields are read-only.

首先,我们看看 stats.Handler 的定义:

    type Handler interface {
        // TagRPC can attach some information to the given context.
        // The context used for the rest lifetime of the RPC will be derived from
        // the returned context.
        TagRPC(context.Context, *RPCTagInfo) context.Context
        // HandleRPC processes the RPC stats.
        HandleRPC(context.Context, RPCStats)

        // TagConn can attach some information to the given context.
        // The returned context will be used for stats handling.
        // For conn stats handling, the context used in HandleConn for this
        // connection will be derived from the context returned.
        // For RPC stats handling,
        //  - On server side, the context used in HandleRPC for all RPCs on this
        // connection will be derived from the context returned.
        //  - On client side, the context is not derived from the context returned.
        TagConn(context.Context, *ConnTagInfo) context.Context
        // HandleConn processes the Conn stats.
        HandleConn(context.Context, ConnStats)
    }

在 grpc 的方法调用过程中,RPCStats 在 stat.Begin、stat.ConnBegin、stat.ConnEnd、stat.End 几个状态之间转换。

其中 Handler.HandleConn 发生在 grpc 的方法调用之前,而 grpc方法调用后,会调用 Handler.HandleRPC (此时 RPCStats 为 stat.End)。由此,我们可以通过这种方式,达到对 grpc 方法 装饰的效果。如:github.com/openzipkin/zipkin-go 的实现,使用示例:

Appendix A

Last updated