討論nginx?location?順序問題
網上有很多討論 nginx location 順序的話題,得到的結論也基本一致,總結為:
- 精準匹配
=
- 前綴匹配
^~
- 正則匹配
~
、~*
- 不帶修飾符的前綴匹配
在很長的一段時間里,我對上述的結論也一直深信不疑,甚至還將這個結論分享給其他小伙伴,直到在有一次配置時發(fā)現,請求 uri 明明是符合了前綴匹配 ^~
規(guī)則,但 nginx 卻沒有使用,這讓我對上述結論產生了疑惑。后續(xù)通過調研、實踐后發(fā)現,上述結論可以說對,但也不對,是不是更疑惑了?沒關系,看完這篇文章你就知道我為什么會這樣說了。
本篇文章會從以下五個方面來介紹 location
順序問題
location
是什么location
的選項有哪些location
的匹配規(guī)則是什么location
的應用規(guī)則是什么- 總結
話不多說,我們直接進入正題。
一、location 是什么
location
翻譯成中文就是定位,已經描述的比較清晰了,因為它的作用就是根據請求 uri 定位到某一個規(guī)則塊,然后在由該規(guī)則塊決定怎么處理用戶的請求。
location
模塊看起來挺簡單的,但真要自己寫,有時候就會覺得無從下手,我應該用 location /images
、 location ^~ /images
還是 location ~ /images
呢?我應該幫這個location
規(guī)則放在什么位置呢?將規(guī)則放在最前面會影響已有的配置嗎?....上面說的問題,相信大部分同學都遇到過,想要徹底解決,就必須要了解 location
的處理邏輯,當然這也是本篇文章的目的所在。
二、location 的選項有哪些?
location [ = | ~ | ~* | ^~ ] uri { ... }
按照匹配模式進行區(qū)分,location
后可以放置兩種類型的匹配規(guī)則,分別是前綴字符串(prefix string)和正則表達式(regular expression)。其中前綴字符串包括 =
、^~
以及不設置(也就是空串),而正則表達式只有 ~
和 ~*
。(這五種選項是經常看到的,還有一種不常用的 location @name { ... }
,并不在今天的討論范圍)
三、location 的匹配規(guī)則
只有請求 uri 滿足 location
的規(guī)則,才有可能被應用,而對于不同的匹配模式,location
的匹配規(guī)則也是不同的
前綴字符串
顧名思義,它表示從請求 uri 的開頭開始進行匹配,如果用 JavaScript 來描述的話,當 uri.indexOf(locationRule.uri) === 0
時表示滿足匹配規(guī)則,其中 uri
表示請求路徑,而 locationRule.uri
表示 location
設置的規(guī)則。
其中 =
選項比較特殊,它又叫做精準匹配,只有匹配規(guī)則與請求 uri 完全相等時才表示滿足條件,即只有當 uri === locationRule.uri
時才表示匹配成功。
正則表達式
其實就是通過正則匹配來驗證是否滿足規(guī)則,nginx 使用的是 Perl 兼容正則表達式 (PCRE),網上可以找到很多驗證正則表達式的站點,這里就不再展開了。但要注意,為了方便配置,nginx 進行了一些非標準的優(yōu)化,例如,不必像在標準正則表達式中那樣轉義 URL 中的正斜杠(/)等等。
而對于 ~
和 ~*
唯一的區(qū)別就是:~
區(qū)分大小寫,而 ~*
不區(qū)分大小寫。
總結
下面,我們對 location
的選項進行一個簡單的總結
選項 | 匹配規(guī)則 | 示例 |
---|---|---|
= | 精準匹配 | location = /test {...} |
^~ | 從請求 uri 的開頭進行匹配 | location ^~ /test {...} |
[空串] | 從請求 uri 的開頭進行匹配 | location /test {...} |
~ | 區(qū)分大小寫的正則匹配 | location ~ /test {...} |
~* | 不區(qū)分大小寫的正則匹配 | location ~* /test {...} |
四、location 的應用規(guī)則
理論篇
在了解完匹配規(guī)則后,我們來看下 nginx 是如何應用這些規(guī)則的,這就要說到 location
的順序問題了。
下面是根據實踐以文檔總結出來的 location
應用規(guī)則的邏輯:
- 將
server
塊中的location
按照匹配模式分成兩個列表,分別為前綴字符串規(guī)則列表和正則匹配規(guī)則列表。 - 首先遍歷前綴字符串規(guī)則列表,當滿足匹配的規(guī)則是精準匹配時(即匹配選項是
=
),直接應用該規(guī)則,結束流程;否則找到匹配規(guī)則最長的那條記錄(記作maxLenthStringPrefixRule
),繼續(xù)執(zhí)行邏輯3; - 如果
maxLenthStringPrefixRule
存在且匹配選項是^~
,應用該規(guī)則,結束流程;否則,執(zhí)行邏輯4; - 遍歷正則匹配規(guī)則列表,如果滿足匹配規(guī)則,則直接應用,流程結束;如果直到循環(huán)結束后依然沒有滿足規(guī)則的
location
,則執(zhí)行邏輯5; - 如果
maxLenthStringPrefixRule
存在,則應用該規(guī)則,流程結束;如果沒有則返回 404,同樣結束流程。
如果上述描述看著很難理解,可以嘗試看下面的偽代碼。
// 字符串匹配的規(guī)則集合,按照在 .conf 文件中的順序放置到該集合中 const stringPrefixRuleList = [...] // 正則匹配的規(guī)則集合,按照在 .conf 文件中的順序放置到該集合中 const regularExpressionRuleList = [...] // 用于存放最長字符串匹配的規(guī)則 let maxLenthStringPrefixRule; // 遍歷字符串匹配的規(guī)則集合 for (let stringPrefixRule of stringPrefixRuleList) { // 符合匹配規(guī)則 if (stringPrefixRule.isMatched()) { // 匹配選項是精準匹配 if (stringPrefixRule.option === '=') { // 應用該規(guī)則,結束流程 applyRule(stringPrefixRule); return; } // 將最長匹配規(guī)則記錄下來,留到后面使用 if (!maxLenthStringPrefixRule || stringPrefixRule.uri.length > maxLenthStringPrefixRule.uri.length) { maxLenthStringPrefixRule = stringPrefixRule } } } // 如果最長匹配規(guī)則的選項是 ^~, 則應用該規(guī)則,流程結束 if (maxLenthStringPrefixRule && maxLenthStringPrefixRule.option === '^~') { applyRule(maxLenthStringPrefixRule); return; } // 遍歷正則匹配規(guī)則集合 for (let regularExpressionRule of regularExpressionRuleList) { // 如果有規(guī)則匹配上,則直接應用,流程結束 if (regularExpressionRule.isMatched()) { applyRule(regularExpressionRule); return; } } // 如果最長字符串匹配的規(guī)則存在,則應用該規(guī)則 if (maxLenthStringPrefixRule) { applyRule(maxLenthStringPrefixRule); return; } // 404,規(guī)則未找到 throw new Error(404)
由此,我們可以得到幾個結論:
- 命中
=
匹配規(guī)則后會終止搜索,并直接使用該規(guī)則。所以,如果某個請求 url 頻繁發(fā)生,例如 /,我們可以在nginx.conf
中添加location = /
規(guī)則,這會加速這些請求的處理速度,因為在命中規(guī)則后會終止搜索。 ^~
和空串的前綴匹配,區(qū)別在于,如果命中^~
的規(guī)則,并且是最長前綴匹配,就會終止搜索正則匹配規(guī)則列表。- 除了命中精準匹配外,前綴字符串匹配列表都會被遍歷一遍,并且找到最長匹配的那條
location
規(guī)則,所以前綴字符串匹配和在文件中的位置無關,但是和匹配長度有關。 - 由于正則匹配的時間、資源消耗較多,所以 nginx 在對
location
規(guī)則進行正則匹配時,命中一個就直接使用了,所以正則匹配和location
規(guī)則在文件中的位置有關
實踐篇
從上面的理論篇中,大家應該能大致了解到 nginx 是如何命中 location
規(guī)則的,為了加深大家記憶,同時也為了能佐證理論是對的,我們來一些實踐吧。
我們的模板是這樣的,后面所有的實踐內容,都是放到兩個 rule section 之間。
server { listen 9001; location / { default_type text/html; return 200 'hello world'; } # ===== rule section ====== # ===== rule section ======
精準匹配優(yōu)先級最高
location /test { default_type text/html; return 200 '/test'; } location ~ /test { default_type text/html; return 200 '~ /test'; } location = /test { default_type text/html; return 200 '= /test'; }
我們在 nginx.conf
放置了 /test
、~ /test
、= /test
三個 location
規(guī)則,隨后在瀏覽器輸入 localhost:9001/test,發(fā)現輸出內容是 = /test
,符合我們的預期,=
選項的優(yōu)先級最高。
正則匹配和順序有關
location ~ /test { default_type text/html; return 200 '~ /test'; } location ~ /test/*/demo { default_type text/html; return 200 '~ /test/*/demo'; }
隨后我們將 rule section 區(qū)塊替換成 ~ /test
和 ~ /test/*/demo
規(guī)則,在瀏覽器輸入 localhost:9001/test/xyz/demo,從正則角度來說,/test/xyz/demo
既滿足 /test
規(guī)則,又滿足 /test/*/demo
規(guī)則,但是由于 ~ /test
在文件中位置靠前,所以優(yōu)先被命中,理論上應該會輸出 ~ /test
,校驗后發(fā)現,確實是這樣。
正則、^~前綴匹配、空前綴匹配混搭
location ^~ /test { default_type text/html; return 200 '^~ /test'; } location /test/more/andmore { default_type text/html; return 200 '/test/more/andmore'; } location ~ /test { default_type text/html; return 200 '~ /test'; }
將 section rule 區(qū)域替換成上述內容,然后在瀏覽器中輸入 localhost:9001/test/more/andmore,猜猜會發(fā)生什么?
我們來一起分析下:
- 沒有精準匹配的規(guī)則
- 找到最長的前綴字符串匹配規(guī)則是
/test/more/andmore
,它不是^~
選項,所以會遍歷正則匹配規(guī)則 - 發(fā)現
~ /test
滿足匹配規(guī)則,直接應用該規(guī)則,所以會輸出location ~ /test
規(guī)則塊的內容,也就是~ /test
我們再來試一下,在瀏覽器輸入 localhost:9001/test/more,會顯示什么呢?
- 沒有精準匹配規(guī)則
- 最長前綴字符串匹配規(guī)則是
^~ /test
,是^~
選項,所以不用遍歷正則匹配規(guī)則列表,所以頁面會顯示^~ /test
。
通過校驗后發(fā)現,上述分析的結果和實際顯示結果是一樣的。
上面幾個實踐都比較簡單,大家也可以嘗試各種組合,然后按照上面的分析步驟來檢驗下自己是不是真的理解了 location
。
五、總結
看完上面的介紹,相信大家對 location
規(guī)則的處理邏輯都有一定的了解,也應該明白為什么在文章開頭說曾經看到的結論對、也不對了。
如果要用對 location
順序進行總結的話,可以在原有的基礎上適當的進行一些擴展:
- 精準匹配
=
- 前綴匹配
^~
,如果該前綴匹配是最長前綴匹配規(guī)則,則應用 - 正則匹配
~
、~*
,和該規(guī)則在文件中的順序有關,執(zhí)行順序從上到下 - 不帶修飾符的前綴匹配,和匹配規(guī)則的長度有關,只會應用最長的匹配規(guī)則,與在文件中的順序無關
參考鏈接:nginx.org/en/docs/htt…
到此這篇關于nginxlocation順序問題的文章就介紹到這了,更多相關nginxlocation順序內容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持本站!
版權聲明:本站文章來源標注為YINGSOO的內容版權均為本站所有,歡迎引用、轉載,請保持原文完整并注明來源及原文鏈接。禁止復制或仿造本網站,禁止在非www.sddonglingsh.com所屬的服務器上建立鏡像,否則將依法追究法律責任。本站部分內容來源于網友推薦、互聯網收集整理而來,僅供學習參考,不代表本站立場,如有內容涉嫌侵權,請聯系alex-e#qq.com處理。