天天看點

Nginx之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 ”的最大字首比對結果。了解這句話,我想通過下面的執行個體來說明。

#1 先普通 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

curl http://localhost:9090/ 的結果是“ 403 Forbidden ”,說明被比對到“ location / {..deny all;} ”了,原因很簡單HTTP 請求 GET / 被“嚴格精确”比對到了普通 location / {} ,則會停止搜尋正則 location ;

curl http://localhost:9090/index.html 結果是“ Welcome to nginx! ”,說明沒有被“ location / {…deny all;} ”比對,否則會 403 Forbidden ,但 /index.html 的确也是以“ / ”開頭的,隻不過此時的普通 location / 的比對結果是“最大字首”比對,是以 Nginx 會繼續搜尋正則 location , location ~ \.html$ 表達了以 .html 結尾的都 allow all; 于是接着就通路到了實際存在的 index.html 頁面。

curl http://localhost:9090/index_notfound.html   同樣的道理先比對 location / {} ,但屬于“普通 location 的最大字首比對”,于是後面被“正則 location ” location ~ \.html$ {} 覆寫了,最終 allow all ; 但的确目錄下不存在index_notfound.html 頁面,于是 404 Not Found 。

如果此時我們通路 http://localhost:9090/index.txt 會是什麼結果呢?顯然是 deny all ;因為先比對上了 location / {..deny all;} 盡管屬于“普通 location ”的最大字首比對結果,繼續搜尋正則 location ,但是 /index.txt 不是以 .html結尾的,正則 location 失敗,最終采納普通 location 的最大字首比對結果,于是 deny all 了。

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

#2 普通 location 的“隐式”嚴格比對

例題 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 的最大字首比對結果”。

#3 普通 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 。

#4 正則 location 與編輯順序

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 的比對規則是“最大字首”比對,而且與編輯順序無關。

#5 “@” 字首 Named Location 使用

REFER:  http://wiki.nginx.org/HttpCoreModule#error_page

假設配置如下:

        location  / {

       #error_page 404 http://www.baidu.com # 直接這樣是不允許的

       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>

當我們 GET /nofound.html 發送給本 nginx , nginx 找不到對應的頁面,于是 error_page 404 = @fallback ,請求被代理到 http://www.baidu.com ,于是 nginx 給 http://www.baidu.com 發送了 GET /nofound.html ,但/nofound.html 頁面在百度也不存在,百度 302 跳轉到錯誤頁。

直接通路 http://www.baidu.com/nofound.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

。。。