Solicitações do Spring WebClient com parâmetros

Solicitações do Spring WebClient com parâmetros

1. Visão geral

Muitos frameworks e projetos estão apresentandoreactive programming and asynchronous request handling. Consequentemente,Spring 5 introduziu uma implementaçãoWebClient reativa como parte da estrutura deWebFlux.

Neste tutorial, veremos comoreactively consume REST API endpoints with WebClient.

2. Pontos de extremidade da API REST

Para começar, vamos definir uma amostraREST API comthe following GET endpoints:

  • /products - obter todos os produtos

  • /products/{id} - obter produto por ID

  • /products/{id}/attributes/{attributeId} - obter o atributo do produto por id

  • /products/?name={name}&deliveryDate={deliveryDate}&color={color} - encontrar produtos

  • /products/?tag[]={tag1}&tag[]={tag2} - obter produtos por tags

  • /products/?category={category1}&category={category2} - obter produtos por categorias

Então, acabamos de definir alguns URIs diferentes. Em um momento, descobriremos como construir e enviar cada tipo de URI comWebClient.

Observe que os URIs dos produtos gettings por tags e por categorias contêm matrizes como parâmetros de consulta. No entanto, a sintaxe é diferente. Comothere is no strict definition of how arrays should be represented in URIs. Principalmente, isso depende da implementação do servidor. Assim, cobriremos os dois casos.

3. WebClient Setup

Primeiramente, precisamos criar uma instância deWebClient. Para este artigo, usaremos ummocked object, na medida em que precisamos apenas para verificar se um URI válido é solicitado.

Vamos definir o cliente e os objetos simulados relacionados:

this.exchangeFunction = mock(ExchangeFunction.class);
ClientResponse mockResponse = mock(ClientResponse.class);
when(this.exchangeFunction.exchange(this.argumentCaptor.capture())).thenReturn(Mono.just(mockResponse));
this.webClient = WebClient
  .builder()
  .baseUrl("https://example.com/api")
  .exchangeFunction(exchangeFunction)
  .build();

Além disso, passamos um URL base que será anexado a todas as solicitações feitas pelo cliente.

Por último, para verificar se um determinado URI foi passado para a instânciaExchangeFunction subjacente, vamos usar o seguinte método auxiliar:

private void verifyCalledUrl(String relativeUrl) {
    ClientRequest request = this.argumentCaptor.getValue();
    Assert.assertEquals(String.format("%s%s", BASE_URL, relativeUrl), request.url().toString());
    Mockito.verify(this.exchangeFunction).exchange(request);
    verifyNoMoreInteractions(this.exchangeFunction);
}

A classeWebClientBuilder tem o métodouri() que fornece a instânciaUriBuilder como argumento. Geralmente, uma chamada de API geralmente é feita da seguinte maneira:

this.webClient.get()
  .uri(uriBuilder -> uriBuilder
    //... building a URI
    .build())
  .retrieve();

UsaremosUriBuilder extensivamente neste guia para construir URIs. É importante notar que podemos construir um URI usando qualquer outra maneira e, em seguida, apenas passar o URI gerado como String.

4. Componente de caminho do URI

A path component consists of a sequence of path segments separated by a slash ( / ). Primeiro, vamos começar com um caso simples em que um URI não tem nenhum segmento variável/products:

this.webClient.get()
  .uri("/products")
  .retrieve();
verifyCalledUrl("/products");

Para esse caso, podemos apenas passar aString como um argumento.

A seguir, vamos pegar o endpoint/products/{id} e construir o URI correspondente:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}")
    .build(2))
  .retrieve();
verifyCalledUrl("/products/2");

No código acima, podemos ver que os valores reais do segmento são passados ​​para o métodobuild(). Agora, de maneira semelhante, podemos criar um URI com vários segmentos de caminho para o ponto de extremidade/products/{id}/attributes/{attributeId}:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/{id}/attributes/{attributeId}")
    .build(2, 13))
  .retrieve();
verifyCalledUrl("/products/2/attributes/13");

Um URI pode ter quantos segmentos de caminho forem necessários. Obviamente, se o comprimento final do URI não exceder as limitações. Por último, lembre-se de manter a ordem correta dos valores reais do segmento passados ​​para o métodobuild().

5. Parâmetros de consulta do URI

Normalmente, um parâmetro de consulta é um par de valores-chave simples comotitle=example. Vamos ver como construir esses URIs.

5.1. Parâmetros de valor único

Vamos começar com parâmetros de valor único e tomar o ponto final/products/?name={name}&deliveryDate={deliveryDate}&color={color}. Para definir um parâmetro de consulta, chamamos o métodoqueryParam() da interfaceUriBuilder:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

Aqui, adicionamos três parâmetros de consulta e atribuímos valores reais imediatamente. Além disso, também é possível deixar marcadores em vez de valores exatos:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "{title}")
    .queryParam("color", "{authorId}")
    .queryParam("deliveryDate", "{date}")
    .build("AndroidPhone", "black", "13/04/2019"))
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13%2F04%2F2019");

Especialmente, isso pode ser útil ao passar um objeto construtor ainda mais em uma cadeia. Observe umdifference between the two code snippets above importante.

Com atenção aos URIs esperados, podemos ver que eles eramencoded differently. Particularmente, o caractere de barra( / ) foi escapado no último exemplo. De modo geral,RFC3986 não requer codificação de barras na consulta.

No entanto, alguns aplicativos do lado do servidor podem exigir essa conversão. Portanto, veremos como alterar esse comportamento posteriormente neste guia.

5.2. Parâmetros de matriz

Da mesma forma, podemos precisar passar uma matriz de valores. Ainda assim, não há regras estritas para passar matrizes em uma string de consulta. Portanto,an array representation in a query string differs from project to project and usually depends on underlying frameworks. Abordaremos os formatos mais usados.

Vamos começar com o endpoint/products/?tag[]={tag1}&tag[]={tag2}:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("tag[]", "Snapdragon", "NFC")
    .build())
  .retrieve();
verifyCalledUrl("/products/?tag%5B%5D=Snapdragon&tag%5B%5D=NFC");

Como podemos ver, o URI final contém vários parâmetros de tag seguidos por colchetes codificados. O métodoqueryParam() aceita argumentos variáveis ​​como valores, portanto, não há necessidade de chamar o método várias vezes.

Alternativamente, podemosomit square brackets and just pass multiple query parameters with the same key, mas valores diferentes -/products/?category={category1}&category={category2}:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", "Phones", "Tablets")
    .build())
  .retrieve();
verifyCalledUrl("/products/?category=Phones&category=Tablets");

Para concluir, há um método mais amplamente usado para codificar uma matriz: passar valores separados por vírgula. Vamos transformar nosso exemplo anterior em valores separados por vírgulas:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("category", String.join(",", "Phones", "Tablets"))
    .build())
  .retrieve();
verifyCalledUrl("/products/?category=Phones,Tablets");

Portanto, estamos apenas usando o métodojoin() da classeString para criar uma string separada por vírgulas. Claro, podemos usar qualquer outro delimitador esperado pelo aplicativo.

6. Modo de codificação

Lembre-se de como mencionamos a codificação de URL anteriormente.

Se o comportamento padrão não atender aos nossos requisitos, podemos alterá-lo. Precisamos fornecer uma implementaçãoUriBuilderFactory ao construir uma instânciaWebClient. Nesse caso, usaremos a classeDefaultUriBuilderFactory. Para definir a codificação, chame o métodosetEncodingMode(). Os seguintes modos estão disponíveis:

  • TEMPLATE_AND_VALUES: pré-codifique o modelo de URI e codifique estritamente as variáveis ​​de URI quando expandidas

  • VALUES_ONLY: Não codifique o modelo de URI, mas codifique estritamente as variáveis ​​de URI após expandi-las no modelo

  • URI_COMPONENTS: codifica o valor do componente URI após expandir as variáveis ​​URI

  • NONE: Nenhuma codificação será aplicada

O valor padrão éTEMPLATE_AND_VALUES. Vamos definir o modo paraURI_COMPONENTS:

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(BASE_URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT);
this.webClient = WebClient
  .builder()
  .uriBuilderFactory(factory)
  .baseUrl(BASE_URL)
  .exchangeFunction(exchangeFunction)
  .build();

Como resultado, a seguinte afirmação terá êxito:

this.webClient.get()
  .uri(uriBuilder - > uriBuilder
    .path("/products/")
    .queryParam("name", "AndroidPhone")
    .queryParam("color", "black")
    .queryParam("deliveryDate", "13/04/2019")
    .build())
  .retrieve();
verifyCalledUrl("/products/?name=AndroidPhone&color=black&deliveryDate=13/04/2019");

E, é claro, podemos fornecer uma implementaçãoUriBuilderFactory totalmente personalizada para lidar com a criação de URI manualmente.

7. Conclusão

Neste tutorial, vimos como construir diferentes tipos de URIs usandoWebClient eDefaultUriBuilder.

Ao longo do caminho, cobrimos vários tipos e formatos de parâmetros de consulta. E concluímos a alteração do modo de codificação padrão do construtor de URLs.

Todos os trechos de código do artigo, como sempre, estão disponíveisover on GitHub repository.