WebClient en Spring 5
¿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”