HTTP200一定请求成功了么

677 阅读4分钟

这还真不是标题党,因为这几天在业务中遇到了这个问题。

背景

接手了一个项目,少说也有两三年了,项目采用的是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。