執行在post-rewrite
階段之後的是所謂的preaccess
階段。該階段在access
階段之前執行,故名preaccess
.
標準模組 ngx_limit_req 和 ngx_limit_zone 就執行在此階段,前者可以控制請求的訪問頻度,而後者可以限制訪問的併發度。這裡我們僅僅和它們打個照面,後面還會有機會專門接觸到這兩個模組。
前面反覆提到的標準模組 ngx_realip 其實也在這個階段註冊了處理程式。有些讀者可能會問:「這是為什麼呢?它不是已經在post-read
階段註冊處理程式了嗎?」我們不妨通過下面這個例子來揭曉答案:
server}
與先看前到的例子相比,此例最重要的區別在於把 ngx_realip 的配置指令放在了location
配置塊中。前面我們介紹過,nginx 匹配location
的動作發生在find-config
階段,而find-config
階段遠遠晚於post-read
階段執行,所以在post-read
階段,當前請求還沒有和任何location
相關聯。在這個例子中,因為ngx_realip 的配置指令都寫在了location
配置塊中,所以在post-read
階段,ngx_realip 模組的處理程式沒有看到任何可用的配置資訊,便不會執行**位址的改寫工作了。
為了解決這個難題,ngx_realip 模組便又特意在preaccess
階段註冊了處理程式,這樣它才有機會執行location
塊中的配置指令。正是因為這個緣故,上面這個例子的執行結果才符合直覺預期:
$ curl -h 'x-real-ip: 1.2.3.4' localhost:8080/testfrom: 1.2.3.4
不幸的是,ngx_realip 模組的這個解決方案還是存在漏洞的,比如下面這個例子:
server}
這裡,我們在rewrite
階段將 $remote_addr 的值儲存到了使用者變數$addr
中,然後再輸出。因為rewrite
階段先於preaccess
階段執行,所以當 ngx_realip 模組尚未在preaccess
階段改寫**位址時,最初的**位址就已經在rewrite
階段被讀取了。上例的實際請求結果證明了我們的結論:
其中第一行除錯資訊
是 set 語句讀取 $remote_addr 變數時產生的。資訊中的字串"127.0.0.1"
便是 $remote_addr 當時讀出來的值。
而第二行除錯資訊
則顯示我們對變數$addr
進行了賦值操作。
後面兩行資訊
[debug] 32488#0: *1 realip: "1.2.3.4"[debug] 32488#0: *1 realip: 0100007f ffffffff 0100007f
是 ngx_realip 模組在preaccess
階段改寫當前請求的**位址。我們看到,改寫後的新位址確實是期望的1.2.3.4
. 但很明顯這個操作發生在$addr
變數賦值之後,所以已經太遲了。
而最後一行資訊
則是 echo 配置指令在輸出時讀取變數$addr
時產生的,我們看到它的值是改寫前的**位址。
看到這裡,有的讀者可能會問:「如果 ngx_realip 模組不在preaccess
階段註冊處理程式,而在rewrite
階段註冊,那麼上例不就可以工作了?」答案是:不一定。因為 ngx_rewrite 模組的處理程式也同樣註冊在rewrite
階段,而前面我們在 (二) 中特別提到,在這種情況下,不同模組之間的執行順序一般是不確定的,所以 ngx_realip 的處理程式可能仍然在 set 語句之後執行。
乙個建議是:盡量在server
配置塊中配置 ngx_realip 這樣的模組,以避免上面介紹的這種棘手的例外情況。
執行在preaccess
階段之後的則是我們的另乙個老朋友,access
階段。前面我們已經知道了,標準模組ngx_access、第三方模組 ngx_auth_request 以及第三方模組 ngx_lua 的 access_by_lua 指令就執行在這個階段。
access
階段之後便是post-access
階段。從這個階段的名字,我們也能一眼看出它是緊跟在access
階段後面執行的。這個階段也和post-rewrite
階段類似,並不支援 nginx 模組註冊處理程式,而是由 nginx 核心自己完成一些處理工作。post-access
階段主要用於配合access
階段實現標準 ngx_http_core 模組提供的配置指令 satisfy 的功能。
對於多個 nginx 模組註冊在access
階段的處理程式,satisfy 配置指令可以用於控制它們彼此之間的協作方式。比如模組 a 和 b 都在access
階段註冊了與訪問控制相關的處理程式,那就有兩種協作方式,一是模組 a 和模組 b 都得通過驗證才算通過,二是模組 a 和模組 b 只要其中任乙個通過驗證就算通過。第一種協作方式稱為all
方式(或者說「與關係」),第二種方式則被稱為any
方式(或者說「或關係」)。預設情況下,nginx 使用的是all
方式。下面是乙個例子:
location /test
這裡,我們在/test
介面中同時配置了 ngx_access 模組和 ngx_lua 模組,這樣access
階段就由這兩個模組一起來做檢驗工作。其中,語句deny all
會讓 ngx_access 模組的處理程式總是拒絕當前請求,而語句access_by_lua 'ngx.exit(ngx.ok)'
則總是允許訪問。當我們通過 satisfy 指令配置了all
方式時,就需要access
階段的所有模組都通過驗證,但不幸的是,這裡 ngx_access 模組總是會拒絕訪問,所以整個請求就會被拒:
$ curl localhost:8080/testnginx
細心的讀者會在 nginx 錯誤日誌檔案中看到類似下面這一行的出錯資訊:
[error] 6549\#0: *1 access forbidden by rule
然而,如果我們把上例中的satisfy all
語句更改為satisfy any
,
location /test
結果則會完全不同:
$ curl localhost:8080/testsomething important
即請求反而最終通過了驗證。這是因為在any
方式下,access
階段只要有乙個模組通過了驗證,就會認為請求整體通過了驗證,而在上例中,ngx_lua 模組的 access_by_lua 語句總是會通過驗證的。
在配置了satisfy any
的情況下,只有當access
階段的所有模組的處理程式都拒絕訪問時,整個請求才會被拒,例如:
location /test
此時訪問/test
介面才會得到403 forbidden
錯誤頁。這裡,post-access
階段參與了access
階段各模組處理程式的「或關係」的實現。
值得一提的是,上面這幾個的例子需要 ngx_lua 0.5.0rc19 或以上版本;之前的版本是不能和satisfy any
配置語句一起工作的。
Nginx 配置指令的執行順序(八)
前面我們詳細討論了rewrite access和content這三個最為常見的 nginx 請求處理階段,在此過程中,也順便介紹了執行在這三個階段的眾多 nginx 模組及其配置指令。同時可以看到,請求處理階段的劃分直接影響到了配置指令的執行順序,熟悉這些階段對於正確配置不同的 nginx 模組並實...
Nginx 配置指令的執行順序(七)
來看乙個ngx static模組服務磁碟檔案的例子。我們使用下面這個配置片段 location 同時在本機的 var www 目錄下建立兩個檔案,乙個檔案叫做index.html,內容是一行文字this is my home 另乙個檔案叫做hello.html,內容是一行文字hello world....
Nginx 配置指令的執行順序(八)
前面我們詳細討論了rewrite access和content這三個最為常見的 nginx 請求處理階段,在此過程中,也順便介紹了執行在這三個階段的眾多 nginx 模組及其配置指令。同時可以看到,請求處理階段的劃分直接影響到了配置指令的執行順序,熟悉這些階段對於正確配置不同的 nginx 模組並實...