Spring web之restTemplate超时问题处理

问题

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

查看代码

代码大概如下

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的时候可以指定其的各种超时属性:

原文地址:https://www.cnblogs.com/talentzemin/p/13815281.html