我有一个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调用。
有更好的方法吗?
如果您可以轻松地更改服务器,则可以让它同时支持这两个名称。您可以考虑使用两个不同的描述符注册服务两次的解决方案。
每个服务都有一个返回ServerServiceDefinition
的bindService()
方法。您可以通过普通的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);