diff --git a/spring-web/src/main/java/org/springframework/http/HttpStatus.java b/spring-web/src/main/java/org/springframework/http/HttpStatus.java index e7b0f4aec5..fd23708b1b 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpStatus.java +++ b/spring-web/src/main/java/org/springframework/http/HttpStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -435,50 +435,62 @@ public enum HttpStatus { return this.reasonPhrase; } + /** + * Return the HTTP status series of this status code. + * @see HttpStatus.Series + */ + public Series series() { + return Series.valueOf(this); + } + /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is1xxInformational() { - return Series.INFORMATIONAL.equals(series()); + return (series() == Series.INFORMATIONAL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is2xxSuccessful() { - return Series.SUCCESSFUL.equals(series()); + return (series() == Series.SUCCESSFUL); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is3xxRedirection() { - return Series.REDIRECTION.equals(series()); + return (series() == Series.REDIRECTION); } - /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is4xxClientError() { - return Series.CLIENT_ERROR.equals(series()); + return (series() == Series.CLIENT_ERROR); } /** * Whether this status code is in the HTTP series * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. * This is a shortcut for checking the value of {@link #series()}. + * @see #series() */ public boolean is5xxServerError() { - return Series.SERVER_ERROR.equals(series()); + return (series() == Series.SERVER_ERROR); } /** @@ -486,17 +498,12 @@ public enum HttpStatus { * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR} or * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. * This is a shortcut for checking the value of {@link #series()}. + * @since 5.0 + * @see #is4xxClientError() + * @see #is5xxServerError() */ public boolean isError() { - return is4xxClientError() || is5xxServerError(); - } - - /** - * Returns the HTTP status series of this status code. - * @see HttpStatus.Series - */ - public Series series() { - return Series.valueOf(this); + return (is4xxClientError() || is5xxServerError()); } /** @@ -522,7 +529,6 @@ public enum HttpStatus { return status; } - /** * Resolve the given status code to an {@code HttpStatus}, if possible. * @param statusCode the HTTP status code (potentially non-standard) @@ -565,18 +571,30 @@ public enum HttpStatus { return this.value; } - public static Series valueOf(int status) { - int seriesCode = status / 100; + /** + * Return the enum constant of this type with the corresponding series. + * @param status a standard HTTP status enum value + * @return the enum constant of this type with the corresponding series + * @throws IllegalArgumentException if this enum has no corresponding constant + */ + public static Series valueOf(HttpStatus status) { + return valueOf(status.value); + } + + /** + * Return the enum constant of this type with the corresponding series. + * @param statusCode the HTTP status code (potentially non-standard) + * @return the enum constant of this type with the corresponding series + * @throws IllegalArgumentException if this enum has no corresponding constant + */ + public static Series valueOf(int statusCode) { + int seriesCode = statusCode / 100; for (Series series : values()) { if (series.value == seriesCode) { return series; } } - throw new IllegalArgumentException("No matching constant for [" + status + "]"); - } - - public static Series valueOf(HttpStatus status) { - return valueOf(status.value); + throw new IllegalArgumentException("No matching constant for [" + statusCode + "]"); } } 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 fd6d84354b..9b45ee9ba9 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 @@ -44,12 +44,29 @@ import org.springframework.util.FileCopyUtils; public class DefaultResponseErrorHandler implements ResponseErrorHandler { /** - * Delegates to {@link #hasError(HttpStatus)} with the response status code. + * Delegates to {@link #hasError(HttpStatus)} (for a standard status enum value) or + * {@link #hasError(int)} (for an unknown status code) with the response status code. + * @see ClientHttpResponse#getRawStatusCode() + * @see #hasError(HttpStatus) + * @see #hasError(int) */ @Override public boolean hasError(ClientHttpResponse response) throws IOException { - HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode()); - return (statusCode != null && hasError(statusCode)); + int rawStatusCode = response.getRawStatusCode(); + HttpStatus statusCode = HttpStatus.resolve(rawStatusCode); + return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode)); + } + + /** + * Template method called from {@link #hasError(ClientHttpResponse)}. + *
The default implementation checks {@link HttpStatus#isError()}. + * Can be overridden in subclasses. + * @param statusCode the HTTP status code as enum value + * @return {@code true} if the response indicates an error; {@code false} otherwise + * @see HttpStatus#isError() + */ + protected boolean hasError(HttpStatus statusCode) { + return statusCode.isError(); } /** @@ -58,16 +75,23 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { * {@code HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or * {@code HttpStatus.Series#SERVER_ERROR SERVER_ERROR}. * Can be overridden in subclasses. - * @param statusCode the HTTP status code - * @return {@code true} if the response has an error; {@code false} otherwise + * @param unknownStatusCode the HTTP status code as raw value + * @return {@code true} if the response indicates an error; {@code false} otherwise + * @since 4.3.21 + * @see HttpStatus.Series#CLIENT_ERROR + * @see HttpStatus.Series#SERVER_ERROR */ - protected boolean hasError(HttpStatus statusCode) { - return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR || - statusCode.series() == HttpStatus.Series.SERVER_ERROR); + protected boolean hasError(int unknownStatusCode) { + int seriesCode = unknownStatusCode / 100; + return (seriesCode == HttpStatus.Series.CLIENT_ERROR.value() || + seriesCode == HttpStatus.Series.SERVER_ERROR.value()); } /** - * Delegates to {@link #handleError(ClientHttpResponse, HttpStatus)} with the response status code. + * Delegates to {@link #handleError(ClientHttpResponse, HttpStatus)} with the + * response status code. + * @throws UnknownHttpStatusCodeException in case of an unresolvable status code + * @see #handleError(ClientHttpResponse, HttpStatus) */ @Override public void handleError(ClientHttpResponse response) throws IOException { @@ -81,10 +105,10 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { /** * 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. + *
The default implementation throws an {@link HttpClientErrorException} + * if the status code is {@link HttpStatus.Series#CLIENT_ERROR}, an + * {@link HttpServerErrorException} if it is {@link HttpStatus.Series#SERVER_ERROR}, + * and an {@link UnknownHttpStatusCodeException} in other cases. * @since 5.0 */ protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException { diff --git a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java index a68c08dbb1..6a11b0f09f 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -36,7 +36,7 @@ public interface ResponseErrorHandler { *
Implementations will typically inspect the * {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response. * @param response the response to inspect - * @return {@code true} if the response has an error; {@code false} otherwise + * @return {@code true} if the response indicates an error; {@code false} otherwise * @throws IOException in case of I/O errors */ boolean hasError(ClientHttpResponse response) throws IOException; 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 4731d43661..0391e74021 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 @@ -65,7 +65,7 @@ public class DefaultResponseErrorHandlerTests { 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("UTF-8"))); + given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8))); try { handler.handleError(response); @@ -101,18 +101,6 @@ public class DefaultResponseErrorHandlerTests { handler.handleError(response); } - @Test(expected = UnknownHttpStatusCodeException.class) // SPR-9406 - public void unknownStatusCode() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.TEXT_PLAIN); - - given(response.getRawStatusCode()).willReturn(999); - given(response.getStatusText()).willReturn("Custom status code"); - given(response.getHeaders()).willReturn(headers); - - handler.handleError(response); - } - @Test // SPR-16108 public void hasErrorForUnknownStatusCode() throws Exception { HttpHeaders headers = new HttpHeaders(); @@ -125,6 +113,66 @@ public class DefaultResponseErrorHandlerTests { assertFalse(handler.hasError(response)); } + @Test(expected = UnknownHttpStatusCodeException.class) // SPR-9406 + public void handleErrorUnknownStatusCode() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(999); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + handler.handleError(response); + } + + @Test // SPR-17461 + public void hasErrorForCustomClientError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(499); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + assertTrue(handler.hasError(response)); + } + + @Test(expected = UnknownHttpStatusCodeException.class) + public void handleErrorForCustomClientError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(499); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + handler.handleError(response); + } + + @Test // SPR-17461 + public void hasErrorForCustomServerError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(599); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + assertTrue(handler.hasError(response)); + } + + @Test(expected = UnknownHttpStatusCodeException.class) + public void handleErrorForCustomServerError() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.TEXT_PLAIN); + + given(response.getRawStatusCode()).willReturn(599); + given(response.getStatusText()).willReturn("Custom status code"); + given(response.getHeaders()).willReturn(headers); + + handler.handleError(response); + } + @Test // SPR-16604 public void bodyAvailableAfterHasErrorForUnknownStatusCode() throws Exception { HttpHeaders headers = new HttpHeaders();