Implementando um serviço gRPC
As implementações de serviço gRPC expostas como beans CDI são automaticamente registradas e servidas pelo quarkus-grpc.
| A implementação de um serviço gRPC requer que as classes gRPC sejam geradas. Coloque seus arquivos 'proto' em 'src/main/proto' e execute 'mvn compile'. |
Código Gerado
O Quarkus gera algumas classes de implementação para serviços declarados no arquivo 'proto':
-
Um service interface usando a API Mutiny
-
o nome da classe é '${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}'
-
-
Uma classe base de implementação que utiliza a API gRPC
-
o nome da classe está estruturado da seguinte forma: '${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase'
-
Por exemplo, se você usar o seguinte trecho de arquivo 'proto':
option java_package = "hello"; (1)
service Greeter { (2)
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
| 1 | hello é o pacote java para as classes geradas. |
| 2 | Greeter é o nome do serviço. |
Em seguida, a interface de serviço é 'Olá. Greeter' e a base de implementação é a classe aninhada estática abstrata: 'hello. GreeterGrpc.GreeterImplBase'.
| Você precisará implementar o service interface ou estender o base class com seu bean de implementação de serviço, conforme descrito nas seções a seguir. |
Implementando um serviço com a API Mutiny
Para implementar um serviço gRPC usando a API Mutiny , crie uma classe que implemente a interface de serviço. Em seguida, implemente os métodos definidos na interface de serviço. Se você não quiser implementar um método de serviço, basta lançar um 'java.lang.UnsupportedOperationException' do corpo do método (a exceção será convertida automaticamente para a exceção gRPC apropriada). Finalmente, implemente o serviço e adicione a anotação '@GrpcService':
import io.quarkus.grpc.GrpcService;
import hello.Greeter;
@GrpcService (1)
public class HelloService implements Greeter { (2)
@Override
public Uni<HelloReply> sayHello(HelloRequest request) {
return Uni.createFrom().item(() ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
);
}
}
| 1 | A gRPC service implementation bean must be annotated with the @GrpcService annotation and should not declare any other CDI qualifier. All gRPC services have the javax.inject.Singleton scope. Additionally, the request context is always active during a service call. |
| 2 | hello.Greeter é a interface de serviço gerada. |
| O bean de implementação de serviço também pode estender a base de implementação do Mutiny , onde o nome da classe é estruturado da seguinte maneira: 'Mutiny${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase'. |
Implementando um serviço com a API gRPC padrão
Para implementar um serviço gRPC usando a API gRPC padrão, crie uma classe que estenda a base de implementação padrão. Em seguida, substitua os métodos definidos na interface de serviço. Finalmente, implemente o serviço e adicione a anotação '@GrpcService':
import io.quarkus.grpc.GrpcService;
@GrpcService
public class HelloService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
String message = "Hello " + name;
responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
responseObserver.onCompleted();
}
}
Implementação do serviço de bloqueio
Por padrão, todos os métodos de um serviço gRPC são executados no loop de eventos. Como consequência, você deve não bloquear. Se a lógica do serviço precisar bloquear, anote o método com 'io.smallrye.common.annotation.Blocking':
@Override
@Blocking
public Uni<HelloReply> sayHelloBlocking(HelloRequest request) {
// Do something blocking before returning the Uni
}
Manipulação de fluxos
gRPC permite receber e retornar fluxos:
service Streaming {
rpc Source(Empty) returns (stream Item) {} // Returns a stream
rpc Sink(stream Item) returns (Empty) {} // Reads a stream
rpc Pipe(stream Item) returns (stream Item) {} // Reads a streams and return a streams
}
Usando o Mutiny, você pode implementá-los da seguinte maneira:
import io.quarkus.grpc.GrpcService;
@GrpcService
public class StreamingService implements Streaming {
@Override
public Multi<Item> source(Empty request) {
// Just returns a stream emitting an item every 2ms and stopping after 10 items.
return Multi.createFrom().ticks().every(Duration.ofMillis(2))
.select().first(10)
.map(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
@Override
public Uni<Empty> sink(Multi<Item> request) {
// Reads the incoming streams, consume all the items.
return request
.map(Item::getValue)
.map(Long::parseLong)
.collect().last()
.map(l -> Empty.newBuilder().build());
}
@Override
public Multi<Item> pipe(Multi<Item> request) {
// Reads the incoming stream, compute a sum and return the cumulative results
// in the outbound stream.
return request
.map(Item::getValue)
.map(Long::parseLong)
.onItem().scan(() -> 0L, Long::sum)
.onItem().transform(l -> Item.newBuilder().setValue(Long.toString(l)).build());
}
}
Verificação de saúde
Para os serviços implementados, o Quarkus gRPC expõe informações de integridade no seguinte formato:
syntax = "proto3";
package grpc.health.v1;
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}
Os clientes podem especificar o nome de serviço totalmente qualificado para obter o status de integridade de um serviço específico ou ignore a especificação do nome do serviço para obter o status geral do servidor gRPC.
Para mais detalhes, confira o documentação do gRPC
Além disso, se o Quarkus SmallRye Health for adicionado ao aplicativo, uma verificação de prontidão para o estado dos serviços gRPC será adicionado à resposta de ponto de extremidade do MicroProfile Health, ou seja, '/q/health'.
Reflection Service
O Quarkus gRPC Server implementa o reflection service. Este serviço permite que ferramentas como grpcurl ou grpcox interajam com seus serviços.
O reflection service é habilitado por padrão no modo dev. No modo de teste ou produção, você precisa habilitá-lo explicitamente definindo 'quarkus.grpc.server.enable-reflection-service' como 'true'.
Dimensionamento
Por padrão, o quarkus-grpc inicia um único servidor gRPC em execução em um único loop de eventos.
Se você deseja dimensionar seu servidor, você pode definir o número de instâncias do servidor definindo 'quarkus.grpc.server.instances'.
Configuração do Servidor
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Tipo |
Padrão |
|
|---|---|---|
Do we use separate HTTP server to serve gRPC requests. Set this to false if you want to use new Vert.x gRPC support, which uses existing Vert.x HTTP server. Environment variable: Show more |
booleano |
|
Explicitly enable use of XDS. Environment variable: Show more |
booleano |
|
Use secure credentials. Environment variable: Show more |
booleano |
|
The gRPC Server port. Environment variable: Show more |
int |
|
The gRPC Server port used for tests. Environment variable: Show more |
int |
|
The gRPC server host. Environment variable: Show more |
string |
|
The gRPC handshake timeout. Environment variable: Show more |
||
The max inbound message size in bytes. Environment variable: Show more |
int |
|
The max inbound metadata size in bytes Environment variable: Show more |
int |
|
The classpath path or file path to a server certificate or certificate chain in PEM format. Environment variable: Show more |
path |
|
The classpath path or file path to the corresponding certificate private key file in PEM format. Environment variable: Show more |
path |
|
An optional key store which holds the certificate information instead of specifying separate files. The key store can be either on classpath or an external file. Environment variable: Show more |
path |
|
An optional parameter to specify the type of the key store file. If not given, the type is automatically detected based on the file name. Environment variable: Show more |
string |
|
A parameter to specify the password of the key store file. If not given, the default ("password") is used. Environment variable: Show more |
string |
|
An optional trust store which holds the certificate information of the certificates to trust The trust store can be either on classpath or an external file. Environment variable: Show more |
path |
|
An optional parameter to specify type of the trust store file. If not given, the type is automatically detected based on the file name. Environment variable: Show more |
string |
|
A parameter to specify the password of the trust store file. Environment variable: Show more |
string |
|
The cipher suites to use. If none is given, a reasonable default is selected. Environment variable: Show more |
list of string |
|
Sets the ordered list of enabled SSL/TLS protocols.
If not set, it defaults to Environment variable: Show more |
list of string |
|
Configures the engine to require/request client authentication. NONE, REQUEST, REQUIRED Environment variable: Show more |
|
|
Disables SSL, and uses plain text instead. If disabled, configure the ssl configuration. Environment variable: Show more |
booleano |
|
Whether ALPN should be used. Environment variable: Show more |
booleano |
|
The path to the certificate file. Environment variable: Show more |
string |
|
The path to the private key file. Environment variable: Show more |
string |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: Show more |
booleano |
|
Number of gRPC server verticle instances. This is useful for scaling easily across multiple cores. The number should not exceed the amount of event loops. Environment variable: Show more |
int |
|
Sets a custom keep-alive duration. This configures the time before sending a Environment variable: Show more |
||
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
|
About the Duration format
The format for durations uses the standard You can also provide duration values starting with a number.
In this case, if the value consists only of a number, the converter treats the value as seconds.
Otherwise, |
| Quando você desabilita 'quarkus.grpc.server.use-separate-server', você está usando a nova implementação do servidor gRPC Vert.x que usa o servidor HTTP existente. O que significa que a porta do servidor agora é '8080' (ou a porta configurada com 'quarkus.http.port'). Além disso, a maioria das outras propriedades de configuração não são mais aplicadas, pois é o servidor HTTP que já deve estar configurado corretamente. |
| Quando você habilita 'quarkus.grpc.server.xds.enabled', é o xDS que deve lidar com a maior parte da configuração acima. |
Exemplo de configuração
Habilitando o TLS
Para habilitar o TLS, use a configuração a seguir.
Observe que todos os caminhos na configuração podem especificar um recurso no classpath (normalmente de 'src/main/resources' ou sua subpasta) ou um arquivo externo.
quarkus.grpc.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
Quando SSL/TLS é configurado, plain-text é automaticamente desativado.
|
TLS com autenticação mútua
Para utilizar o TLS com autenticação mútua, utilize a seguinte configuração:
quarkus.grpc.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
quarkus.grpc.server.ssl.trust-store=tls/ca.jks
quarkus.grpc.server.ssl.trust-store-password=*****
quarkus.grpc.server.ssl.client-auth=REQUIRED
Interceptores de servidor
Os interceptadores de servidor gRPC permitem que você execute lógica, como autenticação, antes que seu serviço seja chamado.
Você pode implementar um interceptor de servidor gRPC criando um bean '@ApplicationScoped' implementando 'io.grpc.ServerInterceptor':
@ApplicationScoped
// add @GlobalInterceptor for interceptors meant to be invoked for every service
public class MyInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
}
| Verifique o ServerInterceptor JavaDoc para implementar corretamente seu interceptador. |
Para aplicar um interceptador a todos os serviços expostos, anote-o com '@io.quarkus.grpc.GlobalInterceptor'. Para aplicar um interceptador a um único serviço, registre-o no serviço com '@io.quarkus.grpc.RegisterInterceptor':
import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.RegisterInterceptor;
@GrpcService
@RegisterInterceptor(MyInterceptor.class)
public class StreamingService implements Streaming {
// ...
}
When you have multiple server interceptors, you can order them by implementing the javax.enterprise.inject.spi.Prioritized interface. Please note that all the global interceptors are invoked before the service-specific
interceptors.
@ApplicationScoped
public class MyInterceptor implements ServerInterceptor, Prioritized {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
// ...
}
@Override
public int getPriority() {
return 10;
}
}
Os interceptadores com a maior prioridade são chamados primeiro. A prioridade padrão, usada se o interceptador não implementar a interface Prioritized , é 0 .
Testando seus serviços
A maneira mais fácil de testar um serviço gRPC é usar um cliente gRPC conforme descrito em Consumindo um serviço gRPC.
Observe que, no caso de usar um cliente para testar um serviço exposto que não usa TLS, Não há necessidade de fornecer nenhuma configuração. Por exemplo, para testar o 'HelloService' definido acima, pode-se criar o seguinte teste:
public class HelloServiceTest implements Greeter {
@GrpcClient
Greeter client;
@Test
void shouldReturnHello() {
CompletableFuture<String> message = new CompletableFuture<>();
client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
.subscribe().with(reply -> message.complete(reply.getMessage()));
assertThat(message.get(5, TimeUnit.SECONDS)).isEqualTo("Hello Quarkus");
}
}
Experimentando seus serviços manualmente
In the dev mode, you can try out your gRPC services in the Quarkus Dev UI. Just go to http://localhost:8080/q/dev and click on Services under the gRPC tile.
Observe que seu aplicativo precisa expor a porta HTTP "normal" para que a interface do usuário de desenvolvimento seja acessível. Se seu aplicativo não expor nenhum ponto de extremidade HTTP, você poderá criar um perfil dedicado com uma dependência em 'quarkus-vertx-http':
<profiles>
<profile>
<id>development</id>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
</dependencies>
</profiles>
</profile>
Tendo isso, você pode executar o modo dev com: 'mvn quarkus:dev -Pdevelopment'.
Se você usar o Gradle, você pode simplesmente adicionar uma dependência para a tarefa 'quarkusDev':
dependencies {
quarkusDev 'io.quarkus:quarkus-vertx-http'
}
Métricas do gRPC Server
Habilitando a coleta de métricas
gRPC server metrics are automatically enabled when the application also uses the quarkus-micrometer extension.
Micrometer collects the metrics of all the gRPC services implemented by the application.
Como exemplo, se você exportar as métricas para o Prometheus, obterá:
# HELP grpc_server_responses_sent_messages_total The total number of responses sent
# TYPE grpc_server_responses_sent_messages_total counter
grpc_server_responses_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_server_processing_duration_seconds The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds summary
grpc_server_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_server_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.016216771
# HELP grpc_server_processing_duration_seconds_max The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds_max gauge
grpc_server_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.007985236
# HELP grpc_server_requests_received_messages_total The total number of requests received
# TYPE grpc_server_requests_received_messages_total counter
grpc_server_requests_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
O nome do serviço, o método e o tipo podem ser encontrados nas tags.