Lenient tolerance of unknown HTTP status codes behind RestTemplate
Issue: SPR-15978
This commit is contained in:
parent
87df393f91
commit
18a3322d2f
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 code of the response.
|
||||||
* @return the HTTP status as an HttpStatus enum value
|
* @return the HTTP status as an HttpStatus enum value
|
||||||
* @throws IOException in case of I/O errors
|
* @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;
|
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
|
* @return the HTTP status as an integer
|
||||||
* @throws IOException in case of I/O errors
|
* @throws IOException in case of I/O errors
|
||||||
|
* @since 3.1.1
|
||||||
|
* @see #getStatusCode()
|
||||||
*/
|
*/
|
||||||
int getRawStatusCode() throws IOException;
|
int getRawStatusCode() throws IOException;
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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()) {
|
if (logger.isDebugEnabled()) {
|
||||||
try {
|
try {
|
||||||
logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " +
|
logger.debug("Async " + method.name() + " request for \"" + url + "\" resulted in " +
|
||||||
response.getStatusCode() + " (" + response.getStatusText() + ")");
|
response.getRawStatusCode() + " (" + response.getStatusText() + ")");
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
// ignore
|
// ignore
|
||||||
|
@ -525,7 +525,7 @@ public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements
|
||||||
if (logger.isWarnEnabled()) {
|
if (logger.isWarnEnabled()) {
|
||||||
try {
|
try {
|
||||||
logger.warn("Async " + method.name() + " request for \"" + url + "\" resulted in " +
|
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) {
|
catch (IOException ex) {
|
||||||
// ignore
|
// ignore
|
||||||
|
|
|
@ -48,6 +48,21 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
|
||||||
return hasError(getHttpStatusCode(response));
|
return hasError(getHttpStatusCode(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template method called from {@link #hasError(ClientHttpResponse)}.
|
||||||
|
* <p>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
|
* This default implementation throws a {@link HttpClientErrorException} if the response status code
|
||||||
* is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException}
|
* 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)}.
|
|
||||||
* <p>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).
|
* Read the body of the given response (for inclusion in a status exception).
|
||||||
* @param response the response to inspect
|
* @param response the response to inspect
|
||||||
|
@ -133,4 +121,16 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
|
||||||
return new byte[0];
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @author Brian Clozel
|
||||||
* @since 4.1.5
|
* @since 4.1.5
|
||||||
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.3">rfc7230 Section 3.3.3</a>
|
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230 Section 3.3.3</a>
|
||||||
*/
|
*/
|
||||||
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
|
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
|
||||||
|
|
||||||
|
@ -56,12 +56,17 @@ class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
|
||||||
* @throws IOException in case of I/O errors
|
* @throws IOException in case of I/O errors
|
||||||
*/
|
*/
|
||||||
public boolean hasMessageBody() throws IOException {
|
public boolean hasMessageBody() throws IOException {
|
||||||
HttpStatus responseStatus = this.getStatusCode();
|
try {
|
||||||
if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT ||
|
HttpStatus responseStatus = getStatusCode();
|
||||||
responseStatus == HttpStatus.NOT_MODIFIED) {
|
if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT ||
|
||||||
return false;
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType)
|
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, Class<T> responseType)
|
||||||
throws RestClientException {
|
throws RestClientException {
|
||||||
|
|
||||||
Assert.notNull(requestEntity, "'requestEntity' must not be null");
|
Assert.notNull(requestEntity, "RequestEntity must not be null");
|
||||||
|
|
||||||
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
|
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
|
||||||
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
|
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
|
||||||
|
@ -594,7 +594,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||||
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
|
public <T> ResponseEntity<T> exchange(RequestEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
|
||||||
throws RestClientException {
|
throws RestClientException {
|
||||||
|
|
||||||
Assert.notNull(requestEntity, "'requestEntity' must not be null");
|
Assert.notNull(requestEntity, "RequestEntity must not be null");
|
||||||
|
|
||||||
Type type = responseType.getType();
|
Type type = responseType.getType();
|
||||||
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
|
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* {@link HttpStatus}, status text, and response body content.
|
||||||
* @param rawStatusCode the raw status code value
|
* @param rawStatusCode the raw status code value
|
||||||
* @param statusText the status text
|
* @param statusText the status text
|
||||||
* @param responseHeaders the response headers, may be {@code null}
|
* @param responseHeaders the response headers (may be {@code null})
|
||||||
* @param responseBody the response body content, 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 responseCharset the response body charset (may be {@code null})
|
||||||
*/
|
*/
|
||||||
public UnknownHttpStatusCodeException(int rawStatusCode, String statusText,
|
public UnknownHttpStatusCodeException(int rawStatusCode, String statusText,
|
||||||
HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {
|
HttpHeaders responseHeaders, byte[] responseBody, Charset responseCharset) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.springframework.web.client.RequestCallback;
|
||||||
import org.springframework.web.client.ResponseExtractor;
|
import org.springframework.web.client.ResponseExtractor;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.client.UnknownHttpStatusCodeException;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
|
@ -205,14 +206,22 @@ public class RestTemplateXhrTransport extends AbstractXhrTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object extractData(ClientHttpResponse response) throws IOException {
|
public Object extractData(ClientHttpResponse response) throws IOException {
|
||||||
if (!HttpStatus.OK.equals(response.getStatusCode())) {
|
try {
|
||||||
throw new HttpServerErrorException(response.getStatusCode());
|
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()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("XHR receive headers: " + response.getHeaders());
|
logger.trace("XHR receive headers: " + response.getHeaders());
|
||||||
}
|
}
|
||||||
InputStream is = response.getBody();
|
InputStream is = response.getBody();
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (this.sockJsSession.isDisconnected()) {
|
if (this.sockJsSession.isDisconnected()) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.CountDownLatch;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.task.SyncTaskExecutor;
|
import org.springframework.core.task.SyncTaskExecutor;
|
||||||
|
@ -67,13 +66,7 @@ public class RestTemplateXhrTransportTests {
|
||||||
|
|
||||||
private static final Jackson2SockJsMessageCodec CODEC = new Jackson2SockJsMessageCodec();
|
private static final Jackson2SockJsMessageCodec CODEC = new Jackson2SockJsMessageCodec();
|
||||||
|
|
||||||
private WebSocketHandler webSocketHandler;
|
private final WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
|
||||||
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Exception {
|
|
||||||
this.webSocketHandler = mock(WebSocketHandler.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue