你惨了,你的 Http Status = 0 了

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

周一一大早就接到了测试的 Bug 反馈,其实也是用户的反馈:

【测试】移动端偶尔会出现 操作异常,请刷新重试! 的提示,但是重新刷新又好了,用户说好不容易填了数据一刷新就白费功夫了,而且在测试环境也能复现,不过目前不是那么好复现了,这是之前复现时的截图,你先看看

你惨了,你的 Http Status = 0 了_第1张图片

【卑微前端】毕竟没得选)好,我看看!

你惨了,你的 Http Status = 0 了_第2张图片

你惨了,你的 Http Status = 0 了_第3张图片

这是之前(被迫)接手的一个 React 老项目,其中的 请求 是通过自定义封装 XMLHttpRequest 的方式来实现的,没有使用 React CLI 的方式,而是自己基于 webpack 构建工具搭建的一个项目。

忠告】对待老项目核心就是要足够尊重,千万不要吐槽它( 老项目很小气的),
04DCD231.png 一不小心就能给你 惊喜,还可能是 无限的惊喜 !!!

大致的复现步骤,如下

就是在 A 页面B 页面 间来回进行 上一步下一步 操作,然后再就会出现请求异常的情况。

有了以上信息,我们就可以开始 BUG 的探索之路,下面就逐步进行分析,欢迎一起探讨!

无需入侵代码的调试方式

为了更好的复现问题和调试 H5 移动端,我们通常会使用一些 移动网页前端开发工具,例如 vconsole,但是这意味着我们还得去修改 代码内容项目依赖,而且 vconsole 的交互也不太友好(懂的都懂),那么除此之外还有没有什么其他办法可以实现 无入侵的便捷调试 呢?

你惨了,你的 Http Status = 0 了_第4张图片

那自然是有的了,最常用的就是 抓包(如 whistle,详细用法可见 Whistle 帮助文档)、Chrome DevTools 远程调试 两种方式,这里也简单介绍一下它们的使用方式。

Chrome DevTools 远程调试手机端

这里的远程调试手机端用 Android 系统来演示,IOS 系统步骤是一致的,只是会有些细节差异,自行查找一下即可。

连接 手机 和 电脑

只需要使用 数据线手机端电脑端 连接起来,同时将手机中的 开发人员选项 中的 USB调试 选项开启,通常插入 USB 数据线后都会自动弹出选项框来让你进行选择 USB调试 模式,但部分手机的弹窗中可能没有和 USB调试 相关的内容,如下:

你惨了,你的 Http Status = 0 了_第5张图片

此时你可以点击手机 设置,然后使用其搜索功能快速搜索定位,如下:

你惨了,你的 Http Status = 0 了_第6张图片

你惨了,你的 Http Status = 0 了_第7张图片

访问 Chrome DevTools 远程调试界面

通过 chrome://inspect/#devices 访问调试界面,然后你就可以使用 微信访问目标页面 或者 手机浏览器访问目标页面,接着等待调试界面加载到对应的信息后,就可以点击对应的 inspect 进行调试了,具体如下:

你惨了,你的 Http Status = 0 了_第8张图片

whistle 抓包

这里的抓包用 Android 系统来演示,IOS 系统步骤是一致的,只是会有些细节差异,自行查找一下即可。

一键安装 Whistle(Mac 和 Windows)

npm i -g whistle && w2 start --init 一行命令完成如下操作:

  • 安装 Whistle
  • 启动 Whistle

    • 设置系统代理,或不代理的白名单域名
    • 安装根证书

访问 Whistle 界面

启动成功后,会在终端输出可访问的地址,默认为如下三个:

你惨了,你的 Http Status = 0 了_第9张图片

移动端安装 CA 证书

whistle 界面点击 HTTPS 选项,在手机端扫码并下载对应的 CA 证书,目的是在代理时能够正常访问 https 协议头 的站点,而且现在的站点大多都是使用 https 协议,因此最好操作这一步。

你惨了,你的 Http Status = 0 了_第10张图片

手机端下载 CA 证书 完毕后,就需要 授权安装手动安装,可以在手机 设置 中快速搜索查找,然后按指示安装最近下载文件即可:

你惨了,你的 Http Status = 0 了_第11张图片

你惨了,你的 Http Status = 0 了_第12张图片

保证手机端和电脑端处于同一网段

也就是保证 手机端 连接的 WIFI 要和 电脑端 正在使用的网络是一致的即可。

设置手机端网络代理

保证是同一网段后,就需要设置代理了:

  • 找到当前连接的 WIFI,长按然后修改网络

    你惨了,你的 Http Status = 0 了_第13张图片

  • 配置代理信息

    • 服务器主机名 就是填写你的电脑的 IP,当成功启动 Whistle 后输出的信息中会包含该内容,也可自己通过 ipconfig 命令查看
    • 服务器端口 就是 Whistle 代理设置的端口,默认为 8899,如果你自定义端口保持一致即可

    你惨了,你的 Http Status = 0 了_第14张图片

    你惨了,你的 Http Status = 0 了_第15张图片

电脑端关闭防火墙

通过上述操作后,针对大部分人来讲可能还是无法实现代理,因为 手机端的代理请求电脑端的防火墙 拦截了,此时就需要将防火墙暂时关闭即可。

防火墙不允许关闭怎么办?

有些小伙伴的办公电脑可能为了安全不允许你 直接关闭防火墙,因此你要查看防火墙对应的 白名单端口 有哪些,接着将 原本的 Whistle 代理端口 切换到 符合的端口 即可,没有的话可以自己添加 白名单端口

值得注意的是,你切换了新的 Whistle 代理端口 后,你的 CA 证书 需要重新安装,最好是电脑端和手机端都重新安装,因为我们要保证手机端在通过代理请求访问代理服务时是可信任的。

Http Status = 0

定位提示原因

确定提示来源

首先会提示这个信息,那么得先确定这个提示的来源,到底是后端返回的,还是前端自定义的,于是把 提示文本 在项目中一搜索就匹配到了,这显然是前端自定义的提示:

你惨了,你的 Http Status = 0 了_第16张图片

分析提示原因

从代码逻辑上看很明显,就是当请求遇到错误时,就会触发 error 事件,于是就会向外返回提示文本并进行提示,并且还会输出相应的相关信息。

配合日志定位

但值得注意的是:提示内容包含了 xhr.status,再看看实际提示内容:

你惨了,你的 Http Status = 0 了_第17张图片

再看看 监控日志,如下:

你惨了,你的 Http Status = 0 了_第18张图片

哦豁!哦豁!哦豁!

xhr.status = 0 !!!

你惨了,你的 Http Status = 0 了_第19张图片

说实话我还是 第一次 遇到这种 状态码,这不免引起我的兴趣,这不得继续了解了解!

Whistle 抓包 + Chrome DevTools 远程调试信息汇总

Whistle 抓包异常信息

你惨了,你的 Http Status = 0 了_第20张图片

Chrome DevTools 远程调试信息

你惨了,你的 Http Status = 0 了_第21张图片

结合以上抓取到的信息,我们大致得到相关的信息就是:

  • ERR\_CONTENT\_FAILED

    • 这个错误也许你没有见过,但它在这可 不是一个无关联的错误,由于响应头的中 content-type 值为 application/json; charset=utf-8,而异常接口显然返回了 不符合编码格式的内容,因此发生了 ERR_CONTENT_FAILED 错误,即 属于同一个问题的不同表现 罢了
  • XMLHttpRequest 请求异常触发 xhr.onerror,但具体是什么异常未知,导致 xhr.status = 0

什么场景下 Http Status = 0 ?

XMLHttpRequest.status

XMLHttpRequest.status 是 XMLHttpRequest 响应 中的一个 无符号短整型 的数字状态码,即其值就是对应的是标准的 HTTP status codes,而 HTTP 响应状态码 又被归为以下五大类:

看着没有问题啊,但是你有没有发现 状态码 0 不在这常见的五大类中!!!

XMLHttpRequest.status 为啥是 0?

实际上在两种情况下 XMLHttpRequest.status = 0

  • 请求完成前status 的值为 0
  • 如果 XMLHttpRequest 出错,浏览器返回的 status 也为 0

请求未完成

请求未完成时 XMLHttpRequest.status = 0 的情况直接看代码示例就知道了,如下:

var xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.status); // UNSENT(未发送)0

xhr.open('GET', '/server', true);
console.log('OPENED', xhr.status); // OPENED(已打开)0

xhr.onprogress = function () {
  console.log('LOADING', xhr.status); // LOADING(载入中)200
};

xhr.onload = function () {
  console.log('DONE', xhr.status); // DONE(完成)200
};

xhr.send(null);

但这显然不符合现在的场景,因为它是在 xhr.onerror 已经被触发。

XMLHttpRequest 出错

这里又会分为几种情况,其实在,具体如下:

  • 请求中止,例如基于 xhrInstance.abort() 将请求中止

    你惨了,你的 Http Status = 0 了_第22张图片

  • 发生跨域

    你惨了,你的 Http Status = 0 了_第23张图片

  • 错误的 URL 或 协议头

    你惨了,你的 Http Status = 0 了_第24张图片

  • 服务端提前结束响应,即未正常进行响应
  • 网络断开,网络异常导致操作过程请求无法正常发送等
  • ...

前端问题?后端问题?

相信不少前端同学看到诸如下图所示的 AJAX\_ERROR 时,就直接撒手不管了,交给对应的后端去看,正常来说确实没问题,但是总归要排查原因(不然凭啥说不是你的问题),例如这里存留的疑问点是:

  • PCMobile 使用的同一个接口,PC 端各个 请求正常,只有 Mobile 端有 异常请求
  • Mobile 端项目在 本地启动(即开发环境和已部署的测试服务),然后以浏览器的方式访问项目,并按 复现步骤 进行操作时是 正常请求,并不会有 异常请求 的情况出现

你惨了,你的 Http Status = 0 了_第25张图片

排除前端嫌疑

为了证明不是前端的问题,我们就需要 列举出可能由前端引发问题的情况,而归结起来就是一种情况,那就是 " 请求被终止发送 "

  • 手机端的 webview 存在 缓存 Ajax 请求 的机制,当 webview 检测到某个 Ajax 请求缓存可用时,会将新的请求 取消 掉,此时 xhr.status = 0

    • 这个是在查询资料过程中看到的一种可能,也好验证,那就是 清除缓存即可,但实际尝试后发现无效,证明不是此情况
  • 手机端 网络异常,请求未能正确发送

    • 这种情况也是不对的,因为除了用户在生产上出现异常请求外,我们在 测试环境 也能够 复现

除此之外,仔细看 Whistle 抓包 结果,异常请求的 result = 200,证明前端请求已经正常发出,只是 后续响应过程出现问题

你惨了,你的 Http Status = 0 了_第26张图片

排除后端嫌疑

后端可能引发问题的情况,总结起来也是一种情况,那就是 " 提前结束响应 "

  • 对接口请求频率有限制,例如频繁请求同一个接口时,多余请求可能就会被忽略

    • 而后端根本没有对请求做任何限制,并且再进行响应时,都会有 输出日志记录,在查找对应的日志后发现,对应的 异常请求并不在日志记录中,因此不是这种情况
  • 响应过程出现编码错误,因为在抓包过程中我们看到了,响应体 body 中是乱码的,不符合响应头中 content-type: application/json; charset=utf-8 的编码格式

    • 基于测试环境的后端日志输出,发现不存在异常请求的情况,因为即便是编码错误,也会有日志记录,因此也不是这种情况

以上情况说明,当产生异常请求时,根本没有进入到后端的 Control 层,因此后端嫌疑也排除了。

你惨了,你的 Http Status = 0 了_第27张图片

交由中间链路进行排查

既然排除了 前端/后端 的嫌疑,那么剩下的可能就是 中间链路 的问题了,用户在手机端访问目标站点时的 链路,如下:

  • 手机 -> 网关 -> Nginx -> 应用服务 -> Nginx -> 网关 -> 手机

你惨了,你的 Http Status = 0 了_第28张图片

而目前看 应用服务 返回的是没有问题的,因此剩下的就交由 网关运维 去进行排查了,跨部门协作 会有一些时间上不对等的问题(懂的都懂),后续具体原因会同步到此处。

你惨了,你的 Http Status = 0 了_第29张图片

后续】排除运维嫌疑

下面是一段对话,这里只表达大致的意思:

  • 前端 A@ 运维 C,上面的问题后来服务端加过日志看过,前端收到乱码,服务端返回的是正常的,还有服务本地部署,通过 whistle 将测试环境的接口转发到本地服务,也不会出现乱码的情况
  • 运维 C@ 前端 B,一起帮忙看一看
  • 前端 B】上次和 前端 A 一起看过了, 查到乱码的数据是服务端返回的, 其他的就没法查下去了,看样子移动端的接口, 应该经过了 好几层的代理,所以现在想看下响应从服务端出来后,经过 ngnix 的时候是否正常
  • 前端 A】是的,现在的想法就是跟随 响应路径 的返回一层一层去排查
  • 运维 C】移动端测试环境是 iis ,不是 ng 代理

你惨了,你的 Http Status = 0 了_第30张图片

总结一下,意思就是只有 生产环境ng 代理测试环境 是没有 ng 代理 的,那现在的情况是:生产环境可复现,测试环境可复现,因此不应该是 ng 代理 的问题,那么只有一种可能:网关

你惨了,你的 Http Status = 0 了_第31张图片

静等 网关 排查中 ......

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

以上就是本文的内容了,大致经过:出现问题 —> 复现问题 —> 排查/定位问题 —> 验证、排除嫌疑 —> 跨部门协作 等过程。

也许你觉得一开始就直接交给 后端、网关、运维 去排查就好了,毕竟一通操作下来发现不是自己的问题,还浪费了时间等等。

但是要明白的是当我们需要把某个问题交给 其他部门 进行排查/定位时,并不是我们随便说一句就可以了,因为大家都有很多事情要做,对你紧急的事情在其他部门中的优先级并不一定是最高的,此时还需要 部门和部门的负责人沟通协调,而在这之前最有必要的是先确保我们这一方是真的不存在问题的,然后还得把你的信息同步给其他部门同事,方便他们更快速的了解情况,更好、更快的实现互相协作和解决问题。

你惨了,你的 Http Status = 0 了_第32张图片

你可能感兴趣的:(你惨了,你的 Http Status = 0 了)