2

I'd like to create a Spring WebClient that ignores a specific HTTP error. From the documentation of WebClient.retrieve():

By default, 4xx and 5xx responses result in a WebClientResponseException. To customize error handling, use ResponseSpec.onStatus(Predicate, Function) handlers.

I want all calls through a WebClient instance to ignore the specific HTTP error. That is why onStatus() is of no use to me (it has to be set per response).

The best I could come up with is this:

        WebClient webClient = WebClient.builder().filter((request, next) -> {
            Mono<ClientResponse> response = next.exchange(request);
            response = response.onErrorResume(WebClientResponseException.class, ex -> {
                return ex.getRawStatusCode() == 418 ? Mono.empty() : Mono.error(ex);
            });
            return response;
        }).build();

        URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
        webClient.get().uri(uri).retrieve().toBodilessEntity().block();

but it does throw the exception instead of ignoring it (the lambda passed to onErrorResume() is never called).

Edited: fixed the mistake pointed out by the first answer.

Gbr
  • 53
  • 1
  • 8

2 Answers2

2

After extensive debugging of spring-webflux 5.3.4 and with the help of some ideas by Martin Tarjányi, I've come to this as the only possible "solution":

WebClient webClient = WebClient.builder().filter((request, next) -> {
    return next.exchange(request).flatMap(res -> {
        if (res.rawStatusCode() == HttpStatus.I_AM_A_TEAPOT.value()) {
            res = res.mutate().rawStatusCode(299).build();
        }
        return Mono.just(res);
    });
}).build();

URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
String body = webClient.get().uri(uri).retrieve().toEntity(String.class).block().getBody();

The background: I am migrating some code from RestTemplate to WebClient. The old code looks like this:

RestTemplate restTemplate = ...;
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        if (response.getRawStatusCode() == HttpStatus.I_AM_A_TEAPOT.value()) {
            return;
        }
        super.handleError(response);
    }
});

URI uri = UriComponentsBuilder.fromUriString("https://httpstat.us/418").build().toUri();
String body = restTemplate.getForEntity(uri, String.class).getBody();

I believe it is a straightforward and common case.

WebClient is not yet a 100% replacement for RestTemplate.

Gbr
  • 53
  • 1
  • 8
1

UPDATE: Turns out this answer doesn't address the core problem of filtering out a specific status code, just addresses a general coding pattern.


The reason onErrorResume lambda is not called is that response.onErrorResume creates a brand new Mono and your code does not use the result (i.e. it's not assigned to the response variable), so in the end a Mono without the onErrorResume operator is returned.

Using Project Reactor it's usually a good practice to avoid declaring local Mono and Flux variables and use a single chain instead. This helps to avoid similar subtle bugs.

WebClient webClient = WebClient.builder()
        .filter((request, next) -> next.exchange(request)
                        .onErrorResume(WebClientResponseException.class, ex -> ex.getRawStatusCode() == 418 ? Mono.empty() : Mono.error(ex)))
        .build();
Martin Tarjányi
  • 8,863
  • 2
  • 31
  • 49
  • Thanks for pointing out my mistake. However, even with your version of the code, the onErrorResume lambda is still not called and the exception gets thrown. – Gbr Mar 08 '21 at 07:15
  • It seems status codes are not errors at that point, so you should rather use `flatMap`: ```WebClient webClient = WebClient.builder() .filter((request, next) -> next.exchange(request) .flatMap(res -> res.rawStatusCode() == 416 ? Mono.empty() : Mono.just(res))) .build();``` But this doesn't work either, because WebClient fails on empty `Mono`, so I don't see a solution for this. Sorry. – Martin Tarjányi Mar 08 '21 at 07:58