一般转发GRPC呼叫



我有一个GRPC API,在重构之后,几个包被重命名。这包括定义API的一个原型文件中的package声明。类似这样的东西:

package foo;
service BazApi {
rpc FooEventStream(stream Ack) returns (stream FooEvent);
}

改为

package bar;
service BazApi {
rpc FooEventStream(stream Ack) returns (stream FooEvent);
}

服务器端使用grpc-java实现,上面有scala和monix。

这一切对于使用新proto文件的客户端来说都很好,但对于在旧proto文件之上构建的旧客户端来说,这会导致问题:UNIMPLEMENTED: Method not found: foo.BazApi/FooEventStream

通过GRPC API传递的消息的实际数据格式没有改变,只有包。

由于我们需要保持向后兼容性,我一直在寻找一种方法,使旧客户端在保持名称更改的同时工作。

我希望用一个通用的ServerInterceptor来实现这一点,它将能够检查传入的调用,查看它是否来自旧客户端(我们在标头中有客户端版本(,并将其重定向/转发到重命名的服务。(由于只是包名称发生了变化,因此很容易理解,例如foo.BazApi/FooEventStream->bar.BazApi/FooEventStream(

然而,似乎没有一种优雅的方式来做到这一点。我认为可以通过向正确的端点启动一个新的ClientCall,然后通过委托给ClientCall来处理拦截器中的ServerCall,但这需要一堆管道代码来正确处理一元/clientStreaming/serverStreaming/bidStreaming调用。

有更好的方法吗?

如果您可以轻松地更改服务器,则可以让它同时支持这两个名称。您可以考虑使用两个不同的描述符注册服务两次的解决方案。

每个服务都有一个返回ServerServiceDefinitionbindService()方法。您可以通过普通的serverBuilder.addService()将定义传递给服务器。

因此,您可以获得普通的ServerServiceDefinition,然后将其重写为新名称,然后注册新名称。

BazApiImpl service = new BazApiImpl();
serverBuilder.addService(service); // register "bar"
ServerServiceDefinition barDef = service.bindService();
ServerServiceDefinition fooDefBuilder = ServerServiceDefinition.builder("foo.BazApi");
for (ServerMethodDefinition<?,?> barMethodDef : barDef.getMethods()) {
MethodDescriptor desc = barMethodDef.getMethodDescriptor();
String newName = desc.getFullMethodName().replace("foo.BazApi/", "bar.BazApi/");
desc = desc.toBuilder().setFullMethodName(newName).build();
foDefBuilder.addMethod(desc, barMethodDef.getServerCallHandler());
}
serverBuilder.addService(fooDefBuilder.build()); // register "foo"

使用较低级别的"通道"API,您可以制作一个代理,而不需要太多的工作。您主要只是将事件从ServerCall.Listener代理到ClientCall,将ClientCall.Listener代理到ServerCall。您可以了解较低级别的MethodDescriptor和很少使用的HandlerRegistry。处理流控制(isReady()request()(也有一些复杂性。

不久前我做了一个例子,但从未花时间将其合并到grpc-java本身。它目前在我的随机分支上可用。只需更改localhost:8980并重写传递给channel.newCall(...)MethodDescriptor,就可以使其正常工作。类似于:

MethodDescriptor desc = serverCall.getMethodDescriptor();
if (desc.getFullMethodName().startsWith("foo.BazApi/")) {
String newName = desc.getFullMethodName().replace("foo.BazApi/", "bar.BazApi/");
desc = desc.toBuilder().setFullMethodName(newName).build();
}
ClientCall<ReqT, RespT> clientCall
= channel.newCall(desc, CallOptions.DEFAULT);

最新更新