资讯详情

HttpClient的使用与连接资源释放

HttpClient

在代码检查中,我写了一篇关于代码检查的文章http连接功能导致一个问题:是否释放httpGet.releaseConnection(),是否重复连接以及如何重复使用。由于我以前没有做过相关的理解,只能使用基本的使用,所以我进行了深入的研究,整理了一些经验分享。欢迎积极评论和纠正不足。

通常使用服务和服务之间的调用和交互Http请求处理,HttpClient主要实现以下功能的常用框架:

(1)实现一切HTTP方法(GET、POST、PUT、DELETE等)

(2)支持自动转向

(3)支持HTTPS协议

(4)支持代理服务器

在进行更深入的学习和分析之前,先简单介绍一下httpclientjdk内部提供HttpURLConnection,对http许多公司和组织使用请求等。Http包装再开发,提供更方便的工具,如org.apache.httpcomponentshttpclient包,com.squareup.okhttp3okhttps等等,本文介绍的是apachehttpClient

一、请求类型

Http要求的基类是HttpRequestBase继承了AbstractExecutionAwareRequest并实现了类HttpUriRequestConfigurable可配置接口。

/**get*/ HttpGet,  /**post*/ HttpPost,  /**put*/ HttpPut, /**patch*/ HttpPatch, /**delete*/ HttpDelete,  /**其他*/ HttpHead, HttpOptions, HttpRequestBase,  HttpRequestWrapper,  HttpTrace,  RequestWrapper HttpEntityEnclosingRequestBase,  EntityEnclosingRequestWrapper, 

二、使用依赖

pom依赖:如果单独使用,可以引入apache里面有对的包Http封装后使用一些类别

<!--httpClient--> <dependency>     <groupId>org.apache.httpcomponents</groupId>     <artifactId>httpclient</artifactId>     <version>4.5.2</version> </dependency>  

三、参考文件

参考文档:

  • HttpClient详细梳理 - 简书 (jianshu.com)
  • CloseableHttpClient使用和优化_superiorpengFight的专栏-CSDN博客
  • HttpClient Connection Management | Baeldung
  • :httpclient结构原理介绍 & 连接池详解_u013332124的专栏-CSDN博客_httpclient连接池

四、使用

4.1 获取httpClient

使用依赖包HttpClients来实例化CloseableHttpClient,其中的HttpClientBuilderCloseableHttpClient建造者(参考设计模式-建造者模式)。

@Immutable public class HttpClients { private HttpClients() { super(); } public static HttpClientBuilder custom() { return HttpClientBuilder.create(); } public static CloseableHttpClient createDefault() { return HttpClientBuilder.create().build(); } public static CloseableHttpClient createSystem() { return HttpClientBuilder.create().useSystemProperties().build(); } public static CloseableHttpClient createMinimal() { return new MinimalHttpClient(new PoolingHttpClientConnectionManager()); } public static CloseableHttpClient createMinimal(final HttpClientConnectionManager connManager) { return new MinimalHttpClient(connManager); } }

使用

// 使用工厂类 HttpClients 进行创建
// 1、默认配置创建
CloseableHttpClient httpClient = HttpClients.createDefault();

// 2、使用 builder来创建,可以添加自定义配置
// 自定义 connectionManager 连接管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .build();

4.2 相关配置

无论是采用的那种工厂方法实例化的CloseableHttpClient,其中都会有很多的配置,主要的有两个HttpClientConnectionManagerRequestConfig

4.2.1 HttpClientConnectionManager

HTTP连接管理器。它负责新HTTP连接的创建、管理连接的生命周期还有保证一个HTTP连接在某一时刻只被一个线程使用。

  • 实现

    • BasicHttpClientConnectionManager:每次只管理一个connection。不过,虽然它是thread-safe的,但由于它只管理一个连接,所以只能被一个线程使用。它在管理连接的时候如果发现有相同route的请求,会复用之前已经创建的连接,如果新来的请求不能复用之前的连接,它会关闭现有的连接并重新打开它来响应新的请求。
    • PoolingHttpClientConnectionManager:它管理着一个连接池。它可以同时为多个线程服务。每次新来一个请求,如果在连接池中已经存在route相同并且可用的connection,连接池就会直接复用这个connection;当不存在route相同的connection,就新建一个connection为之服务;如果连接池已满,则请求会等待直到被服务或者超时。
  • HttpClients.createDefault():默认创建的是PoolingHttpClientConnectionManager

  • 默认配置

        public PoolingHttpClientConnectionManager(
            final HttpClientConnectionOperator httpClientConnectionOperator,
            final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
            final long timeToLive, final TimeUnit tunit) { 
              
            super();
            this.configData = new ConfigData();
            this.pool = new CPool(new InternalConnectionFactory(
                    this.configData, connFactory), 2, 20, timeToLive, tunit);
            this.pool.setValidateAfterInactivity(2000);
            this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");
            this.isShutDown = new AtomicBoolean(false);
        }
    
    

4.2.2 RequestConfig

  • HttpClient.defaultConfig:默认配置参数

            Builder() { 
              
                super();
                // 确定是否要使用陈旧的连接检查。 陈旧的连接检查可能会导致每个请求最多 30 毫秒的开销,并且应该仅在适当的时候使用。
                this.staleConnectionCheckEnabled = false;
                // 确定是否应自动处理重定向
                this.redirectsEnabled = true;
                // 返回要遵循的最大重定向数。 重定向次数限制旨在防止无限循环
                this.maxRedirects = 50;
                // 确定是否应拒绝相对重定向。 HTTP 规范要求位置值是绝对 URI
                this.relativeRedirectsAllowed = true;
                // 确定是否应自动处理身份验证
                this.authenticationEnabled = true;
                // 返回从连接管理器请求连接时使用的超时时间(以毫秒为单位)。 默认值: -1,为无限超时。
                this.connectionRequestTimeout = -1;
                // 确定建立连接之前的超时时间(以毫秒为单位)。 默认值: -1,为无限超时。
                this.connectTimeout = -1;
                // 以毫秒为单位定义套接字超时,它是等待数据的超时,或者换句话说,两个连续数据包之间的最长不活动时间。默认值: -1,为无限超时。 
                this.socketTimeout = -1;
                // 确定是否请求目标服务器压缩内容
                this.contentCompressionEnabled = true;
            }
    
  • :默认配置中有几个超时时间都是-1,这是无限超时的意思,为了更好的使用和管理,在使用的过程中需要对这几个参数进行设置,如果没有设置的话,请求会持续存在,也不会抛出异常,十分

    • connectionRequestTimeout:返回从连接管理器请求连接时使用的超时时间

    • connectTimeout:连接超时

    • socketTimeout:读取数据超时

  • 配置给HttpClient:所有该 httpClient执行的请求,如果没有指定配置,则都会采用该defaultRequestConfig

  • 配置给HttpRequest-methods:配置了requestConfig,在请求时使用该配置

        // 创建http 请求配置
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(5 * 1000)
            .setConnectionRequestTimeout(5 * 1000)
            .setSocketTimeout(5 * 1000)
            .build();
    
         // 1.配置给CloseableHttpClient
         CloseableHttpClient  httpClient = HttpClients.custom()
                    .setConnectionManager(connectionManager)
                    .setDefaultRequestConfig(requestConfig)
                    .build();
    
        // 2.配置http GET请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);
    

4.3 使用示例:GET

在需要进行请求时,创建httpClient,然后创建HttpGet请求,配置路由、请求头、请求参数等,接收execute请求,获取结果并处理。

    public static void test(String url) { 
        

        // 创建http client客户端
        CloseableHttpClient httpClient = HttpClients.createDefault();
        
     	// 创建http 请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5 * 1000)
                .setConnectionRequestTimeout(5 * 1000)
                .setSocketTimeout(5 * 1000)
                .build();
        
        // 创建http GET请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);
        // 设置请求头部编码
        httpGet.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"));
        // 设置返回编码
        httpGet.setHeader(new BasicHeader("Accept", "text/plain;charset=utf-8"));

        // 返回响应
        CloseableHttpResponse response = null;
        try { 
        
            response = httpClient.execute(httpGet);
			// 判断响应码
            if (response.getStatusLine().getStatusCode() == 200) { 
        
                HttpEntity entity = response.getEntity();
                // 使用工具类EntityUtils 从响应中读取内容
                String result = EntityUtils.toString(entity, "utf-8");
                System.out.println(result);
            }

        } catch (Exception e) { 
        
            System.out.print("http GET 请求异常" + e);
        } finally { 
        

            // 释放资源
            try { 
        
                if (response != null) { 
        
                    response.close();
                }
            } catch (IOException e) { 
        
                System.out.print("关闭流异常" + e);
            }
            
            // 关闭客户端
            try { 
        
                httpClient.close();
            } catch (IOException e) { 
        
                System.out.print("关闭HttpClient异常" + e);
            }
        }
    }

五、问题探讨

示例中有多个Close,分别是关闭了什么呢,是否可以省略,又在什么时候调用呢?在使用过程中,有时也会涉及到releaseConnection(),这又是什么?有什么作用?是否是必要的呢?

5.1 关闭

  • response.close():官网的解释是,最底层的HTTP connection是由响应对象response持有的,如果没有完全的消费response content或者正确地关闭,对应的connection是不能被安全重用的,会被connection manager给关闭和丢弃。

  • httpClient.close():关闭客户端,会先关闭客户端中的所有连接,然后销毁客户端。

  • method.releaseConnection():释放连接到连接池。

5.2 不关闭

所有的资源都是有限的,如果持续消费资源而不释放资源,很快就会出现因为资源获取不到而导致进程阻塞,参考一个常见的问题就是**问题并发量**、等上升,问题出现的几率和产生的影响可能成,所以一直强调要资源释放,就是这个问题。

HttpClient使用过程中也会出现这样的问题,下面我们来探讨一下,如果不关闭资源,会出现什么样的问题,不同的方式来关闭又会出现什么样的问题。

5.3 response

问题:消费不彻底

多次请求,对于response消费不彻底,没有进行关闭

// 相同的URL,多次请求
    public static void testNoCloseResponse(String url, int num) { 
        
        // 创建http client客户端
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 创建http GET请求
        HttpGet httpGet = new HttpGet(url);
        // 设置请求头部编码
        httpGet.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"));
        // 设置返回编码
        httpGet.setHeader(new BasicHeader("Accept", "text/plain;charset=utf-8"));

        for (int i = 0; i < num; i++) { 
        
            // 返回响应
            CloseableHttpResponse response = null;
            try { 
        
                response = httpClient.execute(httpGet);
// if (response.getStatusLine().getStatusCode() == 200) { 
        
// HttpEntity entity = response.getEntity();
// // 使用工具类EntityUtils 从响应中读取内容
// String result = EntityUtils.toString(entity, "utf-8");
// System.out.println(result);
// }
            } catch (Exception e) { 
        
                System.out.print("http GET 请求异常" + e);
            }finally{ 
        
// // 释放资源
// try { 
        
// if (response != null) { 
        
// response.close();
// }
// } catch (IOException e) { 
        
// e.printStackTrace();
// }
            }
        }
    }

第一次连接:连接无法被复用,kept alive 0,同时占用了一个route

16:37:15.048 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://172.23.22.58:8081][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
16:37:15.119 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://172.23.22.58:8081][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]

// 连接完成后
httpClient.connManager.pool = [leased: [[id:0][route:{}->http://172.23.22.58:8081][state:null]]][available: []][pending: []]

第二次连接:连接无法被复用,kept alive 0,相同的IP和请求路由,又占用了一个route

16:41:57.223 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://172.23.22.58:8081][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
16:41:57.224 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 1][route: {}->http://172.23.22.58:8081][total kept alive: 0; route allocated: 2 of 2; total allocated: 2 of 20]

// 连接完成后
httpClient.connManager.pool = [leased: [[id:1][route:{}->http://172.23.22.58:8081][state:null], [id:0][route:{}->http://172.23.22.58:8081][state:null]]][available: []][pending: []]

第三次连接:相同的IP和请求路由,由于默认配置中:httpClient.connManager.pool.defaultMaxPerRoute = 2(相同的请求路径最多可以同时存在2个),没有可用的route此时就会一直等待原连接的释放,获取到route之后才可以进行连接。

问题:消费彻底

使用工具类消费能够更加彻底地消费response,可以达到释放资源,复用的效果,但是如果关闭response,仍然无法复用

            // 返回响应
            CloseableHttpResponse response = null;
            try { 
        
                response = httpClient.execute(httpGet);
// if (response.getStatusLine().getStatusCode() == 200) { 
        
// HttpEntity entity = response.getEntity();
// // 使用工具类EntityUtils 从响应中读取内容
// String result = EntityUtils.toString(entity, "utf-8");
// System.out.println(result);
// }
            } catch (Exception e) { 
        
                System.out.print("http GET 请求异常" + e);
            }

第一次连接:total kept alive: 1,连接可以被复用

16:56:23.188 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: { 
        }->http://172.23.22.58:8081] can be kept alive indefinitely
16:56:23.188 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: { 
        }->http://172.23.22.58:8081][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]

第二次连接:虽然有一个连接可以复用,但是在尝试复用的时候,发现该通道对应的流并没有关闭,无法使用,所以在关闭了该连接后,重新生成了一个

16:57:32.922 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: { 
        }->http://172.23.22.58:8081][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
16:57:32.922 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "end of stream"
16:57:32.922 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
16:57:32.922 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 1][route: { 
        }->http://172.23.22.58:8081][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
16:57:32.922 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection { 
        }->http://172.23.22.58:8081

第三次连接:与第二次相同

17:05:07.966 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: { 
        }->http://172.23.22.58:8081][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
17:05:07.966 [main] DEBUG org.apache.http.wire - http-outgoing-1 << "end of stream"
17:05:07.966 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-1: Close connection
17:05:07.966 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 2][route: { 
        }->http://172.23.22.58:8081][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20<

标签: 连接器主线连接器57

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台