Reputation: 2500
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
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