Skip to content

Commit 18a3322

Browse files
committed
Lenient tolerance of unknown HTTP status codes behind RestTemplate
Issue: SPR-15978
1 parent 87df393 commit 18a3322

File tree

8 files changed

+69
-57
lines changed

8 files changed

+69
-57
lines changed

spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,13 +38,18 @@ public interface ClientHttpResponse extends HttpInputMessage, Closeable {
3838
* Return the HTTP status code of the response.
3939
* @return the HTTP status as an HttpStatus enum value
4040
* @throws IOException in case of I/O errors
41+
* @throws IllegalArgumentException in case of an unknown HTTP status code
42+
* @see HttpStatus#valueOf(int)
4143
*/
4244
HttpStatus getStatusCode() throws IOException;
4345

4446
/**
45-
* Return the HTTP status code of the response as integer
47+
* Return the HTTP status code (potentially non-standard and not
48+
* resolvable through the {@link HttpStatus} enum) as an integer.
4649
* @return the HTTP status as an integer
4750
* @throws IOException in case of I/O errors
51+
* @since 3.1.1
52+
* @see #getStatusCode()
4853
*/
4954
int getRawStatusCode() throws IOException;
5055

spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -513,7 +513,7 @@ private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse re
513513
if (logger.isDebugEnabled()) {
514514
try {
515515
logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " +
516-
response.getStatusCode() + " (" + response.getStatusText() + ")");
516+
response.getRawStatusCode() + " (" + response.getStatusText() + ")");
517517
}
518518
catch (IOException ex) {
519519
// ignore
@@ -525,7 +525,7 @@ private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse
525525
if (logger.isWarnEnabled()) {
526526
try {
527527
logger.warn("Async " + method.name() + " request for \"" + url + "\" resulted in " +
528-
response.getStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
528+
response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
529529
}
530530
catch (IOException ex) {
531531
// ignore

spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ public boolean hasError(ClientHttpResponse response) throws IOException {
4848
return hasError(getHttpStatusCode(response));
4949
}
5050

51+
/**
52+
* Template method called from {@link #hasError(ClientHttpResponse)}.
53+
* <p>The default implementation checks if the given status code is
54+
* {@link HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or
55+
* {@link HttpStatus.Series#SERVER_ERROR SERVER_ERROR}.
56+
* Can be overridden in subclasses.
57+
* @param statusCode the HTTP status code
58+
* @return {@code true} if the response has an error; {@code false} otherwise
59+
* @see #getHttpStatusCode(ClientHttpResponse)
60+
*/
61+
protected boolean hasError(HttpStatus statusCode) {
62+
return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
63+
statusCode.series() == HttpStatus.Series.SERVER_ERROR);
64+
}
65+
5166
/**
5267
* This default implementation throws a {@link HttpClientErrorException} if the response status code
5368
* is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException}
@@ -89,33 +104,6 @@ protected HttpStatus getHttpStatusCode(ClientHttpResponse response) throws IOExc
89104
}
90105
}
91106

92-
/**
93-
* Template method called from {@link #hasError(ClientHttpResponse)}.
94-
* <p>The default implementation checks if the given status code is
95-
* {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR}
96-
* or {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR SERVER_ERROR}.
97-
* Can be overridden in subclasses.
98-
* @param statusCode the HTTP status code
99-
* @return {@code true} if the response has an error; {@code false} otherwise
100-
* @see #getHttpStatusCode(ClientHttpResponse)
101-
*/
102-
protected boolean hasError(HttpStatus statusCode) {
103-
return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
104-
statusCode.series() == HttpStatus.Series.SERVER_ERROR);
105-
}
106-
107-
/**
108-
* Determine the charset of the response (for inclusion in a status exception).
109-
* @param response the response to inspect
110-
* @return the associated charset, or {@code null} if none
111-
* @since 4.3.8
112-
*/
113-
protected Charset getCharset(ClientHttpResponse response) {
114-
HttpHeaders headers = response.getHeaders();
115-
MediaType contentType = headers.getContentType();
116-
return (contentType != null ? contentType.getCharset() : null);
117-
}
118-
119107
/**
120108
* Read the body of the given response (for inclusion in a status exception).
121109
* @param response the response to inspect
@@ -133,4 +121,16 @@ protected byte[] getResponseBody(ClientHttpResponse response) {
133121
return new byte[0];
134122
}
135123

124+
/**
125+
* Determine the charset of the response (for inclusion in a status exception).
126+
* @param response the response to inspect
127+
* @return the associated charset, or {@code null} if none
128+
* @since 4.3.8
129+
*/
130+
protected Charset getCharset(ClientHttpResponse response) {
131+
HttpHeaders headers = response.getHeaders();
132+
MediaType contentType = headers.getContentType();
133+
return (contentType != null ? contentType.getCharset() : null);
134+
}
135+
136136
}

spring-web/src/main/java/org/springframework/web/client/MessageBodyClientHttpResponseWrapper.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
3131
*
3232
* @author Brian Clozel
3333
* @since 4.1.5
34-
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.3">rfc7230 Section 3.3.3</a>
34+
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230 Section 3.3.3</a>
3535
*/
3636
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
3737

@@ -56,12 +56,17 @@ public MessageBodyClientHttpResponseWrapper(ClientHttpResponse response) throws
5656
* @throws IOException in case of I/O errors
5757
*/
5858
public boolean hasMessageBody() throws IOException {
59-
HttpStatus responseStatus = this.getStatusCode();
60-
if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT ||
61-
responseStatus == HttpStatus.NOT_MODIFIED) {
62-
return false;
59+
try {
60+
HttpStatus responseStatus = getStatusCode();
61+
if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT ||
62+
responseStatus == HttpStatus.NOT_MODIFIED) {
63+
return false;
64+
}
65+
}
66+
catch (IllegalArgumentException ex) {
67+
// Ignore - unknown HTTP status code...
6368
}
64-
else if (this.getHeaders().getContentLength() == 0) {
69+
if (getHeaders().getContentLength() == 0) {
6570
return false;
6671
}
6772
return true;

spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -583,7 +583,7 @@ public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?>
583583
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType)
584584
throws RestClientException {
585585

586-
Assert.notNull(requestEntity, "'requestEntity' must not be null");
586+
Assert.notNull(requestEntity, "RequestEntity must not be null");
587587

588588
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
589589
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
@@ -594,7 +594,7 @@ public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> r
594594
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
595595
throws RestClientException {
596596

597-
Assert.notNull(requestEntity, "'requestEntity' must not be null");
597+
Assert.notNull(requestEntity, "RequestEntity must not be null");
598598

599599
Type type = responseType.getType();
600600
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);

spring-web/src/main/java/org/springframework/web/client/UnknownHttpStatusCodeException.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,9 +37,9 @@ public class UnknownHttpStatusCodeException extends RestClientResponseException
3737
* {@link HttpStatus}, status text, and response body content.
3838
* @param rawStatusCode the raw status code value
3939
* @param statusText the status text
40-
* @param responseHeaders the response headers, may be {@code null}
41-
* @param responseBody the response body content, may be {@code null}
42-
* @param responseCharset the response body charset, may be {@code null}
40+
* @param responseHeaders the response headers (may be {@code null})
41+
* @param responseBody the response body content (may be {@code null})
42+
* @param responseCharset the response body charset (may be {@code null})
4343
*/
4444
public UnknownHttpStatusCodeException(int rawStatusCode, String statusText,
4545
HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransport.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.web.client.ResponseExtractor;
3838
import org.springframework.web.client.RestOperations;
3939
import org.springframework.web.client.RestTemplate;
40+
import org.springframework.web.client.UnknownHttpStatusCodeException;
4041
import org.springframework.web.socket.CloseStatus;
4142
import org.springframework.web.socket.TextMessage;
4243
import org.springframework.web.socket.WebSocketHandler;
@@ -205,14 +206,22 @@ public XhrReceiveExtractor(XhrClientSockJsSession sockJsSession) {
205206

206207
@Override
207208
public Object extractData(ClientHttpResponse response) throws IOException {
208-
if (!HttpStatus.OK.equals(response.getStatusCode())) {
209-
throw new HttpServerErrorException(response.getStatusCode());
209+
try {
210+
if (!HttpStatus.OK.equals(response.getStatusCode())) {
211+
throw new HttpServerErrorException(response.getStatusCode());
212+
}
213+
}
214+
catch (IllegalArgumentException ex) {
215+
throw new UnknownHttpStatusCodeException(
216+
response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), null, null);
210217
}
218+
211219
if (logger.isTraceEnabled()) {
212220
logger.trace("XHR receive headers: " + response.getHeaders());
213221
}
214222
InputStream is = response.getBody();
215223
ByteArrayOutputStream os = new ByteArrayOutputStream();
224+
216225
while (true) {
217226
if (this.sockJsSession.isDisconnected()) {
218227
if (logger.isDebugEnabled()) {

spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/RestTemplateXhrTransportTests.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@
2626
import java.util.concurrent.CountDownLatch;
2727
import java.util.concurrent.LinkedBlockingDeque;
2828

29-
import org.junit.Before;
3029
import org.junit.Test;
3130

3231
import org.springframework.core.task.SyncTaskExecutor;
@@ -67,13 +66,7 @@ public class RestTemplateXhrTransportTests {
6766

6867
private static final Jackson2SockJsMessageCodec CODEC = new Jackson2SockJsMessageCodec();
6968

70-
private WebSocketHandler webSocketHandler;
71-
72-
73-
@Before
74-
public void setup() throws Exception {
75-
this.webSocketHandler = mock(WebSocketHandler.class);
76-
}
69+
private final WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
7770

7871

7972
@Test

0 commit comments

Comments
 (0)