一、问题的出现

最近在使用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> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> 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)}.
	 * <p>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之类时的响应数据了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐