// stub - это сервис-заглушка, которая принимает запросы
private final GreeterGrpc.GreeterBlockingStub blockingStub;
...
// создание канала связи. target - это адрес сервера
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
// каналы поддерживают защищенное соединение (через SSL/TLS). для упрощения примера будем использовать незащищенное соединение
.usePlaintext()
.build();
// создание сервиса-заглушки
blockingStub = GreeterGrpc.newBlockingStub(channel);
...
// создание запроса с указанием имени. если не указывать явно, то отправится значение по-умолчанию
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
// отправка запроса в процедуру sayHello
final HelloReply response = blockingStub.sayHello(request);
// создание сервиса gRPC, который будет прослушивать 50051 порт. добавляем в него все сервисы, которые должны обрабатывать сообщения
private Server server = ServerBuilder.forPort(50051)
.addService(new GreeterImpl())
.build()
.start();
...
// создание класса-обработчика сообщений на основе сгенерированного protoc-компилятором кода
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
// необходимо реализовать эту процедуру
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
// получаем из аргументов сообщения имя пользователя для создания сообщения приветствия
HelloReply reply = HelloReply.newBuilder().setMessage("Привет " + req.getName()).build();
// отправляем в ответ на запрос ответное сообщение
responseObserver.onNext(reply);
// закрываем запрос, обозначая, что вызов одиночной процедуры выполнен успешно
responseObserver.onCompleted();
}
}
syntax = "proto3";
package helloworld;
// Объявление сервиса приветствия
service Greeter {
// Процедура приветствия
rpc SayHello (HelloRequest) returns (HelloReply)
}
// Сообщение запроса, содержащее имя пользователя
message HelloRequest {
string name = 1;
}
// Сообщение ответа, содержащее фразу-приветствие
message HelloReply {
string message = 1;
}
server {
server_name courier.unitbeandev.com;
location / {
grpc_pass grpc://localhost:50051; # ключевой момент, необходимо вместо proxy_pass http://localhost:{port}. Указание одного и того же порта важно на сервер и на клиенте
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
client_max_body_size 50m;
}
listen 443 ssl http2; # также важный момент конфигурации, поддержка HTTP/2.0
ssl_certificate /etc/letsencrypt/live/courier.unitbeandev.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/courier.unitbeandev.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = courier.unitbeandev.com) {
return 301 https://$host$request_uri;
}
server_name courier.unitbeandev.com;
listen 80;
return 404;
}
В процессе работы над системой неизбежны изменения контракта клиент-серверного взаимодействия. Для этих целей в стандарте proto предусмотрены целочисленные индексы для каждого из полей сообщения. Индексы полей должны быть уникальны для каждого поля в рамках отдельного сообщения. При добавлении поля ему должен присваиваться новый индекс. При удалении ненужного поля его индекс должен удаляться или вноситься в специальный список зарезервированных индексов, чтобы было невозможно его переиспользовать в будущем, получив явное указание на конфликт полей.
Таким образом, каждое из полей сообщения является уникальным и для клиентов разных версий работа будет происходить только с теми полями, про которые знает приложение. На стороне сервера работа будет происходить сразу со всеми полями в рамках тех стандартов версионирования кода, которое будет принято в системе