Apache HttpClient 4.5.x 學習總結十四:高級主題
第7章 高級主題
7.1 自定義客戶端連接
某些場景下需自定義HTTP消息傳輸邏輯(如爬蟲需強制接受畸形響應頭)。自定義流程:
步驟1:自定義消息解析器
class MyLineParser extends BasicLineParser {
@Override
public Header parseHeader(CharArrayBuffer buffer) {
try {
return super.parseHeader(buffer); // 標準解析
} catch (ParseException ex) {
// 忽略異常:強制返回基礎標頭
return new BasicHeader(buffer.toString(), null);
}
}
}
步驟2:自定義連接工廠
// 替換請求寫入器/響應解析器
HttpConnectionFactory connFactory = new ManagedHttpClientConnectionFactory(
new DefaultHttpRequestWriterFactory(),
new DefaultHttpResponseParserFactory(
new MyLineParser(), // 注入自定義解析器
new DefaultHttpResponseFactory()
)
);
步驟3:配置HttpClient
PoolingHttpClientConnectionManager cm =
new PoolingHttpClientConnectionManager(connFactory); // 注入連接工廠
CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(cm)
.build();
7.2 有狀態HTTP連接
雖然HTTP協議要求連接無狀態,但現實場景存在例外:
- NTLM認證連接:綁定特定用戶身份
- SSL客戶端證書認證:綁定安全上下文
?? 此類連接無法跨用戶共享,僅限同一用戶復用
7.2.1 用戶令牌處理器
通過UserTokenHandler判斷執行上下文是否用戶相關:
- 返回唯一用戶標識對象(如
Principal)→ 用戶相關 - 返回
null→ 非用戶相關
// 默認實現:自動提取NTLM/SSL認證的Principal
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
httpclient.execute(new HttpGet("http://localhost:8080/"), context);
// 獲取用戶令牌
Principal principal = context.getUserToken(Principal.class);
自定義令牌處理器:
UserTokenHandler customHandler = context ->
context.getAttribute("my-token"); // 從上下文提取自定義令牌
HttpClients.custom().setUserTokenHandler(customHandler).build();
7.2.2 持久化有狀態連接
關鍵:同一用戶需復用相同上下文或手動綁定令牌
// 首次請求獲取令牌
HttpClientContext context1 = HttpClientContext.create();
httpclient.execute(new HttpGet("http://localhost/"), context1);
Principal principal = context1.getUserToken(Principal.class);
// 后續請求傳遞令牌
HttpClientContext context2 = HttpClientContext.create();
context2.setUserToken(principal); // 手動綁定令牌
httpclient.execute(new HttpGet("http://localhost/"), context2);
7.3 FutureRequestExecutionService
將HTTP調用封裝為Future任務,支持:
- 多線程并發調度
- 任務超時控制
- 響應結果異步處理
7.3.1 創建服務
線程數與連接數需匹配:
CloseableHttpClient httpClient = HttpClients.createDefault()
.setMaxConnPerRoute(5); // 最大連接數=5
ExecutorService executor = Executors.newFixedThreadPool(5); // 線程數=5
FutureRequestExecutionService service =
new FutureRequestExecutionService(httpClient, executor);
7.3.2 調度請求
必須提供ResponseHandler處理響應:
// 定義響應處理器
ResponseHandler<Boolean> handler = response ->
response.getStatusLine().getStatusCode() == 200;
// 提交任務
HttpRequestFutureTask<Boolean> task = service.execute(
new HttpGet("http://google.com"),
HttpClientContext.create(),
handler
);
boolean isSuccess = task.get(); // 阻塞獲取結果
7.3.3 取消任務
task.cancel(true); // true: 中斷請求;false: 忽略響應
task.get(); // 拋出IllegalStateException
?? 注意:取消僅釋放客戶端資源,服務端請求仍可能正常執行
7.3.4 回調機制
FutureCallback<Boolean> callback = new FutureCallback<>() {
public void completed(Boolean result) { /* 成功回調 */ }
public void failed(Exception ex) { /* 失敗回調 */ }
public void cancelled() { /* 取消回調 */ }
};
service.execute(請求, 上下文, 響應處理器, callback); // 綁定回調
7.3.5 監控指標
// 單任務指標
task.scheduledTime(); // 任務調度時間戳
task.startedTime(); // 請求開始時間戳
task.requestDuration; // 請求耗時(網絡IO)
task.taskDuration; // 總任務耗時(含隊列等待)
// 全局指標(通過service.metrics()獲取)
FutureRequestExecutionMetrics metrics = service.metrics();
metrics.getActiveConnectionCount(); // 活躍連接數
metrics.getSuccessfulConnectionCount();// 成功請求數
metrics.getTaskAverageDuration(); // 平均任務耗時
核心知識點總結:
| 主題 | 要點 |
|---|---|
| 自定義連接 | 1. 重寫解析器處理非標協議 2. 通過連接工廠注入自定義組件 3. 連接管理器配置生效 |
| 有狀態連接 | 1. NTLM/SSL認證連接需綁定用戶 2. 通過 UserTokenHandler管理用戶令牌 3. 跨請求需手動傳遞令牌 |
| 異步請求服務 | 1. 線程池與連接數需匹配 2. 響應處理器( ResponseHandler)必填 3. 支持任務取消/回調 4. 內置耗時監控指標 |
通俗易懂的解釋:
自定義連接 ≈ 改裝汽車
- 問題:標準貨車(默認連接)無法運輸特殊貨物(畸形響應)
- 方案:改裝貨箱(自定義解析器)→ 調整生產線(連接工廠)→ 新車交付(注入HttpClient)
有狀態連接 ≈ 會員通道
- 普通通道(無狀態):人人可用,用完即走
- VIP通道(有狀態):需刷會員卡(用戶令牌),且同一會員必須用同一張卡
異步請求服務 ≈ 快遞調度中心
- 倉庫規則:貨車數量(連接數)需匹配調度員數量(線程數)
- 寄件流程:
- 填好運單(
ResponseHandler)- 預約取件(
service.execute())- 可選:電話查進度(
task.get())或 取消寄件(task.cancel())- 智能監控:記錄每單耗時(網絡傳輸/排隊時間),優化調度策略
浙公網安備 33010602011771號