cssmagic/Footprint

如何让 Charles 抓取 Node.js 应用发出的 HTTPS 请求

Opened this issue · 0 comments

背景

  • 我最近在用 Node.js 开发一款命令行工具,名为 “Kup”。
  • Kup 底层使用 Got 这个库来发送请求,请求的接口都是 HTTPS 的。
  • 我希望通过 Charles 来监听 Kup 与服务器之间的通信,完成一些调试工作。
  • 本文理论上也适用于 Fiddler、Whistle 等基于代理的请求调试工具。

初步尝试

准备工作

  • 在本机安装 Charles 的 CA 证书,并设置为 “Always Trust”。
  • 打开 Charles,启动 HTTP 代理(http://127.0.0.1:8888),确认已开启 “SSL Proxying”。

(这也是 Charles 抓取浏览器端 HTTPS 请求的准备工作。)

尝试一

把 Charles 代理设置为 macOS 全局代理。

在命令行运行 Kup。Charles 抓不到它发出的请求,失败。

尝试二  

取消全局代理,改为精准指定终端代理配置:

export https_proxy=http://127.0.0.1:8888

仍然抓不到,失败。

调研

在上面两次失败的尝试中,似乎 Kup 压根就没有走 Charles 的代理。一番调研后发现,想用 Charles 抓 Node.js 请求,基本上有两种方案:

  • 第一种是我们熟悉的常规方案:就是让我们的 Node.js 应用(比如 Kup)通过 Charles 提供的代理来发请求。这与 Charles 抓浏览器请求的原理是一样的。

  • 第二种属于变通方案:让 Charles 启动一个反向代理,把真正的请求地址包装一层,然后把 Node.js 应用的请求指向 Charles 反向代理包装之后的地址。这样也可以完成对请求的抓取。

第二种方案的原理看起来更复杂,但操作上却是相对简单的。因为这个方案不需要在本机安装 CA 证书,Node.js 应用端也只需要改改接口地址就可以了。这个方法临时用用是可以的,但它的缺陷也很明显:Node.js 应用端需要考虑两套地址的切换问题,另外 Charles 这一端也需要做针对性的配置,整个方案的可移植性不好。遂放弃之。

第一种方案在实际操作上要麻烦得多。原因是 Node.js 应用在发送请求时并不会自动走环境变量 https_proxy 指定的代理,这个行为需要由应用自行实现,而这个实现过程往往有点原始:一个支持代理的 HTTP 请求库(比如 Got)通常不会直接集成代理功能,而是允许我们指定一个 http.Agent 实例来作为它的代理;于是我们又需要通过专门的库来创建 http.Agent 实例……

虽然麻烦了点,但所幸这些操作都集中在 Node.js 应用这一端,纯代码就可以搞定;而且我们对这种抓包原理也更熟悉,所以最终还是选它。

最终实现

选好方案之后,实现起来似乎也不算很麻烦:

  1. 选用 https-proxy-agent 这个包来创建代理实例:

    const HttpsProxyAgent = require('https-proxy-agent')
    const agent = new HttpsProxyAgent('http://127.0.0.1:8888')
  2. 把这代理实例提供给 Got:

    got(myApi, {
    	agent: {
    		https: agent,  // 指定 HTTPS 代理
    	},
    })
  3. 由于 Charles CA 证书并不是个正经的证书,我们还需要给 Got 加个配置,忽略对证书的校验:

    got(myApi, {
    	agent: {
    		https: agent,
    	},
    	rejectUnauthorized: false,  // 加这个选项
    })

在此之后,每当 Kup 发出请求,Charles 就可以看到请求的具体情况了。成功!

当然,以上只是跑通流程的最小化实现。为了不影响用户的正常使用,上述代理行为需要限制在调试模式下,这些细节就不赘述了。

结语

我在这个问题上也是一个学习者,本文记录了我的探索过程与决策过程,希望能提供一些参考价值。

抛砖引玉,如果大家有更好的方案,或者上面有错漏,还请多多指教!


© 经验分享 · 日拱一卒   |   Star = 收藏   |   Watch = 订阅