Last 與 break flag 的區別

forest7707發表於2017-05-21

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

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

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

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

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

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



最後說明下,如果把上述配置修改成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;
}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/21220384/viewspace-2139652/,如需轉載,請註明出處,否則將追究法律責任。

相關文章