diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 3f09804079..da40ccde92 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -463,9 +463,7 @@ class DefaultWebClient implements WebClient { .map(MimeType::getCharset) .orElse(StandardCharsets.ISO_8859_1); if (HttpStatus.resolve(response.rawStatusCode()) != null) { - String msg = String.format("ClientResponse has erroneous status code: %d %s", - response.statusCode().value(), response.statusCode().getReasonPhrase()); - return new WebClientResponseException(msg, + return WebClientResponseException.create( response.statusCode().value(), response.statusCode().getReasonPhrase(), response.headers().asHttpHeaders(), diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java index 4fdf4ef587..86763eb55f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java @@ -46,25 +46,28 @@ public class WebClientResponseException extends WebClientException { /** - * Construct a new instance of with the given response data. - * @param message the exception message - * @param statusCode the raw status code value - * @param statusText the status text - * @param headers 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}) + * Constructor with response data only, and a default message. + * @since 5.1 + */ + public WebClientResponseException(int statusCode, String statusText, + @Nullable HttpHeaders headers, @Nullable byte[] body, @Nullable Charset charset) { + + this(statusCode + " " + statusText, statusCode, statusText, headers, body, charset); + } + + /** + * Constructor with a prepared message. */ public WebClientResponseException(String message, int statusCode, String statusText, - @Nullable HttpHeaders headers, @Nullable byte[] responseBody, - @Nullable Charset responseCharset) { + @Nullable HttpHeaders headers, @Nullable byte[] responsebody, @Nullable Charset charset) { super(message); this.statusCode = statusCode; this.statusText = statusText; this.headers = (headers != null ? headers : HttpHeaders.EMPTY); - this.responseBody = (responseBody != null ? responseBody : new byte[0]); - this.responseCharset = (responseCharset != null ? responseCharset : StandardCharsets.ISO_8859_1); + this.responseBody = (responsebody != null ? responsebody : new byte[0]); + this.responseCharset = (charset != null ? charset : StandardCharsets.ISO_8859_1); } @@ -111,4 +114,252 @@ public class WebClientResponseException extends WebClientException { return new String(this.responseBody, this.responseCharset); } + + /** + * Create {@code WebClientResponseException} or an HTTP status specific sub-class. + * @since 5.1 + */ + public static WebClientResponseException create( + int statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + + HttpStatus httpStatus = HttpStatus.resolve(statusCode); + if (httpStatus != null) { + switch (httpStatus) { + case BAD_REQUEST: + return new WebClientResponseException.BadRequest(statusText, headers, body, charset); + case UNAUTHORIZED: + return new WebClientResponseException.Unauthorized(statusText, headers, body, charset); + case FORBIDDEN: + return new WebClientResponseException.Forbidden(statusText, headers, body, charset); + case NOT_FOUND: + return new WebClientResponseException.NotFound(statusText, headers, body, charset); + case METHOD_NOT_ALLOWED: + return new WebClientResponseException.MethodNotAllowed(statusText, headers, body, charset); + case NOT_ACCEPTABLE: + return new WebClientResponseException.NotAcceptable(statusText, headers, body, charset); + case CONFLICT: + return new WebClientResponseException.Conflict(statusText, headers, body, charset); + case GONE: + return new WebClientResponseException.Gone(statusText, headers, body, charset); + case UNSUPPORTED_MEDIA_TYPE: + return new WebClientResponseException.UnsupportedMediaType(statusText, headers, body, charset); + case TOO_MANY_REQUESTS: + return new WebClientResponseException.TooManyRequests(statusText, headers, body, charset); + case UNPROCESSABLE_ENTITY: + return new WebClientResponseException.UnprocessableEntity(statusText, headers, body, charset); + case INTERNAL_SERVER_ERROR: + return new WebClientResponseException.InternalServerError(statusText, headers, body, charset); + case NOT_IMPLEMENTED: + return new WebClientResponseException.NotImplemented(statusText, headers, body, charset); + case BAD_GATEWAY: + return new WebClientResponseException.BadGateway(statusText, headers, body, charset); + case SERVICE_UNAVAILABLE: + return new WebClientResponseException.ServiceUnavailable(statusText, headers, body, charset); + case GATEWAY_TIMEOUT: + return new WebClientResponseException.GatewayTimeout(statusText, headers, body, charset); + } + } + return new WebClientResponseException(statusCode, statusText, headers, body, charset); + } + + + + // Sub-classes for specific, client-side, HTTP status codes.. + + /** + * {@link WebClientResponseException} for status HTTP 400 Bad Request. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class BadRequest extends WebClientResponseException { + + BadRequest(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.BAD_REQUEST.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 401 Unauthorized. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class Unauthorized extends WebClientResponseException { + + Unauthorized(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.UNAUTHORIZED.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 403 Forbidden. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class Forbidden extends WebClientResponseException { + + Forbidden(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.FORBIDDEN.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 404 Not Found. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class NotFound extends WebClientResponseException { + + NotFound(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.NOT_FOUND.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 405 Method Not Allowed. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class MethodNotAllowed extends WebClientResponseException { + + MethodNotAllowed(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.METHOD_NOT_ALLOWED.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 406 Not Acceptable. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class NotAcceptable extends WebClientResponseException { + + NotAcceptable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.NOT_ACCEPTABLE.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 409 Conflict. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class Conflict extends WebClientResponseException { + + Conflict(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.CONFLICT.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 410 Gone. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class Gone extends WebClientResponseException { + + Gone(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.GONE.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 415 Unsupported Media Type. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class UnsupportedMediaType extends WebClientResponseException { + + UnsupportedMediaType(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 422 Unprocessable Entity. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class UnprocessableEntity extends WebClientResponseException { + + UnprocessableEntity(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.UNPROCESSABLE_ENTITY.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 429 Too Many Requests. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class TooManyRequests extends WebClientResponseException { + + TooManyRequests(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.TOO_MANY_REQUESTS.value(), statusText, headers, body, charset); + } + } + + + + // Sub-classes for specific, server-side, HTTP status codes.. + + /** + * {@link WebClientResponseException} for status HTTP 500 Internal Server Error. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class InternalServerError extends WebClientResponseException { + + InternalServerError(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.INTERNAL_SERVER_ERROR.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 501 Not Implemented. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class NotImplemented extends WebClientResponseException { + + NotImplemented(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.NOT_IMPLEMENTED.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP HTTP 502 Bad Gateway. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class BadGateway extends WebClientResponseException { + + BadGateway(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.BAD_GATEWAY.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 503 Service Unavailable. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class ServiceUnavailable extends WebClientResponseException { + + ServiceUnavailable(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.SERVICE_UNAVAILABLE.value(), statusText, headers, body, charset); + } + } + + /** + * {@link WebClientResponseException} for status HTTP 504 Gateway Timeout. + * @since 5.1 + */ + @SuppressWarnings("serial") + public static class GatewayTimeout extends WebClientResponseException { + + GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { + super(HttpStatus.GATEWAY_TIMEOUT.value(), statusText, headers, body, charset); + } + } + } diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index e8a880e206..9406151aaf 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -60,8 +60,10 @@ You can also get a stream of objects decoded from the response: .bodyToFlux(Quote.class); ---- -By default, responses with 4xx or 5xx status codes result in an error of type -`WebClientResponseException` but you can customize that: +By default, responses with 4xx or 5xx status codes result in an +`WebClientResponseException` or one of its HTTP status specific sub-classes such as +`WebClientResponseException.BadRequest`, `WebClientResponseException.NotFound`, and others. +You can also use the `onStatus` method to customize the resulting exception: [source,java,intent=0] [subs="verbatim,quotes"]