从手写到 ADB 配合 Whistle 捣鼓前后端极度舒适的调试环境

37,828 阅读19分钟

前因

相信每一位前端程序员,在日常编写代码中,或多或少都会碰到前端三题:

㈠ 有没有便捷的 H5 页面抓包和模拟假数据方法?

㈡ 在公司网络限制下如何做到手机直连电脑服务,而不是通过费时费力的流水线打包访问测试服务器?

㈢ 学习业界优秀的技术方案时,能不能直接“试”着“改”代码,所见即所得地剖(pōu)析测试?

“工欲善其事,必先利其器”,这个问题一直困扰着渴望“高效工作,健康生活”的我。
那么,有没有一种既要手机直连电脑服务高效调试,又要没有代码仓库仍能想怎么改就怎么改,还要操作简单的前后端调试解决方案

本文以实际操作和核心步骤截图说明的方式,讲述了自己工作中网络抓包和手机连接电脑服务的探索历程,从而引出最佳解决方案 Whistle 的用途以及相关技术原理。

如果想快速使用网络抓包,直接点击 锚点
如果想手机直接连接电脑(手机不用连 wifi 仅通过 usb 线就可以连接电脑服务)是这么做到的?这个只支持 Android,直接点击 锚点
如果想手机通过代理连接电脑,Android 和 iOS 皆可,直接点击 锚点
如果想看看如何学习优秀的大厂前端技术,直接点击 锚点

强烈建议从头到尾花点时间阅读并实操一下,相信你会有所收获,如果有遇到相关问题,欢迎留言评论。

本文示例运行环境为 MacBook Pro 电脑(macOS Monterey 版本12.5.1,Apple M1 Pro芯片)和华为 Mate 40 Pro 手机(HarmonyOS 版本 3.0.0)。

看见

一个偶然的机会,我发现同事子力在使用 ADB(Android Debug Bridge) 端口转发命令(adb reverse tcp:8081 tcp:8081),手机仅通过数据线直连本地 HTTP 服务器,他这么做主要是为了便于控制影响因子,方便二分法排查网页性能影响因素。

我当时眼睛一亮,这不就是我一直在苦苦寻找的手机直连电脑解决方案么?

其实我也是 Android 起家,不禁让我想到 ReactNative 开发时就用过这个命令来启动电脑服务供手机开发调试,不过当时不求甚解,只是把这个命令当工具用,导致现在的我也成了工具...

再举一反三地想一想, Chrome DevTools 的设备检查功能(chrome://inspect/#devices)和 Vysor 的电脑远程控制投影手机功能都是类似手机直连电脑实时预览的解决方案。

图片来源: Remote debug Android devices

话说,高手的世界像星空,你看得见,却看不懂。
但至此,已经看到更大世界的我,思如泉涌,也想试试能不能由浅入深地看懂。

由浅

先把野兽般的想象力收一收,由浅入深地想想,问题是什么,我要干啥?
不就是前后端调试环境么?
要是我能手写一个简单后端服务,自己和自己联调,那感觉,倍爽。

手写前后端

先写一个包含网络请求的简单 H5 页面。

<html><title>书强号</title><body>    <h1>大家好,<br>欢迎大驾光临“书强号”</h1>    <!-- 显示网络请求响应信息 -->    <div id="resDesc"></div>    <script>        console.log("大家好,欢迎大驾光临“书强号”");        // 发送网络请求代码        const xhr = new XMLHttpRequest();        // 线上域名        // const url = "http://sheng.shuqiang.com/hello.json";        // 本地域名        // const url = "http://localhost:3000/hello.json";        const url = "http://127.0.0.1:3000/hello.json";        xhr.open('GET', url);        xhr.onreadystatechange = function () {            // const { readyState, status, statusText, responseText, responseURL } = xhr;            // console.log(`数据返回: ${JSON.stringify({ readyState, status, statusText, responseText, responseURL })}`, xhr);            const resDescElement = document.getElementById("resDesc");            if (xhr.readyState == 4) {                let resDesc, resDescStyle;                if (xhr.status >= 200) {                    resDesc = `数据返回成功:${xhr.responseText}`;                    resDescStyle = "color: green;"                } else {                    resDesc = `数据返回失败`;                    resDescStyle = "color: red;"                }                resDescElement.innerHTML = resDesc;                resDescElement.style = resDescStyle;            }        }        xhr.send();    </script></body></html>

接下来使用备受青睐的 Express Web 框架 (极简风格且开源),搭一个后端服务。
可以直接上网 Ctrl + C 和 Ctrl + V,我参考 一杯茶的时间,上手 Express 框架,复制过来改一下,根据 URL 路径分别返回 Html 主文档和 json 数据。

// 使用 express 框架搭建本地服务,监听端口 3000,可以访问 Html 主文档 和 Json 数据var app = require('express')();// 处理 Html 主文档路由const handlerHtml = function (req, res) {    const htmlPath = __dirname + '/index.html';    console.log("SSU", "用户访问 index.html,后端路径为" + htmlPath);    res.sendfile(htmlPath);};// 处理 Json 数据路由const handlerJson = function (req, res) {    const jsonPath = __dirname + '/hello.json';    console.log("SSU", "用户访问 hello.json,后端路径为" + jsonPath);    res.sendfile(jsonPath);};// 处理浏览器标题栏图标 favicon.ico 图片路由const handlerFavicon = function (req, res) {    const faviconPath = __dirname + '/favicon.ico';    console.log("SSU", "用户访问 favicon.ico,后端路径为" + faviconPath);    res.sendfile(faviconPath);};// Html 主文档路由,http://localhost:3000/ 和 http://localhost:3000/index.html 皆可app.get('/', handlerHtml);app.get('/index.html', handlerHtml);// Json 数据路由,http://localhost:3000/hello.jsonapp.get('/hello.json', handlerJson);// favicon.ico 图片路由app.get('/favicon.ico', handlerFavicon);// 监听 3000 端口app.listen(3000);

因为本地没有 express 包,需要运行 npm install express 手动安装依赖。
为了避免国内安装速度太慢,建议先运行 npm config set registry http://registry.npm.taobao.org 设置国内镜像。
最后运行 node simple-html-and-json-server.js 启动本地服务。

让我们来看看浏览器打开 http://localhost:3000 效果。

可以,整体跑起来了,hello.json 数据请求失败,意料之中,线上本来就没有 sheng.shuqiang.com/hello.json 服务链接,把线上域名改成本地试试 http://localhost:3000/hello.json

漂亮!!!
至此,本地搭建的前后端环境已经成功了,问题 ㈠ 已拿下。
代码在手,天下我有,想咋地咋地,模拟假数据(Mock)自然不在话下。
是不是有点小激动,我再也不用担心和后端同学加班联调了,只要自己和自己联通通过,保证前端这边没问题,剩下的交给后端同学慢慢调。

站住,别走!
你这是在电脑上用浏览器访问本地服务联通自测通过,手机呢?公司局域网内手机能访问电脑么?

手机直连电脑

先手机链接电脑试试行不行?
先整简单点的,我电脑和手机用的是家里的网络,没有网络策略限制,看看能不能连上。

成功了,虽然情理之中,但是还是有点小开心。

多说一句,有的同学运行 ifconfig 命令找电脑 IP ,这样有点费劲还伤眼睛,推荐个简单优雅的。

或者更简单直接的 cmd + option + wifi 图标(感谢 Billy 同学友情提示)。

接下来,连上公司 VPN 试试吧!

电脑连接公司 VPN 后,IP 地址不会变化,但是此时刷新手机网页,毫无意外地访问不了了。

既然走局域网不行,换个思路,直接断开网络,走 USB 直连呢?

是时候让 Android 调试桥上场了,ADB 是 Android 手机提供的调试桥,顾名思义,该方法仅能用于 Android 手机。点击此处进行下载安装 adb 工具软件包。手机打开调试模式,如果您未看到开发者选项,请按照相关说明启用开发者选项

电脑运行 adb reverse tcp:3000 tcp:3000 反向转发 3000 端口请求,简单说就是手机访问 3000 端口会直接转发给电脑 3000 端口代理,手机访问看起来和电脑访问一样了。

让我们拭目以待吧!

说实话,走到这,我的内心是崩溃的,咋还不行...
手机访问 http://192.168.101.17:3000 网络不可用我能理解,毕竟网络已经断开,手机是没法访问电脑 IP 的。
那么 http://localhost:3000 也不行,为什么啊?不是说 adb reverse 是端口反向转发么,手机访问 http://localhost:3000 等同于电脑访问 http://localhost:3000,电脑访问 http://localhost:3000 能正常打开页面,为什么手机就不行了...
掉坑里面去了,淡定!
换成  http://127.0.0.1:3000 试试?
快看,奇迹般地,手机竟然能访问了,喜大普奔。经 Billy 同学提醒,localhost 不能用应该是hosts文件里面缺省没有这条。

一张图总结一下原理。

image.jpeg

于是乎,网络限制已被绕过,问题 ㈡ 被攻下。
又可以开心地在公司手机访问电脑服务了。

拼多多,似乎有着某种魔力吸引着大家。当别人在关心拼多多买东西有多便宜时,作为一个技术工,更吸引我的是为什么拼多多页面这么快?

科学上网拼多多

浏览器直接打开拼多多首页网址 pddwyb.com,不出意外地跳到了登录页,想让我知难而退。

显然,我还在继续,就按他说的,手机号登录试试。
果然,已经防我这一手了,登录后跳到首页后又迅速跳回登录页。

有点意思,代码都在我电脑上了,而且页面还瞬间刷新了首页,我又可以 Debug 页面,这下还能难得倒我?
毕竟大家学得都差不多,电脑在我手上,拿下只是时间问题!

如果我能在页面跳回登录页前断点暂停页面,是不是就可以了?
说干就干,看了一下 Chrome DevTools -> “源代码” -> “事件监听断点” ,把几个可能性比较大的打上对勾,刷新页面,果然不出所料,断住了。
回过头一看,只要把 “DOM 变更” -> “DOMContentLoaded” 勾上即可。

在跳转到登录页前断点停住了,这就是我要的效果。点击浏览器导航栏 “文件” -> “页面存储为...”,这里注意格式要选择“网页,全部”,这样相关的依赖文件也一块存下来了。

直接打开存储在本地的拼多多 Html 主文档试试,第一眼首页可以正常显示,不错。接着会看到控制台一堆 CORS 跨域报错和网络失败。

跨域问题很好解决,页面路径和依赖文件本来就是相同文件夹下,只不过直接通过文件的访问方式会导致跨域问题。如果本地起一个 http-server 服务是不是就行了。
Just do it!

运行 npm install http-server 安装 http-server 依赖包,安装成功后运行 ./node_modules/.bin/http-server . -p 8080 启动本地 http-server 服务。

浏览器输入 http://127.0.0.1:8080/pddwyb.com.html 看看吧。

接下来如果要解决跨域的话,可以像上面的 Express 搭的后端服务一样,在网络响应 header 里面加上 Access-Control-Allow-Origin:*Access-Control-Allow-Headers:Content-Type 就可以,不过这么搞太麻烦。

通过拼多多首页 Html 主文档直接包括首屏静态 DOM 信息可知,拼多多使用了服务端渲染(SSR)首屏优化技术,这就是我们要找的页面打开为什么“快”的原因。

虽然 H5 代码毫无秘密可言,但是毕竟经过混淆了,读起来还是非常费劲的,一般不会直接改混淆后的代码,而是采用追加执行代码或者覆盖代码的方式。
问题 ㈢ 搭建本地运行代码也解决了。

是不是到这就可以了。
答案是否定的!
上面的手写操作只是以最简单的方式方便你理解原理,可以在特殊情况下多一些解题思路。真正的做法当然是站在巨人的肩膀上,借助强大的工具,Whistle(读音[ˈwɪsəl],拼音[wēisǒu]),刚好就是这样的前端调试利器。

入深

Whistle 可以完全胜任前端抓包和 Mock 数据功能,最吸引我的地方是轻量和开源(免费),不过实测过程中还是遇到了一些缺乏说明或者缺乏详细操作步骤等问题,导致始终不生效的情况,这也是我写这篇文章的初衷,记录下来给未来的自己以及屏幕前的你们。
接下来我将 Whistle 可以用于提高我们工作中效率的功能点带大家一步步走一遍,少些踩坑抓狂。

手机抓包

首页必须是安装 whistle ,考虑到国内安装缓慢或失败,运行 npm install whistle -g --registry=https://registry.npmmirror.com 指定镜像安装。
安装完成后,whistle、w2 和 wproxy 三个命令是等价的,都可以用于执行 whistle 命令。可以运行 w2 -V 看一下版本,如果能正常打印出来,说明安装成功了。
安装完成后,运行 w2 start 启动 whistle。

通过在浏览器打开 http://127.0.0.1:8899/#network 即可看到 whistle 网页控制台。
注:除非设置了电脑本地网络代理到 8899 端口(如何设置网络代理下文有讲),否则直接打开 local.whistlejs.com/ 访问不了,必须带上端口号 local.whistlejs.com:8899/ ,效果同 http://127.0.0.1:8899/#network 。

温馨提示,如果业务使用 http2 协议,务必要勾上“Enable HTTP/2”,否则你抓不到数据包,比方说 PDD 就用了 http2 发数据请求。这里要特别感谢一下方晶,解决了我一个百思不得其解的问题,“抓包看不到拼多多的数据请求,难道伪装成图片,或者用了其他黑科技”。

现在网络请求基本都走 https ,要想抓包必须在电脑和手机安装 https 证书。证书入口如上图所示。
证书下载后直接双击安装,中间要求输入密码,然后如下图将 whistle 证书选择“始终信任”。

将电脑中下载好的 whistle 证书拷贝到手机,按下图步骤安装证书。

踩过的坑:华为 P30 上要注意一下,实测“凭据用途” 只有选择“VPN和应用”才生效, 即“受信任的凭据”->“用户”tab 中会显示。如果选择“WLAN” Https 抓包不生效,“用户”tab 中不显示whistle证书记录。

手机证书安装成功后,将手机连接到电脑 whistle 代理服务,即手机网络设置为手动代理到电脑IP地址(我电脑是 192.168.101.17 )和 3000 端口。注意,手机和电脑要连接同一个网络。

手机连接电脑 whistle 代理服务后,你将会在电脑的 whistle 网页控制台抓住所有手机发送的网络请求包,包含 HTTP、HTTPS、WS、WSS等。

如果还不能抓包,可能是电脑防火墙没有关,关掉即可。

image.jpeg

在手机浏览器打开拼多多首页 pddwyb.com/,抓包看看货架瀑布流列表数据吧~

iOS 手机设置代理和安装证书类似,图解如下。

image.jpeg

image.jpeg

电脑浏览器和微信小程序也可以抓包,一站式操作,比 Charles 不能爽太多。

其实大部分开发是使用电脑浏览器,那边电脑上启动的本地服务能抓包 Mock 数据吗?

模拟电脑本地服务假数据

虽然前端起的本地服务,可以在代码里面写假数据(简称 Mock 数据),但这样毕竟对业务代码有侵入性,如果删除不干净很可能带到线上去了,通过前端代码写死假数据测试实属无奈之举。那么能不能在前端代码不修改的情况下模拟假数据?
答案必须能。先就着上面手机抓包拼多多数据,我们来 Mock 一下。
通过抓包,也可以进一步佐证拼多多使用了 SSR 首屏渲染。

竟然首屏已经渲染好了,自然也就没有 json 数据,所以只能 mock 货架瀑布流第二页数据。先给大家看看效果,再说怎么做的?

将拼多多首页第二页第一个标题“【超低价】2022板栗生栗子”改成“要求进步”,第二个标题“批发 白色 红色 全新料无味”改成“不愧是你”。

做到上面 Mock 数据只需要简单两步。

Step 1: 保存mock数据。whistle 控制面板 Network 页加上url过滤 http://127.0.0.1:8899/#network?url=https://mobile.yangkeduo.com/proxy/api/api/jinbao/h5_weak_auth/goods/query_goods_list_by_opt_id_c_v2,点击 Copy 按钮复制货架第二页瀑布流数据。

点击切换到 Values 页,创建新文件 query_goods_list_by_opt_id_c_v2.json, 将复制的货架瀑布流数据列表粘贴过来,修改第一个货架 goodsName 值为“不愧是你”。
注:修改完成后一定要保存文件,未保存时,文件名和 Values 均会飘红点,这个务必注意一下,不保存将不生效。

Step 2: 设置拦截规则。切换到 Rules 页,选中 Default(右侧有对勾才会生效),将第二页网页地址复制过来,空格间隔,后面写上 resBody://{query_goods_list_by_opt_id_c_v2.json} ,其中 resBody:// 表示替换返回数据,{xxx.json} 对应待 mock 数据。

配置好了再次刷新页面,可以抓包看到 mock 修改后的数据已生效。
这里要注意,修改内容后需要手动保存,注意 Values 和 Rules 左上角是否红点,有则切换过去保存,不保存则对应修改的规则和数据不生效,对我来说是一次惨痛的抓狂教训。

至此,我们完成了手机端数据 mock,电脑端 mock 也一样。不过现在的问题是,如果断开手机网络,whistle 控制面板根本抓不到其他的网络包。

如果要想抓包,必须请求走 whistle 的端口(默认 8899)代理,要想电脑浏览器可以被抓包,就需要设置浏览器端口(http 默认端口 80,https 默认端口 443)代理到 8899,有两类方法可以做到。

方法一: 直接通过 whistle 代理命令 w2 proxy on 打开代理, w2 proxy off 关闭代理。这一块我也是被各种文档坑得够呛。有的还打开“网络偏好设置”->“高级”->“代理”->“网页代理 HTTP”设置“127.0.0.1:8899”和“安全网页代理 HTTP”设置“127.0.0.1:8899”。

其实, w2 proxy on 等价于在网络面板高级里面设置 HTTP 和 HTTPS 代理为 127.0.0.1:8899, w2 proxy off 等价于取消设置。运行相关命令后可以在网络面板高级选项中看看代理情况,两种方式如出一辙。

方法二: 方法一的缺点是电脑全局代理,电脑任意程序只要走到了 HTTP 和 HTTPS 请求,都会被代理,这种做法除了会导致网速慢外,部分请求可能因为代理而失败。所以就有了方案二的浏览器插件做法。分别是 SwitchyOmega 插件和 Whistle 第三方插件。

检验代理浏览器有没有生效的最简单方法就是看 local.whistlejs.com) 能否正常打开,能正常打开则表示设置浏览器代理生效。

当然了,代理生效也可以通过刷新页面看是否有对应抓包信息来判断。

整体来说,推荐的做法是安装 SwitchyOmega 插件,这个插件还有其他高阶功能待大家挖掘。
这块比较坑的是网上很多文档都没有讲清楚这块,其实是互斥关系,有的写成了互补...,最坑爹的是有的插件建议下载压缩文件安装,安装后不生效,导致我一直在互斥和互补中间很跳...,惨痛的教训就是一定要去正规渠道 chrome 应用商店下载安装,不行删了再装。

问题还没完呢?虽然解决了电脑浏览器代理问题,但是本地起的服务,比方说上面搭的 express 3000 端口服务,访问 url 为 http://127.0.0.1:3000,根本抓不着。其实本地开发前端代码大都类似这种,如果不能抓包 Mock 数据,等于白忙活一场。

到这,粗暴地一顿瞎猜乱试肯定是行不通的,梳理一下计算机理论知识。
这里涉及对 HTTP 请求和端口的理解。
HTTP 默认端口号 80,正常情况下访问 HTTP 请求不带端口号,即默认 80 端口,也就是 www.baidu.com 等价于 www.baidu.com:80
之所以 whistle 服务可以代理网络请求,无一例外都将网络端口指向了 8899,手机连接电脑对应网络手动代理是 电脑IP:8899, 电脑浏览器对于服务代理是修改 HTTP 代理服务为 127.0.0.1:8899。
也就是只有访问 http://127.0.0.1:8899 才能被代理,http://127.0.0.1:3000 因为不经过 8899 端口,所以不会被代理,如果想被代理,唯一的方案是先访问 http://127.0.0.1:8899,然后在 8899 端口转发到 3000端口。

听起来有点绕,直接上解决方案吧。

在 Rules 页中新增转换规则:^http://sheng.shuqiang.com$ http://127.0.0.1:3000

直接通过 sheng.shuqiang.com 中转访问 http://127.0.0.1:3000 服务。

反向代理手机访问本地电脑服务

这其实是代理工具最强的地方,whistle 和 Charles 均能做到,即手机访问本地电脑服务,可以实时调试。

一张图说明一下正向代理和方向代理原理。

image.jpeg

再来一张图说明一下反向代理本地服务原理。

image.jpeg

只需要在 “Rules” 配置一行 mall.meituan.com 127.0.0.1:8080即可,其中 8080 是我本地电脑服务端口,你可以改成你自己的本地端口。

然后手机直接打开 App 访问线上目标页面,会发现被代理到了本地服务,必要时可以在代码里面加一句 debugger 。
至此,如果本地能复现,没有什么解决不了的 bug 。

关于反向代理科普,肖国栋大牛的深入理解 http 反向代理 讲得特别棒,建议花时间看看。

CORS 跨域问题

正如上面分析拼多多跨域问题的方案一样,在网络响应 header 里面加上 Access-Control-Allow-Origin:*Access-Control-Allow-Headers:Content-Type 就可以。如果单独起服务成本有点高,resHeaders://{corsheaders} Rules 可以完美解决了这个问题。

网页追加脚本

上面科学上网拼多多也讲过,直接看混淆后的代码太费劲,常规操作是追加代码操作 DOM 或者修正逻辑。
恰恰 whistle 也具备这种功能。
先看看点好玩的,一句追加代码直接把“百度一下”改成“不愧是你”。

其实还可以注入代码做 DOM 树监控分析啥的。

image.jpeg

注入 vConsole 调试面板

运行 w2 i whistle.inspect 安装 whistle.inspect 插件,规则中配置 http://sheng.shuqiang.com whistle.inspect://

远程 log

规则中配置 https://sheng.shuqiang.com log:// 即可在 Network 页对应主文档请求打印 console.log 日志。

常用 Whistle Rules 配置

#过滤美团 local.whistlejs.com:8899/#network?ur…

#过滤Owl上报 local.whistlejs.com:8899/#rules?url=…

# 后面规则覆盖前面规则

# 代理到本地 localhost

# www.baidu.com/ localhost:3000

# 代理到本地 html

# www.baidu.com/ file://${projectPath}/test/index.html

# 代理响应内容

# www.baidu.com/ resBody://{test.json}

# 代理

# www.baidu.com/ www.google.com.hk/

# 修改ua

# www.baidu.com/ ua://{ua}

# 自定义样式

# www.baidu.com style://color=@fff&fontStyle=italic&bgColor=red

# 代理资源

# dss0.bdstatic.com/5aV1bjqh\_Q… file://${projectPath}/test

# 跨域

# www.baidu.com resCors://*

# 移动端调试

# 注入 vConsole

# www.baidu.com/ jsPrepend://{vConsole.js}

# 打印日志

# www.baidu.com/ log://

# mock 接口返回

# 修改接口返回状态

# www.baidu.com/ statusCode://404

# 修改接口返回数据

# music.163.com/weapi/cloud… resBody://{netease.json}

# 模拟 5 秒超时

# www.baidu.com reqDelay://5000 enable://abort

# 模拟弱网

# www.baidu.com resSpeed://50

看懂

写到这,我简单总结一下。

  • 通过 Express 前端框架可以简单搭建一下路由服务,用于代码精细化控制返回内容。

  • 通过 ADB 反向转发接口,能做到断网下通过 USB 线实现访问网络服务。

  • 通过前端断点、保存网络文件内容、以及 http-server 可以获取想怎么改就怎么改,有助于迭代更多的科学上网解题思路。

  • 究极利器是属于 whistle 的,强大的功能包含但不限于手机、电脑抓包或 Mock 数据、轻松解决 CORS 跨域问题、网页追加脚本、注入 vConsole 调试面板和远程 log,理论上通过转发链接也能实现手机直连电脑服务。

  • 不止于抓包,你能想到的网络增删改查限流等等,都可以做到,理论上手机上的显示信息和发送至服务端信息都能改,比如微信步数改成朋友圈排名第一(我看到过有人做到但没试过,正常人 5w 到顶了,他 9w)。
    想象空间足够大,但是要干坏事,小心牢底坐穿。

全文完,如果觉得写得不错,那就点个“关注”或者“在看”。

如果转载本文,文末务必注明:“转自微信公众号:书强号”。

我是美团买菜前端盛书强,专注于大前端开发,欢迎和各位大前端大牛做朋友,教学相长。

文中相关代码会上传至 github.com/shengshuqia…