1. 程式人生 > 實用技巧 >Spring web之restTemplate超時問題處理

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的時候可以指定其的各種超時屬性: