NGINX雜談——proxy和rewrite的區別


概述

使用NGINX服務器進行HTTP報文的處理和轉發時,有一些容易混淆的概念。比如像正向代理和反向代理、root和alias、proxy和rewrite。

這篇博客主要想記錄一下自己對proxy和rewrite的一些理解。(這里不去考慮rewrite的最后一項參數和區分301、302,建議想看完整細節的看這篇博客,更多關於NGINX的知識可以看官方文檔)。

Little NGINX

我用C++實現一個閹割版的NGINX,以此來感受proxy和rewrite兩個過程中,HTTP報文變化的區別。具體的項目可以看這個代碼倉庫,我不是依據NGINX的源碼進行的代碼編寫,所以具體的實現上會有所差異,實際使用的功能也沒有NGINX那么豐富。還有就是代碼雖然套了C++的殼,但核心部分是基於Linux C編寫的,所以只支持在Linux系統或MacOS等類Unix系統上使用。

  1. 克隆項目

    git clone https://gitee.com/xuanyusan/little_nginx.git
    cd little_nginx
    
  2. 編譯項目

    g++ little_nginx.cpp -o little_nginx
    
  3. 開啟little_nginx

    ./little_nginx
    

    打印出已配置的服務則運行成功,此時可以在瀏覽器輸入localhost:8880訪問服務。

  4. 開啟nodejs koa服務

    cd servers/node_koa/
    npm install
    node index.js
    

    開啟nodejs服務后,在瀏覽器輸入localhost:8880/koa/可以訪問到nodejs的服務。

    在瀏覽器輸入localhost:8880/koa/path1則可以體驗proxy處理重定向的整個流程,little_nginx的stdout也會有對應輸出,幫助理解。

proxy和rewrite的區別

proxy和rewrite的區別可以通過下面的兩個圖進行簡單理解。對圖中WEB服務的理解應該包括NGINX提供的WEB服務。

Created with Raphaël 2.2.0 rewrite 客戶端 客戶端 NGINX NGINX WEB服務 WEB服務 request 根據rewrite參數構建狀態為302的響應報文 根據響應報文的Location參數進行重定向生成新的request response
Created with Raphaël 2.2.0 proxy 客戶端 客戶端 NGINX NGINX WEB服務 WEB服務 request 根據proxy_pass的規則 修改客戶端的request response 假設發生了重定向 則根據proxy_redirect的規則 修改response的Location參數

rewrite

從圖中可以看出,rewrite必定會發生重定向,因為它會給客戶端返回一個302的響應報文,而其中的Location參數就是由一開始請求的path根據rewrite參數處理得到的。

假設nginx配置為:

rewrite ^/(.*) https://www.baidu.com/$1 redirect;

我們通過curl去看HTTP報文的信息則是這樣的:

$ curl -v http://localhost/test/
*   Trying ::1:80...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /test/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Moved Temporarily
< Server: nginx/1.17.3
< Date: Wed, 15 Sep 2021 10:02:04 GMT
< Content-Type: text/html
< Content-Length: 145
< Connection: keep-alive
< Location: https://www.baidu.com/test/
< 
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx/1.17.3</center>
</body>
</html>
* Connection #0 to host localhost left intact

可以看到相應頭的Location參數就是由請求報文的/test/保留了test/部分並在前面接上https://www.baidu.com/得到的。

而這就必須保證rewrite第二個參數的域名或者ip以及端口必須是客戶端可以訪問的,不行的話,就不能用rewrite。所以rewrite更常用於域名變更和網站搬遷。

proxy

而proxy是不一定會發生重定向的,即使發生重定向,也是由具體的服務決定的,而不是NGINX這個中間代理可以越俎代庖的。NGINX的proxy有兩個關鍵配置,一個是proxy_pass,一個是proxy_redirect,分別對應請求報文和響應報文的處理。

proxy_pass就是修改請求的協議、路徑和host,然后發給具體的服務端。proxy_redirect就是在具體服務端發回來一個302響應報文的時候,根據proxy_redirect參數的規則去修改Location。

假設我們現在的機器上,在本地回環的8881端口跑着一個nodejs的服務,然后我們通過機器某個局域網的ip地址去訪問80端口的/test

nodejs服務入口文件如下:

const Koa = require('koa');
const Router = require('koa-router');
const Koa_Logger = require("koa-logger");
const koaBody = require('koa-body')

const app = new Koa();
const router = new Router();
const logger = new Koa_Logger();

router.get("/baseurl", async (ctx)=>{
    ctx.body = "<h1>Node Koa</h1>"
});

router.get("/baseurl/path1", async (ctx)=>{
    ctx.redirect("/baseurl/path3");
});

router.get("/baseurl/path3", async (ctx)=>{
    ctx.body = "<h1>Node Koa Path3</h1>"
});

app.use(logger);
app.use(koaBody());
app.use(router.routes());

app.listen(8881, () => {
    console.log('應用已經啟動,http://localhost:8881');
});

如果我們只配置了proxy_pass:

proxy_pass http://localhost:8881/baseurl;

訪問非重定向的頁面是正常的:

$ curl -v http://localhost/test/
*   Trying ::1:80...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /test/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.17.3
< Date: Wed, 15 Sep 2021 10:28:57 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 17
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
<h1>Node Koa</h1>

因為這個時候我們的HTTP請求的協議、ip和端口被修改為了proxy_pass參數里的httplocalhost8881,並且發送給了localhost的8881端口。這時,監聽8881端口的服務就可以接收報文並返回結果。

所以proxy_pass的參數就不要求客戶端可以訪問,只要代理服務器可以訪問就可以了。客戶甚至不知道他們訪問的服務到底是誰提供的,這也是反向代理的一大特點。

但是,如果服務端發生了重定向,結果則如下:

$ curl -v http://localhost/test/path1
*   Trying ::1:80...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /test/path1 HTTP/1.1
> Host: localhost
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx/1.17.3
< Date: Wed, 15 Sep 2021 10:29:54 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 59
< Connection: keep-alive
< Location: /baseurl/path3
< 
* Connection #0 to host localhost left intact
Redirecting to <a href="/baseurl/path3">/baseurl/path3</a>.

可以看到它重定向的結果不是我們預期中的http://localhost/test/path3,而是http://localhost/baseurl/path3。而想要讓它重定向到http://localhost/test/path3,我們就不能直接把nodejs扔給我們的響應頭原封不動地扔給客戶端,而是要用proxy_redirect來指定相應頭Location參數的修改規則。比如:

proxy_redirect /baseurl /test;
$ curl -v http://localhost/test/path1
*   Trying ::1:80...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /test/path1 HTTP/1.1
> Host: localhost
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx/1.17.3
< Date: Wed, 15 Sep 2021 10:37:30 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 59
< Location: http://localhost/test/path3
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
Redirecting to <a href="/baseurl/path3">/baseurl/path3</a>.

這里可以明顯看到Location的改變,當然proxy不會去修改報文的body部分,所以body部分還是/baseurl/path3。這里萬一因為網絡原因,重定向沒有完成,顯示了這個頁面,用戶又去點擊,就會出現錯誤。NGINX也不是萬能的,還是需要我們自己去理解代理背后的邏輯,才能有效應對各種問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM