Общая переадресация вызова GRPC - PullRequest
2 голосов
/ 25 октября 2019

У меня есть GRPC API, где после рефакторинга несколько пакетов были переименованы. Это включает в себя объявление package в одном из наших файлов прото, которое определяет API. Примерно так:

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 для правильной конечной точки, а затем обработав ServerCall внутри перехватчика, делегировав ClientCall, но это потребует связки кода для правильной обработки unary / clientStreaming /serverStreaming / bidiStreaming звонки.

Есть ли лучший способ сделать это?

Ответы [ 2 ]

3 голосов
/ 25 октября 2019

Если вы можете легко сменить сервер, он может поддерживать оба имени одновременно. Вы можете рассмотреть решение, при котором вы регистрируете свой сервис дважды, используя два разных дескриптора.

Каждый сервис имеет метод bindService(), который возвращает 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"
2 голосов
/ 25 октября 2019

Используя низкоуровневый API "канал", вы можете создать прокси без слишком большой работы. Вы в основном просто прокси-события от ServerCall.Listener до ClientCall и от ClientCall.Listener до ServerCall. Вы узнаете о низкоуровневом MethodDescriptor и редко используемом HandlerRegistry. Также есть некоторая сложность в управлении потоком (isReady() и request()).

Некоторое время назад я сделал пример, но никогда не тратил время на его слияние с самой grpc-java. В настоящее время он доступен в моей случайной ветке . Вы должны иметь возможность заставить его работать, просто изменив localhost:8980 и переписав MethodDescriptor, переданный в channel.newCall(...). Что-то похожее на:

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);
...