实现自定义扩展点_怎样在Apache Httpclient上做扩展

130c50cd1f159c8c41bb6b1eaf514d69.png

1. 背景

笔者工作中有调用第三方服务提供商REST服务的场景,需要对这些服务提供商接口的请求响应情况做一些打点,记录性能和稳定性相关数据,以对服务接口做质量评估。

如果把打点逻辑埋进业务代码中,每个调用点都需要写相同的逻辑,这引发了典型的高耦合问题,无疑不合理。笔者的视角转为:能否在项目中使用的Apache HttpClient(项目中使用的版本是4.5.3) 工具(以下简称httpclient)上做扩展,完成打点功能。 完成类似的打点功能有两种常用的解决方案。 1. 责任链模式。其代表是servlet中的filter。 2. 切面。其代表是Spring 的AOP增强。 3. 运行时使用JVM Agent挂载技术做类增强。其代表是Btrace。 运行时动态增强不侵入代码,但是可能会带来安全性和性能的不确定性,并且不符合公司的技术栈,所以方案聚焦到了1或者2。笔者带着这两种预选的方案思路开始baidu和google,但是没发现靠谱的解决方案,于是开始Apache HttpClient 4.5.3了读码之旅,也才有了这篇总结。

2. Apache HttpClient调研

2.1 一般使用流程 Httpclient的一般使用步骤: 1. 调用 HttpClientBuilder.create() 创建一个HttpClientBuilder; 2. 对HttpClientBuilder进行配置,包括超时时间、Keepalive等; 3. 调用 HttpClientBuilder.build() 创建CloseableHttpClient; 4. 创建HttpUriRequest(如HttpGet、HttpPost),并填充请求参数; 5. 调用Clo seableHttpClient.execute 执行实际发送请求,并接受响应; 6. 处理返回的CloseableHttpResponse响应,得到返回的数据。 我们的需求是要在发起请求前、收到响应后打点,所以重点调研 CloseableHttpClient.execute 的执行流程。 2.2 CloseableHttpClient.execute执行流程 翻越层层的封装,看到了这一个比较清晰的调用关系:InternalHttpClient.execute -> InternalHttpClient.doExecute -> ClientExecChain.execute。从执行的类名就可以管中窥豹: 这是一个责任链模式,责任链的变量名是 execChain 。接下来看链条中有哪些链节点以及如何构建此执行链。 InternalHttpClient的execChain是由 HttpClientBuilder.build() 实际创建并传入。实际创建过程的细节不表,笔者梳理了一张execChain图:

84bb977bbcec8375f39b02937661d591.png

执行顺序如图中蓝色箭头所示,从左向右,依次调用每一个XXXExec的execute方法(实现 ClientExecChain 接口中的方法)。后面两个节点是执行链必经节点的,前面三个是根据配置可选,配置参数也已列在下方。必要的也是重要的,先看MainClientExec。此节点包含发送、接受数据的逻辑,虽然很重要,但是和我们的需求无关。接下来是ProtocolExec,此节点下文详细说明。 2.3 ProtocolExec.execute执行分析

ProtocolExec.execute还是比较清晰的,先是设置一些HTTP协议的头,然后重点代码来了:

d36615b2e8713f0443b5d0543821ead9.png

相信看到这几行代码,大家肯定也会和笔者当时一样,会心一笑,这不就是 before 和 after 切面嘛,连类型的名字都 叫Interceptor 。现在总结一张execChain执行链完整的执行图:

370fccd22de4623850cc230a2d1d09ab.png

只要能找HttpRequestInterceptor的配置入口,就有了用武之地。在此处扩展,只需要用户在创建builder时增添自定义 HttpProcessor 处理器配置就可以实现扩展,而且配置项完全可以在最后的插件中封装好。通过配置的方法启用功能符合一般编程习惯,不仅不需要修改任何代码,也不会对httpclient内部工作流程做任何的更改,这也增强了用户的使用信心。 真的会如此顺利吗?在制定详细方案时发现,ProtocolExec执行异常时直接抛出了异常,没有异常的切面入口,而我们的需求需要记录,而且更关注执行异常的情况,如果基于HttpProcessor扩展显然无法满足需求。 笔者只好把目光再次转移到execChain,带着是否能够在execChain中添加自己定处理节点的问题,再次考察HttpClientBuilder.build()中创建execChain的过程:

1. HttpClientBuilder.build()中,可以通过HttpClientBuilder配置,增删可选的内建处理节点,但是不支持添加自定义处理节点。

2. 如果想要在execChain添加自定义处理节点,需要提供自定义的HttpClientBuilder。

3. 解决方案

1. 自定义Exec处理节点:MonitorExec implements ClientExecChain。构造时需要传入执行责任链execChain;

2. MonitorExec.execute伪代码:

  excuteBefore();  try {    response = this.requestExecutor.execute();    Integer httpstatus = response.getStatusLine().getStatusCode();    if (httpstatus>=300) {      not200Fail();    } else {      success();    }    return response;  } catch (Exception e) {    exceptionFail();    throw e;  }

3. 提供自定义HttpClientBuilder:MonitorHttpClientBuilder,build方法中把自定义MonitorExec的加入到execChain中。MonitorHttpClientBuilder的其他方法,以及build方法里的其他逻辑不变;

4. 用户使用时,将HttpClientBuilder替换成自定义的MonitorHttpClientBuilder。 这种方案,需要用户将HttpClientBuilder替换成自定义的MonitorHttpClientBuilder,一是对代码业务代码有一定侵入,二是对httpclient版本有依赖,不够完美,但还能够接受。只是copy HttpClientBuilder的代码会不会有开源许可证的问题? 上述方案简述中,没有说明自定义MonitorExec在整个execChain所处的顺序位置。实际上这个问题还经历了一些波折。笔者第一反应是添加在ProtocolExec之后,MainClientExec之前,这样做的最大好处是自定义的MonitorExec可以获取到之前所有节点的增强结果,能得到的数据最丰富。按照这个思路实现后,成功、异常失败、非200失败三种功能测试都符合预期,但是在请求某个URL接口时出现了如下问题: 每次请求,MonitorExec都会被执行两次。增加日志,调试代码发现问题就出在MonitorExec所处execChain执行顺序上。用一张图说明: c8cee3aa3386a082dd65cf3d88974388.png URL接口的响应是301重定向,RediectExec(默认加载)不会向上一级返回301,而是发起了重定向请求(如上图深蓝色请求示意),所以在 RediectExec后面执行的节点,都会执行两次,包括MonitorExec。 重定向处理机制没有 问题,可自定义的 MonitorExec一次用户请求执行多次是否符合需求? 如果想要忠实记录所有请求,包括重定向,那执行多次是合理的。 笔者更倾向于和用户请求相符,用户代码发起一次请求则记录一次,中间的重定向则是内部处理,可以体现在耗时等其他指标中。 最终方案:

fb3a035f58fe24bcaa2d1a9adb6bcb75.png

4. 总结 本文详细描述了Apache HttpClient 4.5.3执行请求过程中执行责任链的工作机制,以及其中ProtocolExec执行节点的切面扩展。并在这两种工作机制的基础上,提出了两种Apache HttpClient扩展方案,供大家参考。

也许想看

  • 除敏捷实践外,项目管理还能做点什么?

  • 你应该知道的异步服务数据防丢失方案设计

  • 一文弄懂Nginx最核心的配置

  • [手把手系列之]Docker 部署 vue 项目

d44c7b49c3a09cc9ab1710dd3341e90e.png

好文我在看?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值