欺骗grpc UnaryHandler以在Go中对gRPC进行单元测试

I'm working on improving coverage for my Go gRPC server but I have run into trouble writing a test for the server's interceptor function because I'm unable to meaningfully satisfy the UnaryHandler type.

I have a function Interceptor with the following signature:

Interceptor func(
  ctx context.Context,
  req interface{},
  info *grpc.UnaryServerInfo,
  handler grpc.UnaryHandler, // <- my issue comes from here
) (interface{}, error)

I assumed that any gRPC method would satisfy the signature of UnaryHandler:

type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)

So I tried passing in a method with this signature:

GetToken(ctx context.Context, req *AuthData) (*Token, error)

I imagined this would work, since this is what the Interceptor is actually doing (forwarding that RPC), but for some reason Go complains:

cannot use authService.GetToken (type func(context.Context, *AuthData) (*Token, error)) as type grpc.UnaryHandler in argument to Interceptor

I went ahead and wrote a dummy function that correctly satisfies:

func genericHandler(ctx context.Context, req interface{}) (interface{}, error) {
    return req, nil
}

which is fine since I don't particularly need to run any specific method when testing the interceptor. However I am curious as to why the actual method doesn't satisfy the constraints because (according to my understanding) it is being passed to the Interceptor function under hood whenever I call that RPC in the wild.

The most likely explanation is that the grpc UnaryHandler doesn't do what I'm thinking it does, but then what does it do?

duaeim2874
duaeim2874 是不是因为它需要interface{}并且您正在传递并返回引用?也许会匹配*interface{}?也就是说,在实现方面,它应该与指针匹配,因为接口采用了指针,但是我不确定语法。
大约一年之前 回复

1个回答



否,函数</ p>

  GetToken(ctx context.Context,req * AuthData  )(* Token,error)
</ code> </ pre>

与</ p>

  type类型不同UnaryHandler func(ctx  context.Context,req interface {})(interface {},错误)
</ code> </ pre>

GetToken </ code>中,第二个参数 req < / code>的类型为 * AuthData </ code>,而在 UnaryHandler </ code>中, req </ code>的类型为 interface {} </ code>。 返回的 * Token </ code>与 interface {} </ code>的类型不同,也都不相同。 这就是为什么您不能直接将 GetToken </ code>传递给拦截器的原因。</ p>

在grpc服务中,您将编写</ p>
之类的方法

  GetToken(ctx context.Context,req * AuthData)(* Token,error)
</ code> </ pre>

作为处理服务器的处理程序。 但是,它不是人们可能认为的 UnaryHandler </ code>。</ p>

大多数转换都是由grpc / protobuf代码生成器完成的。 根据您的原型定义,它会生成一个接口,如下所示:</ p>

  type XXXServer接口{
GetToken(ctx context.Context,req * AuthData)(* Token,error )
}
</ code> </ pre>

您可以看到您的处理程序满足的正是此接口(而不是UnaryHander)。</ p>

在后台,如果您查看生成的 xxx.pb.go </ code>文件,您会发现一些实际上正在完成处理程序工作的 _XXX_GetToken_Handler </ code>。 在此函数中,(实际) UnaryHandler </ code>定义为:</ p>

  func _XXX_GetToken_Handler(srv interface {},ctx context.Context,dec func  (interface {})错误,拦截器grpc.UnaryServerInterceptor)(interface {},错误){
//跳过其他准备工作...
//
处理程序:= func(ctx context.Context,req interface {} )(接口{},错误){
返回srv。(XXXServer).GetToken(ctx,req。(* AuthData))
}
返回拦截器(ctx,in,info,handler)
}
</ code> </ pre>

在此 UnaryHandler </ code>内,它将把您的服务器投射到 XXXServer </ code>接口,然后调用您的处理程序(您的 码)。 这显示了 interceptor </ code>的调用方式。</ p>
</ div>

展开原文

原文

No, the function

GetToken(ctx context.Context, req *AuthData) (*Token, error)

is not of the same type as

type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)

In GetToken the second parameter req is of type *AuthData, while in UnaryHandler req is of type interface{}. The returned *Token is not the same type as interface{}, neither. This is the reason why you can't pass GetToken directly to the interceptor.

In your grpc services, you would write methods like

GetToken(ctx context.Context, req *AuthData) (*Token, error)

as handlers to do your server works. However, it's not an UnaryHandler as one may think.

Most of the transformation is done by the grpc/protobuf code generator. Base on your proto definition, it generates an interface, like this:

type XXXServer interface {
    GetToken(ctx context.Context, req *AuthData) (*Token, error)
}

You can see that it is this interface (not the UnaryHander) that your handler satisfies.

Under the hood, if you take a look on the xxx.pb.go file generated, you'll find some _XXX_GetToken_Handler that's actually doing the handler work. In this function, an (actual) UnaryHandler is defined, as:

func _XXX_GetToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    // skip other preparations...
    // 
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(XXXServer).GetToken(ctx, req.(*AuthData))
    }
    return interceptor(ctx, in, info, handler)
}

Inside this UnaryHandler, it will cast your server to the XXXServer interface, and then invoke your handler (your code). And that shows how the interceptor is called.

douwei1128
douwei1128 同样,对于函数类型UnaryHandler func(ctx context.Context,req interface {})(interface {},错误),您可以将任何类型的值作为UnqHandler的req参数传递的事实不会使静态类型成为静态类型。 interface {}与* AuthData相同。 因此,我们可以知道我们讨论的两个函数不是相同的静态类型,也不是。
大约一年之前 回复
dongtangu8403
dongtangu8403 Go是静态类型的。 Go中的接口仍然是类型。 req * AuthData和req interface {}的静态类型不同(很明显,一个是* AuthData,另一个是interface {})。 假设您声明了变量var req interface {},则可以将其他类型的值分配给此变量,只要它们满足该接口的要求即可(在这种特殊情况下,任何类型都可以满足)。 例如,您可以编写x:= 1; req = x。 但是这个事实并不能使req和x成为相同的静态类型。
大约一年之前 回复
douduan9391
douduan9391 我理解这一点,也感谢您对基本实现中发生的事情所作的出色解释,但对我而言仍然有一点困惑,难道没有任何价值满足interface {}吗? req * AuthData是否与req接口{}相同吗? 由于interface {}用于满足go中未知的类型约束,因此我假设这些函数实际上是同一类型,因此我想知道类型错误的来源
大约一年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐