Previamente utilizábamos RestTemplate para hacer un cliente REST en Spring Boot, pero ésta se va a quedar obsoleta, por lo que a partir de ahora tendremos que usar WebClient.
Para utilizar WebClient la versión del parent de nuestra aplicación (está en el pom.xml) debe ser igual o superior a 2.6.0, si no pueden surgir conflictos con otras librerías. |
Tendremos que añadir la siguiente dependencia a nuestro pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency> |
Para crear un WebClient, tendremos que crearlo como un bean en un archivo de configuración. Nos crearemos una clase para ello, WebClientConfig, donde crearemos cada webClient que necesitemos de esta manera, con la url correspondiente:
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder webClientBuilder) {
return webClientBuilder
.baseUrl("https://apidesa.um.es/acade/expedienteacademico-api/private/v1.0").build();
}
} |
Si tenemos un solo WebClient, simplemente lo usaremos con @Autowired, si tenemos varios, además de @Autowired tendremos que añadir @Qualifier("nombre del bean").
Si queremos añadir headers por defecto, como por ejemplo que el tipo del body es JSON, y que acepta también una respuesta en JSON, lo haremos así:
return webClientBuilder
.baseUrl("https://apidesa.um.es/acade/expedienteacademico-api/private/v1.0")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build(); |
Una vez creado el WebClient, podremos hacer peticiones indicando el método REST, la uri a la que se llama, los headers que queramos añadirle (si queremos añadirle alguno), el body que le pasamos si es necesario (con Mono.just(..) para pasarle el objeto que sea, y su clase), y obtener la respuesta:
String response = webClient.post()
.uri("/recurso")
.header("nombreHeader", "Valor")
.body(Mono.just(Objeto), TipoObjeto.class)
.retrieve()
.bodyToMono(String.class)
.block(); |
También podemos indicar un timeout para nuestra llamada:
String response = webClient.post()
.uri("/recurso")
.header("nombreHeader", "Valor")
.body(Mono.just(Objeto), TipoObjeto.class)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(3))
.block(); |
¡Ojo! Por defecto, las llamadas con WebClient son asíncronas, no bloqueantes. La instrucción block() del final sirve para hacerlas bloqueantes, para esperar a la respuesta antes de seguir.
Para obtener las respuestas, disponemos de dos clases Mono y Flux, vamos a ver para qué usamos cada una.
Como hemos visto en el ejemplo anterior, con .bodyToMono indicamos que se recibe un elemento, y la respuesta se mapeará a la clase que pongamos. De este modo, se podrá mapear directamente a un DTO propio si la estructura de la respuesta coincide con éste.
En el caso de que queramos obtener la respuesta como un Map con las claves y valores que hemos recibido, haremos uso de ParameterizedTypeReference, así:
.bodyToMono( new ParameterizedTypeReference<Map<String, Object>>() {} ).block(); |
En el caso de que vayamos a recibir una lista de elementos, podemos usar Flux. La forma de usarlo es muy parecida a Mono, pero utilizando .bodyToFlux en lugar de .bodyToMono, e indicando .collectList después para obtener la lista. Por ejemplo, para recibir una lista de Strings sería:
.bodyToFlux(String.class).collectList().block(); |
Y para recibir una lista de Maps, al igual que hemos visto en Mono:
.bodyToFlux( new ParameterizedTypeReference<Map<String, Object>>() {} ).collectList().block(); |
Para enviar un JSON como body de una llamada POST, lo haremos como hemos visto, pasándole un Map:
Map<String, String> bodyJson = new HashMap<>();
bodyJson.put("unaPropiedad", "Hola");
bodyJson.put("otraPropiedad", "Mundo");
String response = webClientSello.post().uri("/noseque")
.body(Mono.just(bodyForm), Map.class).retrieve()
.bodyToMono(String.class).block(); |
Para este caso, hay que meter el body utilizando BodyInserters.fromFormData, pasándole un MultiValueMap. Por ejemplo:
MultiValueMap<String, String> bodyForm = new LinkedMultiValueMap<>();
bodyForm.add("login", "correo@prueba.com");
bodyForm.add("clave", "AAERRasdfasdf1asd2asdfasdf:34adsad");
bodyForm.add("nomape", "MESSI");
bodyForm.add("tipo", "externa");
bodyForm.add("validar", "no");
String response = webClient.post()
.uri(metodo).body(BodyInserters.fromFormData(bodyForm))
.retrieve()
.bodyToMono(String.class)
.block(); |
Para gestionar llamadas fallidas, o según el código que se obtenga, utilizamos onStatus:
String response = webClient.post()
.uri("/recurso")
.header("nombreHeader", "Valor")
.body(Mono.just(Objeto), TipoObjeto.class)
.retrieve()
.onStatus(HttpStatus::is5xxServerError, clientResponse -> {
log.error("Error, código {}", clientResponse.statusCode());
throw new UnaExcepcion("HTTP Status 500 error");
})
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(3))
.block(); |
A destacar es que no acepta los códigos de error con las constantes tipo HttpStatus.FORBIDDEN, pide un predicado de tipo HttpStatus::metodo. Normalmente se utilizarán métodos como el mostrado ahí para error 500 (5xx más bien), y también da opción para 4xx (is4xxClientError), para 3xx (is3xxRedirection), o para 2xx (is2xxSuccessful). Si autocompletamos podremos ver todas las opciones.