前言
欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!
周一一大早就接到了测试的 Bug 反馈,其实也是用户的反馈:
【测试】
:移动端偶尔会出现 操作异常,请刷新重试!
的提示,但是重新刷新又好了,用户说好不容易填了数据一刷新就白费功夫了,而且在测试环境也能复现,不过目前不是那么好复现了,这是之前复现时的截图,你先看看
【卑微前端】
:()好,我看看!毕竟没得选
这是之前()接手的一个 React 老项目,其中的 请求 是通过自定义封装 XMLHttpRequest 的方式来实现的,没有使用 React CLI 的方式,而是自己基于 webpack 构建工具搭建的一个项目。被迫
【忠告】对待老项目核心就是要足够尊重,千万不要吐槽它(),老项目很小气的
一不小心就能给你 惊喜,还可能是 无限的惊喜 !!!
大致的复现步骤,如下:
就是在 A 页面 和 B 页面 间来回进行 上一步、下一步 操作,然后再就会出现请求异常的情况。
有了以上信息,我们就可以开始 BUG 的探索之路,下面就逐步进行分析,欢迎一起探讨!
无需入侵代码的调试方式
为了更好的复现问题和调试 H5 移动端,我们通常会使用一些 移动网页前端开发工具,例如 vconsole
,但是这意味着我们还得去修改 代码内容 和 项目依赖,而且 vconsole
的交互也不太友好(),那么除此之外还有没有什么其他办法可以实现 无入侵的便捷调试 呢?懂的都懂
那自然是有的了,最常用的就是 抓包(如 whistle,详细用法可见 Whistle 帮助文档
)、Chrome DevTools 远程调试 两种方式,这里也简单介绍一下它们的使用方式。
Chrome DevTools 远程调试手机端
这里的远程调试手机端用 Android 系统来演示,IOS 系统步骤是一致的,只是会有些细节差异,自行查找一下即可。
连接 手机 和 电脑
只需要使用 数据线 把 手机端 和 电脑端 连接起来,同时将手机中的 开发人员选项 中的 USB调试 选项开启,通常插入 USB 数据线后都会自动弹出选项框来让你进行选择 USB调试 模式,但部分手机的弹窗中可能没有和 USB调试 相关的内容,如下:
此时你可以点击手机 设置,然后使用其搜索功能快速搜索定位,如下:
访问 Chrome DevTools 远程调试界面
通过 chrome://inspect/#devices 访问调试界面,然后你就可以使用 微信访问目标页面 或者 手机浏览器访问目标页面,接着等待调试界面加载到对应的信息后,就可以点击对应的 inspect 进行调试了,具体如下:
whistle 抓包
这里的抓包用 Android 系统来演示,IOS 系统步骤是一致的,只是会有些细节差异,自行查找一下即可。
一键安装 Whistle(Mac 和 Windows)
npm i -g whistle && w2 start --init
一行命令完成如下操作:
- 安装 Whistle
启动 Whistle
- 设置系统代理,或不代理的白名单域名
- 安装根证书
访问 Whistle 界面
启动成功后,会在终端输出可访问的地址,默认为如下三个:
移动端安装 CA 证书
在 whistle 界面点击 HTTPS 选项,在手机端扫码并下载对应的 CA 证书,目的是在代理时能够正常访问 https 协议头 的站点,而且现在的站点大多都是使用 https 协议,因此最好操作这一步。
手机端下载 CA 证书 完毕后,就需要 授权安装 或 手动安装,可以在手机 设置 中快速搜索查找,然后按指示安装最近下载文件即可:
保证手机端和电脑端处于同一网段
也就是保证 手机端 连接的 WIFI 要和 电脑端 正在使用的网络是一致的即可。
设置手机端网络代理
保证是同一网段后,就需要设置代理了:
找到当前连接的 WIFI,长按然后修改网络
配置代理信息
- 服务器主机名 就是填写你的电脑的 IP,当成功启动 Whistle 后输出的信息中会包含该内容,也可自己通过 ipconfig 命令查看
- 服务器端口 就是 Whistle 代理设置的端口,默认为 8899,如果你自定义端口保持一致即可
电脑端关闭防火墙
通过上述操作后,针对大部分人来讲可能还是无法实现代理,因为 手机端的代理请求 被 电脑端的防火墙 拦截了,此时就需要将防火墙暂时关闭即可。
防火墙不允许关闭怎么办?
有些小伙伴的办公电脑可能为了安全不允许你 直接关闭防火墙,因此你要查看防火墙对应的 白名单端口 有哪些,接着将 原本的 Whistle 代理端口 切换到 符合的端口 即可,没有的话可以自己添加 白名单端口。
值得注意的是,你切换了新的 Whistle 代理端口 后,你的 CA 证书 需要重新安装,最好是电脑端和手机端都重新安装,因为我们要保证手机端在通过代理请求访问代理服务时是可信任的。
Http Status = 0
定位提示原因
确定提示来源
首先会提示这个信息,那么得先确定这个提示的来源,到底是后端返回的,还是前端自定义的,于是把 提示文本 在项目中一搜索就匹配到了,这显然是前端自定义的提示:
分析提示原因
从代码逻辑上看很明显,就是当请求遇到错误时,就会触发 error 事件,于是就会向外返回提示文本并进行提示,并且还会输出相应的相关信息。
配合日志定位
但值得注意的是:提示内容包含了 xhr.status,再看看实际提示内容:
再看看 监控日志,如下:
哦豁!哦豁!哦豁!
xhr.status = 0 !!!
说实话我还是 第一次 遇到这种 状态码,这不免引起我的兴趣,这不得继续了解了解!
Whistle 抓包 + Chrome DevTools 远程调试信息汇总
Whistle 抓包异常信息
Chrome DevTools 远程调试信息
结合以上抓取到的信息,我们大致得到相关的信息就是:
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() 将请求中止
发生跨域
错误的 URL 或 协议头
- 服务端提前结束响应,即未正常进行响应
- 网络断开,网络异常导致操作过程请求无法正常发送等
- ...
前端问题?后端问题?
相信不少前端同学看到诸如下图所示的 AJAX\_ERROR 时,就直接撒手不管了,交给对应的后端去看,正常来说确实没问题,但是总归要排查原因(),例如这里存留的疑问点是:不然凭啥说不是你的问题
- PC 和 Mobile 使用的同一个接口,PC 端各个 请求正常,只有 Mobile 端有 异常请求
- Mobile 端项目在 本地启动(即开发环境和已部署的测试服务),然后以浏览器的方式访问项目,并按 复现步骤 进行操作时是 正常请求,并不会有 异常请求 的情况出现
排除前端嫌疑
为了证明不是前端的问题,我们就需要 列举出可能由前端引发问题的情况,而归结起来就是一种情况,那就是 " 请求被终止发送 ":
手机端的 webview 存在 缓存 Ajax 请求 的机制,当 webview 检测到某个 Ajax 请求缓存可用时,会将新的请求 取消 掉,此时 xhr.status = 0
- 这个是在查询资料过程中看到的一种可能,也好验证,那就是 清除缓存即可,但实际尝试后发现无效,证明不是此情况
手机端 网络异常,请求未能正确发送
- 这种情况也是不对的,因为除了用户在生产上出现异常请求外,我们在 测试环境 也能够 复现
除此之外,仔细看 Whistle 抓包 结果,异常请求的 result = 200,证明前端请求已经正常发出,只是 后续响应过程出现问题!
排除后端嫌疑
后端可能引发问题的情况,总结起来也是一种情况,那就是 " 提前结束响应 ":
对接口请求频率有限制,例如频繁请求同一个接口时,多余请求可能就会被忽略
- 而后端根本没有对请求做任何限制,并且再进行响应时,都会有 输出日志记录,在查找对应的日志后发现,对应的 异常请求并不在日志记录中,因此不是这种情况
响应过程出现编码错误,因为在抓包过程中我们看到了,响应体 body 中是乱码的,不符合响应头中
content-type: application/json; charset=utf-8
的编码格式- 基于测试环境的后端日志输出,发现不存在异常请求的情况,因为即便是编码错误,也会有日志记录,因此也不是这种情况
以上情况说明,当产生异常请求时,根本没有进入到后端的 Control 层,因此后端嫌疑也排除了。
交由中间链路进行排查
既然排除了 前端/后端 的嫌疑,那么剩下的可能就是 中间链路 的问题了,用户在手机端访问目标站点时的 链路,如下:
- 手机 -> 网关 -> Nginx -> 应用服务 -> Nginx -> 网关 -> 手机
而目前看 应用服务 返回的是没有问题的,因此剩下的就交由 网关 和 运维 去进行排查了,跨部门协作 会有一些时间上不对等的问题(),后续具体原因会同步到此处。懂的都懂
【后续
】排除运维嫌疑
下面是一段对话,这里只表达大致的意思:
- 【
前端 A
】@运维 C
,上面的问题后来服务端加过日志看过,前端收到乱码,服务端返回的是正常的,还有服务本地部署,通过 whistle 将测试环境的接口转发到本地服务,也不会出现乱码的情况 - 【
运维 C
】@前端 B
,一起帮忙看一看 - 【
前端 B
】上次和前端 A
一起看过了, 查到乱码的数据是服务端返回的, 其他的就没法查下去了,看样子移动端的接口, 应该经过了 好几层的代理,所以现在想看下响应从服务端出来后,经过 ngnix 的时候是否正常 - 【
前端 A
】是的,现在的想法就是跟随 响应路径 的返回一层一层去排查 - 【
运维 C
】移动端测试环境是 iis ,不是 ng 代理
总结一下,意思就是只有 生产环境 有 ng 代理,测试环境 是没有 ng 代理 的,那现在的情况是:生产环境可复现,测试环境可复现,因此不应该是 ng 代理 的问题,那么只有一种可能:网关
。
静等 网关
排查中 ......
最后
欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!
以上就是本文的内容了,大致经过:出现问题 —> 复现问题 —> 排查/定位问题 —> 验证、排除嫌疑 —> 跨部门协作 等过程。
也许你觉得一开始就直接交给 后端、网关、运维 去排查就好了,毕竟一通操作下来发现不是自己的问题,还浪费了时间等等。
但是要明白的是当我们需要把某个问题交给 其他部门 进行排查/定位时,并不是我们随便说一句就可以了,因为大家都有很多事情要做,对你紧急的事情在其他部门中的优先级并不一定是最高的,此时还需要 部门和部门的负责人沟通协调,而在这之前最有必要的是先确保我们这一方是真的不存在问题的,然后还得把你的信息同步给其他部门同事,方便他们更快速的了解情况,更好、更快的实现互相协作和解决问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。