1. 程式人生 > 實用技巧 >理解http反向代理

理解http反向代理

要理解什麼是 反向代理(reverse proxy) , 自然你得先知道什麼是 正向代理(forward proxy).

另外需要說的是, 一般提到反向代理, 通常是指 http 反向代理, 但反向代理的範圍可以更大, 比如 tcp 反向代理, 在這裡, 不打算討論 tcp 之類的反向代理, 當文中說到反向代理時, 指的就是 http 反向代理.

正向代理通常直接稱為 代理(proxy), 無需強調它是正向的, 在 http 協議中, 代理即指正向代理.

直接訪問
而要談論什麼是正向代理, 則需要先討論"直接訪問"的形式.

也就是沒有任何代理的模式.

事實上, 直接訪問對於很多的小網站來說是最常見的方式. 直接訪問用我們日常購物來比喻的話就是類似於"廠家直銷", 你直接向生產廠家下單, 沒有經過任何的中間商.

從系統的角度看, "直接訪問"就是瀏覽器的請求直接到了最終生成網頁的伺服器, 中間沒有經過任何的 http 代理伺服器. 那麼代理或者說更囉嗦點的"正向代理"又是什麼情況呢?

正向代理(forward proxy)
還是用購物來比喻的話, 你從商店裡而不是直接從廠家購買一個商品就類似於代理的模式.

比如, 你從商店裡買了一盒方便麵, 顯然, 你很清楚, 商店本身是不生產方便麵的, 它不過是個"中間商"而已, 轉了一手, 順便賺點你的錢, 店裡的方便麵也是它從廠商那邊進貨來的. 當然, 有些店雁過拔毛拔得有點狠, 那就變成讓人厭惡的"中奸商"了.

那麼, 對於瀏覽器的請求處理來說, 一個代理, 更確切的說一臺代理伺服器, 扮演的角色也是類似的, 它也就是一個請求的中間商而已.

一個正向代理伺服器並沒有直接響應請求的能力, 就像商店不生產方便麵一樣, 它不過是把請求轉發到最終的網頁伺服器上, 再把後者的響應再轉發給請求者, 也就是瀏覽器, 如下圖所示:

forward proxy demo

但這裡還有個問題, 你知道你家周圍有哪些小商店, 你想買些東西的時候直接去找這些"代理商"即可, 問題是瀏覽器怎麼知道代理伺服器在哪?

最終的伺服器瀏覽器是知道的, 比如你輸入我的域名 "xiaogd.net", 通過 DNS 系統瀏覽器就能查到對應的 ip 地址是 118.89.55.54, 可瀏覽器怎麼知道哪裡有代理伺服器, 以及請求是否要經過代理伺服器呢?

答案就是你要主動地告訴瀏覽器, 這個過程通常稱為"配置代理伺服器".

後面將看到, 這是正向代理與反向代理的一個重大的區別.

這是在 IE 瀏覽器上配置代理伺服器的一個示意圖:

IE proxy sample

為什麼要啟用代理?
自然, 有人可能會問, 直接訪問不香嗎? 為何還要這麼麻煩經過代理伺服器去轉手呢? 原因可以有以下這麼一些.

一是出於安全審計及控制方面的一些考慮. 在一些組織內, web 相關的埠如 80 及 443 是被封禁的, 你在裡面直接是根本上不去網的, 這時你要想上網, 就只能配置組織為你指定的一臺內網的代理伺服器了.

當然了, 代理伺服器本身則沒有被限制, 它是可以訪問外部的網路的.

如此一來, 你的所有上網請求都要經過代理伺服器, 而這個代理是由組織控制的, 就可以對請求進行審計了:

比如發現你往外面的一個網站上傳組織內部的保密資料, 就出手阻止你;
又或發現你訪問了一個不安全的網站, 可能會導致你的電腦中毒, 於是出手攔截了;
又或者是發現你在訪問與工作無關的娛樂網站, 於是就給你阻斷了~~(為了你的績效及 KPI 達標, 組織也是操碎了心呀!)
還有一些原因則是出於加速或節省頻寬等的考慮. 因為有的代理伺服器它不僅能轉發, 還能對網頁以及其它一些資源進行快取.

比如我以前在學校唸書時, 就曾被學校告知在宿舍上網可以配置代理伺服器, 我猜測原因可能是學校整體對外的頻寬是有限的.

舉個例子, 假如現在很多同學都要去上 qq.com 的主頁, 那麼第一個同學請求時, 代理伺服器就可以把主頁快取起來一段時間, 碰到後面還要同學想訪問這個主頁, 就無需再去請求了, 代理伺服器直接返回快取的請求.

自然, 快取也會有一個失效期的, 不會一直快取下去, 否則內容就得不到更新了.

至於多長時間更新快取, 怎麼更新等這些就屬於具體的快取策略問題了.

當然了, 現在很多網頁主頁都有個性化推薦, 又或者直接就是要登入的, 那通常就無法快取了, 所以現在配置代理伺服器的行為現在也不那麼時興了, 當然也可能現在頻寬也提高了, 另外很多人也不懂也不想去知道怎麼配置這個代理伺服器. 但另一方面, 很多靜態資源還是可以快取的, 比如圖片呀, js, css 之類的檔案等等, 所以用好了代理伺服器依然還是可以發揮作用的.

最後再說一個原因, 由於國家已經決定, 有些國外的技術網站是訪問不了的, 而我們又想上去查閱資料解決手中的 bug, 這時就需要一些科學的手段, 直接訪問不行, 就必須通過代理來"曲徑通幽"了.

嚴格來講, 這裡面很多的代理就是更廣義的代理了, 而不是狹義上的 http 代理, 但原理是類似的, 也算是代理模式的一種體現. 經我們配置或一些智慧外掛的輔助, 瀏覽器知道直接對某些網站的請求會泥牛入海, 就像掉入了黑洞, 這時就要讓這些請求"走代理"以繞過防火牆的限制了. 對於簡單的代理配置來說, 就是配置一臺代理伺服器地址即可, 但這樣有個問題, 那就是所有的請求都會走代理, 有一些高階的代理外掛還允許你配置具體的規則, 即你可以配置哪些地址要走代理, 哪些又不走代理, 通常還會帶一些預定義的規則, 各種白名單, 黑名單, 你還可以自己新增新的規則.

總之呢, 代理就是這麼個中間的角色, 通過它間接訪問到了所需資源, 而瀏覽器也是知道這麼個角色的存在的, 因為你需要主動為瀏覽器去配置並啟用. 那麼這就是代理, 又或者說"正向代理".

反向代理(reverse proxy)
明白了直接訪問, 明白了所謂的正向代理, 下面就可以來說說反向代理是怎麼回事了.

反向代理與正向代理的一個很大區別就是, 它不需要客戶端(瀏覽器)去做什麼配置, 並沒有什麼配置代理伺服器的操作.

如果說正向代理是主動配置, 主動走代理, 那麼反向代理則是"被代理", 從這點上看, 反向代理有時又稱為"透明代理", 也即是瀏覽器都不知道自己被代理了, 瀏覽器以為發給它響應的就是最終的網頁伺服器, 其實不過是個"代理".

還是舉購物的例子來比喻. 有時你在網上購物會看到有商家聲稱自己就是廠家, 東西都很便宜, 屬於廠家直銷, 於是你下單了. 過段時間, 你又發現有另一家店聲稱自己才是真正的廠家直銷, 然後你仔細看了兩家店鋪的資訊, 才發現前一個商家是假的, 它不是真的廠家.

但為啥這個假的廠家直銷它還是這麼便宜呢? 以至於價格跟真的廠家直銷的沒啥區別. 原因可能則是店家直接就是坐落在廠家旁邊, 然後他可能與廠家有那麼點關係, 認識裡面一些人之類的, 這讓他能以很便宜的價格從廠家拿到貨, 又因為離得近, 幾乎沒有任何物流成本, 從某種層面看, 它聲稱廠家直銷也不算怎麼騙人. 當然嚴格來說, 它屬於偽廠家直銷, 他依然還是個代理商

它聲稱是李逵, 其實它是李鬼.

用一個圖對比一下這兩種情形:

direct sale vs reverse proxy

那麼這樣的一種模式就有點 反向代理 的味道了, 你以為自己買到了直銷, 其實你還是"被代理"了, 還是經過了中間商.

只是這個中間商對你來說不是那麼明顯, 甚至說對你是透明的, 把你蒙在了鼓裡.

雖然都是"代理", 這跟線下店面購買還是很不同的, 線上下你去商店買時, 你很清楚自己經過了代理的中間商, 也即是商店本身, 但在遠端線上這種聲稱自己是廠家直銷的情形, 有時你還真不好判斷自己是不是被代理了.

那麼 http 的反向代理其實也是這樣一個道理. 比如你訪問我的網站 https://xiaogd.net, 然後你看下主頁的請求裡的伺服器資訊, 它告訴你響應這個主頁請求的是一臺 Nginx server, 如下圖所示:

http response server xiaogd net

問題是 Nginx 是最終生成這個網頁的 server 嗎? 其實不是的! 如果你瞭解 Nginx, 就會知道它通常只是一個靜態資源伺服器, 而我的網站主頁是一個動態生成的內容, 其實你要是認真看過我網站底部的一個宣告, 如下圖所示:

xiaogd.net wordpress manifest

就會明白這個主頁其實是 php 的一個叫 wordpress 的建站應用去生成的. 在我的雲主機的內部, Nginx 其實是將主頁的請求轉發給一個所謂的 php-fpm 閘道器

這個 php-fpm 閘道器基本可以看作是個 php 的 web 伺服器, 不過嚴格來說它用的協議不是 http, 而是一種內部簡化的 fastcgi 協議.

如果你要較真的話, 這可以算是 反向代理 模式, 但整體不全是 http 反向代理, 但對外而言則確實是.

從它那裡取得最終響應的內容, 並再次轉發給瀏覽器, 整個情形見如下的示意圖:

nginx reverse proxy demo

這是內部配置的一個情況:

location ~ .php$ {
root /ftp/wwwroot;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
請求被轉發到內部一個在 9000 埠上監聽的 php 應用伺服器.

從外部瀏覽器的角度看, 請求直接發給了 Nginx server, 響應也從 Nginx server 裡回來了, 中間沒有任何的(正向)代理. 至於說你內部請求又被怎麼轉發了, 顯然瀏覽器是無從知道也不需要去知道的.

站在整個體系設計者的角度去看, 當然但很多請求 Nginx 其實是沒有能力去響應的, 它只不過在內部把它代理給了另一個內部的 php 應用伺服器, 內部的 php 應用伺服器才是最終的響應生成者.

在整個體系裡面, Nginx 的角色就是一個"反向代理"伺服器, 瀏覽器被代理了, 但它無從知道自己是否被代理了, 這一切對它而言是透明的, 反正它自己是沒有主動走(正向)代理的.

當然了, 你現在知道了我內部的配置, 如果直接訪問 http://xiaogd.net:9000, 那就是真正的"直接訪問"了, 那就繞過了 Nginx.

不過需要說明的一點時, 直接訪問是訪問不通的, 因為 9000 埠並沒有對外放開. 但是在內部是可以訪問到的, 比如這樣嘗試用 wget 去訪問:

wget localhost:9000
這樣就是真正的"直接訪問"了, 沒有任何的代理, 既沒有正向代理, 也沒有反向代理.

需要說明的一點是, 用 wget 這樣去獲取響應還是會報錯, 因為 wget 使用的是 http 協議, php 的 cgi 閘道器實際使用的是 fastcgi 協議, 是一個比 http 更為簡化的協議, 作為內部通訊更加高效, 不過 wget 不支援這個協議, 但 Nginx 能理解這個協議, 整個過程是這樣的:

browser -- [http] --> Nginx -- [fastcgi] --> php-fpm

嚴格來說, 不完全是 http 代理, 內部的反向代理實際用的是 fastcgi 閘道器協議, 不過這個原理還是一樣的, 如果內部用一個比如 tomcat 來響應, 那麼全程就都可以是 http 協議.

browser -- [http] --> Nginx -- [http] --> tomcat

而如果在內部發請求 80, 比如 wget localhost 那就還是被反向代理, 請求先到在 80 埠監聽的 Nginx, Nginx 再轉給 php-fpm.

另: 關於埠及預設埠相關知識, 可以參考這篇深入理解埠.

為什麼要使用反向代理?
那麼到了這一步我們又面臨一個新的問題, 那就是為啥要整這個反向代理呢? 類似於碰到正向代理時的詰問那樣, 直接訪問不香嗎? 為啥還要走這個反向代理? 關於正向代理前面已經解釋了一些原因, 而反向代理的出現, 正像這個世界上沒有無緣無故的愛與恨一樣, 自然也有它存在的原因.

一個很直接的原因就是利用反向代理可以作為內部 負載均衡(load balance) 的手段.

舉個例子來說, 假如我現在開發了一個 java web 的應用作為我的網站後臺, 我直接部署它到 tomcat 伺服器上, 讓 tomcat 監聽 80 埠, 直接對外服務. 一開始訪問量也不大, 所以這樣也是沒有問題的, 如下圖所示:

no proxy, tomcat only

注: 因為 http 協議的預設埠就是 80, 所以使用者輸入地址時可以省略這個埠號, 也即只需這樣: http://xiaogd.net, 而不是繁瑣的像這樣: http://xiaogd.net:80, 關於預設埠的話題, 還是可以參考前面所提的 深入理解埠.

但過一段時間之後, 訪問量可能上來了, 一個 tomcat 程序處理不過來, 那怎麼辦呢? 於是我打算再起一個新的 tomcat 程序, 但這樣就面臨一個問題, 只有一個 80 埠, 它已經被第一個 tomcat 程序佔用, 如果還要再起另外一個, 則只能選用其它的埠, 比如 8080.

當使用另外一個埠時, 確實可以啟動兩個 tomcat 的程序, 但使用者想訪問到第二個 tomcat 程序的服務, 卻要這樣去訪問: http://xiaogd.net:8080. 顯然, 這樣的方案是有問題的, 使用者根本不知道 8080 埠上服務的存在, 就算你有辦法告訴使用者, 使用者也可能不太理解, 使用者同時也很怕麻煩的, 為啥要我輸入一個冒號加 8080 呢?

此外, 就算有些使用者願意如你所說轉向訪問 8080 埠, 你還是不能很好的控制把訪問量平均地分配在兩個 tomcat 上, 畢竟這是使用者隨機決定的, 也許很多使用者又突然湧過來了 8080 埠的應用上, 造成了這邊的擁擠.

又或者只有很少的使用者願意聽從你的勸告轉到新的 8080 埠上, 訪問還是集中在舊的 80 埠上的, 這樣舊的應用上響應還是很緩慢, 而新的應用卻因為沒幾個使用者訪問而顯得空閒, 沒有得到充分的使用.

那麼, 在這種情況下, 反向代理的好處就體現出來了, 具體的操作是這樣的, 讓 Nginx 作為一個前置的反向代理, 監聽在 80 埠上; 而第一個 tomcat 則躲到幕後, 同時它也不再監聽 80 埠(需要讓給 Nginx), 而改為監聽一個其它沒有被使用的埠, 比如 8081, 然後讓 Nginx 轉發請求給它處理.

當然了, 如果只有一個 tomcat, 配置大概是這樣的:

location / {
proxy_pass http://127.0.0.1:8080;
}
請求處理的流程是這樣的:

請求: browser -- [http] --> Nginx -- [http] --> tomcat

響應: browser <-- [http] -- Nginx <-- [http] -- tomcat

自然, 這種情形下反向代理似乎不太必要, 還加多了一個環節, 響應速度反而慢了.

但如果有兩個 tomcat, 情況就不一樣了, 此時就可以在 Nginx 這個反向代理的層面, 啟用負載均衡的策略, 大概的配置如下:

http {
upstream myapp1 {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

server {
    listen 80;

    location / {
        proxy_pass http://myapp1;
    }
}

}
此時, 如果同時湧入了很多請求, Nginx 會把一半的請求交給 8080 埠上的 tomcat, 另一半的請求交給 8081 埠上的 tomcat, 如下圖所示:

nginx tomcat load balance

對外來看, 所有請求還是 Nginx 來處理, 使用者不需要去做選擇, 也不需要知道什麼 8080, 8081 埠上應用的存在, 他們還是繼續訪問原來的網址 xiaogd.net 即可, 無需做任何改變.