From 134bb6e31fc7da35d2f3ffe409537b70d1e88d2a Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 12 Dec 2023 13:31:27 +0100 Subject: [PATCH] Document exception wrapping in RestClient status handlers This commit documents the fact that any (Unchecked)IOExceptions or HttpMessageNotReadableExceptions thrown from the error handler will be wrapped in a RestClientException. Closes gh-31783 --- .../web/client/DefaultRestClient.java | 9 +++++++- .../web/client/RestClient.java | 10 +++++++++ .../client/RestClientIntegrationTests.java | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index b87c3b8e6ae..da9b4d81456 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -220,8 +220,15 @@ final class DefaultRestClient implements RestClient { responseWrapper.getHeaders(), RestClientUtils.getBody(responseWrapper)); } catch (UncheckedIOException | IOException | HttpMessageNotReadableException ex) { + Throwable cause; + if (ex instanceof UncheckedIOException uncheckedIOException) { + cause = uncheckedIOException.getCause(); + } + else { + cause = ex; + } throw new RestClientException("Error while extracting response for type [" + - ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", ex); + ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause); } } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java index a54db9d93a9..1cd9183a945 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java @@ -705,6 +705,11 @@ public interface RestClient { * Provide a function to map specific error status codes to an error handler. *

By default, if there are no matching status handlers, responses with * status codes >= 400 wil throw a {@link RestClientResponseException}. + *

Note that {@link IOException IOExceptions}, + * {@link java.io.UncheckedIOException UncheckedIOExceptions}, and + * {@link org.springframework.http.converter.HttpMessageNotReadableException HttpMessageNotReadableExceptions} + * thrown from {@code errorHandler} will be wrapped in a + * {@link RestClientException}. * @param statusPredicate to match responses with * @param errorHandler handler that typically, though not necessarily, * throws an exception @@ -717,6 +722,11 @@ public interface RestClient { * Provide a function to map specific error status codes to an error handler. *

By default, if there are no matching status handlers, responses with * status codes >= 400 wil throw a {@link RestClientResponseException}. + *

Note that {@link IOException IOExceptions}, + * {@link java.io.UncheckedIOException UncheckedIOExceptions}, and + * {@link org.springframework.http.converter.HttpMessageNotReadableException HttpMessageNotReadableExceptions} + * thrown from {@code errorHandler} will be wrapped in a + * {@link RestClientException}. * @param errorHandler the error handler * @return this builder */ diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index 0ac02898fc3..ece13445cca 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -592,6 +592,27 @@ class RestClientIntegrationTests { expectRequest(request -> assertThat(request.getPath()).isEqualTo("/greeting")); } + @ParameterizedRestClientTest + void statusHandlerIOException(ClientHttpRequestFactory requestFactory) { + startServer(requestFactory); + + prepareResponse(response -> response.setResponseCode(500) + .setHeader("Content-Type", "text/plain").setBody("Internal Server error")); + + assertThatExceptionOfType(RestClientException.class).isThrownBy(() -> + this.restClient.get() + .uri("/greeting") + .retrieve() + .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> { + throw new IOException("500 error!"); + }) + .body(String.class) + ).withCauseInstanceOf(IOException.class); + + expectRequestCount(1); + expectRequest(request -> assertThat(request.getPath()).isEqualTo("/greeting")); + } + @ParameterizedRestClientTest void statusHandlerParameterizedTypeReference(ClientHttpRequestFactory requestFactory) { startServer(requestFactory);