0

I'm attempting to create a library that leverages Spring Webclient that has common failure logic for all of our applications. There are 2 considerations - configurable retry logic and failover logic to secondary sites.

The failover needs to handle network exceptions and request exceptions with a specific status code, default >= 500.

Would anyone be able to sanity check the below?

i.e.

    public Builder webClientBuilder() throws SSLException {
        return WebClient.builder()
                .baseUrl(baseUrl)
                .filter((request, next) -> exchangeFilterFunction(request, next))
                .clientConnector(createReactorClientHttpConnector());
    }
    private Mono<ClientResponse> exchangeFilterFunction(ClientRequest request, ExchangeFunction next) {
        if (StringUtils.hasLength(failoverBaseUrl)) {
            return nextExchangeWithRetry(request, next)
                    .onErrorResume(e -> {
                        if (isRetriableException(e)) {
                            String uri = request.url().toString().replace(baseUrl, failoverBaseUrl);
                            LOGGER.info("Attempting to call Failover Site: " + uri);
                            ClientRequest retryRequest = ClientRequest.from(request).url(URI.create(uri)).build();
                            return nextExchangeWithRetry(retryRequest, next);
                        }
                        return Mono.error(e);
                    });
        } else {
            LOGGER.debug("No Failover Configured");
            return nextExchangeWithRetry(request, next);
        }
    }

    private Mono<ClientResponse> nextExchangeWithRetry(ClientRequest request, ExchangeFunction next) {
        return next.exchange(request)
                .flatMap(clientResponse -> {
                    HttpStatus httpStatus = clientResponse.statusCode();
                    if (httpStatus.isError()) {
                        return clientResponse.createException().flatMap(Mono::error);
                    }
                    return Mono.just(clientResponse);
                })
                .retryWhen(retrySpec(request));
    }
    private boolean isRetriableException(Throwable throwable) {
        if (nonRetriableExceptions != null && nonRetriableExceptions.contains(throwable.getClass())) {
                return false;
        }
        if (throwable instanceof WebClientResponseException
                && isNotRetryHttpResponseCode(((WebClientResponseException) throwable).getRawStatusCode())) {
            return false;
        }
        return true;
    }
Daire
  • 11
  • 2
  • i did not look through your code that much, except that you should avoid `block` under any circumstances in webflux applications. `doOnNext` is used for side effects. I would instead use `flatMap` and `if error` return `clientResponse.createException()` and remove the block – Toerktumlare Dec 10 '20 at 03:27
  • i have never written anything like this before, but i guess my personal approach would have been to set up two webclients, the main one calling the main endpoint, and then a second webclient for calling the "backup" and then maybe write a custom RetrySpec https://www.logicbig.com/tutorials/misc/reactive-programming/reactor/understanding-retry-when.html that will check for returns and call the secondary endpoint at certain status codes. But thats my personal approach instead of rewriting requests – Toerktumlare Dec 10 '20 at 03:42
  • flatMap & block comments taken on board. I've updated the method and it appears to work in my local testing. – Daire Dec 10 '20 at 09:06

0 Answers0