在自定义拦截器/中间件中访问gRPC请求对象

时间:2018-06-26 06:48:02

标签: go middleware interceptor grpc

gRPC的Go库提供了用于创建您自己的自定义拦截器(即中间件函数)的接口,而我正尝试编写两个日志记录拦截器。第一个是一元服务器拦截器,使用传递到拦截器函数中的对象,我可以轻松地记录请求参数。

func loggingUnary(context context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    start := time.Now()
    resp, err := handler(context, req)

    printLogMessage(err, info.FullMethod, context, time.Since(start), req)

    return resp, err
}

如何使用无法方便地将请求对象作为参数传递的Stream Server Interceptor进行相同操作?还有另一种访问请求的方法吗?

func loggingStream(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    start := time.Now()
    err := handler(srv, stream)

    printLogMessage(err, info.FullMethod, stream.Context(), time.Since(start), "")

    return err
}

2 个答案:

答案 0 :(得分:2)

现在有点老了,但是将拦截扩展到流中的最简单方法是创建一个 grpc.ServerStream 包装器,然后将真实的ServerStream包装在拦截器中。这样,您的拦截代码就可以处理流中已接收和已发送的消息。

// A wrapper for the real grpc.ServerStream
type LoggingServerStream struct {
    inner           grpc.ServerStream
}

func (l LoggingServerStream) SetHeader(m metadata.MD) error {
    return l.SetHeader(m)
}

func (l LoggingServerStream) SendHeader(m metadata.MD) error {
    return l.SendHeader(m)
}

func (l LoggingServerStream) SetTrailer(m metadata.MD) {
    l.SetTrailer(m)
}

func (l LoggingServerStream) Context() context.Context {
    return l.Context()
}

func (l LoggingServerStream) SendMsg(m interface{}) error {
    fmt.Printf("Sending Message: type=%s\n", reflect.TypeOf(m).String())
    return l.SendMsg(m)
}

func (l LoggingServerStream) RecvMsg(m interface{}) error {
    fmt.Printf("Receiving Message: type=%s\n", reflect.TypeOf(m).String())
    return l.RecvMsg(m)
}

拦截器:

func LoggingStreamInterceptor() grpc.StreamServerInterceptor {
    return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
        return handler(srv, LoggingServerStream{inner:ss})
    }
}

任何状态下,您都需要保存并放入包装纸。

答案 1 :(得分:0)

在创建流的请求的生存期内,可以多次调用流处理程序,这就是为什么该请求不属于处理程序(或任何拦截器)参数的原因。您可以将请求(或者更好的是,您要记录的数据的副本,而不是对请求本身的引用)放置在流上下文中(假设您在控制创建ServerStream对象的代码的控制之下)。我宁愿在创建流时记录一次请求参数,而不是在每次调用处理程序时都记录一次(因此每个请求仅记录一次)。