nginx location rewrite匹配順序

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

[root@web108 ~]# curl http://localhost:9090/aaa.html

ddd html file

[root@web108 ~]#

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;

}  

 

測試結果:

[root@web108 ~]# curl http://localhost:9090/aaa.html

ccc html file

[root@web108 ~]#

 

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;

}  

測試結果:

[root@web108 ~]# curl http://localhost:9090/aaa.html

bbb html file

[root@web108 ~]#

 

日誌結果:

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;

        }  

}   

結果:

[root@web108 ~]# curl http://localhost:9090/aaa.html

ddd html file

[root@web108 ~]#

 

日誌:

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;

        }

}   

結果:

[root@web108 ~]# curl http://localhost:9090/aaa.html

eee html file

[root@web108 ~]#

 

日誌:

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 ”頁面。

 

 

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

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匹配迭代總是先執行serverrewrite,再進行location匹配,再執行location級的rewrite,如果URIlocationrewrite指令重寫,則需要進行下一次迭代。但總的迭代次數不超過10次,否則nginx500錯誤。

 

簡單僞代碼描述下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;
}

 

發佈了166 篇原創文章 · 獲贊 16 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章