天天看點

Nginx關于Rewrite執行順序詳解.docxNginx 關于 Rewrite 執行順序詳解Nginx 關于 Rewrite 的疊代 第二篇

http://eyesmore.iteye.com/blog/1142162 

Nginx 關于 Rewrite 執行順序詳解

第一篇: break 和 last 的差別

Rewrite 子產品概述

REFER:   http://wiki.nginx.org/NginxHttpRewriteModule#rewrite

If the directives of this module are given at the server level, then they are carried out before the location of the request is determined. If in that selected location there are further rewrite directives, then they also are carried out. If the URI changed as a result of the execution of directives inside location, then location is again determined for the new URI.

This cycle can be repeated up to 10 times, after which Nginx returns a 500 error.

Rewrite( URL 重寫)指令可以出現在 server{} 下,也可以出現在 location{} 下,它們之間是有差別的!對于出現在 server{} 下的 rewrite 指令,它的執行會在 location 比對之前;對于出現在 location{} 下的 rewrite 指令,它的執行當然是在 location 比對之後,但是由于 rewrite 導緻 HTTP 請求的 URI 發生了變化,是以 location{} 下的rewrite 後的 URI 又需要重新比對 location ,就好比一個新的 HTTP 請求一樣(注意由 location{} 内的 rewrite 導緻的這樣的循環比對最多不超過 10 次,否則 nginx 會報 500 錯誤)。總的來說,如果 server{} 和 location{} 下都有 rewrite ,依然是先執行 server{} ,然後進行 location 比對,如果被比對的 location{} 之内還有 rewrite 指令,那麼繼續執行 rewrite ,同時因為 location{} 内的 rewrite 改變了 URI ,那麼重寫後的結果 URI 需要當做一個新的請求,重新比對 location (應該也包括重新執行 server{} 下的 rewrite 吧)。

Last 與 break flag 的差別

關于 last flag 和 break flag 的差別,官方文檔的描述是:“ last - completes processing of rewrite directives, after which searches for corresponding URI and location ”和“ break - completes processing of rewrite directives ”,都有“不讓繼續執行後面的 rewrite 指令”的含義,但是兩者的差別并沒有展開。

這裡我用實驗來告訴大家差別。實驗準備:

1、  安裝 nginx ;(如果對安裝和 location 不了解的,請參考: http://eyesmore.iteye.com/blog/1141660 )

2、  在 nginx 安裝目錄的 html 子目錄下建立 4 個檔案,分别叫: aaa.html , bbb.html , ccc.html 和 ddd.html,檔案内容分别是各自的檔案名(例 aaa.html 檔案内容不妨寫 aaa html file )。

3、  Nginx 配置檔案初始化是:

error_log  logs/error.log info;  #URL 重寫子產品的日志會寫入此檔案

   server {

        listen       9090;

        server_name  localhost;

        root html;

        rewrite_log on;   # 打開 URL 重寫子產品的日志開關,以便寫入 error_log

        location  /aaa.html {

            rewrite "^/aaa\.html$"  /bbb.html;

            rewrite "^/bbb\.html$"  /ddd.html;

        }  

        location  /bbb.html {

            rewrite "^/bbb\.html$" /ccc.html;

        }  

}

上述配置注意兩點: 1 、打開 rewrite 子產品的日志開關,以便 rewrite 執行日志寫入 error_log (注: rewrite 日志寫入 error_log 的級别是 notice ,是以要注意 error_log 日志級别,此處用 info ); 2 、定義了兩個 location ,分别是 /aaa.html 和 /bbb.html ,但是在 /aaa.html 中,把 /aaa.html 重寫成 /bbb.html ,接着又把 /bbb.html 重寫成 /ddd.html ;在 /bbb.html 中,把 /bbb.html 重寫成 /ccc.html 。

[ 測試 1] 沒有 last 和 break 标記時:請求 aaa.html

[[email protected] ~]# curl http://localhost:9090/aaa.html

ddd html file

[[email protected] ~]#

Error_log 的日志内容:

2011/08/07 22:13:23 [notice] 9066#0: *85 "^/aaa\.html$" matches "/aaa.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:13:23 [notice] 9066#0: *85 rewritten data: "/bbb.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:13:23 [notice] 9066#0: *85 "^/bbb\.html$" matches "/bbb.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:13:23 [notice] 9066#0: *85 rewritten data: "/ddd.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:13:23 [info] 9066#0: *85 client 127.0.0.1 closed keepalive connection

URL 重寫子產品的日志告訴我們:對于一個 HTTP 請求“ GET /aaa.html ”,重寫過程是:先 /aaa.html 被重寫為/bbb.html ;然後 rewritten data: /bbb.html ,繼續執行後面的 rewrite 指令,進而被重寫為 /ddd.html ,然後rewrittern data: /ddd.html 後面沒有重寫了(其實此時 /ddd.html 需要再次重新比對 location 的,隻是日志沒有展現出來,接下來的測試 2 會展現這點),于是輸出 /ddd.html 的内容。

[ 測試 2] 使用 last 标記時:請求 aaa.html

将上述 location /aaa.html {} 修改成:

location  /aaa.html {

       rewrite "^/aaa\.html$"  /bbb.html   last ;

      rewrite "^/bbb\.html$"  /ddd.html;

}  

測試結果:

[[email protected] ~]# curl http://localhost:9090/aaa.html

ccc html file

[[email protected] ~]#

Error_log 日志:

2011/08/07 22:24:31 [notice] 18569#0: *86 "^/aaa\.html$" matches "/aaa.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:24:31 [notice] 18569#0: *86 rewritten data: "/bbb.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:24:31 [notice] 18569#0: *86 "^/bbb\.html$" matches "/bbb.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:24:31 [notice] 18569#0: *86 rewritten data: "/ccc.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:24:31 [info] 18569#0: *86 client 127.0.0.1 closed keepalive connection

不知道讀者看到 GET /aaa.html 顯示的結果“ ccc html file ”會不會驚訝:“為什麼結果不是 bbb html file ”。下面解釋下整個過程:首先 /aaa.html 比對了 location /aaa.html {} ,于是執行 rewrite "^/aaa\.html$"  /bbb.html last ,把 /aaa.html 重寫為 /bbb.html ,同時由于 last flag 的使用,後面的 rewrite 指令(指的是 rewrite "^/bbb\.html$" /ddd.html )不會被執行。似乎此時應該輸出“ bbb html file ”才對,但是我們看看 nginx 官方解釋:“ last - completes processing of rewrite directives, after which searches for corresponding URI and location ”意思是說last 不再比對後面的 rewrite 指令,但是緊接着需要對重寫後的 URI 重新比對 location 。讓我們再看看官方的“ If the directives of this module are given at the server level, then they are carried out before the location of the request is determined. If in that selected location there are further rewrite directives, then they also are carried out. If the URI changed as a result of the execution of directives inside location, then location is again determined for the new URI. This cycle can be repeated up to 10 times, after which Nginx returns a 500 error. ”是以,重新比對的時候,比對到了新的 location /bbb.html {} ,執行“ rewrite "^/bbb\.html$" /ccc.html ”,最後的内容是“ ccc html file ”。

[ 測試 3] 使用 break 标記時:請求 aaa.html

将上述 location /aaa.html {} 修改成使用 break 标記:

location  /aaa.html {

      rewrite "^/aaa\.html$"  /bbb.html  break ;

      rewrite "^/bbb\.html$"  /ddd.html;

}  

測試結果:

[[email protected] ~]# curl http://localhost:9090/aaa.html

bbb html file

[[email protected] ~]#

日志結果:

2011/08/07 22:37:49 [notice] 21069#0: *89 "^/aaa\.html$" matches "/aaa.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:37:49 [notice] 21069#0: *89 rewritten data: "/bbb.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/07 22:37:49 [info] 21069#0: *89 client 127.0.0.1 closed keepalive connection

我想這個結果不用多做解釋了,充分展現了 break 和 last 的差別:“ last - completes processing of rewrite directives, after which searches for corresponding URI and location ”和“ break - completes processing of rewrite directives ”。 Break 和 last 都能阻止繼續執行後面的 rewrite 指令,但是 last 如果在 location 下用的話,對于重寫後的 URI 會重新比對 location ,但是 break 則不會重新比對 location 。簡單的說, break 終止的力度比last 更加徹底(為了記憶的友善,我們可以把重新後的 URI 重新比對 location 了解為“ URI 比對 location 的循環語句的下一次疊代”,進階程式設計裡面 break 一般用做退出循環,是以 break 不僅終止繼續執行 rewrite ,而且退出 URI 重新比對 location 的循環疊代)。

Nginx 關于 Rewrite 的疊代 第二篇

例題 1

配置:

error_log  logs/error.log info;

server {

        listen       9090;

        server_name  localhost;

        root html;

        rewrite_log on;

        rewrite "^/aaa\.html$"  /bbb.html;

        location  /ccc.html {

            rewrite "^/ccc\.html$"  /eee.html;

        }

        location  /bbb.html {

            rewrite "^/bbb\.html$" /ccc.html;

            rewrite "^/ccc\.html$" /ddd.html;

        }  

}   

結果:

[[email protected] ~]# curl http://localhost:9090/aaa.html

ddd html file

[[email protected] ~]#

日志:

2011/08/08 10:05:41 [notice] 31592#0: *90 "^/aaa\.html$" matches "/aaa.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 rewritten data: "/bbb.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 "^/bbb\.html$" matches "/bbb.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 rewritten data: "/ccc.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 "^/ccc\.html$" matches "/ccc.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 rewritten data: "/ddd.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [notice] 31592#0: *90 "^/aaa\.html$" does not match "/ddd.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:05:41 [info] 31592#0: *90 client 127.0.0.1 closed keepalive connection

解釋:

GET /aaa.html 請求,首先執行 server 級的 rewrite 指令,被重寫為 /bbb.html ,然後比對到 location /bbb.html {},接着執行 location 級的 rewrite 指令,先重寫為 /ccc.html ,再重寫為 /ddd.html ;由于 URI 被 location 級的rewrite 指令重寫了,是以需要重新進行 location 的比對,相當于重寫後的 URI 被當做一個新的請求,會重新執行server 級的 rewrite ,然後重新比對 location ,日志“ 2011/08/08 10:05:41 [notice] 31592#0: *90 "^/aaa\.html$" does not match "/ddd.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090" ”展現了重新比對 location 的流程。

例題 2

配置:

error_log  logs/error.log info;

server {

        listen       9090;

        server_name  localhost;

        root html;

        rewrite_log on;

        rewrite "^/aaa\.html$"  /bbb.html;

                   rewrite "^/ccc\.html$"  /ddd.html;

        location  /bbb.html {

            rewrite "^/bbb\.html$" /ccc.html;

        }  

                   location  /ddd.html {

             rewrite "^/ddd\.html$" /eee.html;

        }

}   

結果:

[[email protected] ~]# curl http://localhost:9090/aaa.html

eee html file

[[email protected] ~]#

日志:

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/aaa\.html$" matches "/aaa.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 rewritten data: "/bbb.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/ccc\.html$" does not match "/bbb.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/bbb\.html$" matches "/bbb.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 rewritten data: "/ccc.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/aaa\.html$" does not match "/ccc.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/ccc\.html$" matches "/ccc.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 rewritten data: "/ddd.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/ddd\.html$" matches "/ddd.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 rewritten data: "/eee.html" , args: "", client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/aaa\.html$" does not match "/eee.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [notice] 2218#0: *91 "^/ccc\.html$" does not match "/eee.html" , client: 127.0.0.1, server: localhost, request: "GET /aaa.html HTTP/1.1", host: "localhost:9090"

2011/08/08 10:21:00 [info] 2218#0: *91 client 127.0.0.1 closed keepalive connection

解釋:

第一次疊代 location 比對

GET /aaa.html ,首先執行 server 級的重寫,“ rewrite "^/aaa\.html$"  /bbb.html ”把 /aaa.html 重寫為 /bbb.html,但 /bbb.html 沒比對上“ rewrite "^/ccc\.html$"  /ddd.html ”,最終保留 /bbb.html ;接着,比對 location /bbb.html {} ,執行 location 級的 rewrite 指令,把 /bbb.html 重寫為 /ccc.html ,由于 URI 被 location 級 rewrite重寫,是以需要重新疊代 location 比對。

第二次疊代 location 比對

對于第一次疊代結果 /ccc.html ,首先依然是執行 server 級的 rewrite 指令,“ rewrite "^/aaa\.html$"  /bbb.html;”跟 /ccc.html 不比對,但“ rewrite "^/ccc\.html$"  /ddd.html; ”把 /ccc.html 重寫為 /ddd.html ; server 級 rewrite執行完後,接着 location 比對, /ddd.html 比對到 location /ddd.html {} ,執行 location 級的 rewrite 指令,把/ddd.html 重寫為 /eee.html 。同樣由于 URI 被 location 級的 rewrite 指令重寫,于是需要重新疊代 location 比對。

第三次疊代 location 比對

對于第二次疊代結果 /eee.html ,首先依然執行 server 級的 rewrite 指令,“ rewrite "^/aaa\.html$"  /bbb.html;”和“ rewrite "^/ccc\.html$"  /ddd.html; ”,隻不過它們都沒比對上 /eee.html ,接着 /eee.html 進行 location 比對,也沒有,最終結果是 /eee.html ,傳回“ eee html file ”頁面。

最後說明下,如果把上述配置修改成server級rewrite和location的編輯順序調整:

server {

        listen       9090;

        server_name  localhost;

        root html;

        rewrite_log on;

        location  /bbb.html {

            rewrite "^/bbb\.html$" /ccc.html;

        }   

        location  /ddd.html {

            rewrite "^/ddd\.html$" /eee.html;

        }

        rewrite "^/aaa\.html$"  /bbb.html;

        rewrite "^/ccc\.html$"  /ddd.html;

}

結果是不會受影響的,也就是說location比對疊代總是先執行server級rewrite,再進行location比對,再執行location級的rewrite,如果URI因location級rewrite指令重寫,則需要進行下一次疊代。但總的疊代次數不超過10次,否則nginx報500錯誤。

簡單僞代碼描述下rewrite執行過程:

boolean match_finish = false;

int match_count = 0;

while(!match_finish && match_count < 10) {

        match_count ++;

    (1)按編輯順序執行server級的rewrite指令;

    (2)按重寫後的URI比對location;

    (3)

        String uri_before_location = uri;

        按編輯順序執行location級的rewrite指令;

        String uri_after_location = rewrite(uri);

        if(uri_before_location != uri_after_location) {

            match_finish = false;            

        } else {

            match_finish = true;

        }

        if(location rewrite has last flag) {

            continue;//表示不執行後面的rewrite,直接進入下一次疊代

        }

        if(location rewrite has break flag) {

            break;//表示不執行後面的rewrite,并退出循環疊代

        }

}

if(match_count <= 10) {

    return HTTP_200;

} else {

    return HTTP_500;

}

繼續閱讀