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号