Mono.fromCallable(() -> sendRequestToServer(params))
.doOnSuccess(result -> handleResponse(result, grantMapEntry, deviceInfo))
.retryWhen(RetryBackoffSpec
.backoff(5, ofSeconds(10))
.doAfterRetry(retryInfo -> {
log.warn("retrying");
})
.scheduler(hbRetryScheduler)
.jitter(0.5);)
.subscribe();
Above spec. works fine, but the problem is sometimes server is not able to process the request and returns 106 response code with retryTime in seconds (seconds after which the request should be retried with backoff and jitter). How can I use the returned retryTime in RetryBackoffSpec?
You can create custom RetrySpec
based on criteria from the RetrySignal
(e.g. http headers from the response).
The following example uses Retry-After
header to define custom backoff logic.
private static class DynamicRetrySpec extends Retry {
private final int maxRetries;
private final Duration defaultBackoff;
public DynamicRetrySpec(int maxRetries, Duration defaultBackoff) {
this.maxRetries = maxRetries;
this.defaultBackoff = defaultBackoff;
}
@Override
public Publisher<?> generateCompanion(Flux<RetrySignal> retrySignals) {
return retrySignals.flatMap(this::getRetry);
}
private Mono<Long> getRetry(Retry.RetrySignal rs) {
if (rs.failure() instanceof WebClientResponseException.TooManyRequests) {
if (rs.totalRetries() < maxRetries) {
Duration delay = getBackOffDelayFromHeaders((WebClientResponseException.TooManyRequests) rs.failure());
log.info("retry {} with backoff {}sec", rs.totalRetries(), delay.toSeconds());
return Mono.delay(delay)
.thenReturn(rs.totalRetries());
} else {
log.info("retries exhausted with error: {}", rs.failure().getMessage());
throw Exceptions.propagate(rs.failure());
}
} else {
throw Exceptions.propagate(rs.failure());
}
}
private Duration getBackOffDelayFromHeaders(WebClientResponseException.TooManyRequests exceptionResponse) {
String retryAfter = exceptionResponse.getHeaders().getFirst("Retry-After");
if (retryAfter != null) {
return Duration.ofSeconds(Integer.parseInt(retryAfter));
}
return defaultBackoff;
}
}
and then uses it to the WebClient
webClient.get()
.uri("/api")
.retrieve()
.toBodilessEntity()
.retryWhen(new DynamicRetrySpec(3, Duration.ofSeconds(1)));