Polishing in DefaultResponseErrorHandler

See gh-33980
This commit is contained in:
rstoyanchev
2024-11-28 17:03:39 +00:00
parent 5fa9460bf6
commit a0cc6419f4
4 changed files with 127 additions and 131 deletions

View File

@@ -38,7 +38,6 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ObjectUtils;
/**
@@ -135,8 +134,7 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
*/
@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
handleError(response, statusCode, null, null);
handleError(response, response.getStatusCode(), null, null);
}
/**
@@ -159,17 +157,78 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
*/
@Override
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
HttpStatusCode statusCode = response.getStatusCode();
handleError(response, statusCode, url, method);
handleError(response, response.getStatusCode(), url, method);
}
/**
* Return error message with details from the response body. For example:
* Handle the error based on the resolved status code.
* <p>The default implementation delegates to
* {@link HttpClientErrorException#create} for errors in the 4xx range, to
* {@link HttpServerErrorException#create} for errors in the 5xx range,
* or otherwise raises {@link UnknownHttpStatusCodeException}.
* @since 6.2
* @see HttpClientErrorException#create
* @see HttpServerErrorException#create
*/
protected void handleError(
ClientHttpResponse response, HttpStatusCode statusCode,
@Nullable URI url, @Nullable HttpMethod method) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
String message = getErrorMessage(statusCode.value(), statusText, body, charset, url, method);
RestClientResponseException ex;
if (statusCode.is4xxClientError()) {
ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else if (statusCode.is5xxServerError()) {
ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else {
ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
}
if (!CollectionUtils.isEmpty(this.messageConverters)) {
ex.setBodyConvertFunction(initBodyConvertFunction(response, body));
}
throw ex;
}
/**
* Read the body of the given response (for inclusion in a status exception).
* @param response the response to inspect
* @return the response body as a byte array,
* or an empty byte array if the body could not be read
* @since 4.3.8
*/
protected byte[] getResponseBody(ClientHttpResponse response) {
return RestClientUtils.getBody(response);
}
/**
* 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
*/
@Nullable
protected Charset getCharset(ClientHttpResponse response) {
MediaType contentType = response.getHeaders().getContentType();
return (contentType != null ? contentType.getCharset() : null);
}
/**
* Return an error message with details from the response body. For example:
* <pre>
* 404 Not Found on GET request for "https://example.com": [{'id': 123, 'message': 'my message'}]
* </pre>
*/
private String getErrorMessage(int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset,
private String getErrorMessage(
int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset,
@Nullable URI url, @Nullable HttpMethod method) {
StringBuilder msg = new StringBuilder(rawStatusCode + " " + statusText);
@@ -201,43 +260,6 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
return msg.toString();
}
/**
* Handle the error based on the resolved status code.
* <p>The default implementation delegates to
* {@link HttpClientErrorException#create} for errors in the 4xx range, to
* {@link HttpServerErrorException#create} for errors in the 5xx range,
* or otherwise raises {@link UnknownHttpStatusCodeException}.
* @since 6.2
* @see HttpClientErrorException#create
* @see HttpServerErrorException#create
*/
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode,
@Nullable URI url, @Nullable HttpMethod method) throws IOException {
String statusText = response.getStatusText();
HttpHeaders headers = response.getHeaders();
byte[] body = getResponseBody(response);
Charset charset = getCharset(response);
String message = getErrorMessage(statusCode.value(), statusText, body, charset, url, method);
RestClientResponseException ex;
if (statusCode.is4xxClientError()) {
ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else if (statusCode.is5xxServerError()) {
ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
}
else {
ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
}
if (!CollectionUtils.isEmpty(this.messageConverters)) {
ex.setBodyConvertFunction(initBodyConvertFunction(response, body));
}
throw ex;
}
/**
* Return a function for decoding the error content. This can be passed to
* {@link RestClientResponseException#setBodyConvertFunction(Function)}.
@@ -265,34 +287,4 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
};
}
/**
* Read the body of the given response (for inclusion in a status exception).
* @param response the response to inspect
* @return the response body as a byte array,
* or an empty byte array if the body could not be read
* @since 4.3.8
*/
protected byte[] getResponseBody(ClientHttpResponse response) {
try {
return FileCopyUtils.copyToByteArray(response.getBody());
}
catch (IOException ex) {
// ignore
}
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
*/
@Nullable
protected Charset getCharset(ClientHttpResponse response) {
HttpHeaders headers = response.getHeaders();
MediaType contentType = headers.getContentType();
return (contentType != null ? contentType.getCharset() : null);
}
}

View File

@@ -32,26 +32,27 @@ import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Implementation of {@link ResponseErrorHandler} that uses {@link HttpMessageConverter
* HttpMessageConverters} to convert HTTP error responses to {@link RestClientException
* RestClientExceptions}.
* Implementation of {@link ResponseErrorHandler} that uses
* {@link HttpMessageConverter HttpMessageConverters} to convert HTTP error
* responses to {@link RestClientException RestClientExceptions}.
*
* <p>To use this error handler, you must specify a
* {@linkplain #setStatusMapping(Map) status mapping} and/or a
* {@linkplain #setSeriesMapping(Map) series mapping}. If either of these mappings has a match
* for the {@linkplain ClientHttpResponse#getStatusCode() status code} of a given
* {@code ClientHttpResponse}, {@link #hasError(ClientHttpResponse)} will return
* {@code true}, and {@link #handleError(ClientHttpResponse)} will attempt to use the
* {@linkplain #setMessageConverters(List) configured message converters} to convert the response
* into the mapped subclass of {@link RestClientException}. Note that the
* {@linkplain #setStatusMapping(Map) status mapping} takes precedence over
* {@linkplain #setSeriesMapping(Map) series mapping}.
* {@linkplain #setSeriesMapping(Map) series mapping}. If either of these
* mappings has a match for the {@linkplain ClientHttpResponse#getStatusCode()
* status code} of a given {@code ClientHttpResponse},
* {@link #hasError(ClientHttpResponse)} will return {@code true}, and
* {@link #handleError(ClientHttpResponse)} will attempt to use the
* {@linkplain #setMessageConverters(List) configured message converters} to
* convert the response into the mapped subclass of {@link RestClientException}.
* Note that the {@linkplain #setStatusMapping(Map) status mapping} takes
* precedence over {@linkplain #setSeriesMapping(Map) series mapping}.
*
* <p>If there is no match, this error handler will default to the behavior of
* {@link DefaultResponseErrorHandler}. Note that you can override this default behavior
* by specifying a {@linkplain #setSeriesMapping(Map) series mapping} from
* {@code HttpStatus.Series#CLIENT_ERROR} and/or {@code HttpStatus.Series#SERVER_ERROR}
* to {@code null}.
* {@link DefaultResponseErrorHandler}. Note that you can override this default
* behavior by specifying a {@linkplain #setSeriesMapping(Map) series mapping}
* from {@code HttpStatus.Series#CLIENT_ERROR} and/or
* {@code HttpStatus.Series#SERVER_ERROR} to {@code null}.
*
* @author Simon Galperin
* @author Arjen Poutsma
@@ -126,11 +127,11 @@ public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler
@Override
protected boolean hasError(HttpStatusCode statusCode) {
if (this.statusMapping.containsKey(statusCode)) {
return this.statusMapping.get(statusCode) != null;
return (this.statusMapping.get(statusCode) != null);
}
HttpStatus.Series series = HttpStatus.Series.resolve(statusCode.value());
if (this.seriesMapping.containsKey(series)) {
return this.seriesMapping.get(series) != null;
return (this.seriesMapping.get(series) != null);
}
else {
return super.hasError(statusCode);
@@ -138,15 +139,14 @@ public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler
}
@Override
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
handleError(response, response.getStatusCode(), url, method);
}
protected void handleError(
ClientHttpResponse response, HttpStatusCode statusCode,
@Nullable URI url, @Nullable HttpMethod method) throws IOException {
@Override
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode, @Nullable URI url, @Nullable HttpMethod method) throws IOException {
if (this.statusMapping.containsKey(statusCode)) {
extract(this.statusMapping.get(statusCode), response);
}
HttpStatus.Series series = HttpStatus.Series.resolve(statusCode.value());
if (this.seriesMapping.containsKey(series)) {
extract(this.seriesMapping.get(series), response);
@@ -156,8 +156,9 @@ public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler
}
}
private void extract(@Nullable Class<? extends RestClientException> exceptionClass,
ClientHttpResponse response) throws IOException {
private void extract(
@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response)
throws IOException {
if (exceptionClass == null) {
return;
@@ -165,6 +166,7 @@ public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler
HttpMessageConverterExtractor<? extends RestClientException> extractor =
new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters);
RestClientException exception = extractor.extractData(response);
if (exception != null) {
throw exception;