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 = newHttpComponentsClientHttpRequestFactory(); 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類的部分方法:
/** * 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的時候可以指定其的各種超時屬性: