从Whistle看抓包代理工具的使用和基本原理

插话host原理:

  1. hosts 文件是操作系统中一个普通文本文件,用于将主机名(域名)映射到 IP 地址。该文件通常位于:

    • Linux/macOS/etc/hosts
    • WindowsC:\Windows\System32\drivers\etc\hosts
     2.当你在浏览器中访问一个域名时,操作系统首先会查询 hosts 文件,检查是否有该域名的            映射。如果有,它就直接使用文件中的映射,绕过 DNS 服务器进行解析

       浏览器访问域名时,实际上是通过操作系统提供的 DNS 解析机制来进行域名解析。当你输入一个域名并按下回车,浏览器会向操作系统的 DNS 解析库请求解析该域名。操作系统会按照以下顺序进行处理:

  1. 查找 hosts 文件:操作系统首先会检查 hosts 文件是否包含该域名的映射。如果有对应的记录,它会直接使用 hosts 文件中的 IP 地址,跳过 DNS 服务器。

  2. DNS 查询:如果 hosts 文件没有该域名的记录,操作系统会向配置的 DNS 服务器发送查询请求,查找域名的 IP 地址。

这个流程是由操作系统的网络栈负责的,浏览器只是通过调用操作系统的网络接口来发起请求。

从Whistle看抓包代理工具的使用和基本原理

本文主要讲解了Whistle抓包工具的基本使用和其实现原理,可以举一反三搞懂其他的抓包工具的原理

前言

在前端的日常开发过程中,我们肯定避免不了使用web调试代理工具进行抓包,修改包内容等工作。这可以帮助我们mock数据,解决跨域问题,定位线上问题等等。所以一个好的工具是非常有必要的。

正向代理 反向代理

用更通俗的话语来描述就是,假如我们去租房,这里我就是”客户端“,中介是”正向代理服务器“,房东是”互联网“,这里我为了找房子,就去某家找了中介,让他(代理服务器)帮我去寻找房源,这是一个主动的行为,是正向代理。

如果我们去租房,没有去找代理,我在某瓣租房小组找到了房源,现场去看房,结果发现这是个二房东,很生气。在这种场景下,”二房东“就是反向代理服务器,我们的目的是找房,但真正的房东(服务器)被中介(代理服务器)给屏蔽了,我甚至不知道房东(服务器)是谁。

1. 什么是代理及其原理

如果你是对代理还不熟悉,那么可能就不太能理解whistle这个工具的实际意义以及它的使用方式。那么接下来就先介绍一下代理的基本概念。

代理分为两种:普通代理以及隧道代理

1.1 普通代理

1.1.1 什么是普通代理

HTTP客户端向代理发送请求报文,普通代理服务器需要正确地处理请求和连接(例如正确处理 Connection: keep-alive),同时向服务器发送请求,并将收到的响应转发给客户端。

image-20210626171518768.png

如上图所示,普通代理在客户端和服务器之间成为了一个“中间人”的角色。在客户端和服务器发送接收信息的过程中,它需要正确处理HTTP请求和响应的内容并转发到正确的目的地。如果有需要,它也可以对其转发内容进行进行增删改查的操作,甚至是修改目的地。Whistle就相当于这个普通代理服务器。

1.1.2 实现一个简单的普通代理
启动代理服务器

我们可以通过node去实现一个极简版本的普通代理,只对代理的HTTP请求和响应进行简单的转发。代码如下(需要一些最基础的node有关网络服务相关的知识,这部分可以自己搜索,基本一看就懂):

 

js

代码解读

复制代码

var http = require('http'); const { URL } = require('url'); function request(clientReq, clientRes) { // 解析客户端的url请求 var u = new URL(clientReq.url); var options = { hostname : u.hostname, port : u.port || 80,ao path : u.pathname, method : clientReq.method, headers : clientReq.headers }; console.log(`httpRequest from ${u.hostname}`); // 代理服务器重新自己发送客户端要发送的请求 var proxyReq = http.request(options, function(proxyRes) { // 代理通过回调函数在拿到服务器的响应后重写客户端响应 clientRes.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(clientRes); }).on('error', function() { clientRes.end(); }); clientReq.pipe(proxyReq); } //启动一个服务器开启request监听事件 http.createServer().on('request', request).listen(8899, '0.0.0.0');

这个node搭建的代理服务器运行在127.0.0.1:8899,它起到的作用就是把收到的http请求重新发送到服务器,再把服务器的响应返回给客户端。

配置代理

既然这个代理服务器要接受客户端的请求,那么怎么让客户端在发送请求的时候发送给它而不是直接发送给目的服务器?这个时候就需要进行代理的配置从而告诉客户端你要使用的代理服务器的地址。

进行代理的配置有如下两种主要的方式:

  1. 全局代理:直接配置系统代理:
  • Windows
  • Mac: System Preferences > Network > Advanced > Proxies > HTTP or HTTPS
  1. 浏览器代理:安装浏览器代理插件 (推荐)

image-20210703143642186.png

在这里就用SwitchyOmega举例,大家可以在chrome插件商城中进行下载安装。下载完后打开该插件加入新的代理情景,配置对应的代理服务器的地址并在右上角进行配置切换。这样浏览器中的所有请求就不会直接发送给目标服务器而是先发送给配置的代理服务器再由代理服务器处理。

操作检验

配置成功后我们就可以随便访问一个http协议的网站,通过对发送的请求和响应做出任何我们想要做的事情。

比如查看请求内容和增加一个响应头部字段:

 

js

代码解读

复制代码

var http = require('http'); const { URL } = require('url'); function request(clientReq, clientRes) { //1:查看响应内容 console.log(clientReq); var u = new URL(clientReq.url); var options = { hostname : u.hostname, port : u.port || 80, path : u.pathname, method : clientReq.method, headers : clientReq.headers }; console.log(`httpRequest from ${u.hostname}`); var proxyReq = http.request(options, function(proxyRes) { //2:增加响应头设置Cookie clientRes.setHeader('Set-Cookie', ['type=proxy']); clientRes.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(clientRes); }).on('error', function() { clientRes.end(); }); clientReq.pipe(proxyReq); } http.createServer().on('request', request).listen(8899, '0.0.0.0');

  • 查看请求:

image-20210703173538648.png

  • 设置cookie:

image-20210627162253872.png

1.2 隧道代理

1.2.1 什么是隧道代理

客户端通过CONNECT 方法请求隧道网关创建一条到达任意目的服务器和端口的 TCP 连接,建立连接后隧道网关对客户端和服务器之间的后继数据进行盲转发。

我们知道HTTP连接基于TCP连接,并且TCP连接是全双工通信且工作在运输层,那么就可以利用HTTP连接建立的TCP连接让代理服务器来双向传递非HTTP协议的流量,比如HTTPS协议的流量。隧道代理就使用了这样的思想。

隧道代理数据流向:

image-20210703152050732.png

下面这张图很直观的展示了其实现的过程:

image-20210702130810642.png

通过这张图我们来细说一下隧道代理的实现过程:

  • (a)过程客户端发送method为CONNECT的请求到网关(网关可以理解为代理服务器
  • (b)(c)过程由网关建立到真正请求的服务器的TCP连接
  • 建立TCP连接后,在(d)过程网关向客户端发送HTTP连接成功报文通知客户端
  • 在(e)过程客户端就可以正式发起HTTPS请求,其中的网关(代理服务器)只是做一个TCP层面的转发而不关心请求的内容

其中有几个特别值得关注的点:

  • (a)过程是用HTTP协议进行发送的,即是明文传输的。为了安全所以在这个请求中只包含了方法,目标位置和user-agent。

  • 建立隧道连接的判断是系统内部实现的。比如我们在浏览器中设置了代理HTTPS的流量,那么浏览器在发起HTTPS请求之前就会默认采用隧道连接的方式向代理服务器发起CONNECT请求从而和真实服务器建立隧道连接。

  • 在这个过程中代理服务器只做简单的TCP层面的转发。如果传输的内容是加密的内容,那么在不经过处理的情况下就无从得知其中的内容。

1.2.2 使用隧道连接实现一个HTTPS隧道代理

刚刚我们讲到,什么时候请求建立隧道连接是系统内部实现的,而代理HTTPS流量就是其中的一种情况。所以当设置代理后再发起HTTPS请求时,客户端就会先向代理服务器发送CONNECT请求建立隧道连接。实现的代码如下:

 

js

代码解读

复制代码

const http = require("http"); const { URL } = require("url"); const net = require("net"); // 启动端口 let port = 8899; let httpTunnel = new http.Server(); // 启动隧道代理服务 httpTunnel.listen(port, () => {   console.log("Tunnel Server is running on: https://localhost:%s", port); }); // 监听connect请求 httpTunnel.on("connect", (req, clientSocket, head) => {   var serverUrl = new URL(`http://${req.url}`);   console.log(`CONNECT ${serverUrl.hostname}:${serverUrl.port}`);   // 建立到服务端的TCP连接   var serverSocket = net.connect(serverUrl.port, serverUrl.hostname, () => {     clientSocket.write(       "HTTP/1.1 200 Connection Established\r\n" +         "Proxy-agent: MITM-proxy\r\n" +         "\r\n"     );     serverSocket.write(head);     // 转发数据     serverSocket.pipe(clientSocket);     clientSocket.pipe(serverSocket);   });   serverSocket.on("error", (e) => {     console.error(e);   }); });

在本地启动服务后,同普通代理一样需要进行代理的配置。经过配置过后发现可以正常的浏览网页。

1.3 实现HTTPS代理

通过隧道代理实现的HTTPS代理只能在TCP层盲目转发请求内容,并不能对请求的内容进行查看,修改,转发等操作。那么怎么获取转发的内容呢?“中间人攻击”就是一个很好的手段。

1.3.1 HTTPS与中间人攻击

理解HTTPS的工作原理是实现中间人攻击的前提。关于HTTPS工作原理的文章已经有很多,这里介绍一篇讲得比较清楚的文章。HTTPS 详解一:附带最精美详尽的 HTTPS 原理图。如果有不清楚的同学可以先移步查看。

image-20210707204606332.png

如上图所示,包含公钥的数字证书的正确性是整个传输过程安全的保障。如果包含公钥的数字证书被一个中间人截获,并在客户端上安装了中间人伪造的CA根证书并让客户端信任,那么中间人就可以在客户端和服务器的交流过程中拿到传输的数据并不引起双方的注意。

image-20210703173707759.png

中间人攻击的具体过程如下:

(1) 中间人向客户端注入伪造的CA根证书

(2) 捕获服务器真实的CA证书

(3) 根据域名伪造服务器的证书返回给客户端,该伪造证书的根证书指向伪造的CA根证书

(4) 截获双方数据

1.3.2 实现HTTPS代理的原理

在理解了中间人攻击后,就可以理清代理HTTPS的基本思路。

  1. 建立客户端到中间人的隧道连接

image-20210702162137600.png

(a) 客户端向隧道代理服务器发起CONNECT请求

(b) 隧道代理服务器和HTTPS代理服务器(中间人)建立TCP连接

(c) 成功建立隧道连接返回响应

这一步和实现隧道代理HTTPS一样,只是把目的服务器换为HTTPS代理服务器。

  1. 建立客户端到HTTPS代理服务器和HTTPS代理服务器到目的服务器的HTTPS连接

image-20210702163844394.png

(d) 客户端向HTTPS代理服务器发起HTTPS连接的请求

(e) HTTPS代理服务器向目标服务器发起同样的HTTPS连接请求

(f) HTTPS代理服务器获取目标服务器证书

(g) HTTPS代理服务器向客户端发送伪造的证书

  1. 发送随机码

image-20210703160210462.png

(h) 客户端向HTTPS代理服务器发送随机码

(i) HTTPS代理服务器向目的服务器发送随机码

  1. 正常传输数据
1.3.3 具体实现HTTPS代理
  1. 申请一个CA证书作为根证书

本文使用在本地签发证书的方式,创建命令行如下:

 

shell

代码解读

复制代码

openssl genrsa -out private.pem 2048 openssl req -new -x509 -key private.pem -out public.crt -days 99999

第二行命令运行后,需要填写一些证书信息。需要注意的是 Common Name 一定要填写后续提供 HTTPS 服务的域名或 IP。例如你打算在本地测试,Common Name 可以填写 127.0.0.1

创建后两个证书在当前目录下,点击 public.crt证书打开后信任该证书。

image-20210703173728567.png

  1. 本地实现代理服务

为了使代码简单易读,这里只给出最简单的实现版本,没有根据访问地址动态生成伪造证书,在真实使用的过程中也还有很多的问题。但是这篇文章只注重原理的讲解,对于各种请求的处理有兴趣的同学可以自行阅读whistle源码。最简单实现代码如下:

 

js

代码解读

复制代码

const http = require("http"); const { URL } = require("url"); const net = require("net"); var https = require("https"); var fs = require("fs"); var path = require('path'); var { portConfig } = require("../port-config.js"); let tunnelPort = 8899; // 启动隧道的端口 let httpsPort = 8900; // 启动HTTPS代理服务的端口 //根据项目的路径导入生成的证书文件 var privateKey = fs.readFileSync(path.join(__dirname, "../private.pem")); var certificate = fs.readFileSync(path.join(__dirname, "../public.crt")); var credentials = { key: privateKey, cert: certificate }; // 建立代理服务器 let httpTunnel = new http.Server(); var httpsServer = https.createServer(credentials); // 启动代理服务 httpTunnel.listen(tunnelPort, () => { console.log(`http tunnel server is running on ths port of ${tunnelPort}`); }); httpsServer.listen(httpsPort, () => { console.log(`https server is running on ths port of ${httpsPort}`); }); // 监听connect请求 httpTunnel.on("connect", (req, clientSocket, head) => { var serverUrl = new URL(`http://${req.url}`); console.log(`CONNECT ${serverUrl.hostname}:${serverUrl.port}`); // 建立到HTTPS代理服务端的TCP var serverSocket = net.connect(httpsPort, "127.0.0.1", () => { clientSocket.write( "HTTP/1.1 200 Connection Established\r\n" + "Proxy-agent: MITM-proxy\r\n" + "\r\n" ); serverSocket.write(head); // 转发数据 serverSocket.pipe(clientSocket); clientSocket.pipe(serverSocket); }); serverSocket.on("error", (e) => { console.error(e); }); }); // HTTPS代理服务端监听转发请求 httpsServer.on("request", (clientReq, clientRes) => { // 解析客户端请求 var options = { hostname: clientReq.headers.host, method: clientReq.method, headers: clientReq.headers, path:clientReq.url }; // 再重新发送请求到真实的服务器 var proxyReq = https .request(options, function (proxyRes) { // 转发请求 clientRes.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(clientRes); }) .on("error", function (e) { clientRes.end(); }); clientReq.pipe(proxyReq); });

  1. 设置浏览器流量代理

在本地启动服务后仍然需要设置对应的浏览器服务代理,注意应该将浏览器流量代理到隧道代理的端口。此例的代理配置如下图:

image-20210703160935901.png

注意:

  • chrome浏览器现在不信任本地签发的根证书,所以当启动服务去访问一个https网站后可能会出现如下界面,这需要你手动的输入thisisunsafe告诉浏览器用户明确已存在的风险。注意不是在地址栏输入,直接敲击键盘即可。

image-20210702174107118.png

2. Whistle的使用

在理解了代理的基本概念以及怎么实现一个简单的代理服务后,就能够更加好地理解whistle的使用步骤而不需要死记硬背,比如为什么在使用whistle时要配置浏览器代理,为什么查看HTTPS流量之前要安装根证书等等。

2.1 Whistle的基本概念

whistle(读音 [ˈwɪsəl],拼音 [wēisǒu] )是基于 Node 实现的跨平台抓包调试代理工具,有以下基本功能:

  1. 查看 HTTP、HTTPS、HTTP2、WebSocket、TCP 请求响应数据
  2. 修改 HTTP、HTTPS、HTTP2、WebSocket、TCP 请求响应数据
    • 修改请求 url、方法、头部、内容等
    • 修改响应状态码、头部、内容,并支持本地替换等
    • 修改 WebSocket 和 TCP 收发的帧数据
  3. 设置 hosts(支持 IPv6)、http-proxy、https-proxy、socks
  4. 作为HTTP代理或反向代理
  5. 集成常用的 web 调试工具,如 weinre 和 log 等
  6. 支持用 Node 编写插件扩展

总结一下这句话的意思:

  • 基于Node,意味着只要有安装了node环境就可以运行,实现了跨平台
  • 可以对常用的HTTP、HTTPS、HTTP2、WebSocket、TCP协议的请求响应进行增删改查的操作
  • 可以直接作为代理服务器对请求进行代理,也可以自定义代理规则(等下详细展开)
  • 除了这些基本功能还集成了其他的调试工具,并支持插件自定义扩展

2.2 Whistle的基本安装配置

具体的安装步骤已经在官网说得很详细,所以更多细节请移步Whistle安装,在这里只会简写官网中的每一步或者做出一些补充。相信在实现了一个简单的代理服务器后这一步将会很容易理解。

  1. 安装Node

推荐尽量安装最新版本

  1. 安装whistle
 

sh

代码解读

复制代码

$ npm install -g whistle

  1. 启动whistle
 

sh

代码解读

复制代码

$ w2 start

启动一个运行在本地的代理服务器,默认启动在127.0.0.1:8899

  1. 配置代理

推荐使用switchOmyga进行代理的配置,与“实现一个简单代理“中的步骤相同。

  1. [安装根证书][wproxy.org/whistle/web…]

查看修改HTTPS请求的关键一步,把whistle这个"中间人"代理服务器的根证书安装到本地并信任,等同于”中间人“向客户端注入证书。如果没有安装根证书就只能通过隧道代理进行简单的转发,也拿不到请求的内容。在Whistle中隧道代理的请求方法为CONNECT,HOST为Tunnel to。

6c98051660eeff240e7e0b354c8a8_w1661_h335.png

  1. 在浏览器中进入Whistle配置页面

whistle默认启动在127.0.0.1:8899 。也可以在启动whistle时自定义。最终在浏览器中进入whistle配置页面如下。

image-20210703162906257-9837730.png

2.3 Whistle的使用

2.3.1 功能界面

首先介绍whistle的界面,这一块的内容官网已经说得比较详细并且内容也不多,大家可以对照官网进行查看:whistle界面功能。这里将简单罗列几个最常用的界面功能方便后续展开。

  1. Network

查看请求响应的详细信息及请求列表的Timeline,还有请求匹配到的规则(见Overview)。

image-20210627224516875.png

  1. Rules

这是whistle的规则配置界面,也是我们自定义whistle的代理行为的最重要的一个界面。

image-20210627224844726.png

  1. Values

配置key-value的数据,在Rules里面配置可以通过{key}获取,如:www.ifeng.com file://{key}。大家可以先有一个values的概念,之后会详细阐述。

image-20210627225034788-9837837.png

2.4 Rules配置方式

通过在Rules界面中添加不同的rules,我们可以控制whistle的代理行为。

rules的一条配置规则可以抽象为:

 

shell

代码解读

复制代码

pattern operatorURI

  • pattern是匹配模式,用于筛选请求
  • operatorURI是操作协议,是对匹配这个pattern的请求进行操作的行为
  • operatorURI不为 url的时候,patternoperatorURI的位置可以交换。

例(1)

 

代码解读

复制代码

www.qq.com 127.0.0.1:8899

这一条规则的意思是把域名为www.qq.com的请求解析到127.0.0.1:8899上,所以配置了这条规则后访问www.qq.com实际上访问的是127.0.0.1:8899。

此时的operatorURI为IP地址不是url,所以此时patternoperatorURI的位置可以交换:

 

bash

代码解读

复制代码

# 这条规则可以和上一条规则起到一样的效果 127.0.0.1:8899 www.qq.com

例(2)

 

代码解读

复制代码

sports.qq.com cloud.tencent.com

这条规则的pattern是sports.qq.com,operatorURI是cloud.tencent.com。用于把域名为sports.qq.com的请求代理到cloud.tencent.com上。由于cloud.tencent.com是一个url,所以此时不能交换两者的位置。

之所以支持位置的交换是为了向传统的hosts文件对齐,但是为了避免一些不必要的麻烦,推荐大家在使用whistle的时候尽量使用同一套规则。

接下来我们来详细说一下patternoperatorURI的配置。

2.4.1 匹配模式

HTTPS、Websocket需要开启HTTPS拦截才可以正常抓包及使用所有匹配模式,否则只能用域名匹配

有些老版本可能不支持以下的某种匹配模式,遇到这种情况可以升级下whistle即可。

whistle的匹配模式(pattern)大体可以分成 域名、路径、正则、精确匹配、通配符匹配

  1. 域名匹配

域名匹配,不仅支持匹配某个域名,也可以限定端口号以及协议,比如httphttpswswsstunnel等协议。当operatorURI是url的时候,patternoperatorURI位置不能调换。

 

shell

代码解读

复制代码

# 匹配域名www.test.com下的所有请求,包括http、https、ws、wss,tunnel www.test.com operatorURI # 匹配域名www.test.com下的所有http请求 http://www.test.com operatorURI # 匹配域名www.test.com下的所有https请求 https://www.test.com operatorURI # 上述匹配也可以限定域名的端口号 www.test.com:8888 operatorURI # 8888端口 www.test.com/ operatorURI # 这种情况不能交换顺序,否则将 www.test.com www.example.com/index.html

  1. 正则匹配
    • 和js正则一致,支持两种模式:/reg/ , /reg/i

    • 不支持/reg/g的全局匹配

    • 支持正则中的分组引用,可以在operatorURI中使用$1 , $2....来动态获取正则表达式中()引用的内容

    • 支持非匹配 !pattern

 

shell

代码解读

复制代码

#匹配所有请求 * operatorURI #匹配url里面包含某个关键字的请求,且忽略大小写 /keyword/i operatorURI # 利用分组引用把url里面的参数带到匹配的操作uri # 下面正则将把请求里面的([^\/]+),带到匹配的操作uri # 最多支持10个子匹配 $0...9,其中$0表示整个请求url,其它跟正则的子匹配一样 /[^?#]\/([^\/]+)\.html/ protocol://...$1... #对正则表达式取反 !/keyword/i operatorURI

  1. 路径匹配

匹配某一个路径,可以选择指定域名、协议、端口号等,也可以不指定。

 

shell

代码解读

复制代码

# 限定请求协议,只能匹配http请求 http://www.test.com/path operatorURI http://www.test.com:8080/path operatorURI # 匹配指定路径下的所有请求 www.test.com/path operatorURI www.test.com:8080/path operatorURI

  1. 精确匹配

路径匹配不仅匹配对应的路径,而且还会匹配该路径下面的子路径,而精确匹配只能指定的路径,只要在路径前面加$即可变成精确匹配。

 

shell

代码解读

复制代码

$http://www.test.com operatorURI #只能匹配 http://www.test.com

 

shell

代码解读

复制代码

$https://www.test.com/path? operatorURI #只能匹配 https://www.test.com/path?

  1. 通配符匹配

正则匹配可以解决所有的匹配情况,但是需要使用人懂正则的规则,为了让使用的门槛低一些,wistle提供了通配符来进行匹配。相比于正则表达式通过分组即()的方式来提取匹配的内容,通配符使用*的方式来进行匹配,匹配规则如下:

  • ^表示开头位置,$表示结尾位置
  • 如果通配符串在请求url的protocol里面,不管是一个还是多个 * 都只能匹配 [a-z\d]*
  • 如果通配符串在domain里面,一个 * 表示匹配 [^/.](除了/.的部分),两个及以上的 * 表示匹配 [^/]*(只要不是/就可以匹配)
  • 如果通配符串在path里面,一个 * 表示匹配 [^/],两个 * 表示匹配 [^?]*,三个及以上的 * 表示匹配 .*
  • 如果通配符串在query里面,一个 * 表示匹配 [^&],两个及以上的 * 表示匹配 .*

举个例子:

 

bash

代码解读

复制代码

^*://*.test.**.com:*/**?a=*&** opProtocol://opValue($0, $1, ..., $9) # 第一个*匹配协议 # 第二个*匹配/ . 之间的内容,这个里面的内容不能出现/或者是. # 第三个**匹配. .之间的内容,这个内容里面只要不出现/就可以 # 滴四个*匹配:/之间的内容,这个内容里面是端口号,同样不能出现. / # 第5个**匹配路径,只要不出现?就可以 # 第6个*匹配a的值 # 第7个**匹配剩下的所有内容 # $0,$1...$9表示第几个*的内容

2.4.2 操作协议

operatorURI除了可以是一个地址将符合pattern的请求发送到operatorURI之外,还可以是一些作者定义的操作词对请求或者响应进行不同的操作。

比如:

 

sql

代码解读

复制代码

pattern method://newMethod

表示把所有符合pattern的请求的方法改为newMethod。

再比如:

 

bash

代码解读

复制代码

www.aliexpress.com referer://http://www.aliexpress.com

表示把www.aliexpress.com这个请求的referer改为www.aliexpress.com。

为了尽可能满足web开发中方方面面的需要,whistle提供基本上覆盖抓包调试工具可以做的所有事情的对应协议,比如修改请求URL,修改请求方法,修改请求头修改请求内容等等。所有的操作协议可以在官网中自行查找。

2.4.3 操作值

operatorURI中可以使用变量的方式来定义值,变量的值可以分为两类,字符串JSON对象

字符串
  1. 如果字符串不包含空格,可以不采用变量的方式直接写在规则中
 

bash

代码解读

复制代码

pattern opProtocol://(str) # 有些操作值不能放到本地文件,则可以不用括号,如:proxy、referer等等,具体参见协议列表 pattern opProtocol://strValue

  1. 如果字符串里面包含空格,则可以把操作值先放到whistle界面的Values或本地文件然后用{}引用,创建values的过程见Values
 

bash

代码解读

复制代码

# 在Values里面创建一个key为 test.txt 的 key-value 对 pattern opProtocol://{test.txt} # 或者放到本地文件 /User/docs/test.txt pattern opProtocol:///User/docs/test.txt # windows pattern opProtocol://E:\docs\test.txt

JSON

如果操作值为JSON对象,则同样需要把操作值放到whistle界面Values或者本地文件中。可以用以下几种格式书写:

  1. 正常的JSON格式:
 

bash

代码解读

复制代码

{ "key1": value1, "key2": value2, "keyN": valueN }

  1. 行格式:
 

bash

代码解读

复制代码

# 以 `冒号+空格` 分隔 key1: value1 key2: value2 keyN: valueN # 如果没有 `冒号+空格` ,则以第一个冒号分隔,如果没有冒号,则value为空字符串 key1: value1 key2:value2 key3 keyN: valueN

  1. 内联格式(请求参数格式):
 

bash

代码解读

复制代码

# key和value最好都encodeURIComponent key1=value1&key2=value2&keyN=valueN

注意:最后一种内联格式可以把JSON对象直接转化为字符串,这样可以用第一种方式直接写到配置里面,如果key或value里面出现 空格&%=,则需要把它们 encodeURIComponent,whistle会对每个key和value尝试 decodeURIComponent

内联多行操作值

whistle v1.12.12开始支持Rules内联多行的Value,即写在rules中的变量名,格式如下:

 

bash

代码解读

复制代码

#使用键值 pattern protocol://{keyName} # 定义键值 ​``` keyName content ​```

举个例子:

 

bash

代码解读

复制代码

www.test.com/index.html file://{test.html} ``` test.html Hello world. Hello world1. Hello world2. ```

这种方式设置的Value只对当前阶段的规则生效,且优先级高于Values设置的Key-Value。

2.4.4 匹配原则

定义了当两条whistle规则出现冲突的时候的优先级。

  1. 相同协议规则的默认优先级从上到下,即前面的规则优先级匹配高于后面,如:
 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com/xxx 127.0.0.1:8080

  1. ruleproxy对应规则除外,可以同时匹配不同协议的规则
 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com/xxx 127.0.0.1:8080 www.test.com proxy://127.0.0.1:8888 www.test.com/xxx socks://127.0.0.1:1080 www.test.com pac://http://www.pac-server.com/test.pac www.test.com/xxx http://www.abc.com www.test.com file:///User/xxx/test

请求 https://www.test.com/xxx/index.html 按从上到下的匹配顺序,及第二点原则,会匹配以下规则:

 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com proxy://127.0.0.1:8888 www.test.com pac://http://www.pac-server.com/test.pac www.test.com/xxx http://www.abc.com

proxyhttp-proxyhttps-proxysocks都属于proxyhtmlfile等都属于rule,所以这两个对应的协议只能各种匹配其中优先级最高的一个。

  1. 一些属于不同协议,但功能有冲突的规则,如 rulehostproxy,按常用优先级为 rule > host > proxy,如:
 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com/xxx 127.0.0.1:8080 www.test.com proxy://127.0.0.1:8888 www.test.com/xxx socks://127.0.0.1:1080 www.test.com file:///User/xxx/test www.test.com/xxx http://www.abc.com

  1. 部分相同协议会匹配及合并所有可以匹配的规则,如:
 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com/xxx 127.0.0.1:8080 www.test.com proxy://127.0.0.1:8888 www.test.com/xxx socks://127.0.0.1:1080 www.test.com pac://http://www.pac-server.com/test.pac www.test.com/xxx http://www.abc.com www.test.com file:///User/xxx/test www.test.com/xxx reqHeaders://{test.json} www.test.com reqHeaders:///User/xxx/test.json www.test.com/xxx htmlAppend:///User/xxx/test.html www.test.com htmlAppend://{test.html} www.test.com/xxx reqHeaders:///User/xxx/test2.json www.test.com htmlAppend://{test2.html}

  1. 请求 https://www.test.com/xxx/index.html 会匹配以下规则:
 

bash

代码解读

复制代码

www.test.com 127.0.0.1:9999 www.test.com proxy://127.0.0.1:8888 www.test.com pac://http://www.pac-server.com/test.pac www.test.com/xxx http://www.abc.com www.test.com/xxx reqHeaders://{test.json} www.test.com reqHeaders:///User/xxx/test.json www.test.com/xxx htmlAppend:///User/xxx/test.html www.test.com htmlAppend://{test.html} www.test.com/xxx reqHeaders:///User/xxx/test2.json www.test.com htmlAppend://{test2.html}

3. Whistle的实用场景

whistle的功能总结来说就是拦截所有的请求响应并对其进行查看修改转发等一系列的操作。那么基于这样的功能可以有多种多样的玩儿法。在这里列举一些常用的应用。

  1. 解决跨域

以前跨域的解决比较麻烦,现在有了whistle之后只需要在配置规则中加入一条规则即可:

 

shell

代码解读

复制代码

pattern resHeaders://{Allow-Origin.json}

对于要解决跨域的域名使用合适的pattern进行匹配,然后在响应头部中加入Access-Control-Allow-Origin:*字段即可实现跨越请求。

  1. Mock数据

虽然使用umi可以很好的解决Mock数据的需求,但是对于一些没有使用umi的场景,使用whistle将请求代理到本地的一个装有mock数据的文件也是一个可以接受的方式,比如配置如下规则:

 

bash

代码解读

复制代码

^http://qq.com/** file:///Users//mockData/$1

将域名为qq.com的请求代理到本地mockData路径下的对应文件

  1. 移动端调试

vConsole是微信团队开发的轻量、可拓展、针对手机网页的前端开发者调试面板,主要原理是通过在页面注入js实现模拟PC浏览器的Console功能,这边利用whistle的js协议往指定网页(m.baidu.com/)注入`vConsol…

 

arduino

代码解读

复制代码

m.baidu.com js://{vConsole.js}

4. 线上调试

当项目部署到线上后出现的bug比较难以定位,因为项目代码已经经过丑化压缩合并变得不可读,并且线上的项目不能随意修改打断点不方便调试。这个时候如果可以找到线上请求文件对应本地是哪一个文件,就可以通过:

 

bash

代码解读

复制代码

pattern file://[本地文件地址]

将线上的文件请求代理到本地从而打断点或者是使用console.log调试。

链接:https://juejin.cn/post/7257708221360963643

whitle 应用举例:

4.1 代理到本地

4.1.1 代理 html 文件

除了代理到另一个域名,还可以将线上代理到本地的文件。随便找一个目录,里面创建一个 index.html 文件,在文件里面写上 hello, 我是百度

接着来设置一下新的代理规则,将代理地址设置为这个 index.html 的绝对路径。

 

bash

代码解读

复制代码

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

上面使用到了 projectPath 这个变量,在 whistle 里面支持我们定义变量,方便之后复用。如果我们的工作目录是固定的,那就可以将其设置为变量,避免重复写路径前缀。以 Mac 为例:

 

go

代码解读

复制代码

    ``` projectPath     # 上面访问的就是 /Users/你的用户名/Desktop/project/test/index.html     /Users/你的用户名/Desktop/project     ```

除了这种方式,还可以将变量定义在 Values 目录下,在 Values 下创建一个叫 projectPath 的 key,将上面的路径写进去。

那么我们打开百度看看效果吧。

image.png-16kB

4.1.2 代理静态资源

除了代理 html 文件,我们还可以代理静态资源到本地,如果你想修改线上的 JS 资源,这是一个很好的办法。

打开百度,可以看到他的静态资源都在 dss0.bdstatic.com 这个域名下,那么可以对这些资源进行代理。

 

shell

代码解读

复制代码

# https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman file://${projectPath}/test

Disable Cache 后刷新页面再来看一下,发现请求的资源都返回了 404,这是因为我本地的 test 目录里面没有这些资源,也说明已经成功代理过去了。

image.png-96.4kB

4.2 代理响应内容

whistle 还支持我们修改响应内容,方便对接口进行一系列的开发调试。我在这里将请求百度的响应代理到 test.json,在 Values 里面创建一个叫 test.json 的 key,里面写上一串 json 数据,这样返回的就是一个 json 数据。

 

perl

代码解读

复制代码

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

最终的效果是将这个 json 的内容输出到页面上了,这是因为 Content-Type: text/html 这句告诉浏览器当前响应的是个文档。

image.png-7.3kB

如果对响应 json 的接口进行代理,那么就能 mock 各种返回的数据和异常场景了。

4.3 解决跨域

跨域是 Web 开发中经常遇到的问题,常见解决方式是 CORS,通过设置 Access-Control-Allow-Origin 响应头来允许指定的域名跨域访问。

通过 whistle 我们可以增加 Access-Control-Allow-Origin 响应头,这样就能实现跨域。

 

arduino

代码解读

复制代码

www.baidu.com resCors://*

在 Network 里面可以看到,www.baidu.com 的响应头里面已经增加了 access-control-allow-origin: * 这一项。

image.png-76kB

4.4 修改 ua

whistle 提供了语法来支持修改请求的 userAgent,但这个只对请求生效,不会对本地的 userAgent 生效。因为它本质上还是修改了请求头里面的 userAgent。

 

perl

代码解读

复制代码

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

那么我们将请求百度的 ua 设置为 windows phone 来看一下效果。

 

scss

代码解读

复制代码

Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; RM-1113) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586

再次打开百度,发现页面已经变成了 H5 模式,虽然我们是用 PC 打开的。

image.png-1218.1kB

来看一下请求的详情,发现请求头里面的 userAgent 已经被我们修改了,但本地 navigator.userAgent 的并不会被修改,因为 whistle 只会修改请求。

image.png-86.4kB

4.5 自定义样式

除了修改请求的信息,whistle 还支持自定义某个域名下的请求展示样式。试试下面这串代码:

 

ini

代码解读

复制代码

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

image.png-97.1kB

5. 实战 —— 绕过网站付费

前面讲了这么多年规则用法,那么这里来带大家进行一次实战。通过 whistle 来绕过某付费网站。

成人依恋类型量表 是一个在线测评网站,但它是付费的,当我们点击开始测试,就会弹出这个付费的弹窗。那么有没有办法绕过这个付费呢?

image.png-221.5kB

那么我们先来看一下点击开始测试的时候它做了什么事情吧,我们盲猜会去向后台发送接口,获取用户的 vip 信息,然后判断是否弹窗。那么打开开发者工具来看一下吧。

image.png-29.9kB

很显然,这里调用了 toolcheck 接口来获取是否 vip,那么我们是否可以通过修改接口返回的数据来绕过 vip check 呢?答案是可以的。

我们先来看一下这个请求的调用栈,很容易发现是在 tool.js 的 loadtoolcheck 里面调用的。

image.png-55.4kB

这个函数明显是发送了一个 ajax 请求,数据返回后就会调用 success 方法,那么在 success 的判断条件里面都打上断点,重新点击开始测试看一下效果。

image.png-200kB

可以看到,这里有进行了一次判断。如果接口响应结果是 ok 的话,那就将某个变量设置为 true,并且隐藏加载弹窗。这里不得不吐槽一下这个变量的拼音命名了,hideLoading 写成了 hidejiazai。

所以在这里,我们只需要将这个接口的返回结果通过 resBody 修改成 ok,那么就绕过了这个网站的 check vip 逻辑。再次打开发现已经不会弹窗了,可以直接进入了答题测试页面,很完美。

 

perl

代码解读

复制代码

    ```vip     ok     ```     https://www.zxgj.cn/gongju/toolcheck resBody://{vip}

除了修改接口响应,这里还有另外一种办法,那就是把这个 js 文件复制一份到本地,并且将这些 if else 的判断逻辑删掉,只保留 if 里面的内容。

然后我们将这个 JS 文件代理到我们本地修改后的 JS 资源里面,这样也完美绕过了 check 逻辑。刷新页面后再次打开,发现 JS 文件的内容已经变成了我们本地的。

image.png-53.8kB

6. 移动端调试

whistle 很常用的一个功能就是移动端调试。虽然在移动端测试环境可以通过 chrome://inspect 的形式来调试 webview,但对于线上的一些应用来说,经常会屏蔽掉,导致我们调试也没那么容易。

想象一下,线上的 H5 页面出了 bug,但你又不知道到底是哪里有问题呢?是接口返回的不对?是 localStorage 不对?是某个样式没生效?还是...?

完全靠盲猜来定位 bug 是非常痛苦的。最恐怖的方式是通过临时增加日志,然后发版的形式来定位 bug。

6.1 安装

我们在移动端依然需要进行 whistle 的配置,首先要保证手机和电脑连接的是同一个 wifi 网络,然后打开 wifi 详细配置,将代理设置为手动,服务器是你自己电脑的 ip 地址,端口号是 8899,然后需要在手机上去下载 whistle 的证书,安装后对其信任。

当我们在 Network 里面可以看到手机上的请求时,说明代理已经配置生效了,这个时候你在电脑上的代理规则,对手机也一样起效。

image.png-851.1kB

6.2 注入 vConsole

vConsole 是一个非常好用的移动端调试小工具,它的界面类似 Chrome 开发者工具,有 Console、Element、Network、Storage 等几个功能,可以帮助我们快速获取页面的一些基本信息。

Kapture 2022-03-20 at 00.01.23.gif-5999.3kB

但是 vConsole 只适合在测试环境使用,或者在生产环境通过某种特别的手段调出来(比如1s内连续点击10次某个按钮),它不应该展示给用户,这样就容易暴露一些信息。

所幸的是,whistle 提供了 jsPrepend 这个语法允许我们对某个页面注入 JS 脚本。这样我们打开页面的时候,vConsole 就已经注入了,只对我们连接了代理的手机才会生效。

 

perl

代码解读

复制代码

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

我们可以从网上把 vConsole 的源码下载下来,在 Values 里面创建一个叫 vConsole.js 的 key,将源码复制进行,并且将 vConsole 这个类进行实例化。

再次打开百度,发现右下角已经出现了 vConsole 的小按钮,接下来我们就可以愉快的进行各种调试了。

image.png-125.1kB

6.3 打印日志

除了注入 vConsole 这种方法,还可以开启日志打印,只需要设置下面这一条规则:

 

perl

代码解读

复制代码

https://www.baidu.com/  log://

再次打开百度,就能够在 Network 的 Tools 下面的 Console 选项下面看到控制台打印的所有 log 信息了。这种方式也比较适合在移动端进行一些调试。

image.png-262.1kB

7. mock 接口返回

最后这部分是用于进行接口的一些调试,方便测试开发对后端的接口提前进行测试,也方便前端开发来模拟各种接口的异常场景。

7.1 修改接口返回状态码

whistle 支持我们修改接口返回的状态码,如果你想测试接口返回异常状态码(403、404、500、503等等)的情况,此时你的异常处理逻辑是否生效,但后端不愿意配合你把接口改成500,那么你就可以自己用 whistle 来模拟。

比如我们将 www.baidu.com 的返回设置为 404,可以看到下面这个找不到网页的报错。

 

perl

代码解读

复制代码

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

image.png-44.7kB

7.2 修改接口返回数据

我们可以对接口返回的数据进行一些修改,这样可以模拟各种场景。假如我们有一个列表页,我想测试没有数据、100条数据、1000条数据、10000条数据的情况。

参考网易云音乐的搜索页面,我们搜索【周杰伦】出现了很多搜索结果,假如我们想测试没有搜索结果的情况呢?

可以拦截列表的请求接口,将接口里面返回的数据设置为空数组。再次搜索周杰伦,神奇的发现居然搜不到相关的结果了!

 

ruby

代码解读

复制代码

https://music.163.com/weapi/cloudsearch/get/web resBody://{netease.json}

image.png-123kB

7.3 模拟超时

whistle 还可以模拟超时,对于前端开发比较有帮助。一个是我们需要测试超时的异常处理逻辑是否正确,另一个是我们可能需要验证超时重试机制是否生效,这个完全依赖于后端来配合效率会比较低。

 

bash

代码解读

复制代码

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

image.png-104.9kB

7.4 模拟弱网

whistle 除了可以模拟超时,还可以模拟弱网。虽然 Chrome 本身也可以模拟 2G、3G、4G 等网络环境,但 whistle 可以精确到具体的网络传输速度。

比如我们设置请求 www.baidu.com 的网速是 10kb/s,会发现网页打开速度非常慢,加载耗时达到了 14s 之久。

image.png-231kB

8. nohost

nohost 是腾讯开源的一个基于 Whistle 实现的多用户多环境配置及抓包调试系统,个人觉得是 whistle 生态的一个大杀器。借用 Github 的介绍就是:

  1. 环境共享:前端无需配后台环境,后台无需配前端环境,其他人无需配任何环境
  2. 抓包调试:远程实时抓包调试,支持各种 Whistle 规则,以及通过链接分享抓包数据
  3. 历史记录:可以把环境配置及抓包数据沉淀下来,供后续随时切换查看
  4. 插件扩展:可以通过插件扩展实现诸如 inspect,vase,autosave 等功能
  5. 对外接口:提供对外接口,可供发布系统、CI等工具操作,实现自动化增删查改环境配置

目前 nohost 在腾讯这边广泛使用,我们这边也是通过 nohost 来进行的多环境配置和调试。它的好处在于,假设你在自己分支实现了一个功能,想给产品看一下,那么就可以通过 nohost 来部署一套自己的环境,产品只需要在界面上一键切换到你的环境,那么就能够看到你分支上的代码了。

通过界面来一键切换环境:

image.png-58.9kB

在开发环境中:

image.png-112.1kB

一般来说,nohost 需要配合 CI 来使用。比如我们分支的代码推送后,触发了 CI 构建,它会将构建结果放到以你的特性分支为目录名的文件夹里面。

在界面切换环境的时候,就会根据选中的环境从对应的文件夹里面读取我们的静态资源,这样就实现了多套环境切换,非常方便。

最后,本文的所有代理配置如下

perl

代码解读

复制代码

# 这里的 ``` 语法和 md 的代码块冲突了,所以缩进了一个 tab,看起来怪怪的。     ``` projectPath     /Users/gloryyin/Desktop/project     ```     ```ua     Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; RM-1113) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586     ```     ```vip     ok     ``` # 设置 hosts # 代理到本地 localhost https://www.baidu.com/ localhost:3000 # 代理到本地 html https://www.baidu.com/ file://${projectPath}/test/index.html # 代理响应内容 https://www.baidu.com/ resBody://{test.json} # 代理 https://www.baidu.com/ https://www.google.com.hk/ # 修改ua https://www.baidu.com/ ua://{ua} # 自定义样式 www.baidu.com style://color=@fff&fontStyle=italic&bgColor=red # 代理资源 https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman file://${projectPath}/test # 跨域 www.baidu.com resCors://* # 例子 https://www.zxgj.cn/g/ecr?key=0cTtkPxO https://www.zxgj.cn/gongju/toolcheck resBody://{vip} https://www.zxgj.cn/resource/js/tool.js file://${projectPath}/test/tool.js # 移动端调试 # 注入 vConsole https://www.baidu.com/ jsPrepend://{vConsole.js} # 打印日志 https://www.baidu.com/  log:// # mock 接口返回 # 修改接口返回状态 https://www.baidu.com/ statusCode://404 # 修改接口返回数据 https://music.163.com/weapi/cloudsearch/get/web resBody://{netease.json} # 模拟 5 秒超时 www.baidu.com reqDelay://5000 enable://abort # 模拟弱网 www.baidu.com resSpeed://50

    

链接:https://juejin.cn/post/7077385311642189832
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值