Using Spring's WebClient
I'd like to implement the following behavior.
When the server returns HTTP 429 (Too Many Requests), then I'd like to extract the Retry-After
header value. The request shall then be retried after this interval. Here's the current implemenation which works for that case. But I'm experiencing a problem I cannot solve.
If the server responds with, for example, a 500 or any other error status except 429, then the application seems to do or wait for something "indefinitely" but I cannot figure it out. It seems as if the .retryWhen(retryAfterFixedDelay())
swallows/suppresses the WebClientResponseException
. When commenting out this line, the exception is thrown.
public SomeObject getSomeObject(int id) {
return apiClient.get()
.uri("/some-object/{id}", id)
.retrieve()
.onStatus(status -> status.equals(TOO_MANY_REQUESTS), this::throwRateLimitException)
.bodyToMono(SomeObject.class)
.retryWhen(retryAfterFixedDelay())
.block());
}
private static Mono<RateLimitException> throwRateLimitException(ClientResponse response) {
// extract header value...
final RateLimitException rateLimitException = new RateLimitException(message, retryAfterSeconds);
return Mono.error(rateLimitException);
}
private static Retry retryAfterFixedDelay() {
return Retry.withThrowable(throwableFlux -> throwableFlux
.filter(RateLimitException.class::isInstance)
.flatMap(throwable -> {
RateLimitException rateLimitException = (RateLimitException) throwable;
log.warn(rateLimitException.getMessage());
return Mono.delay(ofSeconds(rateLimitException.getRetryAfterSeconds()));
}));
}
Please try to return either Mono.error(throwable)
or e.g. Mono.delay(ofSeconds(5))
for exceptions different from RateLimitException
, depending on what you want.
Something like this
private static Retry retryAfterFixedDelay() {
return Retry.withThrowable(throwableFlux -> throwableFlux
.flatMap(throwable -> {
if (throwable instanceof RateLimitException rateLimitException) {
// for RateLimitException, use delay from exception
return Mono.delay(ofSeconds(rateLimitException.getRetryAfterSeconds()));
} else {
return Mono.error(throwable);
}
}));
}
or like this if you want all errors to be retried
private static Retry retryAfterFixedDelay() {
return Retry.withThrowable(throwableFlux -> throwableFlux
.ofType(RateLimitException.class)
.flatMap(rateLimitException -> Mono.delay(ofSeconds(rateLimitException.getRetryAfterSeconds())))
.switchIfEmpty(Mono.delay(ofSeconds(5))));
}