最近終於是安奈不住升級的衝動,將自己項目的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的依賴版本。
<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,下面是我舊代碼:
ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Constant.DEFAULT_CHARSET).setMessageConstraints(messageConstraints).build();
下面是新代碼:
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();
總體講沒有太大差異,後兩個配置項對於性能測試來講也不重要,畢竟連接資源還有連接管理器和異步的資源回收線程負責。
連接池管理器
舊代碼:
// 採用繞過驗證的方式處理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()方法源碼:
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是同步管理器沒有的,設置方法如下:
.setTlsStrategy(new BasicClientTlsStrategy(sslContext))
builder創建這次新的API用了create()方法,舊的API還是使用custom()。
請求配置
在請求配置中,依然取消了不少API,主要是跟連接怕這出重複的配置項,比較喜歡這種,同一個配置多處配置會導致額外的問題和排查成本,新代碼如下:
private static RequestConfig getRequestConfig() {
return RequestConfig.custom().setConnectionRequestTimeout(Timeout.ofMilliseconds(CONNECT_REQUEST_TIMEOUT)).setCookieSpec("ignoreCookies").setRedirectsEnabled(false).build();
}
這裏有一個cookieSpec的設置比較尷尬,保留了API,卻取消了配置項的枚舉類,之後先用字符串代替一下,放迷路內容:
@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
的配置,因爲我兩處都設計了,暫時沒有發現異常。
攔截器
方法參數多了一個,舊代碼:
public void process(HttpResponse httpResponse, HttpContext httpContext)
新代碼:
public void process(HttpResponse httpResponse, EntityDetails entityDetails, HttpContext httpContext)
資源回收
連接池管理器有兩個可供調用的資源回收方法,通常會異步調用防止資源異常:
connManager.closeExpiredConnections();
connManager.closeIdleConnections(HttpClientConstant.IDLE_TIMEOUT, TimeUnit.SECONDS);
新代碼如下:
connManager.closeExpired();
connManager.closeIdle(TimeValue.ofSeconds(IDLE_TIMEOUT));
異步客戶端
啓動異步客戶端的方法start()不變,但是源碼中判斷邏輯有些區別,特別是在狀態屬性上。舊代碼:
public void start() {
if (this.status.compareAndSet(CloseableHttpAsyncClientBase.Status.INACTIVE, CloseableHttpAsyncClientBase.Status.ACTIVE) && this.reactorThread != null) {
this.reactorThread.start();
}
}
新代碼:
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中設置纔行,代碼不變,如下:
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。