基于 HTTP/2 的 WEB 內(nèi)網(wǎng)穿透實(shí)現(xiàn)
HTTP/2 引入了二進(jìn)制分幀層,將 HTTP/1.1 中的請(qǐng)求和響應(yīng)拆成顆粒度更細(xì)的幀(frame),從而實(shí)現(xiàn)了優(yōu)先級(jí)、流量控制和 Server Push 等功能;HTTP/2 在單條 TCP 連接上可以打開(kāi)多個(gè)流,從而實(shí)現(xiàn)了多路復(fù)用;HTTP/2 使用靜態(tài)字典、動(dòng)態(tài)字典以及哈夫曼編碼,對(duì)請(qǐng)求 / 響應(yīng)頭部進(jìn)行壓縮??傊?,HTTP/2 從協(xié)議層面解決了 HTTP/1.1 的諸多問(wèn)題。
在我之前寫(xiě)的文章里,我介紹了如何通過(guò) ngrok 讓內(nèi)網(wǎng) WEB 在其它網(wǎng)絡(luò)環(huán)境中能夠被訪問(wèn)。本文要實(shí)現(xiàn)的服務(wù)與 ngrok 類(lèi)似,我把它稱(chēng)之為 Pangolin,中文是穿山甲的意思(名字來(lái)自于同事的類(lèi)似項(xiàng)目,在此表示感謝)。Pangolin 客戶(hù)端和服務(wù)端之間的報(bào)文轉(zhuǎn)發(fā),是用 node-http2 這個(gè) Node.js 模塊提供的 HTTP/2 服務(wù)來(lái)實(shí)現(xiàn)的。
Pangolin 的需求來(lái)自于本博客用戶(hù)評(píng)論(via)。實(shí)際上,能實(shí)現(xiàn)類(lèi)似功能的軟件很多,有使用私有協(xié)議進(jìn)行轉(zhuǎn)發(fā)的,有使用 WebSocket 進(jìn)行轉(zhuǎn)發(fā)的。而我認(rèn)為 HTTP/2 應(yīng)該是個(gè)不錯(cuò)的選擇,打算試一下。最終我花了一個(gè)小時(shí)實(shí)現(xiàn)了一個(gè)初步能用的版本,除開(kāi) node-http2,全部代碼不超過(guò) 200 行。代碼我放在了 github 上,有興趣的同學(xué)可以玩一下。
下面簡(jiǎn)單介紹它的原理,我畫(huà)了一張草圖:
最左側(cè)是最終用來(lái)訪問(wèn)服務(wù)的瀏覽器,它可能位于公網(wǎng),也可能位于其它內(nèi)網(wǎng);最右側(cè)是實(shí)際提供 WEB 服務(wù)的 HTTP Server,它位于內(nèi)網(wǎng)。顯然,左側(cè)瀏覽器沒(méi)辦法直接訪問(wèn)右側(cè) WEB 服務(wù),只能借助公網(wǎng)節(jié)點(diǎn)作為橋梁。中間的 pangolin 服務(wù)端運(yùn)行在公網(wǎng)節(jié)點(diǎn)上;Pangolin 客戶(hù)端運(yùn)行在與 WEB 服務(wù)同臺(tái)機(jī)器或者同一網(wǎng)段內(nèi)。
瀏覽器發(fā)起請(qǐng)求后,請(qǐng)求報(bào)文沿著綠色箭頭從左到右流動(dòng),每個(gè)節(jié)點(diǎn)都相當(dāng)于左側(cè)相鄰節(jié)點(diǎn)的 HTTP Server。***的問(wèn)題出現(xiàn)在 Pangolin 服務(wù)端和客戶(hù)端之間:客戶(hù)端位于內(nèi)網(wǎng),正常情況下 Pangolin 服務(wù)端連不上客戶(hù)端提供的 HTTP Server。
這個(gè)問(wèn)題我用了一個(gè)取巧的辦法解決:由于 Pangolin 服務(wù)端有公網(wǎng) IP,可以開(kāi)啟 TCP Server,客戶(hù)端可以通過(guò) IP 和約定的端口與服務(wù)端建立 TCP 連接。那么只要稍微改造一下 node-http2 的代碼,使它可以基于指定 socket 創(chuàng)建 HTTP/2 Server、發(fā)送 HTTP/2 Request,就可以打通所有節(jié)點(diǎn)了。這個(gè)問(wèn)題解決后,左側(cè)的請(qǐng)求可以順利到達(dá)右側(cè),響應(yīng)數(shù)據(jù)也可以沿著之前的連接逐級(jí)返回。
Pangolin 服務(wù)端和客戶(hù)端內(nèi)部之間使用 HTTP/2,可以大幅提高性能,降低程序復(fù)雜性;對(duì)外使用 HTTP/1.1,保證了與已有系統(tǒng)的兼容性。
為了實(shí)現(xiàn)內(nèi)網(wǎng)穿透,Pangolin 需要做以下準(zhǔn)備工作:
Pangolin 服務(wù)端開(kāi)啟 TCP Server;
Pangolin 客戶(hù)端啟動(dòng) TCP Client,與 Pangolin 服務(wù)端連接,得到 socket 長(zhǎng)連接;
Pangolin 客戶(hù)端基于這個(gè) socket 連接,開(kāi)啟 HTTP/2 Server;
Pangolin 服務(wù)端開(kāi)啟 HTTP/1.1 Server,等待瀏覽器來(lái)訪問(wèn);
實(shí)際的數(shù)據(jù)傳輸流程如下:
瀏覽器向 Pangolin 服務(wù)端發(fā)起請(qǐng)求(HTTP/1.1);
Pangolin 服務(wù)端基于已有 socket,向 Pangolin 客戶(hù)端發(fā)起請(qǐng)求(HTTP/2);
Pangolin 客戶(hù)端向內(nèi)網(wǎng) WEB 服務(wù)發(fā)起請(qǐng)求,得到響應(yīng)(HTTP/1.1);
Pangolin 客戶(hù)端基于已有 socket,將響應(yīng)返回給 Pangolin 服務(wù)端(HTTP/2);
Pangolin 服務(wù)端將響應(yīng)返回給瀏覽器(HTTP/1.1);
由于 Pangolin 客戶(hù)端采用了 HTTP 轉(zhuǎn)發(fā),而不是 TCP 隧道,所以可以輕松實(shí)現(xiàn) ngrok 那樣的管理界面,用來(lái)查看完整的 Request/Response 信息。目前我還只是簡(jiǎn)單地打印了請(qǐng)求日志。
HTTP/2 協(xié)議本身并沒(méi)有規(guī)定它必須基于 TLS 部署,沒(méi)有安全層的 HTTP/2 被稱(chēng)之為 h2c(HTTP/2 Cleartext)。目前來(lái)看,所有瀏覽器都不打算支持 h2c。但如果一個(gè)系統(tǒng)的某些環(huán)節(jié)對(duì)安全沒(méi)有那么高的要求,或者已經(jīng)通過(guò)了其它方案確保了安全,部署 h2c 也是一個(gè)非常好的選擇?,F(xiàn)在很多 HTTP/2 工具和類(lèi)庫(kù)同時(shí)支持 h2 和 h2c,node-http2 也是如此。
實(shí)際上,我為了測(cè)試方便,在實(shí)現(xiàn) pangolin 時(shí)也選擇了 h2c。通過(guò) Wireshark 抓包可以看出,HTTP/2 層下面直接就是 TCP 層: