最近终于是安奈不住升级的冲动,将自己项目的HttpClient版本从4升级到了5,其过程不可谓不艰辛,很多API改动让人无从下手。
Apache HttpClient 5(也称为 HttpClient 5.x)是 Apache HttpComponents 项目中的一个重要组件,用于发送 HTTP 请求和处理 HTTP 响应。它在与网络通信和处理方面提供了许多优势:
- 模块化设计: HttpClient 5 采用了模块化的设计,将核心功能拆分为不同的模块。这种设计使得用户可以根据自己的需求选择性地引入和使用不同的功能模块,从而降低了依赖的复杂性。
- 高度可定制: 提供了丰富的配置选项和可定制性,允许开发人员根据特定需求配置连接管理、超时、代理、安全策略等参数。
- 异步支持: 提供了对异步请求的支持,可以利用异步方式发送请求并处理响应,有助于提高系统的并发能力和性能。
- 优化的连接管理: 引入了更灵活和高效的连接管理机制,包括连接池管理、连接复用,可有效减少连接的建立和关闭次数,提高资源利用率。
- HTTP/2 支持: 支持 HTTP/2 协议,允许客户端使用 HTTP/2 进行通信,提高了性能和效率,尤其是在处理大量并行请求时。
- 最新的标准和协议支持: 支持最新的 HTTP 标准和协议,包括 HTTP/1.1、HTTP/2、TLS/SSL 等,使得 HttpClient 5 在安全性和性能方面都能够保持更新和竞争力。
- 优化的代码结构和性能: 重新设计和优化的代码结构,使得 HttpClient 5 在处理请求和响应时更加高效和可靠。
- 易于使用的 API: 提供了简单易用的 API,使得发送 HTTP 请求和处理响应变得更加直观和简单。
针对以上好处,本人仅仅感受到了一点点,但是成本远高于好处,经过简单自测,整体感觉没有质的提升。唯一吸引我的还是HTTP/2的支持,不过本地没有开发该协议接口,暂时还没测试,目前主流还是HTTP 1.1。
这是FunTester项目中升级到HttpClient 5的依赖版本。
代码语言:javascript复制 <dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3</version>
</dependency>
其中依赖版本不变,如果你项目里面其他库依赖了HttpClient 4.x版本,记得排除掉,避免干扰。
下面开始分享API改动点,内容不分先后,按照我FunTester项目从上至下列举。以下问容中旧版本指的是4.x,新版指的是5.3。
包名
包名改成了 org.apache.hc.client5.
开头的,需要不少手动工作量。
重试
在旧版本中叫HttpRequestRetryHandler,新版本叫做HttpRequestRetryStrategy,中文应该是重测策略。实现方法上也有所不同。旧版本方法 public boolean retryRequest(IOException exception, int executionCount, HttpContext context)
,而新版本需要实现多个方法:public boolean retryRequest(HttpRequest httpRequest, IOException e, int i, HttpContext httpContext)
、 public boolean retryRequest(HttpResponse httpResponse, int i, HttpContext httpContext)
、 public TimeValue getRetryInterval(HttpResponse httpResponse, int i, HttpContext httpContext)
第一个方法跟旧接口很相似,代码直接可以套用。第二个方法用于对响应信息进行判断重试,这个方法挺不错的,很有市场。第三个方法获取重试间隔,由于我并没有设置改功能,所以并没有什么用。但是大家注意引入了新类 org.apache.hc.core5.util.TimeValue
,在HttpClient 5中,大量使用这个类作为时间配置。
连接配置
新的版本取消了一批API,下面是我旧代码:
代码语言:javascript复制 ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Constant.DEFAULT_CHARSET).setMessageConstraints(messageConstraints).build();
下面是新代码:
代码语言:javascript复制ConnectionConfig connectionConfig = ConnectionConfig.custom()// 设置连接配置
.setConnectTimeout(Timeout.of(Duration.ofMillis(CONNECT_TIMEOUT))) // 设置连接超时
.setSocketTimeout(Timeout.of(Duration.ofMillis(SOCKET_TIMEOUT))) // 设置 socket 超时
.setTimeToLive(Timeout.of(Duration.ofMillis(MAX_ACCEPT_TIME))) // 设置生存时间
.setValidateAfterInactivity(Timeout.of(Duration.ofMillis(MAX_ACCEPT_TIME))) // 设置在不活动之后验证
.build();
总体讲没有太大差异,后两个配置项对于性能测试来讲也不重要,毕竟连接资源还有连接管理器和异步的资源回收线程负责。
连接池管理器
旧代码:
代码语言:javascript复制// 采用绕过验证的方式处理https请求
// 设置协议http和https对应的处理socket链接工厂的对象
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)).build();
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, dnsResolver);
// 消息约束
MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(HttpClientConstant.MAX_HEADER_COUNT).setMaxLineLength(HttpClientConstant.MAX_LINE_LENGTH).build();
新代码依旧是取消了一些配置API,其中改动比较大就是创建API,虽然PoolingHttpClientConnectionManager重载构造方法非常多,但是顺序写死了,我只想设置连接配置和DNS解析器,如果用构造方法,必须使用一个N个参数的,非常不优雅。这里推荐builder来完成,我们来看build()方法源码:
代码语言:javascript复制 PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(RegistryBuilder.create().register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory()).register(URIScheme.HTTPS.id, this.sslSocketFactory != null ? this.sslSocketFactory : (this.systemProperties ? SSLConnectionSocketFactory.getSystemSocketFactory() : SSLConnectionSocketFactory.getSocketFactory())).build(), this.poolConcurrencyPolicy, this.poolReusePolicy, (TimeValue)null, this.schemePortResolver, this.dnsResolver, this.connectionFactory);
默认是有HTTP的连接工厂类注册到连接池管理器中的,所以不用重复设置了,而且也没有预留设置HTTP的API。
异步连接池管理器大差不差,其中有一个TlsStrategy是同步管理器没有的,设置方法如下:
代码语言:javascript复制.setTlsStrategy(new BasicClientTlsStrategy(sslContext))
builder创建这次新的API用了create()方法,旧的API还是使用custom()。
请求配置
在请求配置中,依然取消了不少API,主要是跟连接怕这出重复的配置项,比较喜欢这种,同一个配置多处配置会导致额外的问题和排查成本,新代码如下:
代码语言:javascript复制private static RequestConfig getRequestConfig() {
return RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(CONNECT_REQUEST_TIMEOUT)).setCookieSpec("ignoreCookies").setRedirectsEnabled(false).build();
}
这里有一个cookieSpec的设置比较尴尬,保留了API,却取消了配置项的枚举类,之后先用字符串代替一下,放迷路内容:
代码语言:javascript复制@Deprecated
public static final String BROWSER_COMPATIBILITY = "compatibility";
public static final String NETSCAPE = "netscape";
public static final String STANDARD = "standard";
public static final String STANDARD_STRICT = "standard-strict";
/** @deprecated */
@Deprecated
public static final String BEST_MATCH = "best-match";
public static final String DEFAULT = "default";
public static final String IGNORE_COOKIES = "ignoreCookies";
在性能测试当中,不需要 CookieStore
来管理cookie,所以选择忽略。
创建HttpClient
我用到了一个新的API org.apache.hc.client5.http.impl.classic.HttpClientBuilder#disableCookieManagement
看源码文档,看着是取消 CookieStore
的配置,因为我两处都设计了,暂时没有发现异常。
拦截器
方法参数多了一个,旧代码:
代码语言:javascript复制 public void process(HttpResponse httpResponse, HttpContext httpContext)
新代码:
代码语言:javascript复制public void process(HttpResponse httpResponse, EntityDetails entityDetails, HttpContext httpContext)
资源回收
连接池管理器有两个可供调用的资源回收方法,通常会异步调用防止资源异常:
代码语言:javascript复制connManager.closeExpiredConnections();
connManager.closeIdleConnections(HttpClientConstant.IDLE_TIMEOUT, TimeUnit.SECONDS);
新代码如下:
代码语言:javascript复制connManager.closeExpired();
connManager.closeIdle(TimeValue.ofSeconds(IDLE_TIMEOUT));
异步客户端
启动异步客户端的方法start()不变,但是源码中判断逻辑有些区别,特别是在状态属性上。旧代码:
代码语言:javascript复制public void start() {
if (this.status.compareAndSet(CloseableHttpAsyncClientBase.Status.INACTIVE, CloseableHttpAsyncClientBase.Status.ACTIVE) && this.reactorThread != null) {
this.reactorThread.start();
}
}
新代码:
代码语言:javascript复制public final void start() {
if (this.status.compareAndSet(AbstractHttpAsyncClientBase.Status.READY, AbstractHttpAsyncClientBase.Status.RUNNING)) {
DefaultConnectingIOReactor var10001 = this.ioReactor;
this.executorService.execute(var10001::start);
}
}
代理
在旧代码中,代理配置可以直接在HttpClient中设置,新代码将API设置为过时,需要在RequestConfig中设置才行,代码不变,如下:
代码语言:javascript复制setProxy(new HttpHost(ip, port))
请求、响应对象名称
启用了一大批classic开头的对象,例如 org.apache.hc.core5.http.message.BasicClassicHttpRequest
、 org.apache.hc.core5.http.ClassicHttpResponse
,而且各类封装号的HTTP请求对象的报名也变成了 org.apache.hc.client5.http.classic.methods
。看来这个版本要回归经典了。
实体接口
在旧版代码中,想要处理请求或者响应实体,必须是 org.apache.http.HttpEntityEnclosingRequest
对象,在新版代码中变成了 org.apache.hc.core5.http.HttpEntityContainer
,而且取消了 boolean expectContinue()
方法。
全员携带实体
在旧版代码中,GET和DELETE请求默认是不携带请求实体的,如果想实现该功能需要使用者自己实现,新版中,全员携带实体。这个改变还是很喜闻乐见的。
设置实体
设置实体的API也有少许变动,原来是设置String类型编码格式,现在直接设置 java.nio.charset.Charset
,真是一大进步。
获取header
方法名从 getAllHeader 变成了 getHeaders,别的没了。
响应行
HttpClient 5取消了 获取响应行的的API getStatusLine
,如果想获取状态码,请用:org.apache.hc.client5.http.impl.classic.CloseableHttpResponse#getCode
,个人感觉并不合适,这跟HTTP请求构成有点不一致,但是方便了倒是真的。
获取URI
旧版方法:getURI
,返回URI对象,新版:getUri
,也返回URI对象,还有一个 getRequestUri
返回String对象。就是大小写的差异,怀疑是不是为了适配代码自动补充的工具。
异步请求
在同步的HttpClient中也是支持异步请求的,旧版代码和同步请求公用请求对象,新版代码增加了新的请求对象:org.apache.hc.client5.http.async.methods.SimpleHttpRequest
,这个类明白子类,也不继承于前文提到的 HttpUriRequestBase
,感觉就是独立分支一样。同样的响应对象也是 org.apache.hc.client5.http.async.methods.SimpleHttpResponse
。
HttpClient 5中两者都提供了从同步对象拷贝的方法copy()
,奇怪的是请求的拷贝被标记成了过时方法,迷惑行为。从源码中看到可以方便快捷创建GET和POST请求。
响应中有直接获取body的方法 org.apache.hc.client5.http.async.methods.SimpleHttpResponse#getBodyText
,看了一下,不太好借鉴到同步方法中。
总结
一个字:折腾。API调用已经完活儿了,后期再根据测试结果分享其他方面的变更感受。
如果没有强需求,不建议升级 HttpClient 5。