Daniel
Daniel

Reputation: 2500

How to retry on response timeout with Spring WebClient?

I'm having trouble understanding how to configure my web client to retry in case of a response timeout. Below code works fine when the server returns a 5xx error, but I'm unable to handle the error when there is no response. I'm also not able to find any info related to this in the Spring Webclient docs.

I've setup a MockWebServer like this

    MockWebServer mockBackEnd = new MockWebServer();
    mockBackEnd.start();
    mockBackEnd.enqueue(new MockResponse().setBody("TestBody").setSocketPolicy(SocketPolicy.NO_RESPONSE));
    mockBackEnd.enqueue(new MockResponse().setResponseCode(200).setBody("OK"));

The client and the call to the mock server

    String baseUrl = String.format("http://localhost:%s?", mockBackEnd.getPort());
    HttpClient client = HttpClient.create()
            .responseTimeout(Duration.ofSeconds(3));

    WebClient webClient = WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(client))
            .build();

    webClient
                .get()
                .uri(baseUrl)
                .retrieve()
                .onStatus(HttpStatus::is5xxServerError,
                        response -> response.bodyToMono(String.class).map(body ->
                                new HttpServerErrorException(response.statusCode(), body))
                )
                .bodyToMono(CustomResponse.class)
                .retryWhen(Retry.backoff(3, Duration.ofSeconds(5))
                        .filter(throwable -> throwable instanceof HttpServerErrorException)
                        .filter(throwable -> throwable instanceof ReadTimeoutException)
                )
                .block();

The resulting exception on timeout is

Caused by: org.springframework.web.reactive.function.client.WebClientRequestException: nested exception is io.netty.handler.timeout.ReadTimeoutException
at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:141)

How can I get the code above to retry not only on 5xx errors but on timeout errors as well?

Upvotes: 3

Views: 14449

Answers (1)

Daniel
Daniel

Reputation: 2500

I got a response over on Gitter which pointed me to the fact that you can only have a single filter in the retryWhen. That in combination with the response from Stephane Nicoll to my original post finally solved the issue. (Note that the last instanceof here checks for io.netty.handler.timeout.TimeoutException and not java.util.concurrent.TimeoutException which is thrown in case you set a specific timeout on the request level)

Final working code:

String baseUrl = String.format("http://localhost:%s?", mockBackEnd.getPort());
    HttpClient client = HttpClient.create()
            .responseTimeout(Duration.ofSeconds(3));

    WebClient webClient = WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(client))
            .build();

    webClient
                .get()
                .uri(baseUrl)
                .retrieve()
                .onStatus(HttpStatus::is5xxServerError,
                        response -> response.bodyToMono(String.class).map(body ->
                                new HttpServerErrorException(response.statusCode(), body))
                )
                .bodyToMono(CustomResponse.class)
                .retryWhen(Retry.backoff(5, Duration.ofSeconds(5))
                            .filter(throwable -> throwable instanceof HttpServerErrorException ||
                                    throwable instanceof WebClientRequestException && throwable.getCause() instanceof TimeoutException)
                    )
                .block();

Upvotes: 8

Related Questions