Nginx 配置指令的執行順序(六)
前面我們在 (五) 中提到,在一個 location
中使用 content
階段指令時,通常情況下就是對應的 Nginx 模塊注冊該 location
中的“內容處理程序”。那么當一個 location
中未使用任何 content
階段的指令,即沒有模塊注冊“內容處理程序”時,content
階段會發生什么事情呢?誰又來擔負起生成內容和輸出響應的重擔呢?答案就是那些把當前請求的 URI 映射到文件系統的靜態資源服務模塊。當存在“內容處理程序”時,這些靜態資源服務模塊并不會起作用;反之,請求的處理權就會自動落到這些模塊上。
Nginx 一般會在 content
階段安排三個這樣的靜態資源服務模塊(除非你的 Nginx 在構造時顯式禁用了這三個模塊中的一個或者多個,又或者啟用了這種類型的其他模塊)。按照它們在 content
階段的運行順序,依次是 ngx_index 模塊,ngx_autoindex 模塊,以及 ngx_static
模塊。下面就來逐一介紹一下這三個模塊。
ngx_index 和 ngx_autoindex 模塊都只會作用于那些 URI 以 /
結尾的請求,例如請求 GET /cats/
,而對于不以 /
結尾的請求則會直接忽略,同時把處理權移交給 content
階段的下一個模塊。而 ngx_static
模塊則剛好相反,直接忽略那些 URI 以 /
結尾的請求。
ngx_index 模塊主要用于在文件系統目錄中自動查找指定的首頁文件,類似 index.html
和 index.htm
這樣的,例如:
location / {
root /var/www/;
index index.htm index.html;
}
這樣,當用戶請求 /
地址時,Nginx 就會自動在 root 配置指令指定的文件系統目錄下依次尋找 index.htm
和index.html
這兩個文件。如果 index.htm
文件存在,則直接發起“內部跳轉”到 /index.htm
這個新的地址;而如果 index.htm
文件不存在,則繼續檢查 index.html
是否存在。如果存在,同樣發起“內部跳轉”到/index.html
;如果 index.html
文件仍然不存在,則放棄處理權給 content
階段的下一個模塊。
我們前面已經在 Nginx 變量漫談(二) 中提到, echo_exec 指令和 rewrite 指令可以發起“內部跳轉”。這種跳轉會自動修改當前請求的 URI,并且重新匹配與之對應的 location
配置塊,再重新執行rewrite
、access
、content
等處理階段。因為是“內部跳轉”,所以有別于 HTTP 協議中定義的基于 302 和 301 響應的“外部跳轉”,最終用戶的瀏覽器的地址欄也不會發生變化,依然是原來的 URI 位置。而ngx_index 模塊一旦找到了 index 指令中列舉的文件之后,就會發起這樣的“內部跳轉”,仿佛用戶是直接請求的這個文件所對應的 URI 一樣。
為了進一步確認 ngx_index 模塊在找到文件時的“內部跳轉”行為,我們不妨設計下面這個小例子:
location / {
root /var/www/;
index index.html;
}
location /index.html {
set $a 32;
echo "a = $a";
}
此時我們在本機的 /var/www/
目錄下創建一個空白的 index.html
文件,并確保該文件的權限設置對于運行 Nginx worker 進程的帳戶可讀。然后我們來請求一下根位置(/
):
$ curl 'http://localhost:8080/'
a = 32
這里發生了什么?為什么輸出不是 index.html
文件的內容(即空白)?首先對于用戶的原始請求 GET /
,Nginx 匹配出 location /
來處理它,然后 content
階段的 ngx_index 模塊在 /var/www/
下找到了index.html
,于是立即發起一個到 /index.html
位置的“內部跳轉”。
到這里,相信大家都不會有問題。接下來有趣的事情發生了!在重新為 /index.html
這個新位置匹配location
配置塊時,location /index.html
的優先級要高于 location /
,因為 location
塊按照 URI 前綴來匹配時遵循所謂的“最長子串匹配語義”。這樣,在進入 location /index.html
配置塊之后,又重新開始執行rewrite
、access
、以及 content
等階段。最終輸出 a = 32
自然也就在情理之中了。
我們接著研究上面這個例子。如果此時把 /var/www/index.html
文件刪除,再訪問 /
又會發生什么事情呢?答案是返回 403 Forbidden
出錯頁。為什么呢?因為 ngx_index 模塊找不到 index 指令指定的文件(在這里就是 index.html
),接著把處理權轉給 content
階段的后續模塊,而后續的模塊也都無法處理這個請求,于是 Nginx 只好放棄,輸出了錯誤頁,并且在 Nginx 錯誤日志中留下了類似這一行信息:
[error] 28789#0: *1 directory index of "/var/www/" is forbidden
所謂 directory index
便是生成“目錄索引”的意思,典型的方式就是生成一個網頁,上面列舉出 /var/www/
目錄下的所有文件和子目錄。而運行在 ngx_index 模塊之后的 ngx_autoindex 模塊就可以用于自動生成這樣的“目錄索引”網頁。我們來把上例修改一下:
location / {
root /var/www/;
index index.html;
autoindex on;
}
此時仍然保持文件系統中的 /var/www/index.html
文件不存在。我們再訪問 /
位置時,就會得到一張漂亮的網頁:
$ curl 'http://localhost:8080/'
<html>
<head><title>Index of /</title></head>
<body bgcolor="white">
<h1>Index of /</h1><hr><pre><a href="../">../</a>
<a href="cgi-bin/">cgi-bin/</a> 08-Mar-2010 19:36 -
<a href="error/">error/</a> 08-Mar-2010 19:36 -
<a href="htdocs/">htdocs/</a> 05-Apr-2010 03:55 -
<a href="icons/">icons/</a> 08-Mar-2010 19:36 -
</pre><hr></body>
</html>
生成的 HTML 源碼顯示,我本機的 /var/www/
目錄下還有 cgi-bin/
, error/
, htdocs/
, 以及 icons/
這幾個子目錄。在你的系統中嘗試上面的例子,輸出很可能會不太一樣。
值得一提的是,當你的文件系統中存在 /var/www/index.html
時,優先運行的 ngx_index 模塊就會發起“內部跳轉”,根本輪不到 ngx_autoindex 執行。感興趣的讀者可以自己測試一下。
在 content
階段默認“墊底”的最后一個模塊便是極為常用的 ngx_static
模塊。這個模塊主要實現服務靜態文件的功能。比方說,一個網站的靜態資源,包括靜態 .html
文件、靜態 .css
文件、靜態 .js
文件、以及靜態圖片文件等等,全部可以通過這個模塊對外服務。前面介紹的 ngx_index 模塊雖然可以在指定的首頁文件存在時發起“內部跳轉”,但真正把相應的首頁文件服務出去(即把該文件的內容作為響應體數據輸出,并設置相應的響應頭),還是得靠這個 ngx_static
模塊來完成。