WebClient en Spring 5

webclient

webclient


¿Qué es WebClient en Spring 5 ?

En esta entrada vamos a hablar sobre una nueva funcionalidad que fue introducida en Spring 5, WebClient.

Este módulo de Spring ha sido creado como una parte del móudlo de Spring Web Reactive, y con el objetivo de reemplazar al RestTemplate, para estos casos. Este nuevo cliente, ha sido diseñado para que sea reactivo, no bloqueante (non-blocking) y para que funcione sobre HTTP/1.1.

En resumen, WebClient es una clase de Spring que te permite realizar solicitudes HTTP de manera reactiva y no bloqueante en aplicaciones basadas en Spring. Es una opción recomendada para nuevas aplicaciones y reemplaza gradualmente a RestTemplate en el ecosistema de Spring.

Dependencias Maven

Para poder importar las librerías necesarias, tenemos que añadir las siguientes dependencias en nuestro pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.projectreactor</groupId>
    <artifactId>reactor-spring</artifactId>
    <version>${reactor-version}</version>
</dependency>

Creando un WebClient

Podemos crear un WebClient mediante la creación de una instancia, pasando una URI al constructor cuando se crea la instancia, o creando la instancia usando la clase DefaultWerbClientBuilder para pasarle diferentes parámetros.

WebClient client = WebClient.create();

WebClient client = WebClient.create("http://localhost:8080");

WebClient client = WebClient.builder()
    .baseUrl("http://localhost:8080")
    .defaultCookie("cookieKey", "cookieValue")
    .defaultHeader(HttpHeaders.CONTENT_TYPE, 
           MediaType.APPLICATION_JSON_VALUE) 
    .defaultUriVariables(Collections.singletonMap("url", 
          "http://localhost:8080")).build();

Configurando un WebClient

Por defecto HTTP ofrece un timeout de 30 segundos pero en muchas ocasiones es necesario aumentarlo por requerimientos o necesidad. Para ello, podemos usar la clase de TCPClient y las ReadTimeoutHandler y WriteTimeOutHandler para establecer el valor que necesitemos:

TcpClient tcpClient = TcpClient
  .create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15000)
  .doOnConnected(connection -> {
      connection.addHandlerLast(new ReadTimeoutHandler(15000, TimeUnit.MILLISECONDS));
      connection.addHandlerLast(new WriteTimeoutHandler(15000, TimeUnit.MILLISECONDS));
  });
 
WebClient client = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
  .build();

Realizando una petición HTTP

Lo primero que vamos a hacer es definir nuestra petición HTTP, con su verbo y su URI.

WebClient.UriSpec<WebClient.RequestBodySpec> post = client.method(HttpMethod.POST);

//Le proporcionamos la uri
WebClient.RequestBodySpec uri = client
  .method(HttpMethod.POST)
  .uri("/data");

También podemos pasar un body en la petición; por ejemplo en una llamada Post nos vendría bien pasarle un objeto como body:

WebClient.RequestHeadersSpec<?> requestSpec2 = WebClient
  .create("http://localhost:8080")
  .post()
  .uri(URI.create("/resource"))
  .body(BodyInserters.fromObject("data"));

La clase utilizada anteriormente, BodyInserters es una interfaz que es la responsable de popular durante la inserción un ReactiveHttpOutputMessage.

Además, como hacemos con otros clientes, podemos añadir cabeceras, If-None-Match”, “If-Modified-Since”, “Accept”, “Accept-Charset”.

WebClient.ResponseSpec response = uri
  .body(inserter3)
    .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .accept(MediaType.APPLICATION_JSON)
    .acceptCharset(Charset.forName("UTF-8"))
    .ifNoneMatch("*")    
  .retrieve();

Recibiendo una Respuesta

La respuesta recibida se puede hacer con clase exchange o retrieve. Particularmente, me gusta más el retrieve que es lo más rápido para obtener un cuerpo directamente. En cambio el método exchange, proporciona una clase ClientResponse, con sus encabezados y su estado. A continuación escribimos un ejemplo de retrieve.

String res = request
  .retrieve()
  .bodyToMono(String.class)
  .block();

Con el bodyToMono lanzaremos WebClientException, si tenemos un error 4XX o 5XX.

Probando tu ClienteWeb con WebTestClient

Con la incorporación en Spring de WebFlux, WebTestClient ha sido la entrada para testear este tipo de endpoints. Tiene un API muy similiar a la de WebClient.

El cliente para la prueba puede estar vinculado a un servidor real o trabajar con controladores o funciones específicos. Para completar las pruebas de integración de punto a punto con solicitudes reales, podemos usar el método bindToServer:

WebTestClient webTestClient = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();

Podemos testear un RouterFunction, pasándole el método bindToRouterFunction:

RouterFunction routerFunction = RouterFunctions.route(
  RequestPredicates.GET("/data"),
  request -> ServerResponse.ok().build()
);
 
WebTestClient
  .bindToRouterFunction(routerFunction)
  .build().get().uri("/data")
  .exchange()
  .expectStatus().isOk()
  .expectBody().isEmpty();

Algo bastante curioso que ha sido introducido es cuando usamos bindToApplicationContext. Ya que coge el ApplicationContext, hace un control sobre los beans y hace una configuración sobre WebFlux, vamos, que utiliza @EnableWebFlux:

@Autowiredprivate ApplicationContext context; 

WebTestClient testClient = WebTestClient.bindToApplicationContext(context).build();

Otra manera de hacerlo y más rápida, podría ser proporcionando una lista de arrays, en lugar de utilizar bindToApplicationContext hacer uso de bindToController e inyectar el controlador

Una vez que hemos creado el WebTestClient, el resto de operaciones serían como las que que se han realizado con el WebClient, a continuación un ejemplo del objeto WebTestClient:

Este enlace, tiene una formidable documentación, en PDF sobre el WebTestClient, muestra todos los campos, propiedades etc…

WebTestClient
  .bindToServer()
     .baseUrl("http://localhost:8080")
     .build()
     .post()
     .uri("/resource")
  .exchange()
    .expectStatus()
    .isCreated()
    .expectHeader()
    .valueEquals("Content-Type", "application/json").expectBody().isEmpty();


1 pensamiento sobre “WebClient en Spring 5

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *