背景
經常我們要去看一些頁面所發出的請求時,經常會用到 Charles 做為抓包工具來進行介面抓取,但一方面市面是很多抓包工具都是收費或者無法二次開發的。當前我們團隊大多數用的也都是 Charles,但是對於一般新手來說,單純想抓個包或者修改和介面返回資料,直接上手 Charles 不管配置成本和學習成本都相對較高。所以我們有必要自己按照自己最爽的狀態來擼一個適合自己的抓包工具。
結果
基於以上訴求,我們自己重新設計並修改了互動,搞了一個符合我們訴求,使用簡單的桌面端抓包工具。本身這個外掛是可以整合到 utools
裡面的,但是由於很多涉及到內部的功能,我們沒法通過 utools 進行釋出,所以我們自己做了一個可以使用 utools 所有生態的工具箱 Rubick
。此次抓包工具也是基於 Rubick
的外掛工具。
試玩地址:
Rubick: https://github.com/clouDr-f2e/rubick
抓包外掛:https://github.com/clouDr-f2e/rubick-network
這次我們先不看程式碼,直接來看我們實現的抓包工具的效果:
支援HTTP/HTTPS請求抓取。
支援介面mock
只需要將需要mock
的介面,右擊加入 mock
即可完成對介面資料的mock
動作,後續所有介面請求將會走到該mock場景:
支援代理轉發
如何使用
首先我們需要clone Rubick
工具箱,他是一個基於 Electron
的類似於 utools
的工具箱,
倉庫地址:Rubick: https://github.com/clouDr-f2e/rubick 然後可以本地執行:
npm i
npm run dev
之後我們會看到這樣的介面:
如果熟悉 utools
的同學,可以直接略過後面的步驟了。
然後再 clone
下 rubick-network 外掛,一切準備就緒後,直接複製 rubick-network
下的 plugin.json
到 rubick
輸入框 選擇新建外掛即可看到外掛資訊:
然後開啟外掛,即可進行搜尋使用!
相比 utools
而言,rubick
最大的優勢是開源!!! 歡迎社群一起建設完善 rubick
。
技術實現
以後我們再來介紹一下是如何實現這樣一款抓包代理工具的。在實現抓包代理工具之前,首先大家需要去自學一下關於 nodejs 實現代理 的一些知識點,這裡推薦幾篇不錯的文章:
簡單來講就是要實現一箇中間人,使用者通過設定代理,網路請求就會通過中間人代理,再發往正式伺服器。
這種中間人的實現方式有兩種。
一種為普通的HTTP代理,通過Node.js開啟一個HTTP服務,並將我們的瀏覽器或手機設定到該服務所在的ip和埠,那麼HTTP流量就會經過該代理,從而實現資料的攔截。
對於非HTTP請求,比如HTTPS, 或其他應用層請求。可以通過在Node.js 中開啟一個TCP服務,監聽CONNECT請求,因為應用層也是基於傳輸層的,所以資料在到達應用層之前會首先經過傳輸層,從而我們能實現傳輸層資料監聽。
但是對於CONNECT捕抓到的請求,無法獲取到HTTP相關的資訊,包括頭資訊等,這對一般的前端分析作用不大,那麼想要真正監聽HTTPS,還需要支援證書相關的驗證。
關於證書如何生成網上也有很多教程,這裡就不在贅述,可以自行百度。不過看了一圈別人的設計,自己再動手實現一個 http 代理服務就是輕而易舉的事情了,但是為了更加快捷的實現功能,這裡我們選擇了 anyproxy
作為基礎服務,站在巨人的肩膀上進行開發。
anyproxy
安裝後提供了一個 websocket
服務,我們只需要監聽 websocket
埠對代理過來的資料進行動態展示即可:
function initWs(wsPort = 8002, path = 'do-not-proxy') {
if(!WebSocket){
throw (new Error('WebSocket is not supported on this browser'));
}
const wsClient = new WebSocket(`ws://localhost:${wsPort}/${path}`);
wsClient.onerror = (error) => {
console.error(error);
};
wsClient.onopen = (e) => {
console.info('websocket opened: ', e);
};
wsClient.onclose = (e) => {
console.info('websocket closed: ', e);
};
return wsClient;
}
// 連結websocket
const connectWs = () => {
const wsClient = ws.initWs();
wsClient.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
store.commit('addRecord', data.content);
});
}
但是 anyproxy
僅僅提供 node
端的能力,所以正好 electron
可以使用 nodejs
能力,也就是我們可以藉助 electron
來實現 nodejs
能力。這裡由於我們是外掛化的,所以參考 utools
的設計,實現方式如下:
// preload.js
const AnyProxy = require('anyproxy');
const options = {
port: 8001,
webInterface: {
enable: true,
webPort: 8002
},
forceProxyHttps: true,
wsIntercept: false, // 不開啟websocket代理
silent: true
};
class Network {
constructor() {
this.mockList = [];
this.proxyServer = null;
}
initNetwork(op, {
beforeSendRequest,
beforeSendResponse,
success,
onRecord,
}) {
if (op === 'start') {
if (!this.proxyServer || !this.proxyServer.recorder) {
const _this = this;
options.rule = {
*beforeSendRequest(requestDetail) {
if (beforeSendRequest) {
return beforeSendRequest(requestDetail);
}
return requestDetail
},
*beforeSendResponse (requestDetail, responseDetail) {
if (beforeSendResponse) {
return beforeSendResponse(requestDetail, responseDetail);
}
return responseDetail;
}
};
this.proxyServer = new AnyProxy.ProxyServer(options);
this.proxyServer.once('ready', () => {
console.log('啟動完成');
success && success(this.proxyServer);
});
}
this.proxyServer.start();
} else {
AnyProxy.utils.systemProxyMgr.disableGlobalProxy('http');
AnyProxy.utils.systemProxyMgr.disableGlobalProxy('https');
this.proxyServer.close();
success && success();
}
}
getIPAddress() {
const interfaces = require('os').networkInterfaces();
for (const devName in interfaces) {
const iface = interfaces[devName];
for (let i = 0; i < iface.length; i++) {
const alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
return alias.address;
}
}
}
}
}
只需要在 preload.js
中加上 anyproxy
服務的實現,即可完成我們的訴求!
結語
什麼是 rubick
?
基於 electron 的工具箱,媲美 utools的開源外掛,已實現 utools 大部分的 API 能力,所以可以做到無縫適配 utools 開源的外掛。 之所以做這個工具箱一方面是 utools 本身並未開源,但是公司內部的工具庫又無法釋出到 utools 外掛中,所以為了既要享受 utools 生態又要有定製化需求,我們自己參考 utools 設計,做了 Rubick.
歡迎大家給rubick
pr 和提 issue 幫助我們完善
Rubick: https://github.com/clouDr-f2e/rubick