吐血加死一窝脑细胞--记录vertx3版本框架bug发现和解决策略
场景:http流代理
精力和代价: 三天一夜 无数调试和尝试
问题发现:对于tomcat等目标服务器,如果尝试用post文件流的形式尝试访问一个简单的get请求的小文件,比如localhost:8080/tomcat.png,现象会是怎样呢?尝试如下过程得出结论:
1 通过postman发起一个大文件流给tomcat,可以立马得到响应,多次发起多次响应没任何问题,看不出任何端倪,监控磁盘也没有发现有大的磁盘IO,初步结论,tomcat不会收取大文件流
2 通过自己写httpclient post一个大文件给tomcat,现象如下:post传完请求头就会收到tomcat返回的连接关闭异常,收不到响应的图片。进一步分析发现,postman可以得到响应但是httpclient的不到响应的原因是:httpclient是先发送完全部的流后才开始同步接收响应,postman应该是异步接收响应,不会等待请求流全部发送完毕
通过上述 1、2总结得出结论,对于这种奇葩的http请求场景(通过查阅http相关规范,其实也是符合规范的)tomcat采取的措施是:只收取完大post的请求头后就给请求方响应,然后直接关闭连接,不再接收后续的post流。
对代理的影响和正确的处理姿势:
对于这种奇葩请求,作为代理方,正确的合http规范的处理方式应该是:对请求方的大post流直接透传给tomcat端,对tomcat的响应也直接透传到请求方(当然是异步的请求和响应,不会像httpclient那样先发送完请求体给tomcat再接收响应),如果tomcat响应完毕后并关闭了连接,作为代理方应该监控到连接关闭信息,然后关闭请求方的连接。
用vertx3版本做代理,实际发现的BUG流程和现象是:
1 对于这种大post流直接转发给tomcat
2 很快就就收到tomcat返回的响应结果,将响应结果直接转发给请求方
3 然后又立马收到了tomcat发回的关闭连接的异常,对于代理的逻辑,应该是监控到异常,然后关闭客户端的连接,(如果不关闭客户端的连接,客户端的连接流就会被阻塞挂起,虽然vertx是异步框架,但是连接会越积越多,所以必须关闭请求方连接),所以我根据vertx3的API给httpClientRequest和httpClientResponse设置了异常监听回调,一旦收到异常信息就关闭请求方的连接。但是这时候出问题了,对于vertx3框架,经过调试,发现框架连接层面,确实收到了tomcat的断链信息,但是缺没有回调上层的httpClientRequest和httpClientResponse对象,经过无数次的调试和源码跟踪发现:vert3对于断链的后端异常,先是传递给httpClientResponse对象,但是加了个判断 如果httpClientResponse已经结束了就不再回调了,所以我设置的httpClientResponse收不到回调,这也算正常,但是httpClientRequest却不给回调了,这就是不合理的了:主观分析:vertx3认为 大部分的请求都是请求流接收完毕才会给响应,所以响应都结束了,请求自然就结束了,所以不给httpClientRequest回调通知关闭了,但现在的情况确实是奇葩又合http规范的请求:即请求没有传递完 响应就先结束了。基于Vetx3的这个BUG,导致了这种奇葩请求在代理方越积累越多,最终无法接收新连接。
对于vertx3 正确的处理流程应该是,对于后端关闭连接的异常,应该既传播给httpClientRequest又传播给httpClientResponse。
BUG处理办法: 虽然vertx3没给请求异常传播,但是在连接层面可以设置监听回调,通过给HttpClientConnnect对象设置closeHandler来接收到关闭事件,然后再通过反射机制遍历当前连接中的所有请求流(一个连接支持多个请求流排队,管线http),然后反射调用每个请求流的handeException方法来将断链异常传播给使用当前连接的请求。
(完)