HTTP 代理服務器技術選型之旅

很久不寫博客了,在元旦到來前水一篇文章,聊聊我在實現代理服務器的過程當中遇到的一些坑,同時祝各位讀者新年快樂。python

背景

長期以來,貼吧開發人員多,業務耦合大,需求變化頻繁,所以容易產生 bug。而我所負責的廣告相關業務,和 UI 密切相關,一旦由於某種緣由(甚至是被別人改了代碼)產生了 bug,必然大幅度影響廣告收入。git

解決問題的一種方法在於頻繁的測試,既然避免不了代碼層面的耦合,那老是能夠經過定時的檢查來避免問題。因此咱們維護了一組核心 case,密切關注最核心的功能。選擇核心 case 其實是在覆蓋面和測試成本之間的權衡,然而多個 case 有不一樣的測試步驟,測試效率始終難以提升。github

所以,咱們的目標是創建一個代理服務器,可以在運行時把任何包(包括線上包)的數據改爲我但願的樣子。換句話說,這個代理服務器也能夠理解爲一個私服,它可以得到客戶端的請求數據並做出修改,也能夠得到服務端的響應數據並作修改。swift

代理服務器工做模型

在早期版本中,咱們選擇了簡單的 HTTP 協議。這種選擇對技術的要求最低,咱們本身實現了一個代理服務器,開啓 socket,監聽端口,而後將客戶端的請求發送給服務器,再把服務器的返回數據傳回客戶端。這種模式也被稱爲:「中間人模式」(MITM: Man In The Middle)。瀏覽器

雖然道理很簡單,但實現起來仍是有些地方要注意。首先,當 socket 接受數據後,應該新開一個進程/線程 進行處理。既然涉及到新的進程/線程,就必定要注意它的釋放時機,不然會致使內存無限制增長。bash

其次,對於 socket 來講,它並無等待函數,也就是說我無從得知什麼時候有數據可讀,所以這個艱鉅的任務就交給了 select。咱們把須要監聽的 socket 對象做爲參數傳入其中,函數會一直阻塞,直到有可讀、可寫的對象,或者達到超時時間。服務器

Keep-Alive 字段能夠複用 TCP 鏈接,是一種常見的 HTTP 協議的優化方式,在 HTTP 1.1 中已是默認選項。填寫這個字段後,Server 返回的數據多是分批次的,這樣可以改善用戶體驗,但也會增長代理服務器的實現難度。因此代理服務器在做爲客戶端,向真正服務器請求數據時,應該刪除這個字段。app

因爲整套流程都是本身實現,所以能夠比較容易的 HOOK 住上下行數據並作修改。只有注意在接收到所有數據後再作修改即,整個流程能夠用下圖簡單表示:socket

代理服務器的工做模式

當時作完這一套東西之後,我在團隊內部作了一次分享, 感興趣的讀者能夠去 images.bestswifter.com/Proxy 2.key 下載 PPT。函數

技術選型

短鏈接

因爲長鏈接基於 TCP,不用每次新建鏈接,也省略了沒必要要的 HTTP 報文頭部,效率明顯優於 HTTP。因此各大公司基本上選擇了長鏈接做爲實際生產環境下的鏈接方式。然而因爲不熟悉 WebSocket 協議,而且咱們依然支持短鏈接,因此代理服務器最終選擇了 HTTP 協議。

要想實現這一點, 就得在應用啓動時,模擬後臺向客戶端發送一段控制信息,強制客戶端選擇 HTTP 請求。這樣一來,即便是線上包也能夠走代理服務器。

HTTPS

因爲蘋果強制要求使用 HTTPS,雖然已經延期,但也是明年的趨勢。考慮到後續的使用,咱們決定對以前實現的代理服務器進行升級。因爲 HTTPS 涉及到請求協議的解析,以及加密解密和證書管理,上述自研方案很難 hold 住。通過一番調研,最後選擇了一個比較知名的開源庫 mitmproxy

Mitmproxy

選擇這個庫最主要的理由是它直接支持 HTTPS,不過沒有中文文檔,國內的使用相對來講比較少,因此在接入的時候可能會略花一點時間。

這是一個 python 庫, 首先要安裝 virtualenv,若是本地沒裝的話輸入:

sudo pip install virtualenv
複製代碼

安裝好了之後,進入 mitmproxy/venv3.5/bin 文件夾輸入:

source ./active
複製代碼

這樣就能夠啓用 virtualenv 環境了。

Hook 腳本

這個庫能夠理解爲命令行中可交互版本的 Charles,不過我並不打算用它的這個功能。由於個人需求主要是利用腳原本 Hook 請求, 因此我選擇了 mitmdump 這個工具。使用它的時候能夠指定腳本:

mitmdump -s "xxx.py"
複製代碼

腳本也很簡單,咱們能夠重寫 requeest 或者 receive 函數:

def request(flow):
flow.response.content = "<p>hello world</p>"
複製代碼

運行腳本之後,把手機的代理設爲本機 ip 地址,端口號改成 8080,而後用手機瀏覽器打開 mitm.it/,若是一切配置順利,你會看到證書的安裝界面。

安裝好證書後,用手機訪問任何一個網站(包括 HTTPS),你應該都會看到一個小小的 hello world,至此全部的配置就完成了。

bug 修改

這個開源庫有一個很嚴重的 bug,在解析 multipart 類型的數據時可能會發生。它使用了 splitline 方法來分割換行符,然而若是數據中有 \n 的話,就會所以丟失。很不幸的是,不少 protobuf 編碼後的數據都有 \n,一旦丟失就會致使解析失敗。

若是你不幸遇到了和我同樣的坑,能夠把相關代碼改爲個人版本:

for i in content.split(b"--" + boundary):
parts = i.split(b'\r\n\r\n', 2)
if len(parts) > 1 and parts[0][0:2] != b"--":
match = rx.search(parts[0])
if match:
key = match.group(1)
value = parts[1][0:len(parts[1])-2] # Remove last \r\n
r.append((key, value))
複製代碼

More

到了這一步,基本上已經成功實現支持 HTTPS 的代理服務器了。後續要處理的可能就是解析 protobuf,完善業務代碼等等瑣碎的事情,只要當心謹慎,基本上不會有問題。

相關文章
相關標籤/搜索