这还真不是标题党,因为这几天在业务中遇到了这个问题。
背景
接手了一个项目,少说也有两三年了,项目采用的是Flask开发的。到这里一切都还正常。
老项目里存在一些bug是很正常的。这天,正在修复bug,一个很简单的bug,不便于直接贴源代码,但是可以用伪代码描述一下。
在一个外层循环中,首先获取一下状态码
code = requests.get(url).json()["code"] # 类似于这种,code是200,500这种状态码
然后判断一下状态码是否为200,再决定跳出循环或者进入下次循环。
源代码是这么写的:
while True:
code = (
requests.get(url).json()["code"]
)
if code != 200:
break
不知道眼尖的你有没有发现问题所在。
问题发生
这个bug不难修,想着上线就可以完事儿了,结果上线之后,接连来了好几个OnCall问题。
仔细看了报警问题,跟这段代码的改动并没有任何关系,是另一个完全不相关的文件。
这就纳闷了,我没有修改另一个文件里的东西,怎么会出错呢。
是一个POST接口,打开浏览器页面去看了一下现场,很常规的打开了F12,熟练地切换到网络选项卡,F5刷新页面,所有接口调用都顺利结束了。
等一下,Console的错误消息数增加了两条。
打开Console一看,出现了神奇的报错:net::ERR_HTTP2_PROTOCOL_ERROR 200
这是一个ERROR,但是状态码是200.
可是在网络选项卡页面,对应接口并没有飘红。点开接口查看详情,诡异的一幕发生了。
在“标头”选项卡里,状态码显示的是绿勾200,“负载”选项卡也正常显示了内容,但是“预览”/“响应” 两个选项卡并没有展示任何内容,一片空白。
一直以来,认为HTTP状态码200就应该是请求成功了,而这时候现实摆在面前,确实没有成功。
排查
赶紧去线上查看一下log日志,这个接口的日志没有任何问题,按照预期,该打印出来的日志都打印出来了,接口的req、resp、耗时都在装饰器里正常打印了。
在网上搜了一下,相关的帖子很少,有几个帖子提到可能是nginx的配置问题导致的,赶紧联系运维,请他们协助。他们也没见过这种诡异的状态码报错。他们从nginx日志中找到了一段内容:
upstream prematurely closed connection while reading upstream
解释说,和网上提到的nginx配置不同,并不是nginx主动断开了连接,而是上游服务自行断开的。
继续追问运维,为啥断开连接了却给了200状态码,给到的答复是:
先返回了200状态码之后断开连接的
紧急行动
这个问题在这次发版更新代码之前没有发生,那先紧急回滚一下发布镜像吧。
回滚了之后,果然没有问题,一切都正常了。
又使用F12调试看了一下,这个接口返回的内容有 61kB,着实非常巨大。
思考
既然代码没有改动到这个文件,讲道理就不会出现代码逻辑上的问题。
仔细回想一下,线上哪些环节发生过改动。
看了一下自己提交的MR,调整了requirements.txt文件,有一些版本升级调整。之前代码使用的是Flask 1.0 版本,给升级到2.x版本了,gevent也从1.x升级到21.x版本,gzip,protobuf等库也升级了一些版本,难道是版本导致的?
尝试
赶紧登录到线上容器,pip3 list一波依赖库,把各个版本都记录下来,再创建了一个出问题版本的实例,跑一遍对比,若然有很多不同之处,不管了,把依赖都回退到之前的版本,再创建个镜像运行一个实例瞧瞧。
这次没有再报HTTP200的错误。把Flask升级到2.x,相关的几个依赖自动也升级了一下,又报出来这个错误。 但是这个接口并不是每次都会报错,仅当某个指定入参的时候,从数据库里拿到的结果非常多,才会在高版本Flask里出现这个报错。如果是某些内容少的结果,也会正常展示,没有异常。
猜测是跟返回结果的压缩有关,不管了,依赖都回退到能用的版本,上线。
也算是在工作中不断学习了,HTTP200状态码,请求一定成功了么,答案是不一定。
希望以后不要再出现这种corner case了,愿代码都无bug。