使用Apache的HttpClient发送Http请求

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_45124488/article/details/102613035

使用Apache的HttpClient发送Http请求

1 基础概念

1.1 HttpClient、TCP/IP、Socket的区别

HttpClient是Apache中一个开源的项目。它实现了HTTP标准中Client端的所有功能,使用它能够很容易的进行HTTP信息的传输。

网络从下往上分为:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

TCP/IP协议:主要解决的是数据传输层面的事情,如果没有规范的应用协议,数据能从网络里的A节点传到B节点,但是无法有效识别,建立在TCP/IP上的应用协议很多,比如:rpc、ftp等。所以,无论应用协议多强大,最终都要依靠传输层协议进行数据传输。

Http、Https:主要解决的是数据规范层面的事情,作用于应用层。而HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层。

Socket:是tcp/ip的一个编程实现,在程序里,Http请求最终一定要绑定到一个具体的Socket连接进行上行和下行传输。Socket本身并不是协议,而是一个调用接口(API)。

1.2 HttpClient的介绍

对于简单应用,HttpURLConnection完全可以满足。但是对于

​ 1)系统复杂度高,

​ 2)性能要求高,

​ 3)可靠性要求也高的应用,

则需要一个更强大的组件。HttpClient可以满足这些条件。

2 HttpClient的使用

2.1 创建一个HttpClient对象

创建HttpClient对象的方式是使用CloseableHttpClient的builder类的HttpClientBuilder,先对一些属性进行配置(采用装饰者模式,不断的setXxx().setXxx() … 就行),再调用build方法来创建实例。

public InternalHttpClient(ClientExecChain execChain,
                          HttpClientConnectionManager connManager, 
                          HttpRoutePlanner routePlanner, 
                          Lookup<CookieSpecProvider> cookieSpecRegistry,
                          Lookup<AuthSchemeProvider> authSchemeRegistry, 
                          CookieStore cookieStore, 
                          CredentialsProvider credentialsProvider, 
                          RequestConfig defaultConfig, 
                          List<Closeable> closeables) {
    Args.notNull(execChain, "HTTP client exec chain");
    Args.notNull(connManager, "HTTP connection manager");
    Args.notNull(routePlanner, "HTTP route planner");
    this.execChain = execChain;
    this.connManager = connManager;
    this.routePlanner = routePlanner;
    this.cookieSpecRegistry = cookieSpecRegistry;
    this.authSchemeRegistry = authSchemeRegistry;
    this.cookieStore = cookieStore;
    this.credentialsProvider = credentialsProvider;
    this.defaultConfig = defaultConfig;
    this.closeables = closeables;
}

需要关注的几个重要的初始化配置如下:
HttpCLientConnectionManager、RequestConfig

2.2 创建RequestConfig

RequestConfig是对Request的一些配置,里面比较重要的有三个超时时间,默认情况下这三个时间都是0(如果不设置request的config,会在execute的过程中使用HttpClientParamConfig的getRequestConfig中使用默认参数进行设置,默认设置为-1),这也就意味着无限等待,很容易导致所有的请求阻塞在这个地方无限期等待。

这三个超时时间为:

A :connectionRequestTimeout—从连接池中取连接的超时时间

这个时间定义的是从ConnectionManager管理的连接池中取出连接的超时时间,如果连接池中没有可用的连接,则request会被阻塞,最长等待connectionRequestTimeout的时间,如果还没有服务,则抛出ConnectionPoolTimeoutException异常,不继续等待。

B :connectTimeout—连接超时时间

这个时间定义了通过网络与服务器建立连接的超时时间,也就是取得了连接池中某个连接之后接通目标url等待时间,会发生超时,抛出ConnectionTimeoutException异常。

C :socketTimeout—请求超时时间

这个时间定义了socket读数据的超时时间,也就是连接到服务器之后到从服务器获取响应数据需要等待的时间,或者说是连接上一个url之后到获取response的返回等待时间。发生超时,会抛出SocketTimeoutException异常。

在这里插入图片描述

2.3 创建HttpClientConnectionManager

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

关键的概念——Route

在HttpClient中,一个Route指运行环境机器 --> 目标机器(域名)的一条线路,也就是如果目标url的域名是同一个,那么它们的Route也是一样的。

在这里插入图片描述

HttpClientConnectionManager有两种具体实现:

A : BasicHttpClientConnectionManager:每次只管理一个connection,但由于它只管理一个连接,所以只能被一个线程使用,它在管理连接的时候如果发现有相同的Route请求,会复用之前已经创建的连接,如果新来的请求不能复用之前的连接,它会关闭现有连接并重新打开它来相应新的请求。

B:PoolingHttpClientConnectionManager:与A不同(这个是默认设置),它管理着一个连接池,可以同时为多个线程服务,每次新来一个请求,如果在连接池中已经存在Route相同并且可用的connection,连接池就会直接复用这个connection;当不存在Route相同的connection,就新建一个connection为之服务;如果连接池已满,则请求会等待直到被服务或者超时。

整个PoolingHttpClientConnectionManager生命周期从HttpClient初始化开始到整个应用结束调用httpClient.close()。
在PoolingHttpClientConnectionManager的配置中有两个最大连接数量,分别控制着总的最大连接数量和每个route的最大连接数量。如果没有显式设置,默认每个route只允许最多2个connection,总的connection数量不超过20。

在这里插入图片描述

2.4 创建一个Request对象以及执行Request请求

HttpClient支持所有HTTP1.1中所有定义的请求类型:GET、HEAD、POST、PUT、DELETE、TRACE和OPTIONS。对使用的类为HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace和HttpOptions。Request的对象建立很简单,一般用目标url来构造就好了。下面是一个HttpPost的创建代码:

HttpPost httpPost = new HttpPost(url);

执行request请求的方式是:

httpclient.execute(HttpUriRequest request)

重点强调的是:HttpClient允许http连接在特定的Http上下文中执行,HttpContext是跟一个连接相关联的,所以它也只能属于一个线程,如果没有特别设定,在execute的过程中,HttpClient会自动为每一个connection new一个HttpClientHttpContext。

final HttpClientContext localContext 
	= HttpClientContext.adapt(context != null ? context : new BasicHttpContext);

整个执行execute的过程如下:

在这里插入图片描述

3 重点关注

3.1 连接池管理

连接池的获取的完整逻辑是:在每收到一个route请求后,连接池都会建立一个以这个route为key的子连接池,当有一个新的连接请求到来的时候,它会优先匹配已经存在的子连接池们,如果之前已经有过以这个route为key的子连接池,那么就会去试图取这个子连接池中状态为available的连接,如果此时有可用的连接,则将取得的available连接状态改为leased的,取连接成功。
如果此时子连接池没有可用连接,那再看是否达到了所设置的最大连接数和每个route所允许的最大连接数的上限,如果还有余量则new一个新的连接,或者取得lastUsedConnection,关闭这个连接、把连接从原来所在的子连接池删除,再lease取连接成功。
如果此时的情况不允许再new一个新的连接,就把这个请求连接的请求放入一个queue中排队等待,直到得到一个连接或者超时才会从queue中删去。

在这里插入图片描述

3.2 连接池回收

目前连接池的回收机制有二种:

第一种设置
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); httpClientBuilder.evictExpiredConnections();//制定过期连接,httpClientBuilder.evictIdleConnections(60, TimeUnit.SECONDS);
用来关闭闲置连接,它会启动一个守护线程进行清理工作。用户可以通过builder#evictIdleConnections开启该组件,并通过builder#setmaxIdleTime设置最大空闲时间。

第二种设置:初始化PoolingHttpClientConnectionManager设置每个连接的生命周期
PoolingHttpClientConnectionManager connManager =new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, 360, TimeUnit.SECONDS);

3.3 keep-alive

在HttpClient.execute得到response之后的相关代码中,它会先取出response的keep-alive头来设置connection是否resuable以及存活的时间。如果服务器返回的响应中包含了Connection:Keep-Alive(默认有的),但没有包含Keep-Alive时长的头消息,HttpClient认为这个连接可以永远保持,也就是长连接。

3.4 常见问题

有时会出现线程阻塞,经过上面的介绍,主要原因是:

一:没有增加连接池的回收机制,造成长连接一直保持。

二:未设置connectionRequestTimeout,造成请求在连接池中一直等待,未在服务器中抛出异常。

4 小结

本篇文章简单介绍了发送Http请求——Apache的HttpClient,由于纯手打,难免会有纰漏,如果发现错误的地方,请第一时间告诉我,这将是我进步的一个很重要的环节。

猜你喜欢

转载自blog.csdn.net/weixin_45124488/article/details/102613035