在使用nginx重寫(即rewrite)機制時,大家一般會用到last和break,關於這兩個指令的作用,網友問的挺多,網上的討論也挺多,這裏做個總結:
網友的給力解釋:
last:
重新將rewrite後的地址在server標籤中執行
break:
將rewrite後的地址在當前location標籤中執行
nginx官方解釋:
last:
- stops processing the current set of
ngx_http_rewrite_module
directives followed by a search for a new location matching - the changed URI;
break:
- stops processing the current set of
ngx_http_rewrite_module
directives;
- 其實網友的解釋更容易懂一些,nginx官方的解釋則是從偏重實現的角度來說的,說到這裏,許多人可能還是對這兩個指令的使用不是太自信,感覺心裏沒底,說實話我當初也是這麼感覺的,那麼就讓我們打破沙鍋問到底,看看代碼到底是怎麼實現的,畢竟,”代碼面前無祕密“。
爲了方便我們的討論我們做出以下的假想配置:
-
location /download/ {
-
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
-
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break;
-
return 403;
-
}
在分析之前,看官們需要熟悉nginx各個phase handler的處理,以及nginx變量的基本原理,不熟悉的同學看起來可能會有點難度,那麼這裏給出了相關的連接,方面不熟悉的同學學習。前兩個是關於handler的,後一個是關於變量的:
現在進入正題:
在函數ngx_http_rewrite中:
-
if (cf->args->nelts == 4) {
-
if (ngx_strcmp(value[3].data, "last") == 0) {
-
last = 1;
-
-
} else if (ngx_strcmp(value[3].data, "break") == 0) {
-
regex->break_cycle = 1;
-
last = 1;
-
-
}
-
...
-
}
重點在於last = 1的處理,在稍後:
-
if (last) {
-
code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex);
-
if (code == NULL) {
-
return NGX_CONF_ERROR;
-
}
-
-
*code = NULL;
-
}
lcf->codes是個數組裏面保存了當前各個rewrite執行對應的相關操作(即各種handler)和數據,這裏的操作是在這個數組中添加一個null,這個null的意義重大,在rewrite實際執行時,如ngx_http_rewrite_handler的調用,就會對事先放置在這個數組裏的handler進行處理:
-
e->ip = rlcf->codes->elts;
-
...
-
-
-
-
while (*(uintptr_t *) e->ip) {
-
code = *(ngx_http_script_code_pt *) e->ip;
-
code(e);
-
}
比如在開始的配置裏面,我們寫成:
- location /download/ {
- rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3;
- rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra;
- return 403;
- }
即不寫last和break,那麼流程就是依次執行這些rewrite,直到最後以403結束這次請求,這種情況下codes數組中的handler都得以執行了,而由於
last和break的出現,處理可能在中間的某個位置終止,後面的rewrite,就不會執行了。
在rewrite階段的處理結束之後,則會轉到find config階段,這個階段本來是在rewrite階段之前的,這樣的過程也刻畫了rewrite的基本流程,url經過rewrite階段被改變了,而一個請求處理的關鍵步驟之一就是要確定對應的server conf和location conf,而find config的作用恰恰就是如此,重寫之後url可以看做是一個新的請求,所以這些關鍵步驟需要走一遍就是理所當然了。
另一個問題,在ngx_http_rewrite函數中break_cycle的設置,也就是在出現break的時候,這個變量會被置1,而這個變量的設置,最終會導致r->uri_changed被置爲0,那麼它的直接影響可以在下面的地方看到:
-
-
ngx_http_core_post_rewrite_phase
-
{
-
-
-
-
-
if (!r->uri_changed) {
-
r->phase_handler++;
-
return NGX_AGAIN;
-
}
-
...
-
}
關於r->uri_changed被置爲0的操作,可以參考:
ngx_http_script_regex_start_code和ngx_http_script_break_code
所以這裏概括下:
last其實就相當於一個新的url,對nginx進行了一次請求,需要走一遍大多數的處理過程,最重要的是會做一次find config,提供了一個可以轉到其他location的配置中處理的機會,而break則是在一個請求處理過程中將原來的url(包括uri和args)改寫之後,在繼續進行後面的處理,這個重寫之後的請求始終都是在同一個location中處理。