利用 whistle 进行混合开发调试

3,617 阅读5分钟

介绍

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 作为全局代理使用,可以参考这里

下载并且安装插件之后,在插件配置页配置代理服务:

F813ACAB-7C5F-4a09-84B7-994F584D02C2.png

如果本机有使用其他代理,比如翻墙,SwitchyOmega 允许切换到“系统代理”。

3388C496-D697-44c0-901E-D678E21E0745.png

使用

常用功能

在 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用来过滤已经匹配的规则,主要有两种用法:

  1. excludeFilter表示从已匹配结果中排除满足过滤条件的。
  2. includeFilter表示已匹配结果中只有满足过滤条件的是有效的。
www.test.com 127.0.0.1:8080 excludeFilter://*/cgi-bin excludeFilter://m:get excludeFilter://m:/^c/

上面用到了m:,这是用来匹配 method 的方法,还有其他一些匹配方法:

  1. m:name,name为方法名称或正则表达式,表示匹配对应方法
  2. i:ip,ip表示具体客户(服务)端ip或匹配ip的正则表达式
  3. clientIp:ip,ip表示具体客户端ip或匹配ip的正则表达式
  4. serverIp:ip,ip表示具体服务端ip或匹配ip的正则表达式
  5. s:code,code表示响应状态码,或正则表达式
  6. h:name=pattern,匹配请求或响应头字段 name,pattern为该字段对应值里面的关键字或正则表示
  7. reqH:name=pattern,同上,但只会匹配请求头
  8. resH:name=pattern,同上,但只会匹配响应头
  9. 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 当中以外,还有下面几种方式。

  1. 本地文件
 www.baidu.com resBody:///User/docs/test.txt
 # windows
 www.baidu.com resBody://E:\docs\test.txt
  1. 内联多行
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。

  1. 模板字符串

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 为例。

真机连接本地开发环境(开发时调试)

  1. devServer 不需要设置 host 等,但是需要设置 disableHostCheck:
devServer: {
    disableHostCheck: true
}
  1. 在 whistle 中设置 rules:
www.test.com/hello/basepage localhost:8080/basepage

有时候 localhost 不生效的话,尝试用本机IP代替。

  1. 给移动端 wifi 设置代理,代理地址为 {本机IP}:8899

  2. 如果移动端是请求https,参照这里进行设置。

注意:上面的例子,www.test.com/hello/basepage被代理后,本地 vue router 解析到的 path 仍然是 hello/basepage 而不是 basepage

bug调试,真机可用

如果需要真机连接到本机资源:

  1. 设置publicPath,然后 build。
  2. 在 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

调试工具:

  1. 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:

weinre.gif

log:

FnjMsJAaJffOLKkY9mpNRHttQ9ck.gif

如果你嫌弃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分钟免费。这样的解决方案非常适合小型互联网公司。