Implementing a gRPC service
As implementações de serviço gRPC expostas como beans CDI são automaticamente registradas e servidas pelo quarkus-grpc.
Implementing a gRPC service requires the gRPC classes to be generated.
Place your proto files in src/main/proto and run 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'.
| You’ll need to implement the service interface or extend the base class with your service implementation bean as described in the following sections. |
Implementando um serviço com a API Mutiny
To implement a gRPC service using the Mutiny API, create a class that implements the service interface.
Then, implement the methods defined in the service interface.
If you don’t want to implement a service method just throw a java.lang.UnsupportedOperationException from the method body (the exception will be automatically converted to the appropriate gRPC exception).
Finally, implement the service and add the @GrpcService annotation:
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 | Um bean de implementação de serviço gRPC deve ser anotado com a anotação '@GrpcService' e não deve declarar nenhum outro qualificador CDI. Todos os serviços gRPC têm o escopo 'jakarta.inject.Singleton'. Além disso, o contexto de solicitação está sempre ativo durante uma chamada de serviço. |
| 2 | hello.Greeter é a interface de serviço gerada. |
The service implementation bean can also extend the Mutiny implementation base, where the class name is structured as follows: 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'.
Quarkus exposes both the reflection service v1 and v1alpha.
|
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
Configuration property |
Tipo |
Padrão |
|---|---|---|
The max inbound message size in bytes. Environment variable: Show more |
int |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: Show more |
booleano |
|
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
When you disable quarkus.grpc.server.use-separate-server, you are then using the new Vert.x gRPC server implementation
which uses the existing HTTP server. Which means that the server port is now 8080 (or the port configured with quarkus.http.port).
Also, most of the other configuration properties are no longer applied, since it’s the HTTP server that should already be properly configured.
|
When you enable quarkus.grpc.server.xds.enabled, it’s the xDS that should handle most of the configuration above.
|
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
When SSL/TLS is configured, plain-text is automatically disabled.
|
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
Custom server building
When Quarkus builds a gRPC server instance, users can apply their own Server(Builder) customizers. The customizers are applied by priority, the higher the number the later customizer is applied. The customizers are applied before Quarkus applies user’s server configuration; e.g. ideal for some initial defaults.
There are two customize methods, the first one uses gRPC’s ServerBuilder as a parameter - to be used with Quarkus' legacy gRPC support, where the other uses GrpcServerOptions - to be used with the new Vert.x gRPC support. User should implement the right customize method per gRPC support type usage, or both if the customizer is gRPC type neutral.
public interface ServerBuilderCustomizer<T extends ServerBuilder<T>> {
/**
* Customize a ServerBuilder instance.
*
* @param config server's configuration
* @param builder Server builder instance
*/
default void customize(GrpcServerConfiguration config, T builder) {
}
/**
* Customize a GrpcServerOptions instance.
*
* @param config server's configuration
* @param options GrpcServerOptions instance
*/
default void customize(GrpcServerConfiguration config, GrpcServerOptions options) {
}
/**
* Priority by which the customizers are applied.
* Higher priority is applied later.
*
* @return the priority
*/
default int priority() {
return 0;
}
}
Domain Socket
The gRPC server uses the Quarkus HTTP server, so you can configure it to listen on a Unix domain socket using the standard HTTP configuration properties:
quarkus.http.domain-socket-enabled=true
quarkus.http.domain-socket=/var/run/grpc.sock
When a domain socket is enabled, the gRPC server automatically accepts gRPC requests on it — no gRPC-specific configuration is required.
To listen only on the domain socket (disabling TCP), set:
quarkus.http.host-enabled=false
Unix domain sockets require JDK 16+ and are not available on Windows. Native transport (quarkus.vertx.native-transport) is not 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) {
// ...
}
}
Também é possível anotar um método produtor como um interceptor global:
import io.quarkus.grpc.GlobalInterceptor;
import jakarta.enterprise.inject.Produces;
public class MyProducer {
@GlobalInterceptor
@Produces
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
}
| Check the ServerInterceptor JavaDoc to properly implement your interceptor. |
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 {
// ...
}
Quando você tem vários interceptadores de servidor, você pode encomendá-los implementando a interface 'jakarta.enterprise.inject.spi.Priorizd'. Observe que todos os interceptores globais são invocados antes do serviço específico Interceptadores.
@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 .
There is also a support to inject Vert.x RoutingContext instance into your gRPC service, if / when needed.
Quarkus doesn’t do that by default, you will need to add RoutingContextGrpcInterceptor to your gRPC service.
@GrpcService
@RegisterInterceptor(RoutingContextGrpcInterceptor.class)
public class HelloWorldService extends GreeterGrpc.GreeterImplBase {
@Inject
RoutingContext context;
// ...
}
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-ui 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>
</profile>
</profiles>
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
As métricas do servidor gRPC são ativadas automaticamente quando o aplicativo também usa a extensão 'quarkus-micrometer'. O Micrometer coleta as métricas de todos os serviços gRPC implementados pelo aplicativo.
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.
By default, processing duration is exported as a Prometheus summary (_count, _sum, _max).
To publish aggregatable histogram buckets (for example for histogram_quantile), enable:
quarkus.micrometer.binder.grpc-server.histogram=true
This uses Micrometer’s default percentile histogram buckets (about 1ms to 30s).
Optionally add extra SLO boundaries and/or clamp the published bucket range to reduce cardinality:
quarkus.micrometer.binder.grpc-server.slos=5ms,10ms,25ms,50ms,100ms,1s
quarkus.micrometer.binder.grpc-server.minimum-expected-value=1ms
quarkus.micrometer.binder.grpc-server.maximum-expected-value=10s
Histograms are disabled by default because they increase memory usage and metric cardinality.
Desativar a coleta de métricas
Para desabilitar as métricas do servidor gRPC quando 'quarkus-micrometer' for usado, adicione a seguinte propriedade à configuração do aplicativo:
quarkus.micrometer.binder.grpc-server.enabled=false
Usar threads virtuais
Para usar threads virtuais na implementação do serviço gRPC, verifique o guia dedicado.
gRPC Server authorization
Quarkus includes built-in security to allow authorization using annotations when the Vert.x gRPC support, which uses existing Vert.x HTTP server, is enabled.
Add the Quarkus Security extension
Security capabilities are provided by the Quarkus Security extension, therefore make sure your pom.xml file contains following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
To add the Quarkus Security extension to an existing Maven project, run the following command from your project base directory:
quarkus extension add security
./mvnw quarkus:add-extension -Dextensions='security'
./gradlew addExtension --extensions='security'
Overview of supported authentication mechanisms
Some supported authentication mechanisms are built into Quarkus, while others require you to add an extension. The following table maps specific authentication requirements to a supported mechanism that you can use in Quarkus:
| Authentication requirement | Authentication mechanism |
|---|---|
Username and password |
|
Client certificate |
|
Custom requirements |
|
Bearer access token |
Do not forget to install at least one extension that provides an IdentityProvider based on selected authentication requirements.
Please refer to the Basic authentication guide for example how to provide the IdentityProvider based on username and password.
If you use a separate HTTP server to serve gRPC requests, Custom authentication is your only option.
Set the quarkus.grpc.server.use-separate-server configuration property to false so that you can use other mechanisms.
|
Secure gRPC service
The gRPC services can be secured with the standard security annotations like in the example below:
package org.acme.grpc.auth;
import hello.Greeter;
import io.quarkus.grpc.GrpcService;
import jakarta.annotation.security.RolesAllowed;
@GrpcService
public class HelloService implements Greeter {
@RolesAllowed("admin")
@Override
public Uni<HelloReply> sayHello(HelloRequest request) {
return Uni.createFrom().item(() ->
HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
);
}
}
Most of the examples of the supported mechanisms send authentication headers, please refer to the gRPC Headers section of the Consuming a gRPC Service guide for more information about the gRPC headers.
Autenticação básica
Quarkus Security provides built-in authentication support for the Basic authentication.
quarkus.grpc.server.use-separate-server=false
quarkus.http.auth.basic=true (1)
| 1 | Enable the Basic authentication. |
package org.acme.grpc.auth;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import org.acme.proto.Greeter;
import org.acme.proto.HelloRequest;
import io.grpc.Metadata;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcClientUtils;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@QuarkusTest
public class GreeterServiceTest {
private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
@GrpcClient
Greeter greeterClient;
@Test
void shouldReturnHello() throws ExecutionException, InterruptedException, TimeoutException {
Metadata headers = new Metadata();
// Set the headers - Basic auth for testing
headers.put(AUTHORIZATION, "Basic YWxpY2U6YWxpY2U="); // alice:alice with "admin" role
var client = GrpcClientUtils.attachHeaders(greeterClient, headers);
// Call the client
CompletableFuture<String> message = new CompletableFuture<>();
client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
.subscribe().with(reply -> message.complete(reply.getMessage()));
// Get the values
String theValue = message.get(5, TimeUnit.SECONDS);
// Assert
assertThat(theValue, is("Hello Quarkus"));
}
}
Mutual TLS authentication
Quarkus provides mutual TLS (mTLS) authentication so that you can authenticate users based on their X.509 certificates. The simplest way to enforce authentication for all your gRPC services is described in the TLS com autenticação mútua section of this guide. However, the Quarkus Security supports role mapping that you can use to perform even more fine-grained access control.
quarkus.grpc.server.use-separate-server=false
quarkus.http.insecure-requests=disabled
quarkus.http.ssl.certificate.files=tls/server.pem
quarkus.http.ssl.certificate.key-files=tls/server.key
quarkus.http.ssl.certificate.trust-store-file=tls/ca.jks
quarkus.http.ssl.certificate.trust-store-password=**********
quarkus.http.ssl.client-auth=required
quarkus.http.auth.certificate-role-properties=role-mappings.txt (1)
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.txt
| 1 | Adds certificate role mapping. |
testclient=admin (1)
| 1 | Map the testclient certificate CN (Common Name) to the SecurityIdentity role admin. |
Custom authentication
You can always implement one or more GrpcSecurityMechanism beans if above-mentioned mechanisms provided by Quarkus do no meet your needs.
GrpcSecurityMechanismpackage org.acme.grpc.auth;
import jakarta.inject.Singleton;
import io.grpc.Metadata;
import io.quarkus.security.credential.PasswordCredential;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest;
@Singleton
public class CustomGrpcSecurityMechanism implements GrpcSecurityMechanism {
private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);
@Override
public boolean handles(Metadata metadata) {
String authString = metadata.get(AUTHORIZATION);
return authString != null && authString.startsWith("Custom ");
}
@Override
public AuthenticationRequest createAuthenticationRequest(Metadata metadata) {
final String authString = metadata.get(AUTHORIZATION);
final String userName;
final String password;
// here comes your application logic that transforms 'authString' to user name and password
return new UsernamePasswordAuthenticationRequest(userName, new PasswordCredential(password));
}
}