<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Whistle 實(shí)現(xiàn)原理 —— 從 0 開始實(shí)現(xiàn)一個(gè)抓包工具

          共 5922字,需瀏覽 12分鐘

           ·

          2021-12-11 14:32

          導(dǎo)語(yǔ)?通過(guò)這篇文章可以大致了解 Whistle 的實(shí)現(xiàn)原理,并學(xué)習(xí)如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的抓包調(diào)試工具。

          項(xiàng)目 Github 地址:https://github.com/avwo/whistle

          Whistle?是基于 Node.js 實(shí)現(xiàn)的跨平臺(tái) Web 抓包調(diào)試(HTTP)代理,主要功能:

          1. 實(shí)時(shí)抓包:支持 HTTP、HTTPS、HTTP2、WebSocket、TCP 等常見(jiàn) Web 請(qǐng)求的抓包;

          2. 修改請(qǐng)求響應(yīng):與一般抓包調(diào)試工具采用斷點(diǎn)的方式不同,Whistle 采用類似系統(tǒng) host 的配置規(guī)則方式;

          3. 擴(kuò)展功能:支持通過(guò) Node 編寫插件,或作為獨(dú)立 NPM 包引入項(xiàng)目?jī)煞N擴(kuò)展方式。

          本文將從最基本的概念開始逐步講解 Whistle 功能,包含以下內(nèi)容:

          1. 什么是 HTTP 代理

          2. 實(shí)現(xiàn)簡(jiǎn)單 HTTP 代理

          3. 完整 HTTP 代理架構(gòu)(Whistle)

          4. 具體實(shí)現(xiàn)原理

          5. 參考資料

          1. 什么是 HTTP 代理

          代理是客戶端到服務(wù)端的中轉(zhuǎn)服務(wù),其中:

          1. 不經(jīng)過(guò)代理的請(qǐng)求:客戶端和服務(wù)端直接建立連接后,即可開始交換數(shù)據(jù)。

          2. 經(jīng)過(guò)代理的請(qǐng)求:客戶端不與服務(wù)端直接建立連接,而是先跟代理建立連接后,將目標(biāo)服務(wù)器的地址發(fā)送給代理,通過(guò)代理再跟服務(wù)端建立連接,這里如果代理服務(wù)為 HTTP Server,則稱為 HTTP 代理。

          接下來(lái)看下客戶端如何將目標(biāo)服務(wù)器地址傳給 HTTP 代理,以及 HTTP 代理如何跟目標(biāo)服務(wù)器建立連接。

          2. 實(shí)現(xiàn)簡(jiǎn)單 HTTP 代理

          先看一個(gè)用 Node.js 實(shí)現(xiàn)的最簡(jiǎn)單 HTTP 代理:

          const http = require('http');
          const { connect } = require('net');

          /****************** 工具方法 ******************/
          const getHostPort = (host, defaultPort) => {
          let port = defaultPort || 80;
          const index = host.indexOf(':');
          if (index !== -1) {
          port = host.substring(index + 1);
          host = host.substring(0, index);
          }
          return {host, port};
          };

          const getOptions = (req, defaultPort) => {
          // 這里假定 host 一定存在,完整實(shí)現(xiàn)參見(jiàn) Whistle
          const { host, port } = getHostPort(req.headers.host, defaultPort);
          return {
          hostname: host, // 指定請(qǐng)求域名,用于通過(guò) DNS 獲取服務(wù)器 IP 及設(shè)置請(qǐng)求頭 host 字段
          port, // 指定服務(wù)器端口
          path: req.url || '/',
          method: req.method,
          headers: req.headers,
          rejectUnauthorized: false, // 給 HTTPS 請(qǐng)求用的,HTTP 請(qǐng)求會(huì)自動(dòng)忽略
          };
          };

          // 簡(jiǎn)單處理,出錯(cuò)直接斷開,完整實(shí)現(xiàn)邏輯參考 Whistle
          const handleClose = (req, res) => {
          const destroy = (err) => { // 及時(shí)關(guān)閉無(wú)用的連接,防止內(nèi)存泄露
          req.destroy();
          res && res.destroy();
          };
          res && res.on('error', destroy);
          req.on('error', destroy);
          req.once('close', destroy);
          };


          /****************** 服務(wù)代碼 ******************/
          const server = http.createServer();
          // 處理 HTTP 請(qǐng)求
          server.on('request', (req, res) => {
          // 與服務(wù)端建立連接,透?jìng)骺蛻舳苏?qǐng)求及服務(wù)端響應(yīng)內(nèi)容
          const client = http.request(getOptions(req), (svrRes) => {
          res.writeHead(svrRes.statusCode, svrRes.headers);
          svrRes.pipe(res);
          });
          req.pipe(client);
          handleClose(res, client);
          });

          // 隧道代理:處理 HTTPS、HTTP2、WebSocket、TCP 等請(qǐng)求
          server.on('connect', (req, socket) => {
          // 與服務(wù)端建立連接,透?jìng)骺蛻舳苏?qǐng)求及服務(wù)端響應(yīng)內(nèi)容
          const client = connect(getHostPort(req.url), () => {
          socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
          socket.pipe(client).pipe(socket);
          });
          handleClose(socket, client);
          });

          server.listen(8080);

          上述代碼實(shí)現(xiàn)了一個(gè)具有轉(zhuǎn)發(fā)請(qǐng)求功能的 HTTP 代理,從代碼可知 HTTP 代理就是一個(gè)普通的 HTTP Server,并監(jiān)聽(tīng)?request?和?connect?這兩個(gè)事件,客戶端會(huì)通過(guò)這兩個(gè)事件將目標(biāo)服務(wù)器地址傳過(guò)來(lái),其中:

          1. request:一般普通 HTTP 會(huì)通過(guò)該事件將目標(biāo)服務(wù)器地址傳過(guò)來(lái)。

          2. connect:一般非 HTTP 請(qǐng)求,如 HTTPS、HTTP/2、WebSocket、TCP 等會(huì)通過(guò)該事件將目標(biāo)服務(wù)器地址傳過(guò)來(lái),觸發(fā)該事件的代理請(qǐng)求也叫隧道代理

          可以在事件里面的?req.url?或?req.headers.host?獲取目標(biāo)服務(wù)器的地址(host:port),再跟該服務(wù)器地址建立連接并將結(jié)果通過(guò) HTTP 響應(yīng)的方式返回給客戶端,這里只是實(shí)現(xiàn)代理的最基本功能,完整的 HTTP 除了請(qǐng)求轉(zhuǎn)發(fā),至少應(yīng)該還有:

          1. 查看實(shí)時(shí)抓包;

          2. 解析 HTTPS 請(qǐng)求;

          3. 修改請(qǐng)求響應(yīng)內(nèi)容;

          4. 擴(kuò)展功能。

          下面以 Whistle 為例看下如何用 Node.js 實(shí)現(xiàn)一個(gè)完整的 HTTP 代理。

          3. 完整 HTTP 代理架構(gòu)(Whistle)

          主要分五個(gè)模塊:

          1. 請(qǐng)求接入模塊

          2. 隧道代理模塊

          3. 處理 HTTP 請(qǐng)求模塊

          4. 規(guī)則管理模塊

          5. 插件管理模塊

          4. 具體實(shí)現(xiàn)原理

          下面分別看下這五個(gè)模塊具體是怎么實(shí)現(xiàn)的。

          4.1 請(qǐng)求接入模塊

          所有請(qǐng)求先會(huì)經(jīng)過(guò)請(qǐng)求接入模塊,Whistle 支持四種請(qǐng)求接入方式:

          1. HTTP & HTTPS 直接請(qǐng)求:相當(dāng)于配 hosts 或 DNS 的方式,將請(qǐng)求轉(zhuǎn)發(fā)到 Whistle;

          2. HTTP 代理:Whistle 默認(rèn)接入方式,即配系統(tǒng)代理或通過(guò)瀏覽器插件配 HTTP 代理的方式;

          3. HTTPS 代理:在 HTTP 代理之上對(duì)代理請(qǐng)求進(jìn)行了加密,即 HTTPS Server,可以通過(guò)指定證書轉(zhuǎn)成 HTTP 代理請(qǐng)求;

          4. Socks5 代理:利用 npm 包?socksv5?轉(zhuǎn)成普通的 TCP 請(qǐng)求,并將 TCP 請(qǐng)求轉(zhuǎn)成隧道代理請(qǐng)求。

          基實(shí)現(xiàn)原理是:將所有請(qǐng)求都轉(zhuǎn)成 HTTP 代理的?隧道代理請(qǐng)求?或?HTTP 請(qǐng)求,再解析?隧道代理請(qǐng)求?轉(zhuǎn)成 HTTP 請(qǐng)求。

          如何將普通 tcp 請(qǐng)求轉(zhuǎn)成隧道代理請(qǐng)求參見(jiàn):?lack-proxy

          下面看下如何從?隧道代理請(qǐng)求?解析出 HTTP 請(qǐng)求。

          4.2 隧道代理模塊

          關(guān)鍵點(diǎn)(HTTP 請(qǐng)求也可以走隧道代理):

          1. 通過(guò)匹配的全局規(guī)則判斷是否要解析隧道代理請(qǐng)求,如果不解析,則當(dāng)成普通 TCP 請(qǐng)求處理;

          2. 如果需要,則通過(guò)?socket.once('data', handler)?讀取請(qǐng)求點(diǎn)第一幀數(shù)據(jù);

          3. 將第一幀數(shù)據(jù)轉(zhuǎn)成字符串,通過(guò)正則?/^(\w+)\s+(\S+)\s+HTTP\/1.\d$/mi?是否是 HTTP 請(qǐng)求?如果是 HTTP 請(qǐng)求,再判斷下是否是?CONNECT?請(qǐng)求,即隧道代理請(qǐng)求(隧道代理請(qǐng)求也可以代理隧道代理請(qǐng)求),如果是,則轉(zhuǎn)回隧道代理方法處理,如果不是,則轉(zhuǎn)到?HTTP 請(qǐng)求模塊處理;

          4. 如果不是 HTTP 請(qǐng)求,則當(dāng)成 HTTPS 請(qǐng)求處理,這里需要用到中間人的方式將 HTTPS 請(qǐng)求轉(zhuǎn)成 HTTP 請(qǐng)求;

          5. Whistle 會(huì)先按以下順序獲取請(qǐng)求證書:

            • 通過(guò)匹配的插件獲取(可以通過(guò)規(guī)則?sniCallback://plugin?指定加載證書的插件);

            • 通過(guò)啟動(dòng)參數(shù)?-z certDir?指定目錄或?~/.WhistleAppData/custom_certs?加載的自定義證書;

            • 如果沒(méi)有上述兩種自動(dòng)證書,Whistle 會(huì)自動(dòng)生成一個(gè)默認(rèn)的證書。

          6. 獲取到證書后,再利用該證書啟動(dòng)一個(gè) HTTPS Server,將 HTTPS 請(qǐng)求轉(zhuǎn)成 HTTP 請(qǐng)求交給?HTTP 請(qǐng)求模塊處理。

          4.3 HTTP 請(qǐng)求處理模塊

          HTTP 請(qǐng)求處理可以分兩個(gè)階段:

          1. 請(qǐng)求階段:

            • 匹配全局規(guī)則;

            • 如果規(guī)則里類似 whistle.xxx 的規(guī)則,執(zhí)行對(duì)應(yīng)插件鉤子,獲取插件規(guī)則并跟匹配的全局規(guī)則合并;

            • 執(zhí)行規(guī)則、記錄狀態(tài)并請(qǐng)求到指定服務(wù)。

          2. 響應(yīng)階段:

            • 執(zhí)行匹配插件的鉤子,獲取插件規(guī)則并跟匹配的全局規(guī)則合并;

            • 執(zhí)行規(guī)則、記錄狀態(tài)并請(qǐng)求返回客戶端。

          4.4 規(guī)則管理

          與傳統(tǒng)抓包調(diào)試代理 采用斷點(diǎn)修改請(qǐng)求響應(yīng)數(shù)據(jù)不同,Whistle 采用配置規(guī)則的方式修改請(qǐng)求響應(yīng),采用配置方式的好處是操作簡(jiǎn)單,且可以將操作持久化存儲(chǔ)及共享給他人,先看幾個(gè)例子:

          Whistle 的規(guī)則管理主要兩個(gè)功能:

          1. 解析規(guī)則

          2. 匹配規(guī)則

          解析規(guī)則

          Whistle 有兩類規(guī)則:

          1. 全局規(guī)則(公共規(guī)則),所有請(qǐng)求都會(huì)嘗試匹配的規(guī)則,由以下規(guī)則組成:

          2. 插件規(guī)則(私有規(guī)則),即進(jìn)入插件的請(qǐng)求(匹配的全局規(guī)則里有 whistle.xxx 協(xié)議)才會(huì)匹配到的規(guī)則,由以下規(guī)則組成:

            文檔:https://wproxy.org/whistle/plugins.html

            • 插件 reqRulesServer 等 hooks 動(dòng)態(tài)返回;

            • 插件根目錄 _rules.txt 等文件配置的靜態(tài)規(guī)則;

          匹配規(guī)則

          Whistle 規(guī)則的完整結(jié)構(gòu)為:

          文檔:https://wproxy.org/whistle/mode.html

          4.5 插件管理

          Whistle 插件的功能很多,不僅具備 Node 的所有能力,且可以操作 Whistle 的所有規(guī)則(理論上可以基于插件實(shí)現(xiàn)一個(gè) Whistle),主要用來(lái)做以下事情:

          1. 鑒權(quán)功能

          2. 提供 UI 交互界面

          3. 作為請(qǐng)求 Server(直接響應(yīng)或轉(zhuǎn)發(fā)并修改請(qǐng)求響應(yīng))

          4. 統(tǒng)計(jì)請(qǐng)求信息(查看上報(bào) / 打點(diǎn)數(shù)據(jù)等)

          5. 設(shè)置規(guī)則(動(dòng)態(tài),靜態(tài),全局及私有規(guī)則)

          6. 獲取抓包數(shù)據(jù)

          7. 編解碼請(qǐng)求響應(yīng)數(shù)據(jù)流(pipe stream 功能)

          8. 擴(kuò)展界面右鍵菜單(如:分享抓包數(shù)據(jù))

          9. 保存并同步 Rules & Values 數(shù)據(jù)

          10. 自定義 HTTPS 請(qǐng)求的證書

          比如:

          1. whistle.script:實(shí)現(xiàn)通過(guò)自定義腳本動(dòng)態(tài)設(shè)置規(guī)則

          2. whistle.vase:提供靈活強(qiáng)大的 mock 能力

          3. whistle.inspect:方便快速注入 vConsole、eruda 等頁(yè)面調(diào)試工具

          4. whistle.sni-callback:自定義證書插件

          其它插件例子參見(jiàn):https://github.com/whistle-plugins

          Whistle 是如何實(shí)現(xiàn)插件功能?主要遵循以下三個(gè)設(shè)計(jì)原則:

          1. 完備性:

            確保所有功能點(diǎn)都可擴(kuò)展,如:請(qǐng)求鑒權(quán)、生成證書、獲取抓包、設(shè)置規(guī)則、請(qǐng)求處理等。

          2. 穩(wěn)定性:

            插件內(nèi)部異常不影響其它功能,Whistle 的每個(gè)插件獨(dú)立進(jìn)程,插件與 Whistle 之間通過(guò) HTTP 協(xié)議交互。

            Whistle 是使用 npm 包?pfork?來(lái)啟動(dòng)插件進(jìn)程,進(jìn)程間的交換是直接通過(guò) Node 的 http 模塊實(shí)現(xiàn)的),方便開發(fā)者利用 http 的生態(tài)開發(fā)插件。

          3. 易用性:

            方便用戶開發(fā)及使用。

          4. 開發(fā):結(jié)構(gòu)簡(jiǎn)單 (npm 包) + 腳手架?lack

            使用:安裝 npm 包即可,用法跟內(nèi)置協(xié)議一樣,且可內(nèi)置交互界面。

          有關(guān)插件的更多細(xì)節(jié)參見(jiàn):https://wproxy.org/whistle/plugins.html

          事實(shí)上,Whistle 除了支持插件擴(kuò)展,還可以同時(shí)作為獨(dú)立模塊引入項(xiàng)目使用;除了本地開發(fā)使用,也可以基于 Whistle 開發(fā)出支持多人使用的開發(fā)聯(lián)調(diào)協(xié)作工具,比如后面會(huì)給大家介紹其實(shí)現(xiàn)原理的:

          1. 基于 Whistle 實(shí)現(xiàn)的多人多環(huán)境遠(yuǎn)程抓包調(diào)試工具。

            Nohost:https://github.com/Tencent/nohost

          2. 基于 Whistle 和 Nohost 實(shí)現(xiàn)的分布式遠(yuǎn)程抓包調(diào)試工具 TDE 等等。

            TDE 目前只在騰訊內(nèi)部使用,后續(xù)后逐步對(duì)外開源。

          5. 參考資料

          1. Github 倉(cāng)庫(kù):https://github.com/avwo/whistle

          2. 官方插件倉(cāng)庫(kù):https://github.com/whistle-plugins

          3. 詳細(xì)文檔:https://wproxy.org/whistle/



          往期推薦


          基于WebAssembly的圖片渲染/視頻處理/云原生的場(chǎng)景應(yīng)用有哪些?
          面試題:說(shuō)說(shuō)事件循環(huán)機(jī)制(滿分答案來(lái)了)
          專心工作只想搞錢的前端女程序員的2020

          最后


          • 歡迎加我微信,拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日屄免费视频 | 五月深深爱亭亭 | 精品无码人妻一区二区免费蜜桃 | 亚洲男女激情91免费网站 | 欧美A欧美A |