Skip to content

Low level exception handling in HTTP Interface Clients #33353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ZIRAKrezovic opened this issue Aug 8, 2024 · 1 comment
Closed

Low level exception handling in HTTP Interface Clients #33353

ZIRAKrezovic opened this issue Aug 8, 2024 · 1 comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid

Comments

@ZIRAKrezovic
Copy link

Affects: 6.1.11

I am following https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface-exceptions and I'm trying to map exceptions from all 3 supported adapter clients to ones that do not have a dependency on concrete web stack dependency.

For handling response from WebFlux WebClient, the following is recommended in the linked guide

By default, WebClient raises WebClientResponseException for 4xx and 5xx HTTP status codes. To customize this, register a response status handler that applies to all responses performed through the client:

WebClient webClient = WebClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
		.build();

However, this does not cover the low level exception , org.springframework.web.reactive.function.client.WebClientRequestException. Simplest way to trigger it is to give non-existing hostname as a URL.

Is there a possibility to somehow plug in into the exchange function and re-map the org.springframework.web.reactive.function.client.WebClientRequestException to something else? I am not able to find a way with Spring Framework 6.1.11

If I understand correctly, this is the equivalent of ResourceAccessException usually thrown in non-reactive RestClient(RestTemplate).

I have tried

var client = WebClient.builder()
                .filter(ExchangeFilterFunction.ofRequestProcessor(request -> Mono.just(request)))
                .filter(ExchangeFilterFunction.ofResponseProcessor(response -> Mono.just(response)))
                .defaultStatusHandler(HttpStatusCode::isError, SpringWebCompatibleStatusHandler::handle)
                .build();

var adapter =  WebClientAdapter.create(client);

Where SpringWebCompatibleStatusHandler is as follows

public final class SpringWebCompatibleStatusHandler {
    private SpringWebCompatibleStatusHandler() {}

    public static Mono<Throwable> handle(@NonNull ClientResponse response) {
        return response.createException().map(SpringWebCompatibleStatusHandler::map);
    }

    private static Throwable map(WebClientResponseException ex) {
        var statusCode = ex.getStatusCode();
        var statusText = ex.getStatusText();
        var headers = ex.getHeaders();
        var message = ex.getMessage();
        var body = ex.getResponseBodyAsByteArray();

        var charset =
                Optional.ofNullable(headers.getContentType())
                        .map(MediaType::getCharset)
                        .orElse(null);

        RestClientResponseException throwable;

        if (statusCode.is4xxClientError()) {
            throwable =
                    HttpClientErrorException.create(
                            message, statusCode, statusText, headers, body, charset);
        } else if (statusCode.is5xxServerError()) {
            throwable =
                    HttpServerErrorException.create(
                            message, statusCode, statusText, headers, body, charset);
        } else {
            throwable =
                    new UnknownHttpStatusCodeException(
                            message, statusCode.value(), statusText, headers, body, charset);
        }

        return throwable;
    }
}

None of the functions is called before or after I get WebClientRequestException

Caused by: org.springframework.web.reactive.function.client.WebClientRequestException: Failed to resolve 'ua-integration' [A(1)]
	at org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.lambda$wrapException$9(ExchangeFunctions.java:136)
	Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Assembly trace from producer [reactor.core.publisher.MonoErrorSupplied] :
	reactor.core.publisher.Mono.error(Mono.java:315)
	org.springframework.web.reactive.function.client.ExchangeFunctions$DefaultExchangeFunction.wrapException(ExchangeFunctions.java:136)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 8, 2024
@jhoeller jhoeller added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Aug 8, 2024
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Sep 6, 2024

A filter provides full control, including handling of early exception like WebClientRequestException. However, ExchangeFilterFunction#ofResponseProcessor isn't the right hook. The flatMap here is called if the exchange produces a response. What you need instead is to handle an error signal.

For example:

ResponseEntity<String> entity = WebClient.builder()
		.filter((request, next) ->
				next.exchange(request)
						.onErrorResume(WebClientRequestException.class, ex -> {
							System.out.println("Got: " + ex.getMessage());
							return Mono.error(ex);
						}))
		.build()
		.get().uri("http://invalid/path")
		.retrieve()
		.toEntity(String.class)
		.block();

Results in:

19:45:54.246 [Test worker] DEBUG o.s.w.r.f.c.ExchangeFunctions - [350ec690] HTTP GET http://invalid/path
Got: Failed to resolve 'invalid' [A(1)]

The above just intercepts, prints, and propagates the same exception, but you can handle it in any way you want, or user another error operator as needed.

@rstoyanchev rstoyanchev closed this as not planned Won't fix, can't repro, duplicate, stale Sep 6, 2024
@rstoyanchev rstoyanchev added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

4 participants