日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

討論nginx?location?順序問題_nginx

作者:月亮與六便士丶 ? 更新時間: 2022-07-26 編程語言

網上有很多討論 nginx location 順序的話題,得到的結論也基本一致,總結為:

  • 精準匹配 =
  • 前綴匹配 ^~
  • 正則匹配 ~~*
  • 不帶修飾符的前綴匹配

在很長的一段時間里,我對上述的結論也一直深信不疑,甚至還將這個結論分享給其他小伙伴,直到在有一次配置時發現,請求 uri 明明是符合了前綴匹配 ^~ 規則,但 nginx 卻沒有使用,這讓我對上述結論產生了疑惑。后續通過調研、實踐后發現,上述結論可以說對,但也不對,是不是更疑惑了?沒關系,看完這篇文章你就知道我為什么會這樣說了。

本篇文章會從以下五個方面來介紹 location 順序問題

  • location 是什么
  • location 的選項有哪些
  • location 的匹配規則是什么
  • location 的應用規則是什么
  • 總結

話不多說,我們直接進入正題。

一、location 是什么

location 翻譯成中文就是定位,已經描述的比較清晰了,因為它的作用就是根據請求 uri 定位到某一個規則塊,然后在由該規則塊決定怎么處理用戶的請求。

location 模塊看起來挺簡單的,但真要自己寫,有時候就會覺得無從下手,我應該用 location /imageslocation ^~ /images 還是 location ~ /images 呢?我應該幫這個location 規則放在什么位置呢?將規則放在最前面會影響已有的配置嗎?....上面說的問題,相信大部分同學都遇到過,想要徹底解決,就必須要了解 location 的處理邏輯,當然這也是本篇文章的目的所在。

二、location 的選項有哪些?

location [ = | ~ | ~* | ^~ ] uri { ... }

按照匹配模式進行區分,location 后可以放置兩種類型的匹配規則,分別是前綴字符串(prefix string)和正則表達式(regular expression)。其中前綴字符串包括 =^~ 以及不設置(也就是空串),而正則表達式只有 ~~*。(這五種選項是經常看到的,還有一種不常用的 location @name { ... } ,并不在今天的討論范圍)

三、location 的匹配規則

只有請求 uri 滿足 location 的規則,才有可能被應用,而對于不同的匹配模式,location 的匹配規則也是不同的

前綴字符串

顧名思義,它表示從請求 uri 的開頭開始進行匹配,如果用 JavaScript 來描述的話,當 uri.indexOf(locationRule.uri) === 0 時表示滿足匹配規則,其中 uri 表示請求路徑,而 locationRule.uri 表示 location 設置的規則。

其中 = 選項比較特殊,它又叫做精準匹配,只有匹配規則與請求 uri 完全相等時才表示滿足條件,即只有當 uri === locationRule.uri 時才表示匹配成功。

正則表達式

其實就是通過正則匹配來驗證是否滿足規則,nginx 使用的是 Perl 兼容正則表達式 (PCRE),網上可以找到很多驗證正則表達式的站點,這里就不再展開了。但要注意,為了方便配置,nginx 進行了一些非標準的優化,例如,不必像在標準正則表達式中那樣轉義 URL 中的正斜杠(/)等等。

而對于 ~~* 唯一的區別就是:~ 區分大小寫,而 ~* 不區分大小寫。

總結

下面,我們對 location 的選項進行一個簡單的總結

選項 匹配規則 示例
= 精準匹配 location = /test {...}
^~ 從請求 uri 的開頭進行匹配 location ^~ /test {...}
[空串] 從請求 uri 的開頭進行匹配 location /test {...}
~ 區分大小寫的正則匹配 location ~ /test {...}
~* 不區分大小寫的正則匹配 location ~* /test {...}

四、location 的應用規則

理論篇

在了解完匹配規則后,我們來看下 nginx 是如何應用這些規則的,這就要說到 location 的順序問題了。

下面是根據實踐以文檔總結出來的 location 應用規則的邏輯:

  • server 塊中的 location 按照匹配模式分成兩個列表,分別為前綴字符串規則列表和正則匹配規則列表。
  • 首先遍歷前綴字符串規則列表,當滿足匹配的規則是精準匹配時(即匹配選項是 =),直接應用該規則,結束流程;否則找到匹配規則最長的那條記錄(記作 maxLenthStringPrefixRule),繼續執行邏輯3;
  • 如果 maxLenthStringPrefixRule 存在且匹配選項是 ^~ ,應用該規則,結束流程;否則,執行邏輯4;
  • 遍歷正則匹配規則列表,如果滿足匹配規則,則直接應用,流程結束;如果直到循環結束后依然沒有滿足規則的 location,則執行邏輯5;
  • 如果 maxLenthStringPrefixRule 存在,則應用該規則,流程結束;如果沒有則返回 404,同樣結束流程。

如果上述描述看著很難理解,可以嘗試看下面的偽代碼。

// 字符串匹配的規則集合,按照在 .conf 文件中的順序放置到該集合中
const stringPrefixRuleList = [...]
// 正則匹配的規則集合,按照在 .conf 文件中的順序放置到該集合中
const regularExpressionRuleList = [...]
// 用于存放最長字符串匹配的規則
let maxLenthStringPrefixRule;
// 遍歷字符串匹配的規則集合
for (let stringPrefixRule of stringPrefixRuleList) {
    // 符合匹配規則
    if (stringPrefixRule.isMatched()) {
        // 匹配選項是精準匹配
        if (stringPrefixRule.option === '=') {
            // 應用該規則,結束流程
            applyRule(stringPrefixRule);
            return;
        }
        // 將最長匹配規則記錄下來,留到后面使用
        if (!maxLenthStringPrefixRule 
            || stringPrefixRule.uri.length > maxLenthStringPrefixRule.uri.length) {
            maxLenthStringPrefixRule = stringPrefixRule
        }
    }
}
// 如果最長匹配規則的選項是 ^~, 則應用該規則,流程結束
if (maxLenthStringPrefixRule && maxLenthStringPrefixRule.option === '^~') {
    applyRule(maxLenthStringPrefixRule);
    return;
}
// 遍歷正則匹配規則集合
for (let regularExpressionRule of regularExpressionRuleList) {
    // 如果有規則匹配上,則直接應用,流程結束
    if (regularExpressionRule.isMatched()) {
        applyRule(regularExpressionRule);
        return;
    }
}
// 如果最長字符串匹配的規則存在,則應用該規則
if (maxLenthStringPrefixRule) {
    applyRule(maxLenthStringPrefixRule);
    return;
}
// 404,規則未找到
throw new Error(404)

由此,我們可以得到幾個結論:

  • 命中 = 匹配規則后會終止搜索,并直接使用該規則。所以,如果某個請求 url 頻繁發生,例如 /,我們可以在 nginx.conf 中添加 location = / 規則,這會加速這些請求的處理速度,因為在命中規則后會終止搜索。
  • ^~ 和空串的前綴匹配,區別在于,如果命中 ^~ 的規則,并且是最長前綴匹配,就會終止搜索正則匹配規則列表。
  • 除了命中精準匹配外,前綴字符串匹配列表都會被遍歷一遍,并且找到最長匹配的那條 location 規則,所以前綴字符串匹配和在文件中的位置無關,但是和匹配長度有關
  • 由于正則匹配的時間、資源消耗較多,所以 nginx 在對 location 規則進行正則匹配時,命中一個就直接使用了,所以正則匹配和 location 規則在文件中的位置有關

實踐篇

從上面的理論篇中,大家應該能大致了解到 nginx 是如何命中 location 規則的,為了加深大家記憶,同時也為了能佐證理論是對的,我們來一些實踐吧。

我們的模板是這樣的,后面所有的實踐內容,都是放到兩個 rule section 之間。

server {
    listen	9001;
    location / {
        default_type text/html;
        return 200 'hello world';
    }
    # ===== rule section ======
    # ===== rule section ======

精準匹配優先級最高

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 規則,隨后在瀏覽器輸入 localhost:9001/test,發現輸出內容是 = /test,符合我們的預期,= 選項的優先級最高。

正則匹配和順序有關

location ~ /test {
   default_type text/html;
   return 200 '~ /test';
}

location ~ /test/*/demo {
   default_type text/html;
   return 200 '~ /test/*/demo';
}

隨后我們將 rule section 區塊替換成 ~ /test~ /test/*/demo 規則,在瀏覽器輸入 localhost:9001/test/xyz/demo,從正則角度來說,/test/xyz/demo 既滿足 /test規則,又滿足 /test/*/demo 規則,但是由于 ~ /test 在文件中位置靠前,所以優先被命中,理論上應該會輸出 ~ /test,校驗后發現,確實是這樣。

正則、^~前綴匹配、空前綴匹配混搭

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 區域替換成上述內容,然后在瀏覽器中輸入 localhost:9001/test/more/andmore,猜猜會發生什么?

我們來一起分析下:

  • 沒有精準匹配的規則
  • 找到最長的前綴字符串匹配規則是 /test/more/andmore,它不是 ^~ 選項,所以會遍歷正則匹配規則
  • 發現 ~ /test 滿足匹配規則,直接應用該規則,所以會輸出 location ~ /test 規則塊的內容,也就是 ~ /test

我們再來試一下,在瀏覽器輸入 localhost:9001/test/more,會顯示什么呢?

  • 沒有精準匹配規則
  • 最長前綴字符串匹配規則是 ^~ /test,是 ^~ 選項,所以不用遍歷正則匹配規則列表,所以頁面會顯示 ^~ /test

通過校驗后發現,上述分析的結果和實際顯示結果是一樣的。

上面幾個實踐都比較簡單,大家也可以嘗試各種組合,然后按照上面的分析步驟來檢驗下自己是不是真的理解了 location

五、總結

看完上面的介紹,相信大家對 location 規則的處理邏輯都有一定的了解,也應該明白為什么在文章開頭說曾經看到的結論對、也不對了。

如果要用對 location 順序進行總結的話,可以在原有的基礎上適當的進行一些擴展:

  • 精準匹配 =
  • 前綴匹配 ^~如果該前綴匹配是最長前綴匹配規則,則應用
  • 正則匹配 ~~*和該規則在文件中的順序有關,執行順序從上到下
  • 不帶修飾符的前綴匹配,和匹配規則的長度有關,只會應用最長的匹配規則,與在文件中的順序無關

參考鏈接:nginx.org/en/docs/htt…

原文鏈接:https://juejin.cn/post/7102064180563279908

欄目分類
最近更新