天天看點

Nginx之location 比對規則詳解

1、 location 的比對順序是“先比對正則,再比對普通”。

矯正: location 的比對順序其實是“先比對普通,再比對正則”。我這麼說,大家一定會反駁我,因為按“先比對普通,再比對正則”解釋不了大家平時習慣的按“先比對正則,再比對普通”的實踐經驗。這裡我隻能暫時解釋下,造成這種誤解的原因是:正則比對會覆寫普通比對(實際的規則,比這複雜,後面會詳細解釋)。

2、 location 的執行邏輯跟 location 的編輯順序無關。

矯正:這句話不全對,“普通 location ”的比對規則是“最大字首”,是以“普通 location ”的确與 location 編輯順序無關;但是“正則 location ”的比對規則是“順序比對,且隻要比對到第一個就停止後面的比對”;“普通location ”與“正則 location ”之間的比對順序是?先比對普通 location ,再“考慮”比對正則 location 。注意這裡的“考慮”是“可能”的意思,也就是說比對完“普通 location ”後,有的時候需要繼續比對“正則 location ”,有的時候則不需要繼續比對“正則 location ”。兩種情況下,不需要繼續比對正則 location :( 1 )當普通 location 前面指定了“ ^~ ”,特别告訴 Nginx 本條普通 location 一旦比對上,則不需要繼續正則比對;( 2 )當普通location 恰好嚴格比對上,不是最大字首比對,則不再繼續比對正則。

總結一句話:  “正則 location 比對讓步普通 location 的嚴格精确比對結果;但覆寫普通 location 的最大字首比對結果”

location

syntax: location [=|~|~*|^~|@] /uri/ { … }

default: no

context: server

This directive allows different configurations depending on the URI.

(譯者注:1 、different configurations depending on the URI 說的就是文法格式:location [=|~|~*|^~|@] /uri/ { … } ,依據不同的字首“= ”,“^~ ”,“~ ”,“~* ”和不帶任何字首的(因為[A] 表示可選,可以不要的),表達不同的含義, 簡單的說盡管location 的/uri/ 配置一樣,但字首不一樣,表達的是不同的指令含義。2 、查詢字元串不在URI範圍内。例如:/films.htm?fid=123 的URI 是/films.htm 。)

It can be configured using both literal strings and regular expressions. To use regular expressions, you must use a prefix:

“~” for case sensitive matching

“~*” for case insensitive matching

譯文:上文講到location /uri/ 可通過使用不同的字首,表達不同的含義。對這些不同字首,分下類,就2 大類:正則location ,英文說法是location using regular expressions 和普通location ,英文說法是location using literal strings 。那麼其中“~ ”和“~* ”字首表示正則location ,“~ ”區分大小寫,“~* ”不區分大小寫;其他字首(包括:“=”,“^~ ”和“@ ”)和無任何字首的都屬于普通location 。

To determine which location directive matches a particular query, the literal strings are checked first.

譯文:對于一個特定的 HTTP 請求( a particular query ), nginx 應該比對哪個 location 塊的指令呢(注意:我們在 nginx.conf 配置檔案裡面一般會定義多個 location 的)?比對 規則是:先比對普通location (再比對正規表達式)。注意:官方文檔這句話就明确說了,先普通location ,而不是有些同學的誤區“先比對正則location ”。

Literal strings match the beginning portion of the query – the most specific match will be used.

前面說了“普通location ”與“正則location ”之間的比對規則是:先比對普通location ,再比對正則location 。那麼,“普通location ”内部(普通location 與普通location )是如何比對的呢?簡單的說:最大字首比對。原文:1、match the beginning portion of the query (說的是比對URI 的字首部分beginning portion ); 2 、the most specific match will be used (因為location 不是“嚴格比對”,而是“字首比對”,就會産生一個HTTP 請求,可以“字首比對”到多個普通location ,例如:location /prefix/mid/ {} 和location /prefix/ {} ,對于HTTP 請求/prefix/mid/t.html ,字首比對的話兩個location 都滿足,選哪個?原則是:the most specific match ,于是選的是location /prefix/mid/ {} )。

Afterwards, regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search.

這段話說了兩層意思,第一層是:“Afterwards, regular expressions are checked ”, 意思是普通location 先比對,而且選擇了最大字首比對後,不能就停止後面的比對,最大字首比對隻是一個臨時的結果,nginx 還需要繼續檢查正則location (但至于最終才能普通location 的最大字首比對,還是正則location 的比對,截止目前的内容還沒講,但後面會講)。第二層是“regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search. ”,意思是說“正則location ”與“正則location”内部的比對規則是:按照正則location 在配置檔案中的實體順序(編輯順序)比對的(這句話就說明location 并不是一定跟順序無關,隻是普通location 與順序無關,正則location 還是與順序有關的),并且隻要比對到一條正則location ,就不再考慮後面的(這與“普通location ”與“正則location ”之間的規則不一樣,“普通location ”與“正則location ”之間的規則是:選擇出“普通location ”的最大字首比對結果後,還需要繼續搜尋正則location )。

If no regular expression matches are found, the result from the literal string search is used.

這句話回答了“普通location ”的最大字首比對結果與繼續搜尋的“正則location ”比對結果的決策關系。如果繼續搜尋的“正則location ”也有比對上的,那麼“正則location ”覆寫 “普通location ”的最大字首比對(因為有這個覆寫關系,是以造成有些同學以為正則location 先于普通location 執行的錯誤了解);但是如果“正則location ”沒有能比對上,那麼就用“普通location ”的最大字首比對結果。

For case insensitive operating systems, like Mac OS X or Windows with Cygwin, literal string matching is done in a case insensitive way (0.7.7). However, comparison is limited to single-byte locale’s only.

Regular expression may contain captures (0.7.40), which can then be used in other directives.

It is possible to disable regular expression checks after literal string matching by using “^~” prefix.If the most specific match literal location has this prefix: regular expressions aren’t checked.

通常的規則是,比對完了“普通location ”指令,還需要繼續比對“正則location ”,但是你也可以告訴Nginx :比對到了“普通location ”後,不再需要繼續比對“正則location ”了,要做到這一點隻要在“普通location ”前面加上“^~ ”符号(^ 表示“非”,~ 表示“正則”,字元意思是:不要繼續比對正則)。

By using the “=” prefix we define the exact match between request URI and location. When matched search stops immediately. E.g., if the request “/” occurs frequently, using “location = /” will speed up processing of this request a bit as search will stop after first comparison.

除了上文的“^~ ”可以阻止繼續搜尋正則location 外,你還可以加“= ”。那麼如果“^~ ”和“= ”都能阻止繼續搜尋正則location 的話,那它們之間有什麼差別呢?差別很簡單,共同點是它們都能阻止繼續搜尋正則location ,不同點是“^~ ”依然遵守“最大字首”比對規則,然而“= ”不是“最大字首”,而是必須是嚴格比對(exact match )。

On exact match with literal location without “=” or “^~” prefixes search is also immediately terminated.

前面我們說了,普通location 比對完後,還會繼續比對正則location ;但是nginx 允許你阻止這種行為,方法很簡單,隻需要在普通location 前加“^~ ”或“= ”。但其實還有一種“隐含”的方式來阻止正則location 的搜尋,這種隐含的方式就是:當“最大字首”比對恰好就是一個“嚴格精确(exact match )”比對,照樣會停止後面的搜尋。原文字面意思是:隻要遇到“精确比對exact match ”,即使普通location 沒有帶“= ”或“^~ ”字首,也一樣會終止後面的比對。

先舉例解釋下,後面例題會用實踐告訴大家。假設目前配置是:location /exact/match/test.html { 配置指令塊1},location /prefix/ { 配置指令塊2} 和 location ~ \.html$ { 配置指令塊3} ,如果我們請求 GET /prefix/index.html ,則會被比對到指令塊3 ,因為普通location /prefix/ 依據最大比對原則能比對目前請求,但是會被後面的正則location 覆寫;當請求GET /exact/match/test.html ,會比對到指令塊1 ,因為這個是普通location 的exact match ,會禁止繼續搜尋正則location 。

To summarize, the order in which directives are checked is as follows:

Directives with the “=” prefix that match the query exactly. If found, searching stops.

All remaining directives with conventional strings. If this match used the “^~” prefix, searching stops.

Regular expressions, in the order they are defined in the configuration file.

If #3 yielded a match, that result is used. Otherwise, the match from #2 is used.

這個順序沒必要再過多解釋了。但我想用自己的話概括下上面的意思“正則 location 比對讓步普通location 的嚴格精确比對結果;但覆寫普通 location 的最大字首比對結果”。

It is important to know that nginx does the comparison against decoded URIs. For example, if you wish to match “/images/ /test”, then you must use “/images/ /test” to determine the location.

在浏覽器上顯示的URL 一般都會進行URLEncode ,例如“空格”會被編碼為  ,但是Nginx 的URL 的比對都是針對URLDecode 之後的。也就是說,如果你要比對“/images/ /test ”,你寫location 的時候比對目标應該是:“/images/ /test ”。

Example:

 # matches the query / only.

 [ configuration A ]

}

  # matches any query, since all queries begin with /, but regular

  # expressions and any longer conventional blocks will be

  # matched first.

 [ configuration B ]

    # matches any query beginning with /images/ and halts searching,

 # so regular expressions will not be checked.

 [ configuration C ]

 # matches any request ending in gif, jpg, or jpeg. However, all

 # requests to the /images/ directory will be handled by

 # Configuration C.  

 [ configuration D ]

上述這4 個location 的配置,沒什麼好解釋的,唯一需要說明的是location / {[configuration B]} ,原文的注釋嚴格來說是錯誤的,但我相信原文作者是了解規則的,隻是文字描述上簡化了下,但這個簡化容易給讀者造成“誤解:先檢查正則location ,再檢查普通location ”。原文:“matches any query, since all queries begin with /, butregular expressions and any longer conventional blocks will be matched first. ”大意是說:“location / {} 能夠比對所有HTTP 請求,因為任何HTTP 請求都必然是以‘/ ’開始的(這半句沒有錯誤)。但是,正則location 和其他任何比‘/ ’更長的普通location (location / {} 是普通location 裡面最短的,是以其他任何普通location 都會比它更長,當然location = / {} 和 location ^~ / {} 是一樣長的)會優先比對(matched first )。” 原文作者說“ but regular expressions will be matched first. ”應該隻是想說正則 location 會覆寫這裡的 location / {} ,但依然是普通location / {} 先于正則 location 比對,接着再正則 location 比對;但其他更長的普通 location ( any longer conventional blocks )的确會先于 location / {} 比對。

Example requests:

/ -> configuration A

/documents/document.html -> configuration B

/images/1.gif -> configuration C

/documents/1.jpg -> configuration D

Note that you could define these 4 configurations in any order and the results would remain the same.

需要提醒下:這裡說“in any order ”和“… remain the same ”是因為上面隻有一個正則location 。文章前面已經說了正則location 的比對是跟編輯順序有關系的。

While nested locations are allowed by the configuration file parser, their use is discouraged and may produce unexpected results.

實際上 nginx 的配置檔案解析程式是允許 location 嵌套定義的( location / { location /uri/ {} } )。但是我們平時卻很少看見這樣的配置,那是因為 nginx 官方并不建議大家這麼做,因為這樣會導緻很多意想不到的後果。

文章開始說了location 的文法中,可以有“= ”,“^~ ”,“~ ”和“~* ”字首,或者幹脆沒有任何字首,還有“@ ”字首,但是後面的分析我們始終沒有談到“@ ”字首。文章最後點内容,介紹了“@”的用途:“@ ”是用來定義“Named Location ”的(你可以了解為獨立于“普通location (location using literal strings )”和“正則location (location using regular expressions )”之外的第三種類型),這種“Named Location ”不是用來處理普通的HTTP 請求的,它是專門用來處理“内部重定向(internally redirected )”請求的。注意:這裡說的“内部重定向(internally redirected )”或許說成“forward ”會好點,以為内internally redirected 是不需要跟浏覽器互動的,純粹是服務端的一個轉發行為。

tar zxvf nginx-1.1.0.tar.gz

./configure

make

make install

需要注意的是在 configure 這步遇到點小麻煩:

./configure: error: the HTTP rewrite module requires the PCRE library.

安裝 nginx 的時候, rewrite 子產品預設是需要被安裝的。但是 rewrite 子產品所依賴的 PCRE 庫需要額外安裝。

You can either disable the module by using –without-http_rewrite_module

option, or install the PCRE library into the system, or build the PCRE library

statically from the source with nginx by using –with-pcre=<path> option.

先執行: yum -y install pcre-devel openssl openssl-devel  把依賴的東西安裝上。

備注: PCRE (Perl Compatible Regular Expressions )是perl語言正規表達式。 Nginx的rewrite的正規表達式采用的是Perl文法集(常識:正規表達式有不同的文法集,我們常見的grep指令如果需要采用Perl文法集,需要grep -P 來指定)。

location 執行個體練習

Nginx 的文法形式是: location [=|~|~*|^~|@] /uri/ { … } ,意思是可以以“ = ”或“ ~* ”或“ ~ ”或“ ^~ ”或“ @ ”符号為字首,當然也可以沒有字首(因為 [A] 是表示可選的 A ; A|B 表示 A 和 B 選一個),緊接着是 /uri/ ,再接着是{…} 指令塊,整個意思是對于滿足這樣條件的 /uri/ 适用指令塊 {…} 的指令。

上述各種 location 可分兩大類,分别是:“普通 location ”,官方英文說法是 location using   literal strings 和“正則 location ”,英文說法是 location using regular expressions 。其中“普通 location ”是以“ = ”或“ ^~ ”為字首或者沒有任何字首的 /uri/ ;“正則 location ”是以“ ~ ”或“ ~* ”為字首的 /uri/ 。

那麼,當我們在一個 server 上下文編寫了多個 location 的時候, Nginx 對于一個 HTTP 請求,是如何比對到一個 location 做處理呢?用一句話簡單概括 Nginx 的 location 比對規則是:“正則 location ”讓步 “普通 location”的嚴格精确比對結果;但覆寫 “普通 location ”的最大字首比對結果。了解這句話,我想通過下面的執行個體來說明。

周邊不少童鞋告訴我, nginx 是“先比對正則 location 再比對普通 location ”,其實這是一個誤區, nginx 其實是“先比對普通 location ,再比對正則 location ”,但是普通 location 的比對結果又分兩種:一種是“嚴格精确比對”,官方英文說法是“ exact match ”;另一種是“最大字首比對”,官方英文說法是“ Literal strings match the beginning portion of the query – the most specific match will be used. ”。我們做個實驗:

例題 1 :假設 nginx 的配置如下

server {

       listen       9090;

       server_name  localhost;

                  location / {

           root   html;

           index  index.html index.htm;

           deny all;

       }

       location ~ \.html$ {

           allow all;

附錄 nginx 的目錄結構是: nginx->html->index.html

上述配置的意思是: location / {… deny all;} 普通 location 以“ / ”開始的 URI 請求(注意任何 HTTP 請求都必然以“/ ”開始,是以“ / ”的意思是所有的請求都能被比對上),都拒絕通路; location ~\.html$ {allow all;} 正則 location以 .html 結尾的 URI 請求,都允許通路。

測試結果:

[root@web108 ~]# curl http://localhost:9090/

<html>

<head><title>403 Forbidden</title></head>

<body bgcolor=”white”>

<center><h1>403 Forbidden</h1></center>

<hr><center>nginx/1.1.0</center>

</body>

</html>

[root@web108 ~]# curl http://localhost:9090/index.html

<head>

<title>Welcome to nginx!</title>

</head>

<body bgcolor=”white” text=”black”>

<center><h1>Welcome to nginx!</h1></center>

[root@web108 ~]# curl http://localhost:9090/index_notfound.html

<head><title>404 Not Found</title></head>

<center><h1>404 Not Found</h1></center>

[root@web108 ~]#

測試結果如下:

URI 請求

HTTP 響應

curl http://localhost:9090/

403 Forbidden

curl http://localhost:9090/index.html

Welcome to nginx!

curl http://localhost:9090/index_notfound.html

404 Not Found

[root@web108 ~]# curl http://localhost:9090/index.txt

例題 2 :我們在例題 1 的基礎上增加精确配置

                  location /exact/match.html {

測試請求:

[root@web108 ~]# curl http://localhost:9090/exact/match.html

結果進一步驗證了“普通 location ”的“嚴格精确”比對會終止對正則 location 的搜尋。這裡我們小結下“普通 location”與“正則 location ”的比對規則:先比對普通 location ,再比對正則 location ,但是如果普通 location 的比對結果恰好是“嚴格精确( exact match )”的,則 nginx 不再嘗試後面的正則 location ;如果普通 location 的比對結果是“最大字首”,則正則 location 的比對覆寫普通 location 的比對。也就是前面說的“正則 location 讓步普通location 的嚴格精确比對結果,但覆寫普通 location 的最大字首比對結果”。

上面我們示範的普通 location 都是不加任何字首的,其實普通 location 也可以加字首:“ ^~ ”和“ = ”。其中“ ^~”的意思是“非正則,不需要繼續正則比對”,也就是通常我們的普通 location ,還會繼續搜尋正則 location (恰好嚴格精确比對除外),但是 nginx 很人性化允許配置人員告訴 nginx 某條普通 location ,無論最大字首比對,還是嚴格精确比對都終止繼續搜尋正則 location ;而“ = ”則表達的是普通 location 不允許“最大字首”比對結果,必須嚴格等于,嚴格精确比對。

例題 3 :“ ^~ ”字首的使用

                 location ^~ / {

把例題 2 中的 location / {} 修改成 location ^~ / {} ,再看看測試結果:

修改前

修改後

curl http://localhost:9090/exact/match.html

除了 GET /exact/match.html 是 404 Not Found ,其餘都是 403 Forbidden ,原因很簡單所有請求都是以“ / ”開頭,是以所有請求都能比對上“ / ”普通 location ,但普通 location 的比對原則是“最大字首”,是以隻有/exact/match.html 比對到 location /exact/match.html {allow all;} ,其餘都 location ^~ / {deny all;} 并終止正則搜尋。

例題 4 :“ = ”字首的使用

                 location = / {

例題 4 相對例題 2 把 location / {} 修改成了 location = / {} ,再次測試結果:

curl http://localhost:9090/test.jsp

最能說明問題的測試是 GET /test.jsp ,實際上 /test.jsp 沒有比對正則 location ( location ~\.html$ ),也沒有比對 location = / {} ,如果按照 location / {} 的話,會“最大字首”比對到普通 location / {} ,結果是 deny all 。

location 的指令與編輯順序無關,這句話不全對。對于普通 location 指令,比對規則是:最大字首比對(與順序無關),如果恰好是嚴格精确比對結果或者加有字首“ ^~ ”或“ = ”(符号“ = ”隻能嚴格比對,不能字首比對),則停止搜尋正則 location ;但對于正則 location 的比對規則是:按編輯順序逐個比對(與順序有關),隻要比對上,就立即停止後面的搜尋。

配置 3.1

           allow all; 

       }  

       location ~ ^/prefix/.*\.html$ {

           deny all;  

配置 3.2

                  location ~ \.html$ {

       } 

curl http://localhost:9090/regextest.html

curl http://localhost:9090/prefix/regextest.html

解釋:

Location ~ ^/prefix/.*\.html$ {deny all;} 表示正則 location 對于以 /prefix/ 開頭, .html 結尾的所有 URI 請求,都拒絕通路;   location ~\.html${allow all;} 表示正則 location 對于以 .html 結尾的 URI 請求,都允許通路。 實際上,prefix 的是 ~\.html$ 的子集。

在“配置 3.1 ”下,兩個請求都比對上 location ~\.html$ {allow all;} ,并且停止後面的搜尋,于是都允許通路, 404 Not Found ;在“配置 3.2 ”下, /regextest.html 無法比對 prefix ,于是繼續搜尋 ~\.html$ ,允許通路,于是 404 Not Found ;然而 /prefix/regextest.html 比對到 prefix ,于是 deny all , 403 Forbidden 。

配置 3.3

       location  /prefix/ {

               deny all;  

       location  /prefix/mid/ {

               allow all; 

配置 3.4

                  location  /prefix/ {

curl http://localhost:9090/prefix/t.html

curl http://localhost:9090/prefix/mid/t.html

測試結果表明:普通 location 的比對規則是“最大字首”比對,而且與編輯順序無關。

假設配置如下:

        location  / {

       error_page 404 = @fallback;

       location @fallback {

           proxy_pass http://www.baidu.com;

上述配置檔案的意思是:如果請求的 URI 存在,則本 nginx 傳回對應的頁面;如果不存在,則把請求代理到baidu.com 上去做個彌補(注: nginx 當發現 URI 對應的頁面不存在, HTTP_StatusCode 會是 404 ,此時error_page 404 指令能捕獲它)。

測試一:

[root@web108 ~]# curl http://localhost:9090/nofound.html -i

HTTP/1.1 302 Found

Server: nginx/1.1.0

Date: Sat, 06 Aug 2011 08:17:21 GMT

Content-Type: text/html; charset=iso-8859-1

Location: http://localhost:9090/search/error.html

Connection: keep-alive

Cache-Control: max-age=86400

Expires: Sun, 07 Aug 2011 08:17:21 GMT

Content-Length: 222

<!DOCTYPE HTML PUBLIC “-//IETF//DTD HTML 2.0//EN”>

<html><head>

<title>302 Found</title>

</head><body>

<h1>Found</h1>

<p>The document has moved <a href=”http://www.baidu.com/search/error.html”>here</a>.</p>

</body></html>

[root@web108 ~]# curl http://www.baidu.com/nofound.html -i

Date: Sat, 06 Aug 2011 08:20:05 GMT

Server: Apache

Location: http://www.baidu.com/search/error.html

Expires: Sun, 07 Aug 2011 08:20:05 GMT

Connection: Keep-Alive

測試二:通路一個 nginx 不存在,但 baidu 存在的頁面

[root@web108 ~]# curl http://www.baidu.com/duty/ -i

HTTP/1.1 200 OK

Date: Sat, 06 Aug 2011 08:21:56 GMT

P3P: CP=” OTI DSP COR IVA OUR IND COM ”

Set-Cookie: BAIDUID=5C5D2B2FD083737A0C88CA7075A6601A:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Set-Cookie: BAIDUID=5C5D2B2FD083737A2337F78F909CCB90:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT

ETag: “d66-49913b8efe340″

Accept-Ranges: bytes

Content-Length: 3430

Expires: Sun, 07 Aug 2011 08:21:56 GMT

Vary: Accept-Encoding,User-Agent

Content-Type: text/html

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”

“http://www.w3.org/TR/html4/loose.dtd”>

。。。。

顯示,的确百度這個頁面是存在的。

[root@web108 ~]# curl http://localhost:9090/duty/ -i

Date: Sat, 06 Aug 2011 08:23:23 GMT

Set-Cookie: BAIDUID=8FEF0A3A2C31D277DCB4CC5F80B7F457:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Set-Cookie: BAIDUID=8FEF0A3A2C31D277B1F87691AFFD7440:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Expires: Sun, 07 Aug 2011 08:23:23 GMT

。。。

當 curl http://localhost:9090/duty/ -i 時, nginx 沒找到對應的頁面,于是 error_page = @fallback ,把請求代理到 baidu.com 。注意這裡的 error_page = @fallback 不是靠重定向實作的,而是所說的“ internally redirected (forward )”。

繼續閱讀