一、nginx的11個phases
一個請求經過nginx處理的過程中,會經過一系列的階段(phases),下面這個表格列出了nginx的所有phases,每個階段可選的退出方式,包含的模組和對應的指令
| phase |
optional exits |
modules / directives |
description |
| NGX_HTTP_POST_READ_PHASE |
|
HttpRealIpModule |
讀取請求內容階段 |
| NGX_HTTP_SERVER_REWRITE_PHASE (server rewrite) |
|
HttpRewriteModule / rewrite |
請求地址修正階段 |
| NGX_HTTP_FIND_CONFIG_PHASE(location selection) |
|
HttpCoreModule / location |
配置尋找階段 |
| NGX_HTTP_REWRITE_PHASE Location(location rewrite) |
location selection, finalize request |
HttpRewriteModule / rewrite |
請求地址修正階段 |
| NGX_HTTP_POST_REWRITE_PHASE |
|
HttpRewriteModule / rewrite |
請求地址修正提交階段 |
| NGX_HTTP_PREACCESS_PHASE |
|
degradation, NginxHttpLimitZoneModule / limit_zone, HttpLimitReqModule / limit req, HttpRealIpModule |
存取權限檢查準備階段 |
| NGX_HTTP_ACCESS_PHASE |
finalize request |
HttpAccessModule / allow, deny, NginxHttpAuthBasicModule / auth_basic |
存取權限檢查階段 |
| NGX_HTTP_POST_ACCESS_PHASE |
|
|
存取權限檢查提交階段 |
| NGX_HTTP_TRY_FILES_PHASE |
location selection |
HttpCoreModule / try_files |
配置項try_files處理階段 |
| NGX_HTTP_CONTENT_PHASE |
|
HttpAutoindexModule / autoindex,HttpCoreModule / Core, HttpDavModule / DAV, HttpEmptyGifModule / EmptyGif, HttpFcgiModule / FastCGI, HttpFlvStreamModul / FLV, HttpGzipStaticModule / gzip_static, HttpIndexModule / index, HttpMemcachedModule / memcached, EmbeddedPerlModule / perl, HttpProxyModule / proxy, HttpProxyModule / random_index, HttpScgiModule / scgi, HttpStubStatusModule / stub_status, HttpUwsgiModule / uwsgi HttpLuaModule / content_by_lua, HttpCoreModule / proxy_pass |
內容產生階段 |
| NGX_HTTP_LOG_PHASE |
|
HttpLogModuel / access_log |
日誌模組處理階段 |
二、各個phase說明:
post read phase:
讀完要求標頭後就進入了post_read 階段,它位於uri被重寫之前,這個階段允許nginx改變要求標頭中ip地址的值,相關模組HttpRealIpModule.
server_rewrite phase:
這個階段主要進行初始化全域變數,或者server層級的重寫。如果把重寫指令放到 server 中,那麼就進入了server rewrite 階段。(重寫指令見rewrite phase)
find config phase:
這個階段使用重寫之後的uri來尋找對應的location,值得注意的是該階段可能會被執行多次,因為也可能有location層級的重寫指令。
rewrite phase:
如果把重寫指令放到 location中,那麼就進入了rewrite phase,這個階段是location層級的uri重寫階段,重寫指令也可能會被執行多次;
重寫指令有 HttpRewriteModule 的set指令,rewrite指令,HttpLuaModule的 set_by_lua指令, ngx_set_misc模組的set_unescape_uri指令,另外HttpRewriteModule的幾乎所有指令都屬於rewrite階段。
到此,思考一個問題: 既然不用module的不同重寫指令到可以在這個phase,那麼這些指令是否可以在同一個location並存,如果可以,那麼他們的執行順序是怎麼樣的?
例子1 :
思考下面例子的輸出的結果是什嗎?
location /test { set $a 32; set $b 56; set_by_lua $c "return ngx.var.a + ngx.var.b"; set $d "$a + $b = $c"; echo $d; }
當我們訪問 http://localhost/test時,輸出結果為 32 + 56 = 88 ,應該是和我們預期一致的。
但是這並不能證明所有屬於同一個phases的不同的module的指令在一個phase中並存時一定是按照順序執行下來的。 事實上,上面提到的這些第三方模組都採用了特殊的技術,將它們自己的配置指令“注入”到了 HttpRewriteModule的指令序列中(它們都藉助了 Marcus Clyne 編寫的第三方模組 ngx_devel_kit)。換句話說,更多常規的在 Nginx 的 rewrite 階段註冊和運行指令的第三方模組就沒那麼幸運了。這些“常規模組”的指令雖然也運行在 rewrite 階段,但其配置指令和 HttpRewriteModule(以及同一階段內的其他模組)都是分開獨立執行的。在運行時,不同模組的配置指令集之間的先後順序一般是不確定的(嚴格來說,一般是由模組的載入順序決定的,但也有例外的情況)。比如 A 和 B 兩個模組都在 rewrite 階段運行指令,於是要麼是 A 模組的所有指令全部執行完再執行 B 模組的那些指令,要麼就是反過來,把 B 的指令全部執行完,再去運行 A 的指令。除非模組的文檔中有明確的交待,否則使用者一般不應編寫依賴於此種不確定順序的配置。還有不少第三方模組,ngx_array_var 以及用於加解密使用者會話(session)的 ngx_encrypted_session,也都可以和HttpRewriteModule的指令無縫混合工作。標準 HttpRewriteModule的應用是如此廣泛,所以能夠和它的配置指令混合使用的第三方模組是幸運的。不能和HttpRewriteModule混合使用的指令在實際使用的過程要引起注意,它的輸出是不確定的,這與他們在設定檔中的順序無關。
結論:範圍為同一個phase的不同modules的指令,如果modules之間做了特殊的相容,則它們按照指令在設定檔中出現的順序依次執行下來
例子2:
思考下面這個輸出什嗎?
location /test { set $value dog; more_set_input_headers "X-Species: $value"; set $value cat; echo "X-Species: $http_x_species"; }
訪問:curl 'http://localhost/test'
輸出:X-Species: cat
是不是和你的預期有點兒不一樣呢?
說明:第三方模組 ngx_headers_more 提供了一系列配置指令,用於操縱當前請求的要求標頭和回應標頭。其中有一條名叫 more_set_input_headers 的指令可以在 rewrite 階段改寫指定的要求標頭(或者在要求標頭不存在時自動建立)。該指令的文檔中有這麼一行標記 phase: rewrite tail,是說這條指令總是運行在 rewrite 階段的末尾。顯然,寫在 more_set_input_headers 指令之後的 set $value cat 語句卻先執行了。也就是說屬於HttpRewriteModule的set指令先執行完了,才執行ngx_header_more 的set_input_headers指令。
結論:即使運行在同一個請求處理階段,分屬不同模組的配置指令也可能會分開獨立運行(除非像 ngx_set_misc 等模組那樣針對 ngx_rewrite 模組提供特殊支援)。換句話說,在單個請求處理階段內部,一般也會以 Nginx 模組為單位進一步地劃分出內部子階段。下面的例子3同例子2:
例子3:
location /test { set $a 1; rewrite_by_lua "ngx.var.a = ngx.var.a + 1"; set $a 56; echo $a; }
訪問: curl 'http://localhost/test'
輸出: 57
說明:HttpLuaModule的rewrite_by_lua 指令也是處在 rewrite tail phase,它也會在rewrite 階段的末尾執行。因此HttpRewriteModule的所有set執行完後,才執行它。
顯然,rewrite_by_lua 指令的行為不同於我們前面在 (二) 中介紹過的 set_by_lua 指令。
小夥伴們可能要問,既然 more_set_input_headers 和 rewrite_by_lua 指令都運行在 rewrite 階段的末尾,那麼它們之間的先後順序又是怎樣的呢?答案是:不一定。我們應當避免寫出依賴它們二者間順序的配置。
結論:範圍在同一個phase的不同modules的指令,如果沒有做特殊相容處理,則它們指令的執行順序與指令在配置中出現的順序無關,結果具有不確定性
post rewrite phase:
location層級重寫的下一階段,用來檢查上階段是否有uri重寫,並根據結果跳轉到合適的階段;
preaccess phase:
存取權限控制的前一階段,該階段在許可權控制階段之前,一般也用於存取控制,比如限制訪問頻率,連結數等;相關模組/指令 :NginxHttpLimitZoneModule / limit_zone,
HttpLimitReqModule / limit req, HttpRealIpModule
access phase:
存取權限控制階段,比如基於ip黑白名單的許可權控制,基於使用者名稱密碼的認證控制等;相關模組/指令 HttpAccessModule / allow, deny, NginxHttpAuthBasicModule / auth_basic。 HttpAccessModule提供的 allow 和 deny 配置指令可用於控制哪些 IP 位址可以訪問,哪些不可以。HttpAccessModule模組還支援所謂的“CIDR 記法”來表示一個網段,例如 169.200.179.4/24 則表示路由首碼是 169.200.179.0(或者說子網路遮罩是 255.255.255.0)的網段。
思考下下面兩個例子(例子5,例子6)
例子4:
location /hello { allow 127.0.0.1; deny all; echo "hello world"; }
本機訪問:curl http://localhost/hello ,輸出:hello world
其他機器訪問:curl http://localhost/hello ,報403錯誤
例子5:
location /hello { deny all; allow 127.0.0.1; echo "hello world"; }
本機訪問:curl http://localhost/hello ,報403錯誤
其他機器訪問:curl http://localhost/hello ,報403錯誤
例5和例6的區別在於deny all ,和allow 127.0.0.1 這兩條指令的順序不同。但例5中/hello 只允許從本機(IP 位址為保留的 127.0.0.1)訪問,而從其他 IP 位址訪問都會被拒(返回 403 錯誤頁)。而例6中被配置為任何IP訪問都會返回403錯誤。
原因說明:同屬於HttpAccessModule這個模組的多條配置指令之間是按順序執行的,直到遇到第一條滿足條件的指令就不再執行後續的 allow 和 deny 指令。如果首先匹配的指令是 allow,則會繼續執行後續其他模組的指令或者跳到後續的處理階段;而如果首先滿足的是 deny 則會立即中止當前整個請求的處理,並立即返回給用戶端 403 錯誤頁。
結論:同一個phase的同一個module內的多條指令的執行順序由這個module自己來定義。
因為 HttpAccessModule的指令運行在 access 階段,而 access 階段又處於 rewrite 階段之後,所以前面我們見到的所有那些在 rewrite 階段啟動並執行配置指令,都總是在 allow 和 deny 之前執行,而無論它們在設定檔中的書寫順序是怎樣的。所以,為了避免閱讀配置時的混亂,我們應該總是讓指令的書寫順序和它們的實際執行順序保持一致。
post access phase:
存取權限控制的後一階段,該階段根據許可權控制階段的執行結果進行相應處理;
try files phase:
HttpCoreModule的try_files指令的處理階段,如果沒有配置try_files指令,則該階段被跳過; 該指令範圍: server ,location。
當try_files用於server階段時一般是初始化作用,用來載入一些檔案。
文法: try_files file1,file2,..,fileN-1 ... ,fallback 或者try_files file1,file2,..,fileN = code
用來順序檢查file1,file2,...fileN-1是否存在,如果最後一個字元為/表示這是一個目錄。只要找到一個file存在,則進入到content phase,輸出內容。如果前N-1個參數代表的file都不存在,此時最後一個參數發揮作用,最後一個參數用於內部跳轉,並且只有最後一個參數是用作內部跳轉,因此最後一個參數必須存在,否則將會引發一個內部錯誤。前面的file參數只是設定uri指向,不會引發內部跳轉。此外注意: try_files和rewrite不同,rewrite指令會自動儲存原請求的參數$args,而try_files最後的fallback參數如果需要帶上請求的參數,則需要明確指出,如:
try_files $uri $uri/ /index.php?q=$uri&$args
content phase:
內容產生階段,該階段產生響應,並發送到用戶端; 這個階段相關的模組和指令較多。這裡僅拿HttpLuaModule的content_by_lua和HttpEchoModule的echo指令來舉個例子再次證明上述談論到的一個結論。
例子:
location /test { set $a 123; set $b 456; echo $b; content_by_lua ' ngx.say(ngx.var.a) '; }
訪問: curl http://localhost/test
輸出: 123
即只有content_by_lua裡面的ngx.say執行了,外面的echo $b指令沒有執行。echo和content_by_lua分別屬於兩個module的指令,他們的作用phase都是content phase。這兩個module不相容,
這兩條指令不是順序執行,至於執行結果是什麼,不確定,這裡是只有content_by_lua被執行了。再次證明之前的結論:範圍在同一個phase的不同modules的指令,如果沒有做特殊相容處理,則它們指令的執行順序與指令在配置中出現的順序無關,結果具有不確定性,需要盡量避免這種情況出現。
location /test_echo { content_by_lua ' ngx.say(ngx.var.a) '; set $a 123; set $b 456; }
log phase:
日誌記錄階段,該階段記錄訪問日誌;
三、小結:
參考資料:
http://wiki.nginx.org/
http://wiki.nginx.org/Phases
http://blog.sina.com.cn/s/blog_6d579ff40100xpff.html
以上就介紹了nginx phases 介紹,包括了方面的內容,希望對PHP教程有興趣的朋友有所協助。