zoukankan      html  css  js  c++  java
  • 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的时候可以指定其的各种超时属性:

  • 相关阅读:
    [git]git的简单配置使用 (将你的代码上传到Github)
    学习进度报告【第六周】
    [错误解决]SpringMVC接收对象 中文乱码问题解决
    [架构]myeclipse配置SpringMVC 以及简单应用 教程
    [机器学习]AttributeError: module 'tensorflow' has no attribute 'ConfigProto' 报错解决方法
    [机器学习]RuntimeError: The Session graph is empty. Add operations to the graph before calling run(). 报错解决方法
    [python]机器学习 k-mean 聚类分析
    学习进度报告【第五周】
    学习进度报告【第四周】
    unity3d优化总结篇
  • 原文地址:https://www.cnblogs.com/talentzemin/p/13815281.html
Copyright © 2011-2022 走看看