Http+Netty 管道关闭相关

去参加QCon的路上,一个同事给我打了个电话,他在使用Moco的过程中,遇到了一个奇怪的问题:测试会时不时的挂住。在不明就里的情况下,我给出了一些常规性的建议,换个新版本试试,要不,能不能把情况简化到最简单的情形,发给我试试。

几天后,我从QCon回来,那个同事愁眉苦脸地找到我:换了新版本,问题依然,更奇怪的是,当试图用最简单的情形重现时,现象居然就消失了。用抓包工具试试吧,看看底层发送的消息是不是对的。我又想到了一个常规的解决方案。他同意了,尝试了,又失败了,它在中间加了个代理,结果现象又消失了。两边发送的消息从HTTP报文的内容上,看不出什么端倪。

我这个同事在办公室里是出了名的执着,几天之后,他又跑了过来,我已经写了个测试,已经能够稳定的重现这个现象了。我们一起坐下来,用单步跟踪的方式追着代码。经过无数次的运行,我们惊奇地发现,挂住的地方根本没有走到Moco里面,当第一次访问Moco服务器成功之后,离开Moco代码,在Netty框架里,试图select下一个要处理的请求时,就挂住了。而且很有趣的现象是,如果两次访问Moco服务器时间间隔较长,比如5秒,万事大吉,如果时间很短,基本上就会挂住。我们甚至开始怀疑是不是Netty本身出了问题。

第二天,这个同事又取得了新的进展,他已经把这个测试简化到完全不依赖于他们的工程。这样,我们就可以在别的机器上进行调试,而且简化到最简单的情形之后,我们只关注最重要的东西。这次现象是相同的,挂住,但跟进去的结果却截然不同,这回出问题的地方是客户端,当发送了第一个请求之后,试图进行第二次发送时,请求就挂住了。

我能怎么办呢?我又仔细品味了阅读了一下Moco的代码,突然注意到里面有这样一句:

  future.addListener(ChannelFutureListener.CLOSE);

这句话的意思是,当请求处理完成之后,关闭链接。似乎灵光闪现,我查看了一下客户端发起的请求,在HTTP头里面有这样一句:

  Connection: keep-alive

这显然是HTTP协议里面,用来请求服务器端保持链接的一种方式。如果你不知道的话,在一些HTTP协议的实现中,有时为了节省链接建立的资源,会复用之前已经建立好的链接,当然,前提条件是,服务器和客户端都支持。

显然,Moco是不支持的。但客户端又有这种期望,所以,挂住了。简单地删除了负责关闭的代码,代码顺畅地通过了测试。这里面有两个问题,Moco服务器端支持是一方面,客户端实现是另一方面,换句话,当服务器端不支持长链接时,客户端应该做相应的处理,至少这个客户端没有做到,顺便说一下,这个库是CXF。

当我在Moco的代码库里删除了关闭链接的代码时,我惊奇地发现Moco的一个测试失败了。怎么会失败呢?难道测试里面还有人需要强制关闭链接。

带着种种疑问我又踏上了新一轮的代码跟踪之旅,这次的所有代码都在我自己的代码库里,一个简单的单元测试,无非是里面用到了Apache的HTTP Client。又是无数次的重试,我站到了一个InputStream的面前,这是在处理应答体时,要从HTTP应答流中读内容,然后,挂掉了。显然,当我强制关闭链接,读取就会返回,所以,测试可以通过。

为什么会是这样?

我不再简单粗暴地关闭链接,而是根据情况,如果客户端希望保持链接,我就保持,否则才会关闭。结果,测试依然是挂的。原因是这个测试请求保持链接,所以,服务器端不会关闭它的链接,所以,还会遇到相同的问题,它会挂住。

到底什么样的行为才是正确的呢?我找到一本厚厚的《HTTP权威指南》,仔细读了一下保持链接的章节。我对HTTP如何处理链接有了一个相对完整的认识,但对于这个问题,我依然没有答案。

其它测试也会有保持链接的需求,为什么就可以顺利通过,而只有这个测试会挂住。

坐在电脑面前解决不了的问题,往往需要在路上解决。我突然意识到,通过者和不通过者似乎是有些许差别的,赶紧回到电脑前,打开日志,这个不通过的测试与其它测试最大的差别是,它只有应答头,没有应答体。不奇怪,因为这个测试就是测试能够返回正确的HTTP头。

我似乎明白了,当客户端要求保持链接时,它还是需要从链接中读取数据的,但应该读取多少,谁能告诉它这个答案呢?如果没有额外的提示,似乎就只能挂住。答案就在Content-Length上,有了这个HTTP头,客户端就知道该读多少了。

想通这一点,代码就好改了。在保持链接的情况下,如果没有Content-Length,就设置相应的Content-Length。

果然测试一遍通过,又把之前同事的测试用例拿了过来,重新跑了一遍,一切正常。放到他们的工程里面测试,也是顺利通过。

从发现一个不稳定的问题,到最终比较完整地修复了它,前前后后两个星期。如果没有我那个执着的同事,可能我也不会严肃地对待这个问题;没有仔细地解决这个问题,也不会对HTTP协议处理有个新的认识。非常感谢我这个执着的同事,他叫崔鹏飞,下一次Moco发布公告中,我可要着力感谢一下他。

一个同事跑到我面前,我在使用Moco时发现一个问题,它有时会挂住。你用的是不是CXF,我问他。是啊!他回答。我笑了,因为我刚刚修好了这个问题。

你可能感兴趣的:(netty)