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通道(有状态):需刷会员卡(用户令牌),且同一会员必须用同一张卡

异步请求服务 ≈ 快递调度中心

  • 仓库规则:货车数量(连接数)需匹配调度员数量(线程数)
  • 寄件流程
    1. 填好运单(ResponseHandler
    2. 预约取件(service.execute()
    3. 可选:电话查进度(task.get())或 取消寄件(task.cancel()
  • 智能监控:记录每单耗时(网络传输/排队时间),优化调度策略
posted @ 2025-07-24 14:17  hqq的进阶日记  阅读(18)  评论(0)    收藏  举报