HTTP 代理與 SPDY 協議

jopen 11年前發布 | 28K 次閱讀 SPDY

HTTP代理是最經典最常見的代理協議。其用途非常廣泛,普遍見于公司內網環境,一般員工都需要給瀏覽器配置一個HTTP代理才能訪問互聯網。起初,HTTP代理也用來翻越G*W,但是因為G*W不斷發展,普通的HTTP代理早已無效了。但是,基于仍然有不少人使用明文的HTTP代理協議結合 stunnel之類的軟件進行加密翻Q,有時這種代理又被稱為HTTPS代理。再后來,又出現了WebV*N via Spdy之類的代理協議,特點是Chrome瀏覽器直接支持。再加上HTTP代理協議可以代理什么?是只能代理HTTP還是也可以代理HTTPS,還是可 以用來實現SOCKS代理?總之,非常混亂。在fqsocks項目里用python實現了HTTP代理的各種主流變種,終于明白了不同稱謂之后的真正含義。本文試圖總結一二。

代理基礎

所有的代理,其原理都是類似的。其網絡拓撲結構都是這樣的:

[客戶端] <-TCP連接-> [代理] <-TCP連接-> [服務器]

代理左手拿著與客戶端的連接,右手拿著與服務器的連接,然后在兩個TCP連接之間做數據的對拷。各種不同的代理協議,不同的只是TCP連接之上跑的 是什么的協議,數據是怎么經過包裝,拆包的。不存在客戶端與服務器之間建立TCP連接的情況。只有V*N這種在IP包這一層工作的,才能實現客戶端與服務 器的之間連接。

HTTP流量,明文的代理連接

這是最簡單的一種HTTP代理。其工作方式是客戶端與代理之間建立的TCP連接是明文的,也就是不經過SSL加密的。在TCP連接中傳輸的數據就是 明文的HTTP的POST和GET這些。對于這種代理工作方式,客戶端發給服務器的包幾乎不用修改發給代理,就可以獲得幾乎與服務器一樣的返回。其收發包 過程如下

[客戶端] -HTTP GET-> [代理] -HTTP GET-> [服務器]
[客戶端] <-200 OK [代理] <-200 OK [服務器]

HTTPS流量,明文的代理連接

在這種工作方式中,客戶端與代理之間建立的TCP連接仍然是明文的,也就是不經過SSL加密的。但是客戶端發出的請求不是HTTP請求,而是 HTTPS請求。因為HTTPS請求是經過SSL加密的,所以做為代理來說,雖然客戶端與其建立的TCP連接本身沒有經過SSL加密,但是中間傳輸的數據 確是SSL加密的。于是代理就無法通過檢查HTTPS請求的內容知道目的服務器是什么。所以客戶端必須在發HTTPS請求之前用HTTP CONNECT請求告訴代理要連接的服務器是什么,然后等代理服務器左右手的TCP連接都握穩了,客戶端就可以收發HTTPS流量了。代理在其中只是一個 簡單的在兩個TCP連接中做數據對拷,并不知道客戶端與服務器在干什么。事實上,利用HTTP CONNECT,客戶端與服務器之間除了可以交換HTTPS的流量,任何流量都是可以的。經過HTTP CONNECT之后,客戶端與代理的TCP連接就“相當于”是與服務器之間建立的TCP連接。當然我們前面已經說過了,這種直連只是一個假象。其收發包過 程如下:

[客戶端] -HTTP CONNECT a.b.c.d:port-> [代理] -TCP SYN-> [服務器]
[客戶端] <-200 OK- [代理] <-TCP SYN ACK- [服務器] # 這里的200 OK與下面的200 OK的含義不同
[客戶端] -SSL CLIENT HELLO-> [代理] -SSL CLIENT HELLO-> [服務器] # SSL握手包1
[客戶端] <-SSL SERVER HELLO- [代理] <-SSL SERVER HELLO/CERTIFICATE等- [服務器] # SSL握手包2
[客戶端] -SSL CLIENT CERTIFICATE等-> [代理] -SSL CLIENT CERTIFICATE等-> [服務器] # SSL握手包3
[客戶端] <-SSL SERVER FINISHED- [代理] <-SSL SERVER FINISHED- [服務器] # SSL握手包4
[客戶端] -SSL加密的HTTP GET-> [代理] -SSL加密的HTTP GET-> [服務器]
[客戶端] <-SSL加密的200 OK- [代理] <-SSL加密的200 OK- [服務器]

因為CONNECT之后,這個左右手的TCP連接里跑什么流量,代理是完全不管的,所以這樣也是可以的:

[客戶端] -HTTP CONNECT a.b.c.d:port-> [代理] -TCP SYN-> [服務器]
[客戶端] <-200 OK- [代理] <-TCP SYN ACK- [服務器] # 這里的200 OK與下面的200 OK的含義不同
[客戶端] -any request-> [代理] -any request-> [服務器]
[客戶端] <-any response- [代理] <-any response- [服務器]

正因為CONNECT之后TCP連接可以用來跑任意的東西,當然也就可以用來再跑另外一個代理協議了,比如SOCKS代理。

只要SOCKS代理是運行在443這樣HTTP CONNECT允許的端口,通過HTTP-CONNECT+SOCKS代理就可以在公司內網里訪問HTTP之外的互聯網內容了。

HTTP流量,SSL加密的代理連接

傳統HTTP代理,客戶端與代理之間是不經過加密的。因為GFW可以從明文的流量中知道你通過HTTP代理訪問的目標服務器,所以可以從中切斷連接。為了讓HTTP代理協議繼續承擔KX上網的重任,人們發明一種新的方式。其拓撲結構如下

[客戶端] <-TCP連接-> [Stunnel客戶端模式] <-TCP over SSL連接-> [Stunnel服務器模式] <-TCP連接-> [代理] <-TCP連接-> [服務器]

雖然拓撲結構復雜了,但是概念其實是很簡單的。因為客戶端與代理都不直接支持SSL的TCP連接,所以通過Stunnel做一個轉換,在客戶端經過 Stunnel的客戶端模式進行加密,然后在代理上有用Stunnel的服務器段模式進行解密。這樣GFW夾在中間看到的是經過SSL加密的流量,無法進 行URL關鍵字過濾了。收發包的過程如下:

[客戶端] -HTTP GET-> [Stunnel客戶端模式] -SSL加密的HTTP GET-> [Stunnel服務器模式] -HTTP GET-> [代理] -HTTP GET-> [服務器]
[客戶端] <-200 OK- [Stunnel客戶端模式] <-SSL加密的200 OK- [Stunnel服務器模式] <-200 OK- [代理] <-200 OK- [服務器]

HTTPS流量,SSL加密的代理連接

傳統的HTTP代理走HTTPS流量的時候,HTTP GET的內容經過SSL加密的,GFW無法做URL關鍵字過濾。但是客戶端與代理之間的HTTP CONNECT仍然是明文傳輸的。GFW仍然可以根據CONNECT的目標域名或者IP地址來判斷是不是訪問了GFW不允許訪問的網站。所以即便客戶端發 的是HTTPS請求,仍然有必要在客戶端與代理之間進行SSL加密。拓撲結構與上面相同。收發包的過程如下:

[客戶端] -HTTP CONNECT-> [Stunnel客戶端模式] -SSL加密的HTTP CONNECT-> [Stunnel服務器模式] -HTTP CONNECT-> [代理] -TCP SYN-> [服務器]
[客戶端] <-200 OK- [Stunnel客戶端模式] -SSL加密的200 OK-> [Stunnel服務器模式] <-200 OK- [代理] <-TCP SYN ACK- [服務器] # 這里的200 OK與下面的200 OK的含義不同
[客戶端] -SSL CLIENT HELLO->  [Stunnel客戶端模式] -SSL加密的SSL CLIENT HELLO-> [Stunnel服務器模式] -SSL CLIENT HELLO-> [代理] -SSL CLIENT HELLO-> [服務器] # SSL握手包1
... 以下略去。完成SSL握手之后,數據是在雙層的SSL加密之下傳輸的

很明顯,HTTP代理加上SSL傳輸的方式可以有效地對付GFW的關鍵字檢查。但是蛋疼的地方是本來是客戶端,代理與服務器之間三方的事情,現在變 成了五方會談了。轉手的次數越多,效率就越差。出錯了,調試問題也越麻煩。除此之外,我們還額可以看到每個SSL連接建立需要四個包,兩個來回。這個 SSL握手的成本不是一次性的,是附加在每個被代理的連接上的。考慮到很多HTTP請求都是短連接,內容也很少。所以每次多做一次SSL握手,額外負擔相 比之下挺重的。

SPDY

SPDY是Google家提出來的協議。其核心內容是用一個TCP連接,跑多個HTTP的STREAM。對于用SPDY協議跑HTTP代理來說,其 意義就在于之前是每個HTTP請求都要開連接關連接,用了SPDY之后,客戶端與代理之間是保持長連接的,然后在這個連接里,代理訪問不同的HTTP服務 器,就是不同的HTTP STREAM。而且,用SPDY協議雖然沒有強制但是一般客戶端與代理之間是SSL連接的,所以GFW也無法對連接的內容做關鍵字過濾。相比 Stunnel的方案,省去了每個連接額外的SSL過程,而且也省去了每個HTTP請求額外的TCP握手過程。所以從執行效率的角度來看,SPDY是非常 理想的。

以一個最簡單的HTTP GET為例SPDY的交互過程是這樣的:

[客戶端] -SSL CLIENT HELLLO-> [服務器]
[客戶端] <-SSL SERVER HELLO/CERTIFICATE/NPN (HTTP/1.1, SPDY/3, SPDY/2)等 [服務器] # 服務器通過SSL的NPN擴展告訴客戶端我這支持HTTP 1.1也支持SPDY 2和3
[客戶端] -SSL CLIENT CERTIFICATE/NPN(SPDY/3)等-> [服務器] # 客戶端告訴服務器我選擇SPDY3
[客戶端] <-SSL SERVER FINISHED- [服務器] # SSL握手完成
[客戶端] -SSL加密的SYN FRAME(HTTP GET)-> [服務器] # SYN FRAME是SPDY版的HTTP GET,意思是一樣的
[客戶端] <-SSL加密的SYN REPLY FRAME(200 OK)- [服務器] # SYN REPLAY FRAME是SPDY版的200 OK,意思是一樣的

這里與最傳統的HTTP GET過程的不同是:

  1. 經過了SSL加密,客戶端與服務器直接處理了SSL的加解密而不是經過Stunnel轉手
  2. SSL除了用來加密其NPN(Next Protocol Negotiation)擴展還用來溝通協議,所以同樣一個443端口可以同時用來支持傳統的HTTPS和新的SPDY協議
  3. 同一個SSL加密連接可以同時用來做多個HTTP GET,因為SYN FRAME與SYN REPLY FRAME的對應關系是通過Stream Id來完成的。而一個SSL連接中可以同時有多個Stream。

但是直接支持SPDY協議的服務器并不多,大部分都是Google自家的服務器。所以寄期望于所有的服務器都運行SPDY協議,從而 GFW無法進行關鍵字檢測是不現實,比寄期望與所有服務器都部署HTTPS還要不現實。單就反GFW關鍵字過濾來說,服務器支持HTTPS與SPDY并無 區別。

HTTP流量,SPDY的代理連接

有了SPDY協議,客戶端與代理之間就又多了一種選擇,除了明文的不加密的傳統的HTTP,和效率低下的Stunnel模式,還可以用Shrpx+Squid跑SPDY協議。其過程是這樣的

[客戶端] -SSL CLIENT HELLLO-> [代理]
[客戶端] <-SSL SERVER HELLO/CERTIFICATE/NPN (HTTP/1.1, SPDY/3, SPDY/2)等 [代理]
[客戶端] -SSL CLIENT CERTIFICATE/NPN(SPDY/3)等-> [代理]
[客戶端] <-SSL SERVER FINISHED- [代理] # 與代理建立好SSL連接,通過NPN選擇了選擇SPDY3協議
[客戶端] -SSL加密的SYN FRAME(Stream1 HTTP GET)-> [代理] -HTTP GET-> [服務器1]
[客戶端] -SSL加密的SYN FRAME(Stream2 HTTP GET)-> [代理] -HTTP GET-> [服務器2]
[客戶端] <-SSL加密的SYN REPLY FRAME(Stream2 200 OK)- [代理] <-200 OK- [服務器2]
[客戶端] <-SSL加密的SYN REPLY FRAME(Stream1 200 OK)- [代理] <-200 OK- [服務器1]

可見利用SPDY協議,客戶端與代理之間的數據傳輸就是SSL加密的,而且避免了每個HTTP請求都建立新的TCP連接,重新進行SSL握手。而代理與服務器因為都在墻外,所以HTTP明文交流并沒有任何問題。

HTTPS流量,SPDY的代理連接

SPDY協議的SYN FRAME除了支持HTTP GET/POST這些之外,HTTP CONNECT也是可以的。除了需要包裝成SPDY的各種FRAME之外,HTTP協議自身的語義其實都是一樣的。其交互過程是這樣的:

[客戶端] -SSL CLIENT HELLLO-> [代理]
[客戶端] <-SSL SERVER HELLO/CERTIFICATE/NPN (HTTP/1.1, SPDY/3, SPDY/2)等 [代理]
[客戶端] -SSL CLIENT CERTIFICATE/NPN(SPDY/3)等-> [代理]
[客戶端] <-SSL SERVER FINISHED- [代理] # 與代理建立好SSL連接,通過NPN選擇了選擇SPDY3協議
[客戶端] -SSL加密的SYN FRAME(HTTP CONNECT)-> [代理] -TCP SYN-> [服務器]
[客戶端] <-SSL加密的SYN REPLY FRAME(200 OK)- [代理] <-TCP SYN ACK- [服務器]
[客戶端] -SSL加密的DATA FRAME(HTTP GET)-> [代理] -HTTP GET-> [服務器]
[客戶端] <-SSL加密的DATA FRAME(200 OK)- [代理] -200 OK-> [服務器]

完成了HTTP CONNECT之后,對應的SPDY STREAM就是自由的了。無論發送什么樣的DATA FRAME(SPDY的FRAME的一種,傳輸二進制數據流)給代理,代理就直接轉給服務器。服務器無論返回什么樣的數據,代理也以DATA FRAME的形式轉給客戶端。這個SPDY STREAM和一個TCP連接就一樣了。除了可以用來發HTTP GET之外,跑什么樣的協議都行了,代理反正也不管里面是什么,它只管轉發DATA FRAME。

PAC文件

HTTP代理的一個流行用法是寫一個PAC文件,描述什么樣的時候走什么樣的代理。經典的PAC文件只有兩種返回值,PROXY和DIRECT。如 果返回的是PROXY則走代理,DIRECT則是直連。這里的走代理當然是最傳統的HTTP代理方式,也就是瀏覽器與代理之間是明文的連接,不經過SSL 加密的。具體瀏覽器是用HTTP GET/POST還是CONNECT,取決于瀏覽器要訪問的目標服務器是HTTP還是HTTPS的,和PAC文件無關。

Chrome在PAC的PROXY與DIRECT之外添加了第三種返回值,也就是HTTPS。這種返回值的含義與PROXY是一樣,都是走代理。區 別在于PROXY是明文的連接,HTTPS是SSL加密的連接。所以HTTPS這個返回值的歧義在于讓人以為它只用來代理HTTPS流量。因為SPDY是 基于SSL的NPN擴展的,所以當瀏覽器與代理進行SSL握手的時候,可以選擇是用HTTP/1.1協議還是SPDY/2還是SPDY/3。于是 HTTPS這個PAC返回值,還可以用來支持SPDY,前提當然是代理在NPN里說它支持SPDY,而且瀏覽器也知道SSL的NPN擴展,而且支持 SPDY協議。

另外一個PAC的擴展是蘋果給iOS的PAC文件添加的SOCKS返回值。利用這個返回值可以給iPhone配置SOCKS的全局代理。但是這個與本文討論的HTTP代理就無關了。

總結

可見HTTP代理協議自身來說,就兩種:HTTP GET/POST和HTTP CONNECT。一種用來代理HTTP的流量,另外一種用來代理HTTPS的流量。HTTP CONNECT除了可以用來走HTTPS之外,因為代理并不去解析其內容,所以事實上是一個通用的代理隧道。

另外一個維度是客戶端與代理之間是一個什么樣的連接:不加密的連接,SSL加密的連接,SSL加密同時運行SPDY協議的連接。

所以工作模式共有2*3=6種。

引申閱讀: Google官方也給了一些SPDY做代理的收發包示意例子 http://www.chromium.org/spdy/spdy-proxy-examples

給fqrouter做一個廣告。fqrouter支持HTTP代理,包括用SSL連接的HTTP代理和SPDY。

原文出處:http://fqrt.4pu.com/post/54756897533/http-spdy

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!