Spring web之restTemplate超时问题处理

时间:2022-09-19 16:47:11

问题

项目中有个远程服务因为某些原因会访问不通,于是就在调用的那一步挂起无法结束了。

查看代码

代码大概如下

CloseableHttpClient closeableHttpClient = HttpClients.custom()                .setConnectionManager(manager)                .build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();requestFactory.setHttpClient(httpClient);clientHttpRequestFactory.setConnectTimeout(1000);RestTemplate restTemplate = new RestTemplate();restTemplate.setRequestFactory(requestFactory);

分析

HttpComponentsClientHttpRequestFactory底层默认使用了apache的HttpClient,超时设置就针对其设置的。其实我们只需要设置ReadTimeout就好了。

CloseableHttpClient closeableHttpClient = HttpClients.custom()
.setConnectionManager(manager)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
clientHttpRequestFactory.setConnectTimeout(1000);
clientHttpRequestFactory.setReadTimeout(50);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(requestFactory);

但我们发现,我们并没有使用默认的HttpClient,而是显式的指定了Client,我们在创建Client的时候也可以指定其的各种超时属性:

RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(2 * 1000)
.setSocketTimeout(5 * 1000)
.setConnectionRequestTimeout(2 * 1000)
.build(); CloseableHttpClient closeableHttpClient = HttpClients.custom()
.setDefaultRequestConfig(getRequestConfig())
.setConnectionManager(manager)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
clientHttpRequestFactory.setConnectTimeout(1000);
clientHttpRequestFactory.setReadTimeout(50);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(requestFactory);

上面可以看到,我们在创建Client的时候指定了超时时间,也在创建HttpComponentsClientHttpRequestFactory的时候指定了超时时间,那么到底以哪个为准呢?

一探究竟

项目中使用的是spring web 4.3.5, 我们看看源代码的实现,以下是HttpComponentsClientHttpRequestFactory类的部分方法:

https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java

/**
* Set the socket read timeout for the underlying HttpClient.
* A timeout value of 0 specifies an infinite timeout.
* <p>Additional properties can be configured by specifying a
* {@link RequestConfig} instance on a custom {@link HttpClient}.
* @param timeout the timeout value in milliseconds
* @see RequestConfig#getSocketTimeout()
*/
public void setReadTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build();
setLegacySocketTimeout(getHttpClient(), timeout);
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
HttpContext context = createHttpContext(httpMethod, uri);
if (context == null) {
context = HttpClientContext.create();
} // Request configuration not set in the context
if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
// Use request configuration given by the user, when available
RequestConfig config = null;
if (httpRequest instanceof Configurable) {
config = ((Configurable) httpRequest).getConfig();
}
if (config == null) {
config = createRequestConfig(getHttpClient());
}
if (config != null) {
context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
}
} if (this.bufferRequestBody) {
return new HttpComponentsClientHttpRequest(getHttpClient(), httpRequest, context);
}
else {
return new HttpComponentsStreamingClientHttpRequest(getHttpClient(), httpRequest, context);
}
}
    /**
* Create a default {@link RequestConfig} to use with the given client.
* Can return {@code null} to indicate that no custom request config should
* be set and the defaults of the {@link HttpClient} should be used.
* <p>The default implementation tries to merge the defaults of the client
* with the local customizations of this factory instance, if any.
* @param client the {@link HttpClient} (or {@code HttpAsyncClient}) to check
* @return the actual RequestConfig to use (may be {@code null})
* @since 4.2
* @see #mergeRequestConfig(RequestConfig)
*/
protected RequestConfig createRequestConfig(Object client) {
if (client instanceof Configurable) {
RequestConfig clientRequestConfig = ((Configurable) client).getConfig();
return mergeRequestConfig(clientRequestConfig);
}
return this.requestConfig;
}
    /**
* Merge the given {@link HttpClient}-level {@link RequestConfig} with
* the factory-level {@link RequestConfig}, if necessary.
* @param clientConfig the config held by the current
* @return the merged request config
* @since 4.2
*/
protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
if (this.requestConfig == null) { // nothing to merge
return clientConfig;
} RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
int connectTimeout = this.requestConfig.getConnectTimeout();
if (connectTimeout >= 0) {
builder.setConnectTimeout(connectTimeout);
}
int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
if (connectionRequestTimeout >= 0) {
builder.setConnectionRequestTimeout(connectionRequestTimeout);
}
int socketTimeout = this.requestConfig.getSocketTimeout();
if (socketTimeout >= 0) {
builder.setSocketTimeout(socketTimeout);
}
return builder.build();
}

我们可以看到,版本4.2之后,如果二者都进行了参数指定,会有一个mergeRequestConfig的操作,超时以HttpComponentsClientHttpRequestFactory类指定为为准。

结论

所以,针对本文一开始的问题,我们只需要设置HttpComponentsClientHttpRequestFactory实例的setReadTimeout方法即可。

参考

https://leokongwq.github.io/2018/11/21/springboot-resttempate-timout.html

https://www.cnblogs.com/softidea/p/6964347.html

但我们发现,我们显式的指定了Client,我们在创建Client的时候可以指定其的各种超时属性: