nginx的rewrite機制

在使用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官方的解釋則是從偏重實現的角度來說的,說到這裏,許多人可能還是對這兩個指令的使用不是太自信,感覺心裏沒底,說實話我當初也是這麼感覺的,那麼就讓我們打破沙鍋問到底,看看代碼到底是怎麼實現的,畢竟,”代碼面前無祕密“。
爲了方便我們的討論我們做出以下的假想配置:
  1. location /download/ {   
  2.     rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;  
  3.     rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra  break;  
  4.     return  403;  
  5. }  
在分析之前,看官們需要熟悉nginx各個phase handler的處理,以及nginx變量的基本原理,不熟悉的同學看起來可能會有點難度,那麼這裏給出了相關的連接,方面不熟悉的同學學習。前兩個是關於handler的,後一個是關於變量的:

現在進入正題:
在函數ngx_http_rewrite中:
  1. if (cf->args->nelts == 4) {  
  2.         if (ngx_strcmp(value[3].data, "last") == 0) {  
  3.             last = 1;  
  4.   
  5.         } else if (ngx_strcmp(value[3].data, "break") == 0) {  
  6.             regex->break_cycle = 1;  
  7.             last = 1;  
  8.   
  9.         }    
  10.         ...// 其餘處理省略  
  11. }  
重點在於last = 1的處理,在稍後:
  1. if (last) {  
  2.     code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex);  
  3.     if (code == NULL) {  
  4.         return NGX_CONF_ERROR;  
  5.     }  
  6.   
  7.     *code = NULL;  
  8. }  
lcf->codes是個數組裏面保存了當前各個rewrite執行對應的相關操作(即各種handler)和數據,這裏的操作是在這個數組中添加一個null,這個null的意義重大,在rewrite實際執行時,如ngx_http_rewrite_handler的調用,就會對事先放置在這個數組裏的handler進行處理:
  1. e->ip = rlcf->codes->elts;  
  2. ...  
  3. // 在這個while循環中,上面的那個null,就會終止rewrite一系列操作的執行  
  4. // 可以看到,“last”和“break”在這點上作用是相同的,當前codes數組中有剩餘的  
  5. // rewrite指令,那麼由於這裏的null的存在,也就跳過不管了。  
  6. while (*(uintptr_t *) e->ip) {  
  7.     code = *(ngx_http_script_code_pt *) e->ip;  
  8.     code(e);  
  9. }  
比如在開始的配置裏面,我們寫成:
  1. location /download/ {  
  2.     rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3;  
  3.     rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra;  
  4.     return  403;  
  5. }  
即不寫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,那麼它的直接影響可以在下面的地方看到:
  1. // 這個函數之所以名爲“post”,意思就是爲rewrite處理做一些善後工作  
  2. ngx_http_core_post_rewrite_phase  
  3. {     
  4.     // 在通常情況下,即r->uri_changed > 0,r->phase_handler會設置爲ph->next,  
  5.     // 而這個ph->next,在開始初始化phase的時候,已經設置爲ph->next = find_config_index,  
  6.     // 所以在非break或者last情況下,之後的phase就是所謂find config階段了,而這裏卻是  
  7.     // r->phase_handler++,意味着將會執行接下來的處理,不會再去走find config的過程了  
  8.     if (!r->uri_changed) {  
  9.         r->phase_handler++;  
  10.         return NGX_AGAIN;  
  11.     }  
  12.     ... // 此處省略其他處理部分  
  13. }  
關於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中處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章