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 520965640c74..ff72eb1e465d 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 @@ -16,8 +16,13 @@ package org.springframework.web.client; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -25,6 +30,7 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.FileCopyUtils; +import org.springframework.util.ObjectUtils; /** * Spring's default implementation of the {@link ResponseErrorHandler} interface. @@ -96,12 +102,51 @@ protected boolean hasError(int unknownStatusCode) { public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); if (statusCode == null) { - throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), + String message = getErrorMessage( + response.getRawStatusCode(), response.getStatusText(), + getResponseBody(response), getCharset(response)); + throw new UnknownHttpStatusCodeException(message, + response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); } handleError(response, statusCode); } + /** + * Return error message with details from the response body, possibly truncated: + *
+	 * 404 Not Found: [{'id': 123, 'message': 'my very long... (500 bytes)]
+	 * 
+ */ + private String getErrorMessage( + int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) { + + String preface = rawStatusCode + " " + statusText + ": "; + if (ObjectUtils.isEmpty(responseBody)) { + return preface + "[no body]"; + } + + charset = charset == null ? StandardCharsets.UTF_8 : charset; + int maxChars = 200; + + if (responseBody.length < maxChars * 2) { + return preface + "[" + new String(responseBody, charset) + "]"; + } + + try { + Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), charset); + CharBuffer buffer = CharBuffer.allocate(maxChars); + reader.read(buffer); + reader.close(); + buffer.flip(); + return preface + "[" + buffer.toString() + "... (" + responseBody.length + " bytes)]"; + } + catch (IOException ex) { + // should never happen + throw new IllegalStateException(ex); + } + } + /** * Handle the error in the given response with the given resolved status code. *

The default implementation throws an {@link HttpClientErrorException} @@ -118,13 +163,15 @@ protected void handleError(ClientHttpResponse response, HttpStatus statusCode) t HttpHeaders headers = response.getHeaders(); byte[] body = getResponseBody(response); Charset charset = getCharset(response); + String message = getErrorMessage(statusCode.value(), statusText, body, charset); + switch (statusCode.series()) { case CLIENT_ERROR: - throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset); + throw HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset); case SERVER_ERROR: - throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset); + throw HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset); default: - throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset); + throw new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset); } } diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpClientErrorException.java b/spring-web/src/main/java/org/springframework/web/client/HttpClientErrorException.java index a8f3328af2a8..ff6e3f83085a 100644 --- a/spring-web/src/main/java/org/springframework/web/client/HttpClientErrorException.java +++ b/spring-web/src/main/java/org/springframework/web/client/HttpClientErrorException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,6 +66,17 @@ public HttpClientErrorException(HttpStatus statusCode, String statusText, super(statusCode, statusText, headers, body, responseCharset); } + /** + * Constructor with a status code and status text, headers, and content, + * and an prepared message. + * @since 5.2.2 + */ + public HttpClientErrorException(String message, HttpStatus statusCode, String statusText, + @Nullable HttpHeaders headers, @Nullable byte[] body, @Nullable Charset responseCharset) { + + super(message, statusCode, statusText, headers, body, responseCharset); + } + /** * Create {@code HttpClientErrorException} or an HTTP status specific sub-class. @@ -74,31 +85,66 @@ public HttpClientErrorException(HttpStatus statusCode, String statusText, public static HttpClientErrorException create( HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + return create(null, statusCode, statusText, headers, body, charset); + } + + /** + * Variant of {@link #create(HttpStatus, String, HttpHeaders, byte[], Charset)} + * with an optional prepared message. + * @since 5.2.2 + */ + public static HttpClientErrorException create(@Nullable String message, HttpStatus statusCode, + String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + switch (statusCode) { case BAD_REQUEST: - return new HttpClientErrorException.BadRequest(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.BadRequest(message, statusText, headers, body, charset) : + new HttpClientErrorException.BadRequest(statusText, headers, body, charset); case UNAUTHORIZED: - return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.Unauthorized(message, statusText, headers, body, charset) : + new HttpClientErrorException.Unauthorized(statusText, headers, body, charset); case FORBIDDEN: - return new HttpClientErrorException.Forbidden(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.Forbidden(message, statusText, headers, body, charset) : + new HttpClientErrorException.Forbidden(statusText, headers, body, charset); case NOT_FOUND: - return new HttpClientErrorException.NotFound(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.NotFound(message, statusText, headers, body, charset) : + new HttpClientErrorException.NotFound(statusText, headers, body, charset); case METHOD_NOT_ALLOWED: - return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.MethodNotAllowed(message, statusText, headers, body, charset) : + new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset); case NOT_ACCEPTABLE: - return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.NotAcceptable(message, statusText, headers, body, charset) : + new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset); case CONFLICT: - return new HttpClientErrorException.Conflict(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.Conflict(message, statusText, headers, body, charset) : + new HttpClientErrorException.Conflict(statusText, headers, body, charset); case GONE: - return new HttpClientErrorException.Gone(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.Gone(message, statusText, headers, body, charset) : + new HttpClientErrorException.Gone(statusText, headers, body, charset); case UNSUPPORTED_MEDIA_TYPE: - return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.UnsupportedMediaType(message, statusText, headers, body, charset) : + new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset); case TOO_MANY_REQUESTS: - return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.TooManyRequests(message, statusText, headers, body, charset) : + new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset); case UNPROCESSABLE_ENTITY: - return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException.UnprocessableEntity(message, statusText, headers, body, charset) : + new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset); default: - return new HttpClientErrorException(statusCode, statusText, headers, body, charset); + return message != null ? + new HttpClientErrorException(message, statusCode, statusText, headers, body, charset) : + new HttpClientErrorException(statusCode, statusText, headers, body, charset); } } @@ -110,11 +156,17 @@ public static HttpClientErrorException create( * @since 5.1 */ @SuppressWarnings("serial") - public static class BadRequest extends HttpClientErrorException { + public static final class BadRequest extends HttpClientErrorException { - BadRequest(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private BadRequest(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.BAD_REQUEST, statusText, headers, body, charset); } + + private BadRequest(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.BAD_REQUEST, statusText, headers, body, charset); + } } /** @@ -122,11 +174,17 @@ public static class BadRequest extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class Unauthorized extends HttpClientErrorException { + public static final class Unauthorized extends HttpClientErrorException { - Unauthorized(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private Unauthorized(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.UNAUTHORIZED, statusText, headers, body, charset); } + + private Unauthorized(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.UNAUTHORIZED, statusText, headers, body, charset); + } } /** @@ -134,11 +192,17 @@ public static class Unauthorized extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class Forbidden extends HttpClientErrorException { + public static final class Forbidden extends HttpClientErrorException { - Forbidden(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private Forbidden(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.FORBIDDEN, statusText, headers, body, charset); } + + private Forbidden(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.FORBIDDEN, statusText, headers, body, charset); + } } /** @@ -146,11 +210,17 @@ public static class Forbidden extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class NotFound extends HttpClientErrorException { + public static final class NotFound extends HttpClientErrorException { - NotFound(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private NotFound(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.NOT_FOUND, statusText, headers, body, charset); } + + private NotFound(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.NOT_FOUND, statusText, headers, body, charset); + } } /** @@ -158,11 +228,17 @@ public static class NotFound extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class MethodNotAllowed extends HttpClientErrorException { + public static final class MethodNotAllowed extends HttpClientErrorException { - MethodNotAllowed(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private MethodNotAllowed(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.METHOD_NOT_ALLOWED, statusText, headers, body, charset); } + + private MethodNotAllowed(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.METHOD_NOT_ALLOWED, statusText, headers, body, charset); + } } /** @@ -170,11 +246,17 @@ public static class MethodNotAllowed extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class NotAcceptable extends HttpClientErrorException { + public static final class NotAcceptable extends HttpClientErrorException { - NotAcceptable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private NotAcceptable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.NOT_ACCEPTABLE, statusText, headers, body, charset); } + + private NotAcceptable(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.NOT_ACCEPTABLE, statusText, headers, body, charset); + } } /** @@ -182,11 +264,15 @@ public static class NotAcceptable extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class Conflict extends HttpClientErrorException { + public static final class Conflict extends HttpClientErrorException { - Conflict(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private Conflict(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.CONFLICT, statusText, headers, body, charset); } + + private Conflict(String message, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(message, HttpStatus.CONFLICT, statusText, headers, body, charset); + } } /** @@ -194,11 +280,15 @@ public static class Conflict extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class Gone extends HttpClientErrorException { + public static final class Gone extends HttpClientErrorException { - Gone(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private Gone(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.GONE, statusText, headers, body, charset); } + + private Gone(String message, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(message, HttpStatus.GONE, statusText, headers, body, charset); + } } /** @@ -206,11 +296,17 @@ public static class Gone extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class UnsupportedMediaType extends HttpClientErrorException { + public static final class UnsupportedMediaType extends HttpClientErrorException { - UnsupportedMediaType(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private UnsupportedMediaType(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.UNSUPPORTED_MEDIA_TYPE, statusText, headers, body, charset); } + + private UnsupportedMediaType(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.UNSUPPORTED_MEDIA_TYPE, statusText, headers, body, charset); + } } /** @@ -218,11 +314,17 @@ public static class UnsupportedMediaType extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class UnprocessableEntity extends HttpClientErrorException { + public static final class UnprocessableEntity extends HttpClientErrorException { - UnprocessableEntity(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private UnprocessableEntity(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.UNPROCESSABLE_ENTITY, statusText, headers, body, charset); } + + private UnprocessableEntity(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.UNPROCESSABLE_ENTITY, statusText, headers, body, charset); + } } /** @@ -230,11 +332,17 @@ public static class UnprocessableEntity extends HttpClientErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class TooManyRequests extends HttpClientErrorException { + public static final class TooManyRequests extends HttpClientErrorException { - TooManyRequests(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private TooManyRequests(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.TOO_MANY_REQUESTS, statusText, headers, body, charset); } + + private TooManyRequests(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.TOO_MANY_REQUESTS, statusText, headers, body, charset); + } } } diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpServerErrorException.java b/spring-web/src/main/java/org/springframework/web/client/HttpServerErrorException.java index b0f7cc85f3f5..d45eb16da9ce 100644 --- a/spring-web/src/main/java/org/springframework/web/client/HttpServerErrorException.java +++ b/spring-web/src/main/java/org/springframework/web/client/HttpServerErrorException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,27 +66,60 @@ public HttpServerErrorException(HttpStatus statusCode, String statusText, super(statusCode, statusText, headers, body, charset); } + /** + * Constructor with a status code and status text, headers, content, and an + * prepared message. + * @since 5.2.2 + */ + public HttpServerErrorException(String message, HttpStatus statusCode, String statusText, + @Nullable HttpHeaders headers, @Nullable byte[] body, @Nullable Charset charset) { + + super(message, statusCode, statusText, headers, body, charset); + } /** * Create an {@code HttpServerErrorException} or an HTTP status specific sub-class. * @since 5.1 */ - public static HttpServerErrorException create( - HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + public static HttpServerErrorException create(HttpStatus statusCode, + String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + return create(null, statusCode, statusText, headers, body, charset); + } + + /** + * Variant of {@link #create(String, HttpStatus, String, HttpHeaders, byte[], Charset)} + * with an optional prepared message. + * @since 5.2.2. + */ + public static HttpServerErrorException create(@Nullable String message, HttpStatus statusCode, + String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { switch (statusCode) { case INTERNAL_SERVER_ERROR: - return new HttpServerErrorException.InternalServerError(statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException.InternalServerError(message, statusText, headers, body, charset) : + new HttpServerErrorException.InternalServerError(statusText, headers, body, charset); case NOT_IMPLEMENTED: - return new HttpServerErrorException.NotImplemented(statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException.NotImplemented(message, statusText, headers, body, charset) : + new HttpServerErrorException.NotImplemented(statusText, headers, body, charset); case BAD_GATEWAY: - return new HttpServerErrorException.BadGateway(statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException.BadGateway(message, statusText, headers, body, charset) : + new HttpServerErrorException.BadGateway(statusText, headers, body, charset); case SERVICE_UNAVAILABLE: - return new HttpServerErrorException.ServiceUnavailable(statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException.ServiceUnavailable(message, statusText, headers, body, charset) : + new HttpServerErrorException.ServiceUnavailable(statusText, headers, body, charset); case GATEWAY_TIMEOUT: - return new HttpServerErrorException.GatewayTimeout(statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException.GatewayTimeout(message, statusText, headers, body, charset) : + new HttpServerErrorException.GatewayTimeout(statusText, headers, body, charset); default: - return new HttpServerErrorException(statusCode, statusText, headers, body, charset); + return message != null ? + new HttpServerErrorException(message, statusCode, statusText, headers, body, charset) : + new HttpServerErrorException(statusCode, statusText, headers, body, charset); } } @@ -98,11 +131,17 @@ public static HttpServerErrorException create( * @since 5.1 */ @SuppressWarnings("serial") - public static class InternalServerError extends HttpServerErrorException { + public static final class InternalServerError extends HttpServerErrorException { - InternalServerError(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private InternalServerError(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.INTERNAL_SERVER_ERROR, statusText, headers, body, charset); } + + private InternalServerError(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.INTERNAL_SERVER_ERROR, statusText, headers, body, charset); + } } /** @@ -110,11 +149,17 @@ public static class InternalServerError extends HttpServerErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class NotImplemented extends HttpServerErrorException { + public static final class NotImplemented extends HttpServerErrorException { - NotImplemented(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private NotImplemented(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.NOT_IMPLEMENTED, statusText, headers, body, charset); } + + private NotImplemented(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.NOT_IMPLEMENTED, statusText, headers, body, charset); + } } /** @@ -122,11 +167,17 @@ public static class NotImplemented extends HttpServerErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class BadGateway extends HttpServerErrorException { + public static final class BadGateway extends HttpServerErrorException { - BadGateway(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private BadGateway(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.BAD_GATEWAY, statusText, headers, body, charset); } + + private BadGateway(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.BAD_GATEWAY, statusText, headers, body, charset); + } } /** @@ -134,11 +185,17 @@ public static class BadGateway extends HttpServerErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class ServiceUnavailable extends HttpServerErrorException { + public static final class ServiceUnavailable extends HttpServerErrorException { - ServiceUnavailable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private ServiceUnavailable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.SERVICE_UNAVAILABLE, statusText, headers, body, charset); } + + private ServiceUnavailable(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.SERVICE_UNAVAILABLE, statusText, headers, body, charset); + } } /** @@ -146,11 +203,17 @@ public static class ServiceUnavailable extends HttpServerErrorException { * @since 5.1 */ @SuppressWarnings("serial") - public static class GatewayTimeout extends HttpServerErrorException { + public static final class GatewayTimeout extends HttpServerErrorException { - GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + private GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { super(HttpStatus.GATEWAY_TIMEOUT, statusText, headers, body, charset); } + + private GatewayTimeout(String message, String statusText, + HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + super(message, HttpStatus.GATEWAY_TIMEOUT, statusText, headers, body, charset); + } } } diff --git a/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java b/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java index 16715ef81151..b660f15be870 100644 --- a/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java +++ b/spring-web/src/main/java/org/springframework/web/client/HttpStatusCodeException.java @@ -83,8 +83,25 @@ protected HttpStatusCodeException(HttpStatus statusCode, String statusText, protected HttpStatusCodeException(HttpStatus statusCode, String statusText, @Nullable HttpHeaders responseHeaders, @Nullable byte[] responseBody, @Nullable Charset responseCharset) { - super(getMessage(statusCode, statusText), statusCode.value(), statusText, - responseHeaders, responseBody, responseCharset); + this(getMessage(statusCode, statusText), + statusCode, statusText, responseHeaders, responseBody, responseCharset); + } + + /** + * Construct instance with an {@link HttpStatus}, status text, content, and + * a response charset. + * @param message the exception message + * @param statusCode the status code + * @param statusText the status text + * @param responseHeaders the response headers, may be {@code null} + * @param responseBody the response body content, may be {@code null} + * @param responseCharset the response body charset, may be {@code null} + * @since 5.2.2 + */ + protected HttpStatusCodeException(String message, HttpStatus statusCode, String statusText, + @Nullable HttpHeaders responseHeaders, @Nullable byte[] responseBody, @Nullable Charset responseCharset) { + + super(message, statusCode.value(), statusText, responseHeaders, responseBody, responseCharset); this.statusCode = statusCode; } diff --git a/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java b/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java index ac239513ae90..c7b0319018d4 100644 --- a/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java +++ b/spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,8 +45,23 @@ public class UnknownHttpStatusCodeException extends RestClientResponseException public UnknownHttpStatusCodeException(int rawStatusCode, String statusText, @Nullable HttpHeaders responseHeaders, @Nullable byte[] responseBody, @Nullable Charset responseCharset) { - super("Unknown status code [" + rawStatusCode + "]" + " " + statusText, + this("Unknown status code [" + rawStatusCode + "]" + " " + statusText, rawStatusCode, statusText, responseHeaders, responseBody, responseCharset); } + /** + * Construct a new instance of {@code HttpStatusCodeException} based on an + * {@link HttpStatus}, status text, and response body content. + * @param rawStatusCode the raw status code value + * @param statusText the status text + * @param responseHeaders the response headers (may be {@code null}) + * @param responseBody the response body content (may be {@code null}) + * @param responseCharset the response body charset (may be {@code null}) + * @since 5.2.2 + */ + public UnknownHttpStatusCodeException(String message, int rawStatusCode, String statusText, + @Nullable HttpHeaders responseHeaders, @Nullable byte[] responseBody, @Nullable Charset responseCharset) { + + super(message, rawStatusCode, statusText, responseHeaders, responseBody, responseCharset); + } } 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 e9f782609378..c5c05c48e426 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 @@ -19,8 +19,10 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.function.Function; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -69,9 +71,28 @@ public void handleError() throws Exception { given(response.getHeaders()).willReturn(headers); given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8))); - assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> - handler.handleError(response)) - .satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers)); + assertThatExceptionOfType(HttpClientErrorException.class) + .isThrownBy(() -> handler.handleError(response)) + .satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers)) + .satisfies(ex -> assertThat(ex.getMessage()).isEqualTo("404 Not Found: [Hello World]")); + } + + @Test + public void handleErrorWithLongBody() throws Exception { + + Function bodyGenerator = + size -> Flux.just("a").repeat(size-1).reduce((s, s2) -> s + s2).block(); + + given(response.getRawStatusCode()).willReturn(HttpStatus.NOT_FOUND.value()); + given(response.getStatusText()).willReturn("Not Found"); + given(response.getHeaders()).willReturn(new HttpHeaders()); + given(response.getBody()).willReturn( + new ByteArrayInputStream(bodyGenerator.apply(500).getBytes(StandardCharsets.UTF_8))); + + assertThatExceptionOfType(HttpClientErrorException.class) + .isThrownBy(() -> handler.handleError(response)) + .satisfies(ex -> assertThat(ex.getMessage()).isEqualTo( + "404 Not Found: [" + bodyGenerator.apply(200) + "... (500 bytes)]")); } @Test @@ -84,8 +105,7 @@ public void handleErrorIOException() throws Exception { given(response.getHeaders()).willReturn(headers); given(response.getBody()).willThrow(new IOException()); - assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> - handler.handleError(response)); + assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> handler.handleError(response)); } @Test 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 d74c4c767765..f53cf890c79b 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 @@ -254,7 +254,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"); + assertThat(ex.getMessage()).isEqualTo("400 Client Error: [no body]"); }); }