nginx 的 rewrite 理解起來也挺費勁
這週三,有個老同事和我討論了一個 HTTPS 問題,實質上卻是 Nginx 的 rewrite 規則問題,我仔細閱讀了官方文件,發現解釋的不是特別明瞭,逐寫了這篇博文。
rewrite 的語法規則如下:
Syntax: rewrite regex replacement [flag]; Context: server, location, if
我們暫時不考慮 flag 引數,先從簡單的說起,首先務必要理解的就是 rewrite 規則在 server 段和 location 段中語義是不一樣的。
在一個 URL 請求中,rewrite 如果匹配到 regex,那麼 URL 就會替換成 replacement,如果不考慮 flag,匹配規則是順序執行的,即使匹配到了,仍然會繼續匹配下去。
記住,如果 replacement 包含 “http://”, “https://”, or “$scheme”,那麼匹配會理解終止,並直接重定向地址給客戶端。
接下去說說 flag 可選引數,如果有 flag 引數,rewrite 會進一步處理指令。flag 引數有四個:
-
last:stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI;
-
break:stops processing the current set of ngx_http_rewrite_module directives as with the break directive;
-
redirect:returns a temporary redirect with the 302 code;
-
permanent:returns a permanent redirect with the 301 code.
flag 引數如果是 redirect 或 permanent,那麼處理就相對簡單,立刻中止規則匹配,進行 302 或 301 跳轉。
從文件看,last 和 break 看不出太大區別,我總結就是如果在 location 中配置 flag 是 last,立刻跳出本 location 的匹配,同時會順序繼續搜尋其他 location 的匹配,如果還沒匹配到,還會繼續搜尋本 location;而 break 跳出本 location 後就不會再匹配其它 location 了。通過個例子說明:
location / { rewrite ^/a /b last; rewrite ^/b /c last; } location = /b { return 401; } location = /c { return 402; }
如果訪問 https://www.simplehttps.com/a/,則返回 401;如果訪問 https://www.simplehttps.com/b/,則返回 402。
接下去修改配置:
location / { rewrite ^/a /b break; rewrite ^/b /c break; } location = /b { return 401; } location = /c { return 402; }
結果就是,不管訪問 https://www.simplehttps.com/a/ 還是 https://www.simplehttps.com/b/,都返回 404。
那麼如果 flag 配置在 server 段內,會發生什麼呢?不管是 break 還是 last,其行為規則是一樣的,不會有跳出的行為,會順序執行。
rewrite ^/a /b break; # 或者 rewrite ^/a /b last; rewrite ^/b /c break; # 或者 rewrite ^/b /c last; location = /b { return 401; } location = /c { return 402; }
那麼文件中描述的死迴圈什麼情況呢?以下的例子在 location 中就會死迴圈:
location /download/ { rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ralast; return403; }
原因就在於 rewrite 匹配後還是 /download 開頭,如果是 last,會繼續走到 location /download/ { 段內,從而會死迴圈,最終產生 500 錯誤,所以這種情況下,建議將 last 修改為 break。
其實事情也沒有那麼複雜,取決於你清楚自己想達到什麼目標,然後在 last 和 break 之間取捨。
我的書《深入淺出HTTPS:從原理到實戰》程式碼例項已經更新到 github 上了,地址是 https://github.com/ywdblog/httpsbook,歡迎一起討論。