jersey-client发送空的POST请求时服务器返回411错误 - 一次trouble shooting经历及经验教训

问题描述:

DTS v2一直正常工作,突然从6月8日开始regression tests失败。


问题分析过程:

因为代码已经很久没有改动过,唯一的变化就是使用的第三方库因为安全漏洞的问题升级过。但是6月8日这个节点并没有代码或者配置的更新。所以初步认为是依赖的外部服务发生了改变。

接下来做的事情就是在本地重现这个问题以方便调试。这个service开发的时候,使用的都是HTTP而且禁用了oauth header validation,因为多数时间只要保证功能没问题,部署上线之后都没有发生过问题。因为本地开发环境和线上环境的差异,也为这一次诊断问题设置了不小的障碍。

在本地使用HTTP并且禁用validataion的情况下,不能够重现问题。于是推断是validation这部分的错误。启用validation能够重现错误得到外部服务返回411错误,说是Content-Length没有设置。这个错误很让人困惑,让我想起两年前的一次问题。当时出现的问题也是411,Rocky同学用.header("Content-Length", 0)强制设置Content-Length为0,我review过代码所以记得很清楚。当时就这样修复了问题,为什么现在不行了呢?看来老革命遇到新问题了。联系O2 Team报告问题,新加坡的同事说他们那边的log里边找不到我这边发过去的请求。神秘失踪?半信半疑反复检查代码,抓包确定请求已经发给O2了。


于是再联系再催,来了一个senior一点的同事conf call一起看,确定他们收到了请求。只不过不知道请求被转到什么地方去了,汗啊。因为他们的系统有前后两层(或三层?)web servers, 最后的是IIS。IIS的日志里边的确没有查到我发送的请求。

为了调试问题,O2的同事给了我后边worker的DNS,这样我的代码能够绕开中间的各层直接连接上去。修改代码忽略SSL证书的小问题,连接worker成功,但是依然是411错误。对方告诉我,请求收到了但是Content-Length没有加到POST里边。

这个时候我有了另外的发现,我调试的时候讲request输出成curl命令,然后在console里边执行这个curl命令,一切正常。Postman也是一样,成功了。我开始对这个代码不自信了,发送出去的到底是什么啊。于是用NodeJS写了一个最简单的服务器,这个服务器什么都不做,仅仅dump所有的请求头。然后我将DTS中发送请求的代码也摘了出来,单独写了一个client。这样简化的客户端和一个检查请求头信息的服务器很快就完成了,结果让我大吃一惊。Content-Length真的没有发送出来,即使代码里边显式设置它。这个问题在Jersey 2.2, 2.22, 2.26都能重现。那么问题来了,为什么它不起作用? 为什么以前修改就解决了411的问题呢?


周五的晚上了,问题依然还在。不甘心和挫败的情绪占据了我的脑袋,睡不着。我干脆拿出电脑去看看Jersey的代码,说不定有什么发现。顺着感觉,很快在Jersey的github repo里边找到了对应的代码,发现一段注释提到了restricted headers。Jersey代码真的是良心作品,还在注释里写清了怎样启用这些headers。并且,顺着这段注释我找到了OpenJDK中对应的代码,class HttpUrlConnection,也提到了restricted headers。缺省情况下,为了保持对HTTP 1.0的兼容,这些头被禁用。


问题解决方法:

解决这个问题很简单,主要就是一行。

System.setProperty("sun.net.http.allowRestrictedHeaders", "true");


当然,下面的代码设置请求处理模式,也可以加上去,让你的代码更加合理。

ClientConfig config = new ClientConfig();
config.register(CheckRequestFilter.class);
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED);      
Client client = ClientBuilder.newClient(config);
       

response = invocBuilder.header("content-length", 0).post(null);

还有两个问题需要讲明白,为什么O2之前没有问题呢?因为之前他们使用了Akamai,Content-Length如果不在请求里边,会被自动加上。而当他们换成AWS CloudFront后,这个header不会被加上。

为什么两年前同事认为他修好了问题呢?其实不是,虽然代码加上了Content-Length但是并没有真正发送到服务器。代码没有出现问题,只是碰巧服务器配置好了Akamai。


总结:

教训很多。其一,本地环境尽可能跟线上环境一样,这样可以更快重现和定位问题。这一次,为了支持HTTPS,服务器端使用了自签名的证书,配置调试花了一些时间。因为之前使用的ad-hoc client不支持HTTPS,不得不临时修改Desktop端的代码,这花了不少时间。其二,尽早简化和隔离问题。例如,写一个简单的NodeJS App或者找一个proxy就能快速看清request headers。但这个工作,我到了很晚的时候才开始做。那么之前的时间干嘛去了呢?其三,坚持自己的判断,尽管这个并不容易。持续的google并没有多少有用的收获,因为这个case很特别。但是google-and-try的模式真的花了很多时间。另外,每天站会的时候都会受到各种杂音指指点点,这样的歧途误入也花了很多时间。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bruce Jia(上海)

熬夜码字换酒钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值