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

Go 在自定义拦截器/中间件中访问gRPC请求对象,go,middleware,interceptor,grpc,Go,Middleware,Interceptor,Grpc,gRPC的Go库提供了创建自定义拦截器(即中间件函数)的接口,我正在尝试编写两个日志拦截器。第一个是一元服务器拦截器,我可以使用传递到拦截器函数中的对象轻松地记录请求参数 func loggingUnary(context context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { start := time.Now()

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
}
对于不能方便地将请求对象作为参数传递的流服务器拦截器,我如何做同样的事情?是否有其他方式访问请求

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
}

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

这现在有点旧了,但将拦截扩展到流中的最简单方法是创建一个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})
    }
}

你需要保留的任何状态都可以放入包装中。

我有点困惑。如果“控制创建ServerStream对象的代码”,您的意思是控制进行RPC调用的客户端,那么不,我没有控制权。然而,我控制着所有的服务器端代码。否则我如何访问请求?不,我指的是GO程序中创建ServerStream的部分(侦听您想要查看和记录的请求的部分)。这些代码应该是你的可执行文件的一部分(虽然不一定是你写的东西——它可能在别人开发的包中,我不知道)。上面的代码没有回答如何获得实际请求的问题。此代码截取服务器流而不是请求。我确认您的解决方案工作正常,但您犯了一个小错误,它“不是”返回l.RecvMsg(m)”,而是“l.inner.RecvMsg(m)”。RecvMsg中的消息实际上就是请求本身。