一、问题的出现
最近在使用RestTemplate发送HTTP请求时,服务端发送回来的响应结果中,HTTP响应码为400、500之类的,RestTemplate默认不处理这类响应结果,直接抛异常。但是,该请求的响应结果内容却是我需要用到的,因为我需要通过该请求的响应结果内容,告诉用户远程调用接口时,出现错误时问题是什么,以及通过请求返回的自定义结果集,来进行其他操作。
现在我们开发中,不管用户执行该请求是否成功,返回的HTTP状态码都是200,但是会在返回的自定义结果集中的code、message去体现具体操作成功与否。
但是该远程服务器返回的结果不太一样,如果用户的操作失败,首先返回的HTTP状态码是400之类的,但是返回的结果是自定义的结果集。
这就导致了,如果不对RestTemplate进行任何配置的话,RestTemplate在遇到HTTP状态码为400、500之类的状态码,直接就抛异常了,就算请求有返回的结果集,我也拿不到.
二、解决方案
给RestTemplate设置一个自定义的ResponeErrorHandler
/**
* RestTemplate配置类
*/
@Slf4j
@Configuration
public class RestTemplateConfig {
/**
* 常用远程调用RestTemplate
* @return restTemplate
*/
@Bean("restTemplate")
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new AcceptResponseErrorHandler());
return restTemplate ;
}
/**
* 使RestTemplate能够对响应的错误消息不进行处理
* 如:当响应码为400、500等错误时,能够不进行处理,最终用户可以获取到body数据
*/
private static class AcceptResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
}
至此,通过以上配置,RestTemplate在遇到HTTP状态码为400、500错误码时,能够不抛出异常,开发者也能够对其中的响应结果进行处理。
以下,将简单地对源码进行分析,展现,为什么RestTemplate在遇到HTTP状态码为400、500错误码时,会抛出异常。
三、源码分析
1、在RestTemplate中给定了一个默认的ResponseErrorHandler,也就是在该类中,对返回的错误码进行了处理。
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
2、不管是调动get、post或者execute方法,最终都是调用到了doExecute方法。
protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
// 在该函数中对响应的结果进行了处理
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
response.close();
}
}
}
3、由于RestTemplate是对响应的错误码进行了拦截,那么我们将重点放到了doExecute方法中的以下方法。
handleResponse(url, method, response);
该方法对响应结果进行了处理,那么ResponseErrorHandler就在该方法里起了作用。
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
// 拿到了ResponseErrorHandler,如果我们有给RestTemaplate指定,就拿到我们的,
// 没有指定的话就拿到默认的,也就是DefaultResponseErrorHandler
ResponseErrorHandler errorHandler = getErrorHandler();
// 在该处通过调用ResponseErrorHandler 判断是否有错,如果有的话,就进入handleError
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
int code = response.getRawStatusCode();
HttpStatus status = HttpStatus.resolve(code);
logger.debug("Response " + (status != null ? status : code));
}
catch (IOException ex) {
// ignore
}
}
if (hasError) {
// 调用handleError去处理错误
errorHandler.handleError(url, method, response);
}
}
通过以上代码分析,可以知道,是通过调用了ResponseErrorHandler的hasError函数去判断结果中是否有错误,然后通过调用其handleError函数取处理错误。
4、由于我们没有对RestTemplate进行过任何ResponseErrorHandler的设置,那么我们直接进行它默认的DefaultResponseErrorHandler去分析其hasError和handleError函数。
5、首先观察下DefaultResponseErrorHandler的hasError,看其是如何判断接口的返回是错误的。
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
// 对响应码进行处理,可以不管
HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
// 核心是重载的hasError函数
return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode));
}
通过以上函数入口得知,重载的hasError是关键入口。
protected boolean hasError(HttpStatus statusCode) {
return statusCode.isError();
}
/**
* Template method called from {@link #hasError(ClientHttpResponse)}.
* The default implementation checks if the given status code is
* {@code HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or
* {@code HttpStatus.Series#SERVER_ERROR SERVER_ERROR}.
* Can be overridden in subclasses.
* @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(int unknownStatusCode) {
HttpStatus.Series series = HttpStatus.Series.resolve(unknownStatusCode);
return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR);
}
可以看到第二个hasError函数,当对应的响应码为Client_Error(4xx)或者是Server_Error(5xx)时,就断定该响应结果是有错误的。
6、当得知4xx、5xx的响应码断定为请求响应式错误的,那么接下来则进入到了DefaultResponseErrorHandler的handleError函数。
@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = getHttpStatusCode(response);
switch (statusCode.series()) {
case CLIENT_ERROR:
throw new HttpClientErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
case SERVER_ERROR:
throw new HttpServerErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
default:
throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
}
}
可以看到当错误码为Client_Error(4xx)或者是Server_Error(5xx)时,都是抛出异常,既然是在此处抛出异常,那么RestTemplate在调用接口之后遇到此类错误码,自然也就抛出异常,不再进行下一步获取数据了,用户拿不到!
7、最终,只要我们实现一个ResponseErrorHandler,hasError始终返回false,handleError不做任何处理,那么我们能够顺利拿到当响应码为4xx、5xx之类时的响应数据了。