nginx代理屢次302(nginx Follow 302)

用proxy_intercept_errors和recursive_error_pages代理屢次302

302是HTTP協議中的一個常常被使用狀態碼,是多種重定向方式的一種,其語義常常被解釋爲「Moved Temporarily」。這裏順帶提一下,現實中用到的302多爲誤用(與303,307混用),在HTTP/1.1中,它的語義爲「Found」.html

302有時候很明顯,有時候又比較隱蔽。最簡單的狀況,是當咱們在瀏覽器中輸入一個網址A,而後瀏覽器地址欄會自動跳到B,進而打開一個網頁,這種狀況就極可能是302。nginx

比較隱蔽的狀況常常發生在嵌入到網頁的播放器中。例如,當你打開一個優酷視頻播放頁面時,抓包觀察一下就會常常發現302的影子。但因爲這些url並非直接在瀏覽器中打開的,因此在瀏覽器的地址欄看不到變化,固然,若是將這些具體的url特地挑出來複製到瀏覽器地址欄裏,仍是能夠觀察到的。後端

上一段提到了優酷。其實如今多數在線視頻網站都會用到302,緣由很簡單,視頻網站流量通常較大,都會用到CDN,區別只在因而用自建CDN仍是商業CDN。而因爲302的重定向語義(再重複一遍,302的語義普遍的被誤用,在使用302的時候,咱們極可能應該使用303或307,但後面都再也不糾結這一點),能夠與CDN中的調度很好的結合起來。瀏覽器

咱們來看一個例子,打開一個網易視頻播放頁面,抓一下包,找到302狀態的那個url。例如:服務器

http://flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4

咱們把它複製到瀏覽器地址欄中,會發現地址欄迅速的變爲了另一個url,這個Url是不定的,有可能爲:curl

http://14.18.140.83/f6c00af500000000-1408987545-236096587/data6/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4

用curl工具會更清楚的看到整個過程:ide

curl -I "http://flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4" -L
HTTP/1.1 302 Moved Temporarily 
Server: nginx 
Date: Mon, 25 Aug 2014 14:49:43 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
NG: CCN-SW-1-5L2 
X-Mod-Name: GSLB/3.1.0 
Location: http://119.134.254.9/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 

HTTP/1.1 302 Moved Temporarily 
Server: nginx 
Date: Mon, 25 Aug 2014 14:49:41 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
X-Mod-Name: Mvod-Server/4.3.3 
Location: http://119.134.254.7/cc89fdac00000000-1408983581-2095617481/data4/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 
NG: CHN-SW-1-3Y1 

HTTP/1.1 200 OK 
Server: nginx 
Date: Mon, 25 Aug 2014 14:49:41 GMT 
Content-Type: video/mp4 
Content-Length: 3706468 
Last-Modified: Mon, 25 Aug 2014 00:23:50 GMT 
Connection: keep-alive 
Cache-Control: no-cache 
ETag: "53fa8216-388e64" 
NG: CHN-SW-1-3g6 
X-Mod-Name: Mvod-Server/4.3.3 
Accept-Ranges: bytes

能夠看到,這中間經歷了兩次302。工具

先暫時將這個例子放在一邊,再來講說另外一個重要的術語:proxy.咱們一般會戲稱,某些領導是302類型的,某些領導是proxy類型的。302類型的領導,一件事情通過他的手,會迅速的轉給他人,而proxy類型的領導則會參與到事情中來,甚至把事情所有作完。post

回到上面的例子,若是訪問一個url中途會有多個302,那若是須要用Nginx設計一個proxy,來隱藏掉中間全部的這些302,該怎麼作呢?測試

1.原始Proxy

咱們知道,Nginx自己就是一個優秀的代理服務器。所以,首先咱們來架設一個Nginx正向代理,服務器IP爲192.168.109.128(個人一個測試虛擬機)。

初始配置簡化以下:

server {
        listen 80;
        location / {
                rewrite_by_lua '
                        ngx.exec("/proxy-to" .. ngx.var.request_uri)
                ';
        }

        location ~ /proxy-to/([^/]+)(.*) {
                proxy_pass http://$1$2$is_args$query_string;

        }
}

實現的功能是,當使用

http://192.168.109.128/xxxxxx

訪問該代理時,會proxy到xxxxxx所表明的真實服務器。

測試結果以下:

curl -I "http://192.168.109.128/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4" -L
HTTP/1.1 302 Moved Temporarily 
Server: nginx/1.4.6 
Date: Mon, 25 Aug 2014 14:50:54 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
NG: CCN-SW-1-5L2 
X-Mod-Name: GSLB/3.1.0 
Location: http://183.61.140.24/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 

HTTP/1.1 302 Moved Temporarily 
Server: nginx 
Date: Mon, 25 Aug 2014 14:50:55 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
X-Mod-Name: Mvod-Server/4.3.3 
Location: http://183.61.140.20/540966e500000000-1408983655-236096587/data1/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 
NG: CHN-ZJ-4-3M4 

HTTP/1.1 200 OK 
Server: nginx 
Date: Mon, 25 Aug 2014 14:50:55 GMT 
Content-Type: video/mp4 
Content-Length: 3706468 
Last-Modified: Mon, 25 Aug 2014 00:31:03 GMT 
Connection: keep-alive 
Cache-Control: no-cache 
ETag: "53fa83c7-388e64" 
NG: CHN-ZJ-4-3M4 
X-Mod-Name: Mvod-Server/4.3.3 
Accept-Ranges: bytes

可見,雖然使用proxy,但過程與原始訪問沒有什麼區別。訪問過程爲,當訪問

http://192.168.109.128/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4

時,Nginx會將該請求proxy到

http://flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4

然後者立刻就會返回一個302,因此Nginx做爲proxy,將該302傳回到客戶端,客戶端從新發起請求,進而重複以前的屢次302.這裏說明一個問題,一旦Nginx的proxy的後端返回302後,客戶端即與Nginx這個proxy脫離關係了,Nginx沒法起到完整的代理的做用。

2. 第1次修改

將配置文件修改成:

server {
        listen 80;
        location / {
                rewrite_by_lua '
                        ngx.exec("/proxy-to" .. ngx.var.request_uri)
                ';
        }

        location ~ /proxy-to/([^/]+)(.*) {
                proxy_pass http://$1$2$is_args$query_string;
                error_page 302 = @error_page_302;

        }
        location @error_page_302 {
                rewrite_by_lua '
                        local _, _, upstream_http_location = string.find(ngx.var.upstream_http_location, "^http:/(.*)$")
                        ngx.header["zzzz"] = "/proxy-to" .. upstream_http_location
                        ngx.exec("/proxy-to" .. upstream_http_location);
                ';

        }
}

與上面的區別在於,使用了一個error_page,目的是當發現proxy的後端返回302時,則用這個302的目的location繼續proxy,而不是直接返回給客戶端。而且這個邏輯裏面包含着遞歸的意思,一路跟蹤302,直到最終返回200的那個地址。測試結果以下:

curl -I "http://192.168.109.128/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4" -L
HTTP/1.1 302 Moved Temporarily 
Server: nginx/1.4.6 
Date: Mon, 25 Aug 2014 15:01:17 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
NG: CCN-SW-1-5L2 
X-Mod-Name: GSLB/3.1.0 
Location: http://183.61.140.24/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 

HTTP/1.1 302 Moved Temporarily 
Server: nginx 
Date: Mon, 25 Aug 2014 15:01:17 GMT 
Content-Type: text/html 
Content-Length: 154 
Connection: keep-alive 
X-Mod-Name: Mvod-Server/4.3.3 
Location: http://183.61.140.20/a90a952900000000-1408984277-236096587/data1/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 
NG: CHN-ZJ-4-3M4 

HTTP/1.1 200 OK 
Server: nginx 
Date: Mon, 25 Aug 2014 15:01:17 GMT 
Content-Type: video/mp4 
Content-Length: 3706468 
Last-Modified: Mon, 25 Aug 2014 00:31:03 GMT 
Connection: keep-alive 
Cache-Control: no-cache 
ETag: "53fa83c7-388e64" 
NG: CHN-ZJ-4-3M4 
X-Mod-Name: Mvod-Server/4.3.3 
Accept-Ranges: bytes

可見,本次修改仍然沒有成功!
爲何呢?分析一下,咱們在@error_page_302這個location裏已經加了一個頭部打印語句,但是在測試中,該頭部並無打出來,可見流程並無進入到@error_page_302這個location。

緣由在於

error_page 302 = @error_page_302;

error_page默認是本次處理的返回碼。做爲proxy,本次處理,只要轉發上游服務器的響應成功,應該狀態碼都是200.即,咱們真正須要檢查的,是proxy的後端服務器返回的狀態碼,而不是proxy自己返回的狀態碼。查一下Nginx的wiki,proxy_intercept_errors指令正是幹這個的:

Syntax: proxy_intercept_errors on | off;
Default:    
proxy_intercept_errors off;
Context:    http, server, location
Determines whether proxied responses with codes greater than or equal to 300 should be passed to a client or be redirected to nginx for processing with the error_page directive.

3. 第二次修改

server {
        listen 80;
        proxy_intercept_errors on;
        location / {
                rewrite_by_lua '
                        ngx.exec("/proxy-to" .. ngx.var.request_uri)
                ';
        }
        location ~ /proxy-to/([^/]+)(.*) {
                proxy_pass http://$1$2$is_args$query_string;
                error_page 302 = @error_page_302;

        }
        location @error_page_302 {
                rewrite_by_lua '
                        local _, _, upstream_http_location = string.find(ngx.var.upstream_http_location, "^http:/(.*)$")
                        ngx.header["zzzz"] = "/proxy-to" .. upstream_http_location
                        ngx.exec("/proxy-to" .. upstream_http_location);
                ';
        }
}

與上一次修改相比,區別僅僅在於增長了一個proxy_intercept_errors指令。測試結果以下:

curl -I "http://192.168.109.128/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4" -L 
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.4.6
Date: Mon, 25 Aug 2014 15:05:54 GMT
Content-Type: text/html
Content-Length: 160
Connection: keep-alive
zzzz: /proxy-to/183.61.140.24/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4

此次更神奇了,直接返回一個302狀態完事,也不繼續跳轉了。
問題出在,雖然第一次302,請求成功的進入到@error_page_302,但後續的error_page指令卻沒起做用。也就是說,error_page只檢查了第一次後端返回的狀態碼,而沒有繼續檢查後續的後端狀態碼。

查一下資料,這個時候,另外一個指令 recursive_error_pages就派上用場了。

4. 第3次修改

server {
        listen 80;
        proxy_intercept_errors on;
        recursive_error_pages on;
        location / {
                rewrite_by_lua '
                        ngx.exec("/proxy-to" .. ngx.var.request_uri)
                ';
        }
        location ~ /proxy-to/([^/]+)(.*) {
                proxy_pass http://$1$2$is_args$query_string;
                error_page 302 = @error_page_302;

        }
        location @error_page_302 {
                rewrite_by_lua '
                        local _, _, upstream_http_location = string.find(ngx.var.upstream_http_location, "^http:/(.*)$")
                        ngx.header["zzzz"] = "/proxy-to" .. upstream_http_location
                        ngx.exec("/proxy-to" .. upstream_http_location);
                ';
        }
}

與上一次相比,僅僅增長了recursive_error_pages on這條指令。測試結果以下:

curl -I "http://192.168.109.128/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4" -L 
HTTP/1.1 200 OK 
Server: nginx/1.4.6 
Date: Mon, 25 Aug 2014 15:09:04 GMT 
Content-Type: video/mp4 
Content-Length: 3706468 
Connection: keep-alive 
zzzz: /proxy-to/14.18.140.83/f48bad0100000000-1408984745-236096587/data6/flv.bn.netease.com/tvmrepo/2014/8/5/P/EA3I1J05P/SD/EA3I1J05P-mobile.mp4 
Last-Modified: Mon, 25 Aug 2014 00:21:07 GMT 
Cache-Control: no-cache 
ETag: "53fa8173-388e64" 
NG: CHN-MM-4-3FE 
X-Mod-Name: Mvod-Server/4.3.3 
Accept-Ranges: bytes

可見,Nginx終於成功的返回200了。此時,Nginx才真正起到了一個Proxy的功能,隱藏了一個請求本來的多個302鏈路,只返回客戶端一個最終結果。

5. 小結

綜上,經過proxy_pass、error_page、proxy_intercept_errors、recursive_error_pages這幾個指令的配合使用,能夠向客戶端隱藏一條請求的跳轉細節,直接返回用戶一個狀態碼爲200的最終結果。

相關文章
相關標籤/搜索