diff --git a/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java index 05fa7ddd58..431fdaf8da 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 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. @@ -38,13 +38,18 @@ public interface ClientHttpResponse extends HttpInputMessage, Closeable { * Return the HTTP status code of the response. * @return the HTTP status as an HttpStatus enum value * @throws IOException in case of I/O errors + * @throws IllegalArgumentException in case of an unknown HTTP status code + * @see HttpStatus#valueOf(int) */ HttpStatus getStatusCode() throws IOException; /** - * Return the HTTP status code of the response as integer + * Return the HTTP status code (potentially non-standard and not + * resolvable through the {@link HttpStatus} enum) as an integer. * @return the HTTP status as an integer * @throws IOException in case of I/O errors + * @since 3.1.1 + * @see #getStatusCode() */ int getRawStatusCode() throws IOException; diff --git a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java index bcf7cfdeb2..5b8236c345 100644 --- a/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -513,7 +513,7 @@ public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements if (logger.isDebugEnabled()) { try { logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " + - response.getStatusCode() + " (" + response.getStatusText() + ")"); + response.getRawStatusCode() + " (" + response.getStatusText() + ")"); } catch (IOException ex) { // ignore @@ -525,7 +525,7 @@ public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements if (logger.isWarnEnabled()) { try { logger.warn("Async " + method.name() + " request for \"" + url + "\" resulted in " + - response.getStatusCode() + " (" + response.getStatusText() + "); invoking error handler"); + response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler"); } catch (IOException ex) { // ignore 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 399187b46e..ced4e3f8bc 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 @@ -48,6 +48,21 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { return hasError(getHttpStatusCode(response)); } + /** + * Template method called from {@link #hasError(ClientHttpResponse)}. + *

The default implementation checks if the given status code is + * {@link HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or + * {@link 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 + * @see #getHttpStatusCode(ClientHttpResponse) + */ + protected boolean hasError(HttpStatus statusCode) { + return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR || + statusCode.series() == HttpStatus.Series.SERVER_ERROR); + } + /** * This default implementation throws a {@link HttpClientErrorException} if the response status code * is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException} @@ -89,33 +104,6 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { } } - /** - * Template method called from {@link #hasError(ClientHttpResponse)}. - *

The default implementation checks if the given status code is - * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} - * or {@link org.springframework.http.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 - * @see #getHttpStatusCode(ClientHttpResponse) - */ - protected boolean hasError(HttpStatus statusCode) { - return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR || - statusCode.series() == HttpStatus.Series.SERVER_ERROR); - } - - /** - * Determine the charset of the response (for inclusion in a status exception). - * @param response the response to inspect - * @return the associated charset, or {@code null} if none - * @since 4.3.8 - */ - protected Charset getCharset(ClientHttpResponse response) { - HttpHeaders headers = response.getHeaders(); - MediaType contentType = headers.getContentType(); - return (contentType != null ? contentType.getCharset() : null); - } - /** * Read the body of the given response (for inclusion in a status exception). * @param response the response to inspect @@ -133,4 +121,16 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler { return new byte[0]; } + /** + * Determine the charset of the response (for inclusion in a status exception). + * @param response the response to inspect + * @return the associated charset, or {@code null} if none + * @since 4.3.8 + */ + protected Charset getCharset(ClientHttpResponse response) { + HttpHeaders headers = response.getHeaders(); + MediaType contentType = headers.getContentType(); + return (contentType != null ? contentType.getCharset() : null); + } + } diff --git a/spring-web/src/main/java/org/springframework/web/client/MessageBodyClientHttpResponseWrapper.java b/spring-web/src/main/java/org/springframework/web/client/MessageBodyClientHttpResponseWrapper.java index 8b732d8b64..8d3afc897e 100644 --- a/spring-web/src/main/java/org/springframework/web/client/MessageBodyClientHttpResponseWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/client/MessageBodyClientHttpResponseWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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. @@ -31,7 +31,7 @@ import org.springframework.http.client.ClientHttpResponse; * * @author Brian Clozel * @since 4.1.5 - * @see rfc7230 Section 3.3.3 + * @see RFC 7230 Section 3.3.3 */ class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse { @@ -56,12 +56,17 @@ class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse { * @throws IOException in case of I/O errors */ public boolean hasMessageBody() throws IOException { - HttpStatus responseStatus = this.getStatusCode(); - if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT || - responseStatus == HttpStatus.NOT_MODIFIED) { - return false; + try { + HttpStatus responseStatus = getStatusCode(); + if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT || + responseStatus == HttpStatus.NOT_MODIFIED) { + return false; + } } - else if (this.getHeaders().getContentLength() == 0) { + catch (IllegalArgumentException ex) { + // Ignore - unknown HTTP status code... + } + if (getHeaders().getContentLength() == 0) { return false; } return true; diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 333ab0c23c..f2457f377f 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -583,7 +583,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public ResponseEntity exchange(RequestEntity requestEntity, Class responseType) throws RestClientException { - Assert.notNull(requestEntity, "'requestEntity' must not be null"); + Assert.notNull(requestEntity, "RequestEntity must not be null"); RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); @@ -594,7 +594,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat public ResponseEntity exchange(RequestEntity requestEntity, ParameterizedTypeReference responseType) throws RestClientException { - Assert.notNull(requestEntity, "'requestEntity' must not be null"); + Assert.notNull(requestEntity, "RequestEntity must not be null"); Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(requestEntity, type); 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 b8e894b925..e23a433884 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-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -37,9 +37,9 @@ public class UnknownHttpStatusCodeException extends RestClientResponseException * {@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} + * @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}) */ public UnknownHttpStatusCodeException(int rawStatusCode, String statusText, HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransport.java index 3b48a7706d..cc62e43a69 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransport.java @@ -37,6 +37,7 @@ import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.UnknownHttpStatusCodeException; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; @@ -205,14 +206,22 @@ public class RestTemplateXhrTransport extends AbstractXhrTransport { @Override public Object extractData(ClientHttpResponse response) throws IOException { - if (!HttpStatus.OK.equals(response.getStatusCode())) { - throw new HttpServerErrorException(response.getStatusCode()); + try { + if (!HttpStatus.OK.equals(response.getStatusCode())) { + throw new HttpServerErrorException(response.getStatusCode()); + } } + catch (IllegalArgumentException ex) { + throw new UnknownHttpStatusCodeException( + response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), null, null); + } + if (logger.isTraceEnabled()) { logger.trace("XHR receive headers: " + response.getHeaders()); } InputStream is = response.getBody(); ByteArrayOutputStream os = new ByteArrayOutputStream(); + while (true) { if (this.sockJsSession.isDisconnected()) { if (logger.isDebugEnabled()) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java index 1337fcb2d8..bd9e0ebeed 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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. @@ -26,7 +26,6 @@ import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; -import org.junit.Before; import org.junit.Test; import org.springframework.core.task.SyncTaskExecutor; @@ -67,13 +66,7 @@ public class RestTemplateXhrTransportTests { private static final Jackson2SockJsMessageCodec CODEC = new Jackson2SockJsMessageCodec(); - private WebSocketHandler webSocketHandler; - - - @Before - public void setup() throws Exception { - this.webSocketHandler = mock(WebSocketHandler.class); - } + private final WebSocketHandler webSocketHandler = mock(WebSocketHandler.class); @Test