一般转发 GRPC 调用
Generically forwarding a GRPC call
我有一个 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
来完成这项工作,它能够检查来电,看看它来自旧客户端(我们在 headers 中有客户端版本) 和 redirect/forward 它到重命名的服务。 (因为只是更改了包名称,所以很容易弄清楚,例如 foo.BazApi/FooEventStream
-> bar.BazApi/FooEventStream
)
但是,似乎没有一种优雅的方法可以做到这一点。我认为可以通过将新的 ClientCall
启动到正确的端点,然后通过委托给 ClientCall
在拦截器中处理 ServerCall
来实现,但这将需要一堆管道代码来妥善处理 unary/clientStreaming/serverStreaming/bidiStreaming 个电话。
有更好的方法吗?
使用较低级别的 "channel" API 你可以不用 太多的工作就可以创建一个代理。您主要只是代理从 ServerCall.Listener
到 ClientCall
和 ClientCall.Listener
到 ServerCall
的事件。您将了解较低级别的 MethodDescriptor
和很少使用的 HandlerRegistry
。处理流量控制也有一些复杂性(isReady()
和 request()
)。
前段时间我做了一个例子,但一直没有花时间将它合并到 grpc-java 本身。当前可用 on my random branch。您应该能够通过更改 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);
如果您可以轻松更改服务器,则可以同时支持这两个名称。您可以考虑使用两个不同的描述符注册两次服务的解决方案。
每个服务都有一个 bindService()
方法,即 returns 一个 ServerServiceDefinition
。您可以通过正常的 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"
我有一个 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
来完成这项工作,它能够检查来电,看看它来自旧客户端(我们在 headers 中有客户端版本) 和 redirect/forward 它到重命名的服务。 (因为只是更改了包名称,所以很容易弄清楚,例如 foo.BazApi/FooEventStream
-> bar.BazApi/FooEventStream
)
但是,似乎没有一种优雅的方法可以做到这一点。我认为可以通过将新的 ClientCall
启动到正确的端点,然后通过委托给 ClientCall
在拦截器中处理 ServerCall
来实现,但这将需要一堆管道代码来妥善处理 unary/clientStreaming/serverStreaming/bidiStreaming 个电话。
有更好的方法吗?
使用较低级别的 "channel" API 你可以不用 太多的工作就可以创建一个代理。您主要只是代理从 ServerCall.Listener
到 ClientCall
和 ClientCall.Listener
到 ServerCall
的事件。您将了解较低级别的 MethodDescriptor
和很少使用的 HandlerRegistry
。处理流量控制也有一些复杂性(isReady()
和 request()
)。
前段时间我做了一个例子,但一直没有花时间将它合并到 grpc-java 本身。当前可用 on my random branch。您应该能够通过更改 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);
如果您可以轻松更改服务器,则可以同时支持这两个名称。您可以考虑使用两个不同的描述符注册两次服务的解决方案。
每个服务都有一个 bindService()
方法,即 returns 一个 ServerServiceDefinition
。您可以通过正常的 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"