來了來了它來了。要說 Nginx 最早最出名的名頭是什么?相信不少老碼農(nóng)馬上就會(huì)想到,最開始,Nginx 的名頭就是一款性能最高的 反向代理 服務(wù)器?,F(xiàn)在其實(shí)也是,但是這么說的人越來越少了。也就是說,其實(shí) Nginx 最主要的功能就在于反向代理。如果是編程小白,剛剛從事編程相關(guān)工作,沒有配置過負(fù)載集群,可能對這個(gè)概念還是比較陌生的。那么今天咱們就來先說說 代理、反向代理、正向代理 這些概念。
今天的內(nèi)容只有一個(gè)配置指令,只能用在 location 中。
代理是啥意思,之前我們老早就學(xué)過的設(shè)計(jì)模式系列的文章中,就講過一個(gè) 代理模式 的應(yīng)用。說實(shí)話,Nginx 中的代理模塊其實(shí)也是代理模式思維的一個(gè)體現(xiàn)。還記得代理模式的定義嗎?
為其它對象提供一種代理以控制對這個(gè)對象的訪問
從編程語言的角度來說,就是我們要訪問一個(gè)對象,但是可能由于種種原因不能直接訪問它,這就要通過代理來實(shí)現(xiàn)訪問。這個(gè)代理相當(dāng)于是中間的一個(gè)媒介,可以幫我們做一些事情,也可以想像是我們和要使用的對象之間的橋梁。
當(dāng)時(shí)我們也舉了一個(gè)例子,現(xiàn)在也可以用這個(gè)例子,那就是買房子或者買保險(xiǎn),不管是房產(chǎn)中介還是保險(xiǎn)銷售,其實(shí)都可以稱為代理人。他們用他們專業(yè)的知識(shí)幫我們解決問題,甚至買房或者租房都可以不用和房主產(chǎn)生交集,但真正在處理這個(gè)房產(chǎn)證的,其實(shí)還是房主(保險(xiǎn)公司)。
這個(gè)就是代理,說起來很抽象是吧,大家都是碼農(nóng),可以先通過代碼去了解,小伙伴們可以回到 PHP設(shè)計(jì)模式之代理模式https://mp.weixin.qq.com/s/0CvVMuP-_j-0sqRK_4kcZA 這篇文章中再重溫一下哦。
好了,接下來是另外兩個(gè)概念,正向代理與反向代理。
這個(gè)太好解釋了,碼農(nóng)們都喜歡用 Google 吧,因?yàn)橐恍┍娝苤脑?,我們沒法直接訪問,因此,大家也都會(huì)找一些工具方法來訪問一些不可說的網(wǎng)站。在這種情況下,是需要下載一些軟件,或者配置一些東西,讓我們的瀏覽器通過代理,將請求先發(fā)送到一個(gè)外部服務(wù)器,然后由它再轉(zhuǎn)發(fā)請求到目的站點(diǎn),這個(gè)過程就是正向代理。另外,還有一些游戲加速器,以及我們做開發(fā)經(jīng)常會(huì)用到的 Fiddler 以及 Charles 抓包工具,這些也都是正向代理軟件。
簡單來說,正向代理需要我們客戶端去配置東西,或者使用軟件,將請求發(fā)送到指定的地方。
比如說,我們現(xiàn)在通過瀏覽器請求 Google 的網(wǎng)址,代理軟件攔截請求,然后發(fā)送到代理服務(wù)器。代理服務(wù)器接收到后,再將請求發(fā)送到真實(shí)的 Google 的服務(wù)器,處理完成后, Google 返回的響應(yīng)發(fā)送回代理服務(wù)器,代理服務(wù)器再將請求返回給客戶端。
這就是正向代理,另外還有一種透明代理,意思就是這個(gè)代理服務(wù)器像是透明的一樣,只轉(zhuǎn)發(fā),不做別的事情,就和我們正常訪問目標(biāo)網(wǎng)址一樣。
一句話,控制權(quán)在用戶,安裝啟用了代理就走代理,不裝不用不會(huì)走代理。
說完正向的,反向的就好理解了,它和正向代理就是反過來的嘛。不需要我們在客戶端配置或者下載什么軟件,訪問的地址還是原來那個(gè),只不過服務(wù)器上將我們的請求又轉(zhuǎn)發(fā)給了別的服務(wù)器。這個(gè)過程對于我們這些用戶是無感知的。
反向代理不需要在客戶端裝軟件,是由網(wǎng)站來控制用戶看到的內(nèi)容。假如我的網(wǎng)站,www.zyblog.com.cn ,通過反向代理到了 Google ,那么大家訪問我的這個(gè)網(wǎng)址,實(shí)際上打開的會(huì)是 Google 的頁面。但是說實(shí)話,中文搜索分詞百度還是有優(yōu)勢的,哪天一高興,我再將我的網(wǎng)站反向代理到 Baidu ,那么用戶不需要換別的網(wǎng)址,還是繼續(xù)訪問 www.zyblog.com.cn ,看到的就會(huì)是百度的內(nèi)容。
一句話,控制權(quán)在站長,不在用戶,用戶訪問的地址不變,但是會(huì)顯示什么,需要代理到哪里,是后端人員控制的。
再拿實(shí)際的業(yè)務(wù)來說,很多門戶網(wǎng)站其實(shí)是會(huì)把一些頻道外包的,比如說體育、健康頻道,它們的記者和資源其實(shí)也是有限的。一些小的頻道或者不是熱門的頻道就直接讓第三方公司來做。這時(shí)候,有一種方案是直接使用二級域名指到合作公司的服務(wù)器上,而另一種方案,不改變域名,直接將某個(gè)路徑,比如 /sports/ 這樣的反向代理到合作公司指定的地址路徑上就可以了。這是很常見的一種業(yè)務(wù)需求。
而大家做為碼農(nóng),接觸到的更多的業(yè)務(wù)需求,則是用于負(fù)載均衡以及后端程序的代理。負(fù)載均衡的問題我們后面講服務(wù)器組的時(shí)候再說,先來說說后端程序的問題。
因?yàn)槲沂?Java/.NET 出身,工作一年之后才轉(zhuǎn)的 PHP ,所以對前面兩種語言還是略有了解。除了它們之外,現(xiàn)在的 Go 語言以及經(jīng)典的 C/C++ 這些編譯型語言,都是需要編譯之后才能運(yùn)行的。它們并不像 PHP 一樣可以通過 FastCGI 直接運(yùn)行。而且大部分情況下,它們運(yùn)行后都會(huì)直接啟動(dòng)一個(gè)服務(wù),比如說直接運(yùn)行 jar 包或者直接運(yùn)行 go 編譯文件。這時(shí),一般會(huì)啟動(dòng)一個(gè)端口,通過這個(gè)端口就可以訪問服務(wù)。
通常來說,你要是直接在代碼中把端口設(shè)置成 80 ,那么其實(shí)這些程序也可以對外服務(wù)的。但是,這些動(dòng)態(tài)服務(wù)程序普遍對靜態(tài)資源的處理都很差,而且,也不太靈活,比如說我在一臺(tái)服務(wù)器上要啟動(dòng)多個(gè)應(yīng)用,也沒辦法同時(shí)使用 80 端口。因此,大部分情況下,它們也會(huì)借助 Nginx 的反向代理來實(shí)現(xiàn)服務(wù)的部署。比如說,我們用 Java 啟動(dòng)了三個(gè)程序,分別使用 8080、8081、8082 三個(gè)端口,然后在 Nginx 中,直接反向代理,通過不同的 location 路徑,使用統(tǒng)一的一個(gè) 80 端口對外服務(wù)就好了。
Nginx 中,代理模塊是一個(gè)非常重要的模塊,全稱是 ngx_http_proxy_module ,是非常大也非常重要的一個(gè)模塊。其實(shí)在之前講 FastCGI 時(shí)就講過,它也是個(gè)代理模塊,只不過是通過 FastCGI 協(xié)議代理的。而 Proxy 模塊則是通用代理,使用 http 或 https 協(xié)議就可以,在 HTTP 模塊中的 Proxy 是七層負(fù)載的代理協(xié)議,Nginx 現(xiàn)在也有四層負(fù)載的 Stream 模塊,它里面也有一個(gè)代理子模塊,可以做四層負(fù)載均衡。
好了,點(diǎn)題一下,其實(shí)要配置一個(gè)反向代理,使用一個(gè) proxy_pass 配置指令就可以了。就這么簡單,我們先來看它的介紹,后面再進(jìn)行演示。這個(gè)指令只能配置在 location 以及帶條件判斷的 location 和 limit_except 下面,一般來說,就是 location 為主啦。
這個(gè)配置指令的作用就是設(shè)置代理服務(wù)器的協(xié)議和地址以及位置應(yīng)映射到的可選 URI。作為協(xié)議,可以指定“http”或“https”。地址可以指定為域名或 IP 地址,以及可選的端口:
proxy_pass http://localhost:8000/uri/;
或作為在單詞“unix”之后指定并用冒號括起來的 UNIX 域套接字路徑:
proxy_pass http://unix:/tmp/backend.socket:/uri/;
如果一個(gè)域名解析為多個(gè)地址,則所有這些地址都將以循環(huán)方式使用。此外,可以將地址指定為服務(wù)器組。
參數(shù)值可以包含變量。在這種情況下,如果將地址指定為域名,則在所描述的服務(wù)器組中搜索該名稱,如果未找到,則使用解析器確定該名稱。
注意,這里比較重要。請求 URI 被傳遞給服務(wù)器,如下所示:
如果 proxy_pass 指令是用一個(gè) URI 指定的,那么當(dāng)一個(gè)請求被傳遞到服務(wù)器時(shí),與該位置匹配的規(guī)范化請求 URI 的部分將被指令中指定的 URI 替換:
location /name/ {
proxy_pass http://127.0.0.1/remote/;
}
如果 proxy_pass 沒有指定 URI,則請求 URI 以與處理原始請求時(shí)客戶端發(fā)送的相同格式傳遞給服務(wù)器,或者在處理更改的 URI 時(shí)傳遞完整的規(guī)范化請求 URI:在版本 1.1.12 之前,如果指定 proxy_pass 時(shí)沒有 URI,則在某些情況下可能會(huì)傳遞原始請求 URI 而不是更改后的 URI。
location /some/path/ {
proxy_pass http://127.0.0.1;
}
在某些情況下,無法確定要替換的請求 URI 部分:
當(dāng)使用正則表達(dá)式指定位置時(shí),以及在命名位置內(nèi)。在這些情況下,應(yīng)指定不帶 URI 的 proxy_pass。如果指定了 URI ,那么需要帶正則參數(shù)的形式,并且 Get 參數(shù)會(huì)有問題,我們后面的測試會(huì)使用這種形式,需要手動(dòng)傳遞 $args
變量。
當(dāng)使用 rewrite 指令在代理位置內(nèi)更改 URI 時(shí),將使用相同的配置來處理請求(中斷):
location /name/ {
rewrite /name/([^/]+) /users?name=$1 break;
proxy_pass http://127.0.0.1;
}
在這種情況下,指令中指定的 URI 將被忽略,并將完整更改的請求 URI 傳遞給服務(wù)器。
在 proxy_pass 中使用變量時(shí):
location /name/ {
proxy_pass http://127.0.0.1$request_uri;
}
在這種情況下,如果在指令中指定了 URI,它將按原樣傳遞給服務(wù)器,替換原始請求 URI。
WebSocket 代理需要特殊配置,并且從 1.3.13 版本開始支持。
上面的官方文檔的內(nèi)容中,最后關(guān)于 URI 的部分比較重要,需要詳細(xì)看一下。接下來,我們就自己配置一下試試。
首先,咱們配置一個(gè) Server 吧,監(jiān)聽 8027 端口,然后反向代理到本機(jī)的 80 端口,這是最簡單的配置,就是本機(jī)之間不同端口的配置。
server {
listen 8027;
access_log logs/27.log;
root html;
location / {
proxy_pass http://192.168.56.88/;
}
}
或者,這樣寫也可以,反正是本機(jī)代理嘛。
proxy_pass http://127.0.0.1;
直接使用 localhost 也是可以的。你可以訪問一個(gè) PHP 頁面,然后打印 $_SERVER
信息,可以看到 [SERVER_PORT] => 80
這樣的內(nèi)容,PHP 接收到的端口號是 80 ,獲得的是反向代理轉(zhuǎn)發(fā)時(shí)請求的端口號。REMOTE_ADDR
信息也會(huì)跟著我們不同的設(shè)置產(chǎn)生變動(dòng)。
將 proxy_pass 改成一個(gè)外網(wǎng)地址就可以了。
proxy_pass http://www.sina.com.cn/;
現(xiàn)在打開 http://192.168.56.88:8027/ 就會(huì)顯示新浪的頁面。
上面測試了本機(jī)和外網(wǎng),內(nèi)網(wǎng)其它主機(jī)也是可以的,我們可以復(fù)制一臺(tái)虛擬機(jī),然后設(shè)置 IP 為 192.168.56.89 ,部署好 Nginx 應(yīng)用,并且準(zhǔn)備一個(gè) index.html 和一個(gè) 1.php 文件。注意這兩個(gè)文件要與 88 主機(jī)上的內(nèi)容不同,這樣才方便一會(huì)我們的測試。
先來試試直接代理到 89 上。
proxy_pass http://192.168.56.89;
訪問之后可以正常打開 89 主機(jī)上相關(guān)的內(nèi)容。然后我們就來測試下負(fù)載均衡,需要在 http 模塊下配置一個(gè) upstream 。
upstream proxy1 {
server 192.168.56.88:80;
server 192.168.56.89;
}
這個(gè)配置我們后面再學(xué),現(xiàn)在你只要知道,它第一個(gè)參數(shù)是服務(wù)器組的名字,后面花括號內(nèi)部的通過 server 指定多臺(tái)服務(wù)器,形成一個(gè)服務(wù)器組。默認(rèn)的均衡策略是輪詢,也就是請求第一次走 88 的 80 端口,第二次再請求走 89 ,不寫端口號默認(rèn)也是 80 端口。
然后直接 proxy_pass 代理指向這個(gè)服務(wù)器組就好了。
proxy_pass http://proxy1;
現(xiàn)在再次訪問,就會(huì)發(fā)現(xiàn)每次刷新,都會(huì)在 88 和 89 不同的頁面之間切換。這就是最簡單的負(fù)載均衡配置了。如果我們訪問一個(gè) 88 存在,而 89 不存在的頁面,那么就會(huì)一下正常,一下 404 。其它 500 之類的錯(cuò)誤也是類似的,這里也可以解決掉錯(cuò)誤頁面出現(xiàn)的情況,后面學(xué)習(xí)代理模塊的錯(cuò)誤處理時(shí)會(huì)說。
上面的配置是在 / 這個(gè)全局 URI 下配置的。如果是指定 URI ,比如這樣:
location /aaa/ {
proxy_pass http://192.168.56.88;
}
那么實(shí)際請求的后端服務(wù)地址是 http://192.168.56.88/aaa/ ,Proxy 模塊會(huì)自動(dòng)拼接 URI 及請求 GET 參數(shù)。而如果我們在 proxy_pass 中指定了 URI ,則會(huì)直接使用 praxy_pass 的。
location /aaa/ {
proxy_pass http://192.168.56.89/a/;
}
這個(gè)配置最終請求后端的地址是 http://192.168.56.89/a/ 。
而如果是正則,在上面的官方文檔中也說明了,正則,同時(shí)在 proxy_pass 也指定了 URI 的話,因?yàn)闊o法確定需要轉(zhuǎn)發(fā)的內(nèi)容,所以無法正常配置。
location ~ /bbb(.*) {
proxy_pass http://192.168.56.88/aaa/;
}
檢查或者重載配置,會(huì)報(bào)出一個(gè)錯(cuò)誤信息。
nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /etc/nginx/article.http.d/27.conf:37
那么要怎么處理呢?上面文檔也說了,必須要帶正則參數(shù)。比如這樣:
location ~ /bbb(.*) {
proxy_pass http://192.168.56.88/aaa/?$1;
}
就可以正常通過了,如果只是普通正則,沒有 (.*) 內(nèi)容也沒事,只要帶上一個(gè) $1 就行,沒有括號匹配內(nèi)容這個(gè)值就是空的。另外需要注意的是,這種形式代理轉(zhuǎn)發(fā)過來的,不會(huì)帶 GET 參數(shù),因此,可以這么寫:
location ~ /bbb(.*) {
proxy_pass http://192.168.56.88/aaa/?$args&$1;
}
現(xiàn)在嘗試訪問 http://192.168.56.88:8027/bbb/sdf?lksj 這個(gè)路徑,最終請求的代理服務(wù)器的路徑是 http://192.168.56.88/aaa/?lksj&/sdf 這樣的內(nèi)容。
說了半天反向代理,那么 Nginx 能做正向代理嗎?既然都寫在這里了,那肯定是可以的呀。
server {
listen 8027;
access_log logs/27.log;
resolver 114.114.114.114;
location / {
proxy_pass $scheme://$host$request_uri;
}
}
首先需要配置一個(gè) resolver 也就是 DNS 解析服務(wù)器的地址,咱們就通用的 114 就好了。這個(gè)指令之前在基礎(chǔ)的 HTTP 核心模塊的學(xué)習(xí)中就學(xué)過,當(dāng)時(shí)也說過它是在做正向代理的時(shí)候需要用到的。原因嘛,因?yàn)檎虼?,我們接收到的請求都是域名,需要?DNS 解析到真實(shí)的 IP ,不像反向代理,是通過 Nginx 發(fā)送請求,這里請求是直接轉(zhuǎn)發(fā)的,沒有調(diào)用其它發(fā)送請求的函數(shù)。因此,需要有這個(gè)配置,如果不配置它的話,訪問會(huì)報(bào)出 502 錯(cuò)誤,日志中會(huì)有如下記錄。
2022/09/06 10:58:21 [error] 1615#0: *130 no resolver defined to resolve www.baidu.com, client: 192.168.56.1, server: , request: "GET http://www.baidu.com/ HTTP/1.1", host: "www.baidu.com"
然后就是 proxy_pass 的配置。這里我們?nèi)慷际鞘褂米兞?,因?yàn)槲覀円L問的是用戶在客戶端輸入的網(wǎng)址,這些內(nèi)容需要通過變量獲取到。就像上面在概念中說的,正向代理要訪問誰,是用戶決定的,我們做代理時(shí),要拿到用戶請求的網(wǎng)址信息。
接下來,通過瀏覽器進(jìn)行代理配置,所有瀏覽器都可以配置,這里我就直接拿 Postman 配置,在設(shè)置中打開下面的代理并填上代理服務(wù)器的 IP 地址和端口。
試著用配好代理的這個(gè)瀏覽器訪問一下百度吧,正常打開是不是?但是,查看 88 服務(wù)器上 Nginx 的 access_log 日志,你會(huì)發(fā)現(xiàn)這是通過我們的 Nginx 代理請求的。
192.168.56.1 - - [15/Sep/2022:11:09:18 +0800] "GET http://www.baidu.com/ HTTP/1.1" 200 89726 "-" "PostmanRuntime/7.29.2" "-"
使用 WireShark 抓包也能看出來。
今天的內(nèi)容很多都是概念,包括代理、正向代理、反向代理,還接觸到了一點(diǎn)負(fù)載均衡的概念。也說明了即使不用負(fù)載均衡,很多情況下我們也需要代理的原因。而真正學(xué)習(xí)到的配置指令就只有一個(gè) proxy_pass 。是的,就這么簡單,一個(gè)代理服務(wù)器就配置完成了。不過僅有這個(gè)還不夠,整個(gè)代理模塊還有很多其它的配置選項(xiàng),我們在后面還會(huì)一一學(xué)習(xí)。不過大家不用太擔(dān)心,因?yàn)槲覀円呀?jīng)學(xué)習(xí)過 FastCGI 了,整個(gè) Nginx 只要是帶 proxy 這個(gè)單詞的,不管是 FastCGI、SCGI還是UWSGI ,或是我們現(xiàn)在要學(xué)的普通代理,大部分配置指令都是相通的。因此,后面的學(xué)習(xí)還是比較輕松愉快的。不過還是強(qiáng)調(diào)一下,SCGI、UWSGI 這兩個(gè)模塊我們就不單獨(dú)講了哦,一是太像了,二是咱們 PHP 碼農(nóng)大部分情況下用不到,主攻 FastCGI 和 Proxy 就可以啦。
參考文檔:
http://nginx.org/en/docs/http/ngx_http_proxy_module.html