From a31eaf3efa57bb4903a6f4fb302a752bbfe9993a Mon Sep 17 00:00:00 2001 From: jerzykrlk Date: Mon, 20 Aug 2018 21:15:02 +0200 Subject: [PATCH 1/2] http error details in the rest client exception message --- .../client/DefaultResponseErrorHandler.java | 56 +++++++++++++++++-- .../DefaultResponseErrorHandlerTests.java | 18 ++++++ .../client/RestTemplateIntegrationTests.java | 4 +- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index 8200adb1682f..f178fb34a10c 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -20,11 +20,13 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.net.URI; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; @@ -109,15 +111,14 @@ protected boolean hasError(int unknownStatusCode) { * {@link HttpStatus} enum range. * * @throws UnknownHttpStatusCodeException in case of an unresolvable status code - * @see #handleError(ClientHttpResponse, HttpStatus) + * @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatus) */ @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); if (statusCode == null) { byte[] body = getResponseBody(response); - String message = getErrorMessage(response.getRawStatusCode(), - response.getStatusText(), body, getCharset(response)); + String message = getErrorMessage(response.getRawStatusCode(), response.getStatusText(), body, getCharset(response), null, null); throw new UnknownHttpStatusCodeException(message, response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), body, getCharset(response)); @@ -125,6 +126,25 @@ public void handleError(ClientHttpResponse response) throws IOException { handleError(response, statusCode); } + /** + * Delegates to {@link #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatus)} with the + * response status code. + * @throws UnknownHttpStatusCodeException in case of an unresolvable status code + * @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatus) + */ + @Override + public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { + HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); + if (statusCode == null) { + byte[] body = getResponseBody(response); + String message = getErrorMessage(response.getRawStatusCode(), response.getStatusText(), body, getCharset(response), url, method); + throw new UnknownHttpStatusCodeException(message, + response.getRawStatusCode(), response.getStatusText(), + response.getHeaders(), body, getCharset(response)); + } + handleError(url, method, response, statusCode); + } + /** * Return error message with details from the response body, possibly truncated: *
@@ -132,9 +152,10 @@ public void handleError(ClientHttpResponse response) throws IOException {
 	 * 
*/ private String getErrorMessage( - int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) { + int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset, @Nullable URI url, @Nullable HttpMethod method) { + + String preface = getPreface(rawStatusCode, statusText, url, method); - String preface = rawStatusCode + " " + statusText + ": "; if (ObjectUtils.isEmpty(responseBody)) { return preface + "[no body]"; } @@ -162,6 +183,15 @@ private String getErrorMessage( } } + private String getPreface(int rawStatusCode, String statusText, @Nullable URI url, @Nullable HttpMethod method) { + StringBuilder preface = new StringBuilder(rawStatusCode + " " + statusText); + if (!ObjectUtils.isEmpty(method) && !ObjectUtils.isEmpty(url)) { + preface.append(" after ").append(method).append(" ").append(url).append(" "); + } + preface.append(": "); + return preface.toString(); + } + /** * Handle the error based on the resolved status code. * @@ -175,11 +205,25 @@ private String getErrorMessage( * @see HttpServerErrorException#create */ protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { + handleError(null, null, response, statusCode); + } + + /** + * Handle the error in the given response with the given resolved status code. + *

This default implementation throws a {@link HttpClientErrorException} if the response status code + * is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException} + * if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}, + * and a {@link RestClientException} in other cases. + * @since 5.0 + */ + protected void handleError(@Nullable URI url, @Nullable HttpMethod method, ClientHttpResponse response, + HttpStatus statusCode) throws IOException { + String statusText = response.getStatusText(); HttpHeaders headers = response.getHeaders(); byte[] body = getResponseBody(response); Charset charset = getCharset(response); - String message = getErrorMessage(statusCode.value(), statusText, body, charset); + String message = getErrorMessage(statusCode.value(), statusText, body, charset, url, method); switch (statusCode.series()) { case CLIENT_ERROR: diff --git a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java index cea2263411e3..b637a8768b30 100644 --- a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java @@ -18,6 +18,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.function.Function; @@ -25,6 +26,7 @@ import reactor.core.publisher.Flux; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; @@ -78,6 +80,22 @@ public void handleError() throws Exception { .satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers)); } + @Test + public void handleErrorWithUrlAndMethod() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(HttpStatus.NOT_FOUND.value()); + given(response.getStatusText()).willReturn("Not Found"); + given(response.getHeaders()).willReturn(headers); + given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8))); + + assertThatExceptionOfType(HttpClientErrorException.class) + .isThrownBy(() -> handler.handleError(URI.create("https://example.com"), HttpMethod.GET, response)) + .withMessage("404 Not Found after GET https://example.com : [Hello World]") + .satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers)); + } + @Test public void handleErrorWithLongBody() throws Exception { diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index f53cf890c79b..2b1ba504fadb 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -243,6 +243,7 @@ void notFound(ClientHttpRequestFactory clientHttpRequestFactory) { assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(ex.getStatusText()).isNotNull(); assertThat(ex.getResponseBodyAsString()).isNotNull(); + assertThat(ex.getMessage()).isEqualTo("404 Client Error after GET http://localhost:" + port + "/status/notfound : [no body]"); }); } @@ -254,7 +255,7 @@ void badRequest(ClientHttpRequestFactory clientHttpRequestFactory) { template.execute(baseUrl + "/status/badrequest", HttpMethod.GET, null, null)) .satisfies(ex -> { assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(ex.getMessage()).isEqualTo("400 Client Error: [no body]"); + assertThat(ex.getMessage()).isEqualTo("400 Client Error after GET http://localhost:" + port + "/status/badrequest : [no body]"); }); } @@ -268,6 +269,7 @@ void serverError(ClientHttpRequestFactory clientHttpRequestFactory) { assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); assertThat(ex.getStatusText()).isNotNull(); assertThat(ex.getResponseBodyAsString()).isNotNull(); + assertThat(ex.getMessage()).isEqualTo("500 Server Error after GET http://localhost:" + port + "/status/server : [no body]"); }); } From c2cdc6efd50be25025ea86270fc068ec3d4223bd Mon Sep 17 00:00:00 2001 From: jerzykrlk Date: Sun, 31 Jan 2021 13:44:09 +0100 Subject: [PATCH 2/2] http error details in the rest client exception message - javadoc updated --- .../client/DefaultResponseErrorHandler.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index f178fb34a10c..a11c507b5c41 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -115,20 +115,22 @@ protected boolean hasError(int unknownStatusCode) { */ @Override public void handleError(ClientHttpResponse response) throws IOException { - HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); - if (statusCode == null) { - byte[] body = getResponseBody(response); - String message = getErrorMessage(response.getRawStatusCode(), response.getStatusText(), body, getCharset(response), null, null); - throw new UnknownHttpStatusCodeException(message, - response.getRawStatusCode(), response.getStatusText(), - response.getHeaders(), body, getCharset(response)); - } - handleError(response, statusCode); + handleError(null, null, response); } /** - * Delegates to {@link #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatus)} with the - * response status code. + * Handle the error in the given response with the given resolved status code. + *

The default implementation throws: + *

* @throws UnknownHttpStatusCodeException in case of an unresolvable status code * @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatus) */ @@ -209,12 +211,16 @@ protected void handleError(ClientHttpResponse response, HttpStatus statusCode) t } /** - * Handle the error in the given response with the given resolved status code. - *

This default implementation throws a {@link HttpClientErrorException} if the response status code - * is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException} - * if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}, - * and a {@link RestClientException} in other cases. + * Handle the error based on the resolved status code. + * + *

The default implementation delegates to + * {@link HttpClientErrorException#create} for errors in the 4xx range, to + * {@link HttpServerErrorException#create} for errors in the 5xx range, + * or otherwise raises {@link UnknownHttpStatusCodeException}. + * * @since 5.0 + * @see HttpClientErrorException#create + * @see HttpServerErrorException#create */ protected void handleError(@Nullable URI url, @Nullable HttpMethod method, ClientHttpResponse response, HttpStatus statusCode) throws IOException {