介绍
whistle 是一款用 Node 实现的跨平台的 Web 调试代理工具,支持查看修改 http(s)、Websocket 连接的请求和响应内容。简而言之就是 Node 版的 Fiddler、Charles,不过这个工具能远比后两者更加适合 Web 开发者、使用更简单、功能也更加实用,完全可以代替 Fiddler、Charles。
相关工具与安装
whistle 安装与启动
$ npm install -g whistle
$ w2 start // 启动
$ w2 stop // 停止
$ w2 restart // 重启
$ w2 help // 其他命令可查看帮助
启动成功之后,便可以在http://127.0.0.1:8899
查看whistle的web界面。
SwitchyOmega 安装
这是一个 chrome 插件,用来配置浏览器代理,常用来配合 whistle 使用。(如果要把 whistle 作为全局代理使用,可以参考这里)
下载并且安装插件之后,在插件配置页配置代理服务:
如果本机有使用其他代理,比如翻墙,SwitchyOmega 允许切换到“系统代理”。
使用
常用功能
在 web 界面,主要的功能有 network、rules、values、plugins。
network 中可以查看被代理的请求,其中有一系列charles等工具的功能。
rules 用来配置代理规则,是下面主要介绍的功能。
values 配置要常用的键值对。
plugins 中是可以使用的插件,plugins的插件可以看这里。
下面主要来看下 rules 的使用。
rules
替换hosts
在 whistle 添加配置相当于在浏览器层面添加hosts,会优先于系统hosts。
127.0.0.1 www.test.com
www.test.com 127.0.0.1 # 和上面一行同义
www.test.com 127.0.0.1:8080
# www.test.com 127.0.0.1 # 这行被注释掉,不生效
http://www.test.com 127.0.0.1:8080 # 只会代理http请求,https会被忽略
127.0.0.1 www.test.com www.test1.com www.test2.com
whistle 默认的配置方式是 pattern 在左,替代请求的 URL 在右。而传统hosts 配置是 URL 在左 pattern 在右。
127.0.0.1 www.test.com # 传统
www.test.com 127.0.0.1 # whistle默认
whistle会尽量兼容上面两种格式,如果出现不能够兼容的情况,比如:
http://www.baidu.com http://www.sina.com
这样的话会按照 whistle 默认格式来对待。
本地替换
尤其在本地开发完成,或者线上功能优化的时候,你可能需要把请求线上资源的请求替换到本地。
www.test.com/resource/js file://D:/projects/hello/resource
html中插入JS
# 如果是html,则会自动加上 script 标签再替换响应内容,如果是js,则会自动替换整个js文件
www.ifeng.com jsBody://{test.js}
# 如果是html,则会自动加上 script 标签在追加到响应内容,如果是js,则会自动追加到js文本后面
www.ifeng.com jsAppend://{test.js}
同样可以对 css 做这样的操作。
{}
涉及到 values 的使用,具体参考下面的规则操作符和操作值。
whistle的功能总共有那些呢?可以参看协议列表
filter和ignore
ignore 用于忽略指定协议的匹配。whistle 支持的所有协议可以参看上面提到的协议列表。
pattern ignore://protocol1|protocol2|protocolN # 忽略几种协议
pattern ignore://* # 忽略所有协议
pattern ignore://*|-protocol1|-protocol2|-protocolN # 忽略这些以外的协议
来看些实例:
# 表示www.example.com/test/yyy及其路径忽略host和socks规则
www.example.com/test/yyy ignore://host|socks
# 表示wwww.example.com/test/abc及其路径最多只保留socks规则
www.example.com/test/abc igore://*|-socks
filter用来过滤已经匹配的规则,主要有两种用法:
excludeFilter
表示从已匹配结果中排除满足过滤条件的。includeFilter
表示已匹配结果中只有满足过滤条件的是有效的。
www.test.com 127.0.0.1:8080 excludeFilter://*/cgi-bin excludeFilter://m:get excludeFilter://m:/^c/
上面用到了m:
,这是用来匹配 method 的方法,还有其他一些匹配方法:
- m:name,name为方法名称或正则表达式,表示匹配对应方法
- i:ip,ip表示具体客户(服务)端ip或匹配ip的正则表达式
- clientIp:ip,ip表示具体客户端ip或匹配ip的正则表达式
- serverIp:ip,ip表示具体服务端ip或匹配ip的正则表达式
- s:code,code表示响应状态码,或正则表达式
- h:name=pattern,匹配请求或响应头字段 name,pattern为该字段对应值里面的关键字或正则表示
- reqH:name=pattern,同上,但只会匹配请求头
- resH:name=pattern,同上,但只会匹配响应头
- b:pattern,匹配请求内容,pattern为内容的子字符串(不区分大小写),或正则表达式
而上面的*/cgi-bin
用到的是通配路径,这样的用法在配置 rules 时经常需要,具体用法参见下面的匹配模式。
规则操作符和操作值
上面的规则中写道jsAppend://{test.js}
,这里用到了{}
。在配置规则时,可以使用三种操作符:{} () <>
。
而whistle的操作值可以分两类,字符串和JSON对象。由{}
括起来会被当做 JSON,其他情况会当做字符串。
下面结合例子来看。
# '{}' 用于使用values中的key。(values用来设置键值对,在web界面可以方便的探索)
www.example.com resBody://{index.html} // 'index.html'是一个key
# '()' 用于直接在规则中插入值,可以插入字符串或者json
www.baidu.com resBody://({"delay":6000,"body":"1234567890"})
# www.baidu.com 会返回一个括号中的json。
www.baidu.com urlParams://(test=1&token=0)
# www.baidu.com 会被替换为 www.baidu.com?test=1&token=0
# 注意,这种写法不可以包含空格
# '<>'的作用是阻止自动拼接
www.ekwing.com/resource/exam file://D:/svn-projects/exam/resource
www.ekwing.com/resource/exam file://<D:/svn-projects/exam/resource>
# 第一种方式,www.ekwing.com/resource/exam/hello/hello.css 会被替代为 file://D:/svn-projects/exam/resource/hello/hello.css ,这就是whistle 默认的自动拼接。
# 第二种情况,www.ekwing.com/resource/exam/hello/hello.css 会被替代为 file://D:/svn-projects/exam/resource,因为这里自动拼接失效了。
操作值除了可以利用 {}
和()
两种操作符写入到 rules 当中以外,还有下面几种方式。
- 本地文件
www.baidu.com resBody:///User/docs/test.txt
# windows
www.baidu.com resBody://E:\docs\test.txt
- 内联多行
www.test.com/index.html file://{test.html}
``` test.html
Hello world.
Hello world1.
Hello world2.
```
甚至可以嵌套使用:
www.test.com/index2.html reqScript://{test.rules}
``` test.rules
* file://{test.html}
``` test.html
reqScrip,
reqScript,
```
```
这种方法有点像 values,但是优先级要高于 values。
- 模板字符串
whistle 支持像 es6 语法中的模板字符串 ``:
www.test.com http://`www.test.com?${reqCookie.cookieName}`
www.test.com/api http://`${clientIp}:8080` # clientIP会被替换成请求方的IP
模板字符串结合操作符 {}
来使用:
www.baidu.com resBody://`{test.json}`
对应在 values 当中,有个 key 为 'test.json' 的值为:
{
"url": "${url}",
"port": "${port}",
"version": "${version}",
"search": "${url.search}",
"query": "${url.query}",
"queryValue": "${url.query.name}",
"host": "${url.host}",
"hostname": "${url.hostname}",
"path": "${url.path}",
"pathname": "${url.pathname}",
"reqId": "${reqId}",
"now": ${now},
"method": "${method}",
"xff": "${reqHeaders.x-test}",
"other": "${reqHeaders.other}",
"cookie": "${reqCookie}",
"cookieValue": "${reqCookie.cookieName}",
"clientIp": "${clientIp}"
}
模板字符串中支持 replace 操作:
www.test.com http://`www.test.com?${url.query.replace(/ho/g, 'hello')}`
匹配模式
要玩转whistle,除了了解上面的各种功能之外,还需要搞清楚匹配模式。
whistle 的匹配模式可以分为 域名、路径、正则、精确匹配、通配符匹配。
域名和路径匹配
www.test.com 127.0.0.1
http://www.test.com 127.0.0.1 # 限制http协议才匹配
www.test.com:8888 127.0.0.1 # 8888端口
www.test.com/hello 127.0.0.1
http://www.test.com/hello 127.0.0.1 # 限制http协议才匹配
www.test.com:8888/hello 127.0.0.1 # 8888端口
正则匹配
正则语法跟js的正则表达式一致,不支持/reg/g
。可以把匹配的部分子串作为参数传到 operatorURL 中,一样是通过()
,$1
表示第一个 ()
中的子串,可以这样匹配 9 个子串,$1...$9
,$0
被用来表示整个匹配的url。
/mapi.ekwing.com\/([a-z]+).js/ 127.0.0.1:8080/$1.js
精确匹配
路径匹配时默认会自动匹配路径及其子路经,不想要这样的时候,就可以用精确匹配。
$www.test.com/hello 127.0.0.1
这样,www.test.com/hello/world
不会被匹配。
通配符匹配
通配符匹配是作为简化正则匹配而存在的,^
限制开始位置,*
就是通配符,$
可以用来限制结束位置。也支持$0,$1...$9
。
通配符的位置和个数的规则有些复杂:
- 如果通配符串在请求url的protocol里面,不管是一个还是多个 * 都只能匹配 [a-z\d]*
- 如果通配符串在domain里面,一个
*
表示匹配[^.]
,两个及以上的*
表示匹配[^.]*
*.test.com file:///User/xxx/test # 只匹配test.com的子域名,不包括test.com
**.test.com file:///User/xxx/test # 对子域名也生效
- 如果通配符串在path里面,一个
*
表示匹配[^/]?
,两个及以上*
表示匹配[^/]*
。*
在path中时,rule 必须以^
开头。
^test.com/hi/**/ file:///User/xxx/test
# 会匹配 test.com/hi// test.com/hi/jk/ test.com/hi/jk/hi/
^test.com/hi/*/ file:///User/xxx/test
# 会匹配 test.com/hi// test.com/hi/jk/ 不匹配test.com/hi/jk/hi/
- 如果通配符串在query里面,一个
*
表示匹配[^&]?
,两个及以上的*
表示匹配[^&]*
。*
在query中时,rule 必须以^
开头。
^test.com/hi?**a=2 file:///User/xxx/test
# 会匹配 test.com/hi?a=2 test.com/hi?jk=3&a=2 test.com/hi?jk=3&b=78&a=2
^test.com/hi?*a=2 file:///User/xxx/test
# 会匹配 test.com/hi?a=2 test.com/hi?jk=3&a=2 不匹配test.com/hi?jk=3&b=78&a=2
混合开发调试
本地开发调试
以 VUE 为例。
真机连接本地开发环境(开发时调试)
- devServer 不需要设置 host 等,但是需要设置
disableHostCheck
:
devServer: {
disableHostCheck: true
}
- 在 whistle 中设置 rules:
www.test.com/hello/basepage localhost:8080/basepage
有时候 localhost
不生效的话,尝试用本机IP代替。
-
给移动端 wifi 设置代理,代理地址为
{本机IP}:8899
-
如果移动端是请求https,参照这里进行设置。
注意:上面的例子,www.test.com/hello/basepage
被代理后,本地 vue router 解析到的 path 仍然是 hello/basepage
而不是 basepage
。
bug调试,真机可用
如果需要真机连接到本机资源:
- 设置publicPath,然后 build。
- 在 whistle 中代理 html 和各类静态资源:
www.test.com/hello/basepage file://D:/projects/project-name/dist/index.html
www.test.com/your-public-path file://D:/projects/project-name/dist
调试工具:
- whistle 自带的 weinre 和 log。当然手机要先代理到 whistle。
www.test.com weinre://test
# 像浏览器的 devtools 那样查看和修改页面结构
www.test.com log://test
# 可以在 network 的log栏查看页面的log,也可以利用操作值注入自定义 js: log://{test.js},如果你喜欢将调试信息直接输出在手机屏幕上,可以注入微信的vConsole.js,但不可以和weinre一起用
weinre:
log:
如果你嫌弃weinre用的不如devtools顺手,可以看看m-console。 2. 利用chrome inspect 连接 android,可以直接使用devTools,只是连接设置麻烦些。相应的,safari 也可以直接连接 iphone 进行调试。
机型bug调试(无真机)
有时候会遇到某一机型才有的bug,而刚好手头真机都不能重现问题。始终把真机调 bug 作为主要手段会是最好的选择,所以 android 测试机应该至少覆盖到国产机中那些高度定制原生android系统的品牌(比如华为、小米、魅族等)。
尝试模拟器
考虑原生系统问题(ios和原生android版本),尝试用同版本模拟器尝试。
本地模拟器
使用本地模拟器,同样可以像真机那样安装APP,可以设置代理,可以利用js调试,或者用 chrome 或者 safari 直接连接调试。
- ios ios模拟器必须依赖mac,PC用户最多可以远程连接mac上的模拟器,这个方法需要PC安装 Visual Studio,与mac连接。然后启动ios模拟器即可调用远程的ios模拟器。
如果不是用 mac 开发,公司内部又没有把mac作为公用开发主机的情况,上面的连接方法其实也还是需要ios开发同事的配合,又何妨大家坐到一起解决这个问题呢。
- android
android模拟器可以自行下载 android studio,然后 create 相应的 virtual device。
线上模拟器
appetize.io是个不错的提供线上模拟器的网站,可以方便的上传app安装,在网页上进行操作。免费用户有每个月100分钟的使用权,在机型类bug不多的情况,还是足够的。
这种情况就不方便使用代理了,可以利用像 vConsole 这样的工具,直接在模拟器上查看问题。
云真机
通过上面模拟机的尝试,可能会有些 bug 怎么样都还原不了。对于 android 市场那么多国产自家系统的情况,很有可能出现非真机不可的情况,当然随着 webview 越来越好,这种情况也会越来越少。
目前阿里和腾讯都有提供云真机服务。试过腾讯的云真机,同样是上传安装包,然后网页端可以操作手机,另外利用本地手机操控远程手机功能,体验还不错,注册就有30分钟免费。这样的解决方案非常适合小型互联网公司。