Using the REST Client Reactive
Este guia explica como usar o Cliente REST Reativo para interagir com as APIs REST. O Cliente REST Reativo é a implementação do Cliente REST compatível com o RESTEasy Reativo.
Se o seu aplicativo usa um cliente e expõe endpoints REST, use o RESTEasy Reativo para a parte do servidor.
Pré-requisitos
Para concluir este guia, você precisa:
-
Cerca de 15 minutos
-
Um IDE
-
JDK 11+ instalado com 'JAVA_HOME' configurado adequadamente
-
Apache Maven 3.8.6
-
Opcionalmente, o Quarkus CLI se você quiser usá-lo
-
Opcionalmente, Mandrel ou GraalVM instalado e configurado apropriadamente se você quiser criar um executável nativo (ou Docker se você usar uma compilação de contêiner nativo)
Solução
Recomendamos que siga as instruções nas seções seguintes e crie a aplicação passo a passo. No entanto, você pode ir diretamente para o exemplo completo.
Clone o repositório Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git, ou baixe um arquivo.
The solution is located in the rest-client-reactive-quickstart directory.
Criar o projeto Maven
Primeiro, precisamos de um novo projeto. Crie um novo projeto com o seguinte comando:
Este comando gera o projeto Maven com um endpoint REST e com importações:
-
a extensão
resteasy-reactive-jacksonpara o suporte do servidor REST. Useresteasy-reactiveem vez disso se não pretender utilizar Jackson; -
a extensão
rest-client-reactive-jacksonpara o suporte do cliente REST. Userest-client-reactiveem vez disso se não pretender utilizar Jackson
Se já tiver o projeto Quarkus configurado, você pode adicionar a extensão rest-client-reactive-jackson ao projeto executando o seguinte comando no diretório base do projeto:
quarkus extension add 'rest-client-reactive-jackson'
./mvnw quarkus:add-extension -Dextensions='rest-client-reactive-jackson'
./gradlew addExtension --extensions='rest-client-reactive-jackson'
Isto irá adicionar o seguinte trecho no seu arquivo de build:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-client-reactive-jackson")
Configurando o modelo
Neste guia, demonstraremos como consumir parte da API REST fornecida pelo serviço stage.code.quarkus.io . Nosso primeiro passo é configurar o modelo que usaremos, na forma de um POJO Extension .
Crie um arquivo src/main/java/org/acme/rest/client/Extension.java e defina o seguinte conteúdo:
package org.acme.rest.client;
import java.util.List;
public class Extension {
public String id;
public String name;
public String shortName;
public List<String> keywords;
}
O modelo acima é apenas um subconjunto dos campos fornecidos pelo serviço, mas é suficiente para os objetivos deste guia.
Crie a interface
Using the REST Client Reactive is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations. In our case the interface should be created at src/main/java/org/acme/rest/client/ExtensionsService.java and have the following content:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
}
O método getById dá ao nosso código a capacidade de obter uma extensão por id a partir da API do Code Quarkus. O cliente tratará de toda a ligação em rede e da organização, deixando o nosso código livre desses pormenores técnicos.
O objetivo das anotações no código acima é o seguinte:
-
@RegisterRestClientpermite que o Quarkus saiba que essa interface deve estar disponível para injeção de CDI como um Cliente REST -
@Path,@GETand@QueryParamare the standard JAX-RS annotations used to define how to access the service
|
Quando a extensão If you don’t rely on the JSON default, it is heavily recommended to annotate your endpoints with the |
|
The |
Parâmetros de Consulta
A maneira mais fácil de especificar um parâmetro de consulta é anotar um parâmetro de método do cliente com @QueryParam ou @RestQuery . O @RestQuery é equivalente ao @QueryParam , mas com nome opcional. Além disso, ele também pode ser usado para passar parâmetros de consulta como Map , o que é conveniente se os parâmetros não forem conhecidos antecipadamente.
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestQuery;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") Integer id);
@GET
Set<Extension> getByName(@RestQuery String name); (1)
@GET
Set<Extension> getByFilter(@RestQuery Map<String, String> filter); (2)
@GET
Set<Extension> getByFilters(@RestQuery MultivaluedMap<String, String> filters); (3)
}
| 1 | A consulta da requisição incluirá um parâmetro com a chave name |
| 2 | Cada entrada Map representa exatamente um parâmetro de consulta |
| 3 | MultivaluedMap permite enviar valores de vetor |
Usando @ClientQueryParam
Outra forma de adicionar parâmetros de consulta a uma requisição é usar @io.quarkus.rest.client.reactive.ClientQueryParam na interface do cliente REST ou em um método específico da interface. A anotação pode especificar o nome do parâmetro de consulta, enquanto o valor pode ser uma constante, uma propriedade de configuração ou pode ser determinado pela chamada de um método.
O exemplo a seguir mostra as várias utilizações possíveis:
@ClientQueryParam(name = "my-param", value = "${my.property-value}") (1)
public interface Client {
@GET
String getWithParam();
@GET
@ClientQueryParam(name = "some-other-param", value = "other") (2)
String getWithOtherParam();
@GET
@ClientQueryParam(name = "param-from-method", value = "{with-param}") (3)
String getFromMethod();
default String withParam(String name) {
if ("param-from-method".equals(name)) {
return "test";
}
throw new IllegalArgumentException();
}
}
| 1 | Ao colocar @ClientQueryParam na interface, garantimos que my-param será adicionado a todas as requisições do cliente. Como usamos a sintaxe ${…}, o valor real do parâmetro será obtido usando a propriedade de configuração my.property-value . |
| 2 | Quando getWithOtherParam é chamado, além do parâmetro de consulta my-param, some-other-param com o valor de other também será adicionado. |
| 3 | quando getFromMethod é chamado, além do parâmetro de consulta my-param, param-from-method com o valor de test (porque é isso que o método withParam devolve quando invocado com param-from-method) também será adicionado. |
|
Observe que, se um método de interface contiver um argumento anotado com |
More information about this annotation can be found on the javadoc of
Parâmetros do Caminho
Se a requisição GET exigir parâmetros de caminho, você poderá utilizar a anotação @PathParam("parameter-name") em vez de (ou além de) @QueryParam . Os parâmetros de caminho e de consulta podem ser combinados, conforme necessário, como ilustrado no exemplo abaixo.
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
@Path("/stream/{stream}")
Set<Extension> getByStream(@PathParam("stream") String stream, @QueryParam("id") String id);
}
Crie a configuração
Para determinar o URL de base para o qual as chamadas REST serão feitas, o Cliente REST usa a configuração de application.properties. O nome da propriedade precisa seguir uma determinada convenção que é melhor exibida no código a seguir:
# Your configuration properties
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api # (1)
| 1 | Essa configuração significa que todas as requisições realizadas usando org.acme.rest.client.ExtensionsService usarão o https://stage.code.quarkus.io/api como URL base. Usando a configuração acima, chamar o método getById de ExtensionsService com um valor de io.quarkus:quarkus-rest-client-reactive resultaria em uma requisição HTTP GET feita para https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client-reactive. |
Note que org.acme.rest.client.ExtensionsService deve corresponder ao nome totalmente qualificado da interface ExtensionsService que criamos na seção anterior.
Para facilitar a configuração, você pode utilizar a propriedade configKey de @RegisterRestClient que permite utilizar uma raiz de configuração diferente do nome totalmente qualificado da sua interface.
@RegisterRestClient(configKey="extensions-api")
public interface ExtensionsService {
[...]
}
# Your configuration properties
quarkus.rest-client.extensions-api.url=https://stage.code.quarkus.io/api
quarkus.rest-client.extensions-api.scope=javax.inject.Singleton
Desabilitando a Verificação do Nome do Host
Para desabilitar a verificação do nome do host SSL para um cliente REST específico, adicione a seguinte propriedade à sua configuração:
quarkus.rest-client.extensions-api.verify-host=false
|
Esta definição não deve ser utilizada em produção, uma vez que irá desativar a verificação do nome do host SSL. |
Create the JAX-RS resource
Crie o arquivo src/main/java/org/acme/rest/client/ExtensionsResource.java com o seguinte conteúdo:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
@RestClient (1)
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(String id) {
return extensionsService.getById(id);
}
}
Há duas partes interessantes nesta listagem:
| 1 | o stub do cliente é injetado com a anotação @RestClient em vez da habitual anotação CDI @Inject |
Programmatic client creation with RestClientBuilder
Instead of annotating the client with @RegisterRestClient, and injecting
a client with @RestClient, you can also create REST Client programmatically.
You do that with RestClientBuilder.
Com esta abordagem, a interface do cliente poderia ter o seguinte aspecto:
package org.acme.rest.client;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
@Path("/extensions")
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
}
E o serviço é o seguinte:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.net.URI;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
private final ExtensionsService extensionsService;
public ExtensionsResource() {
extensionsService = RestClientBuilder.newBuilder()
.baseUri(URI.create("https://stage.code.quarkus.io/api"))
.build(ExtensionsService.class);
}
@GET
@Path("/id/{id}")
public Set<Extension> id(String id) {
return extensionsService.getById(id);
}
}
Usar Opções HTTP Personalizadas
O Cliente REST Reativo utiliza internamente o Cliente HTTP Vert.x para efetuar as conexões de rede. As extensões do Cliente REST Reativo permitem configurar algumas definições através de propriedades, por exemplo:
-
quarkus.rest-client.client-prefix.connect-timeoutpara configurar o tempo limite de conexão em milissegundos. -
quarkus.rest-client.client-prefix.max-redirectspara limitar o número de redirecionamentos.
No entanto, existem muitas outras opções no Cliente HTTP Vert.x para configurar as conexões. Veja todas as opções na API de Opções do Cliente HTTP Vert.x neste link.
Para personalizar totalmente a instância do Cliente HTTP Vert.x que o Cliente REST Reativo está usando internamente, você pode fornecer a sua instância personalizada de Opções de Cliente HTTP através de CDI ou ao criar programaticamente o seu cliente.
Vejamos um exemplo de como fornecer as Opções de Cliente HTTP através de CDI:
package org.acme.rest.client;
import javax.enterprise.inject.Produces;
import javax.ws.rs.ext.ContextResolver;
import io.vertx.core.http.HttpClientOptions;
import io.quarkus.arc.Unremovable;
@Provider
public class CustomHttpClientOptions implements ContextResolver<HttpClientOptions> {
@Override
public HttpClientOptions getContext(Class<?> aClass) {
HttpClientOptions options = new HttpClientOptions();
// ...
return options;
}
}
Agora, todos os Clientes REST usarão suas Opções de Cliente HTTP personalizadas.
Outra abordagem é fornecer as opções do Cliente HTTP personalizadas ao criar o cliente programaticamente:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.net.URI;
import java.util.Set;
import io.vertx.core.http.HttpClientOptions;
@Path("/extension")
public class ExtensionsResource {
private final ExtensionsService extensionsService;
public ExtensionsResource() {
extensionsService = RestClientBuilder.newBuilder()
.baseUri(URI.create("https://stage.code.quarkus.io/api"))
.register(CustomHttpClientOptions.class) (1)
.build(ExtensionsService.class);
}
// ...
}
public class CustomHttpClientOptions implements ContextResolver<HttpClientOptions> {
@Override
public HttpClientOptions getContext(Class<?> aClass) {
HttpClientOptions options = new HttpClientOptions();
// ...
return options;
}
}
| 1 | o cliente utilizará as opções de Cliente HTTP registradas em vez das opções de Cliente HTTP fornecidas via CDI, se existirem. |
Atualize o teste
Em seguida, precisamos atualizar o teste funcional para refletir as alterações feitas no endpoint. Edite o arquivo src/test/java/org/acme/rest/client/ExtensionsResourceTest.java e altere o conteúdo do teste para:
package org.acme.rest.client;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;
@QuarkusTest
public class ExtensionsResourceTest {
@Test
public void testExtensionsIdEndpoint() {
given()
.when().get("/extension/id/io.quarkus:quarkus-rest-client-reactive")
.then()
.statusCode(200)
.body("$.size()", is(1),
"[0].id", is("io.quarkus:quarkus-rest-client-reactive"),
"[0].name", is("REST Client Reactive"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
}
O código acima utiliza as capacidades json-path do REST Assured.
Suporte Assíncrono
Para obter todo o poder da natureza reativa do cliente, você pode usar a variante não blocante da extensão Cliente REST Reativo, que vem com suporte para CompletionStage e Uni . Vamos ver isso em ação adicionando um método getByIdAsync em nossa interface REST ExtensionsService . O código deve ter a seguinte aparência:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
import java.util.concurrent.CompletionStage;
@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}
Abra o arquivo src/main/java/org/acme/rest/client/ExtensionsResource.java e atualize-o com o seguinte conteúdo:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;
import java.util.concurrent.CompletionStage;
@Path("/extension")
public class ExtensionsResource {
@RestClient
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(String id) {
return extensionsService.getById(id);
}
@GET
@Path("/id-async/{id}")
public CompletionStage<Set<Extension>> idAsync(String id) {
return extensionsService.getByIdAsync(id);
}
}
Observe que, como a invocação agora é não blocante, o método idAsync será invocado no loop de eventos, ou seja, não será transferido para um thread de pool de trabalho, reduzindo assim a utilização de recursos de hardware. Para obter mais detalhes, consulte o modelo de execução reativa Resteasy .
Para testar métodos assíncronos, adicione o método de teste abaixo em ExtensionsResourceTest:
@Test
public void testExtensionIdAsyncEndpoint() {
given()
.when().get("/extension/id-async/io.quarkus:quarkus-rest-client-reactive")
.then()
.statusCode(200)
.body("$.size()", is(1),
"[0].id", is("io.quarkus:quarkus-rest-client-reactive"),
"[0].name", is("REST Client Reactive"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
A versão Uni é muito semelhante:
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {
// ...
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam("id") String id);
}
O ExtensionsResource torna-se:
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
@RestClient
ExtensionsService extensionsService;
// ...
@GET
@Path("/id-uni/{id}")
public Uni<Set<Extension>> idUni(String id) {
return extensionsService.getByIdAsUni(id);
}
}
|
Mutiny
O código anterior usa tipos reativos do Mutiny. Se você não estiver familiarizado com o Mutiny, consulte Mutiny - uma biblioteca de programação reativa intuitiva . |
Ao retornar um Uni , cada assinatura invoca o serviço remoto. Isso significa que você pode reenviar a requisição assinando novamente o Uni , ou usar um retry da seguinte forma:
@RestClient ExtensionsService extensionsService;
// ...
extensionsService.getByIdAsUni(id)
.onFailure().retry().atMost(10);
Se você usar um CompletionStage , precisará chamar o método do serviço para tentar novamente. Essa diferença vem do aspecto preguiçoso do Mutiny e de seu protocolo de assinatura. Mais detalhes sobre isso podem ser encontrados na documentação do Mutiny .
Suporte para cabeçalhos personalizados
Existem algumas formas de especificar cabeçalhos personalizados para as suas chamadas REST:
-
registrando um
ClientHeadersFactoryou umReactiveClientHeadersFactorycom a anotação@RegisterClientHeaders -
especificando o valor do cabeçalho com
@ClientHeaderParam -
especificando o valor do cabeçalho por
@HeaderParam
O código abaixo demonstra como utilizar cada uma destas técnicas:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders(RequestUUIDHeaderFactory.class) (1)
@ClientHeaderParam(name = "my-header", value = "constant-header-value") (2)
@ClientHeaderParam(name = "computed-header", value = "{org.acme.rest.client.Util.computeHeader}") (3)
public interface ExtensionsService {
@GET
@ClientHeaderParam(name = "header-from-properties", value = "${header.value}") (4)
Set<Extension> getById(@QueryParam("id") String id, @HeaderParam("jaxrs-style-header") String headerValue); (5)
}
| 1 | Só pode haver um ClientHeadersFactory por classe. Com ela, é possível não só adicionar cabeçalhos personalizados, mas também transformar os existentes. Veja a classe RequestUUIDHeaderFactory abaixo para um exemplo da fábrica. |
| 2 | @ClientHeaderParam pode ser utilizado na interface do cliente e nos métodos. Pode especificar um valor de cabeçalho constante… |
| 3 | … and a name of a method that should compute the value of the header. It can either be a static method or a default method in this interface |
| 4 | … bem como um valor da configuração da sua aplicação |
| 5 | … or as a normal JAX-RS @HeaderParam annotated argument |
|
Ao usar o Kotlin, se os métodos padrão forem aproveitados, o compilador do Kotlin precisará ser configurado para usar os recursos de interface padrão do Java. Veja isso para obter mais detalhes. |
Um ClientHeadersFactory pode ter o seguinte aspecto:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.UUID;
@ApplicationScoped
public class RequestUUIDHeaderFactory implements ClientHeadersFactory {
@Override
public MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders) {
MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
result.add("X-request-uuid", UUID.randomUUID().toString());
return result;
}
}
Como você vê no exemplo acima, é possível tornar a implementação do ClientHeadersFactory um bean CDI anotando-o com uma anotação de definição de escopo, como @Singleton , @ApplicationScoped , etc.
Para especificar um valor para ${header.value}, basta colocar o seguinte no seu application.properties:
header.value=value of the header
Além disso, existe uma variante reativa de ClientHeadersFactory que permite realizar operações blocantes. Por exemplo:
package org.acme.rest.client;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.UUID;
@ApplicationScoped
public class GetTokenReactiveClientHeadersFactory extends ReactiveClientHeadersFactory {
@Inject
Service service;
@Override
public Uni<MultivaluedMap<String, String>> getHeaders(
MultivaluedMap<String, String> incomingHeaders,
MultivaluedMap<String, String> clientOutgoingHeaders) {
return Uni.createFrom().item(() -> {
MultivaluedHashMap<String, String> newHeaders = new MultivaluedHashMap<>();
// perform blocking call
newHeaders.add(HEADER_NAME, service.getToken());
return newHeaders;
});
}
}
Fábrica de cabeçalhos padrão
A anotação @RegisterClientHeaders também pode ser usada sem nenhuma fábrica personalizada especificada. Nesse caso, será usada a fábrica DefaultClientHeadersFactoryImpl . Se você fizer uma chamada de cliente REST a partir de um recurso REST, essa fábrica propagará todos os cabeçalhos listados na propriedade de configuração org.eclipse.microprofile.rest.client.propagateHeaders da requisição do recurso para a requisição do cliente. Os nomes dos cabeçalhos individuais são separados por vírgulas.
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization
Tratamento de exceções
A especificação do Cliente REST MicroProfile introduz o org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper, cujo objetivo é converter uma resposta HTTP numa exceção.
Um exemplo simples de implementação de tal ResponseExceptionMapper para o ExtensionsService discutido acima, poderia ser:
public class MyResponseExceptionMapper implements ResponseExceptionMapper<RuntimeException> {
@Override
public RuntimeException toThrowable(Response response) {
if (response.getStatus() == 500) {
throw new RuntimeException("The remote service responded with HTTP 500");
}
return null;
}
}
ResponseExceptionMapper também define o método getPriority , que é usado para determinar a prioridade com que as implementações de ResponseExceptionMapper serão chamadas (as implementações com um valor menor para getPriority serão chamadas primeiro). Se toThrowable retornar uma exceção, essa exceção será lançada. Se null for retornado, a próxima implementação de ResponseExceptionMapper na cadeia será chamada (se houver alguma).
A classe, conforme escrito acima, não seria usada automaticamente por nenhum Cliente REST. Para torná-la disponível para todos os Clientes REST da aplicação, a classe precisa ser anotada com @Provider (desde que quarkus.rest-client-reactive.provider-autodiscovery não esteja definido como false ). Como alternativa, se a classe de tratamento de exceções se aplicar apenas a interfaces específicas do Cliente REST, você poderá anotar as interfaces com @RegisterProvider(MyResponseExceptionMapper.class) ou registrá-la usando a configuração com a propriedade providers do grupo de configuração quarkus.rest-client adequado.
Usando @ClientExceptionMapper
Uma forma mais simples de converter códigos de resposta HTTP de 400 pra cima é utilizar a anotação @ClientExceptionMapper.
Para a interface de Cliente REST ExtensionsService definida acima, um exemplo de utilização de @ClientExceptionMapper seria:
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam("id") String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
@ClientExceptionMapper
static RuntimeException toException(Response response) {
if (response.getStatus() == 500) {
return new RuntimeException("The remote service responded with HTTP 500");
}
return null;
}
}
Naturalmente, este tratamento é efetuado por Cliente REST. @ClientExceptionMapper utiliza a prioridade predefinida se o atributo priority não estiver definido e aplicam-se as regras normais de invocação de todos os manipuladores sucessivamente.
Os métodos anotados com @ClientExceptionMapper também podem receber um parâmetro java.lang.reflect.Method, o que é útil se o código de mapeamento de exceções precisar conhecer o método do Cliente REST que foi invocado e causou a ativação do código de mapeamento de exceções.
|
Suporte a Formulários Multi-partes
O Cliente REST Reativo suporta mensagens com multi-parte.
Enviando Mensagens Multi-partes
O Cliente REST Reativo permite o envio de dados como formulários multi-partes. Dessa forma, você pode, por exemplo, enviar arquivos de forma eficiente.
Para enviar dados como um formulário multi-partes, você pode simplesmente utilizar as anotações normais @RestForm (ou @FormParam):
@POST
@Path("/binary")
String sendMultipart(@RestForm File file, @RestForm String otherField);
Os parâmetros especificados como File , Path , byte[] ou Buffer são enviados como arquivos e têm como padrão o tipo MIME application/octet-stream . Outros tipos de parâmetros @RestForm têm como padrão o tipo MIME text/plain . Você pode substituir esses padrões com a anotação @PartType .
Naturalmente, também é possível agrupar estes parâmetros numa classe que os contenha:
public static class Parameters {
@RestForm
File file;
@RestForm
String otherField;
}
@POST
@Path("/binary")
String sendMultipart(Parameters parameters);
Qualquer parâmetro @RestForm do tipo File , Path , byte[] ou Buffer , bem como qualquer parâmetro anotado com @PartType implica automaticamente em um @Consumes(MediaType.MULTIPART_FORM_DATA) no método se não houver um @Consumes presente.
| Se houver parâmetros @RestForm que não implicam em multi-parte, então @Consumes(MediaType.APPLICATION_FORM_URLENCODED) estará implícito. |
Há alguns modos pelos quais os dados do formulário podem ser codificados. Por padrão, o Cliente REST Reativo usa RFC1738. Você pode substituí-la especificando o modo no nível do cliente, definindo a propriedade io.quarkus.rest.client.multipart-post-encoder-mode RESTBuilder como o valor selecionado de HttpPostRequestEncoder.EncoderMode ou especificando quarkus.rest-client.multipart-post-encoder-mode em seu application.properties . Observe que a última opção funciona somente para clientes criados com a anotação @RegisterRESTClient . Todos os modos disponíveis estão descritos na documentação do Netty
Você também pode enviar multi-partes JSON especificando a anotação @PartType:
public static class Person {
public String firstName;
public String lastName;
}
@POST
@Path("/json")
String sendMultipart(@RestForm @PartType(MediaType.APPLICATION_JSON) Person person);
Recebendo Mensagens Multi-partes
O Cliente REST Reativo também oferece suporte ao recebimento de mensagens multi-partes. Assim como no envio, para analisar uma resposta multi-parte, você precisa criar uma classe que descreva os dados da resposta, por exemplo
public class FormDto {
@RestForm (1)
@PartType(MediaType.APPLICATION_OCTET_STREAM)
public File file;
@FormParam("otherField") (2)
@PartType(MediaType.TEXT_PLAIN)
public String textProperty;
}
| 1 | utiliza a anotação abreviada @RestForm para criar um campo como parte de um formulário multi-partes |
| 2 | a norma @FormParam também pode ser utilizada. Permite substituir o nome da parte multi-partes. |
Em seguida, crie um método de interface que corresponda à chamada e faça-o devolver o FormDto:
@GET
@Produces(MediaType.MULTIPART_FORM_DATA)
@Path("/get-file")
FormDto data receiveMultipart();
Atualmente, o suporte de respostas multi-partes está sujeito às seguintes limitações:
-
os arquivos enviados em respostas multi-partes só podem ser analisados em
File,PatheFileDownload -
cada campo do tipo de resposta tem de ser anotado com
@PartType- os campos sem esta anotação são ignorados
O Cliente REST Reativo precisa de conhecer antecipadamente as classes utilizadas como tipos de retorno multi-partes. Se tiver um método de interface que produz multipart/form-data, o tipo de retorno será descoberto automaticamente. No entanto, se pretender utilizar a API ClientBuilder para analisar uma resposta como multi-parte, tem de anotar a sua classe DTO com @MultipartForm.
| Os arquivos que você baixa não são removidos automaticamente e podem ocupar muito espaço no disco. Considere a remoção dos arquivos quando terminar de trabalhar com eles. |
Suporte de proxy
O Cliente REST Reativo suporta o envio de requisições por meio de um proxy. Ele respeita as configurações da JVM para isso, mas também permite especificar ambos:
-
definições globais de proxy de cliente, com
quarkus.rest-client.proxy-address,quarkus.rest-client.proxy-user,quarkus.rest-client.proxy-password,quarkus.rest-client.non-proxy-hosts -
definições de proxy por cliente, com
quarkus.rest-client.<my-client>.proxy-address, etc. Estas são aplicadas apenas a clientes injetados com CDI, ou seja, os criados com@RegisterRestClient
Se proxy-address estiver definido no nível do cliente, o cliente utiliza as suas definições de proxy específicas. Nenhuma definição de proxy é propagada a partir da configuração global ou das propriedades da JVM.
Se proxy-address não estiver definido para o cliente, mas estiver definido no nível global, o cliente usará as configurações globais. Caso contrário, o cliente usará as configurações da JVM.
Um exemplo de configuração para definir o proxy:
# global proxy configuration is used for all clients
quarkus.rest-client.proxy-address=localhost:8182
quarkus.rest-client.proxy-user=<proxy user name>
quarkus.rest-client.proxy-password=<proxy password>
quarkus.rest-client.non-proxy-hosts=example.com
# per-client configuration overrides the global settings for a specific client
quarkus.rest-client.my-client.proxy-address=localhost:8183
quarkus.rest-client.my-client.proxy-user=<proxy user name>
quarkus.rest-client.my-client.proxy-password=<proxy password>
quarkus.rest-client.my-client.url=...
A especificação do Cliente REST MicroProfile não permite a definição de credenciais de proxy. Para especificar o usuário e a senha do proxy de forma programática, é necessário enviar o seu RestClientBuilder para RestClientBuilderImpl.
|
Empacote e execute a aplicação
Execute a aplicação com:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
Abra o seu browser em http://localhost:8080/extension/id/io.quarkus:quarkus-rest-client-reactive.
Você deve ver um objeto JSON que contém algumas informações básicas sobre esta extensão.
Como de costume, a aplicação pode ser empacotada utilizando:
quarkus build
./mvnw install
./gradlew build
E executado com java -jar target/quarkus-app/quarkus-run.jar.
Também é possível gerar o executável nativo com:
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.package.type=native
Registrando tráfego
O Cliente REST Reativo pode registrar as requisições que envia e as respostas que recebe. Para ativar o registro, adicione a propriedade quarkus.rest-client.logging.scope ao seu application.properties e defina-a como:
-
request-responsepara registrar o conteúdo da requisição e da resposta, ou -
allpara permitir também o registro de baixo nível das bibliotecas subjacentes.
Como as mensagens HTTP podem ter corpos grandes, limitamos a quantidade de caracteres do corpo registrado. O limite padrão é 100, pode alterá-lo especificando quarkus.rest-client.logging.body-limit.
| O Cliente REST Reativo está registrando o tráfego com o nível DEBUG e não altera as propriedades do registrador. Poderá ser necessário ajustar a configuração do registrador para utilizar esta funcionalidade. |
Um exemplo de configuração de registro:
quarkus.rest-client.logging.scope=request-response
quarkus.rest-client.logging.body-limit=50
quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG
Simulação do cliente para testes
Se você usar um cliente injetado com a anotação @RestClient, poderá facilmente simulá-lo para testes. Você pode fazer isso com @InjectMock do Mockito ou com QuarkusMock .
Esta seção mostra como substituir o seu cliente por uma simulação. Se pretender obter uma compreensão mais aprofundada de como funciona a simulação no Quarkus, consulte a publicação do blog sobre a simulação de beans CDI.
Mocking does not work when using @NativeImageTest or @QuarkusIntegrationTest.
|
Vamos supor que você tem o seguinte cliente:
package io.quarkus.it.rest.client.main;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/")
@RegisterRestClient
public interface Client {
@GET
String get();
}
Simulando com InjectMock
A abordagem mais simples para simular um cliente para testes é usar Mockito e @InjectMock.
Primeiro, adicione a seguinte dependência à sua aplicação:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-junit5-mockito")
Depois, no seu teste, pode simplesmente utilizar @InjectMock para criar e injetar uma simulação:
package io.quarkus.it.rest.client.main;
import static org.mockito.Mockito.when;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
@QuarkusTest
public class InjectMockTest {
@InjectMock
@RestClient
Client mock;
@BeforeEach
public void setUp() {
when(mock.get()).thenReturn("MockAnswer");
}
@Test
void doTest() {
// ...
}
}
Simulando com o QuarkusMock
Se o Mockito não satisfizer as suas necessidades, você pode criar uma simulação programaticamente utilizando QuarkusMock, e.g.:
package io.quarkus.it.rest.client.main;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusMock;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class QuarkusMockTest {
@BeforeEach
public void setUp() {
Client customMock = new Client() { (1)
@Override
public String get() {
return "MockAnswer";
}
};
QuarkusMock.installMockForType(customMock, Client.class, RestClient.LITERAL); (2)
}
@Test
void doTest() {
// ...
}
}
| 1 | aqui utilizamos uma implementação criada manualmente da interface do cliente para substituir a interface do cliente atual |
| 2 | note-se que RestClient.LITERAL tem de ser passado como último argumento do método installMockForType |
Usando um Servidor HTTP Simulado para testes
Em alguns casos, você pode querer simular o endpoint remoto - o servidor HTTP - em vez de simular o próprio cliente. Isso pode ser especialmente útil para testes nativos ou para clientes criados programaticamente.
Você pode facilmente simular um servidor HTTP com o Wiremock. A seção Wiremock do Quarkus - Usando o Cliente REST descreve detalhadamente como configurá-lo.
Limitações conhecidas
Embora a extensão Cliente REST Reativo pretenda ser uma substituição imediata da extensão Cliente REST, existem algumas diferenças e limitações:
-
o escopo padrão do cliente para a nova extensão é
@ApplicationScoped, enquanto oquarkus-rest-clienttem como padrão@Dependent. Para alterar esse comportamento, defina a propriedadequarkus.rest-client-reactive.scopecomo o nome do escopo totalmente qualificado. -
não é possível definir
HostnameVerifierouSSLContext -
algumas coisas que não fazem sentido para implementações não blocantes, como a definição do
ExecutorService, não funcionam