Nginx 配置指令的執行順序(五)
Nginx 的 content
階段是所有請求處理階段中最為重要的一個,因為運行在這個階段的配置指令一般都肩負著生成“內容”(content)并輸出 HTTP 響應的使命。正因為其重要性,這個階段的配置指令也異常豐富,例如前面我們一直在示例中廣泛使用的 echo 指令,在 Nginx 變量漫談(二) 中接觸到的 echo_exec 指令,Nginx 變量漫談(三) 中接觸到的 proxy_pass 指令,Nginx 變量漫談(五) 中介紹過的 echo_location 指令,以及 Nginx 變量漫談(七) 中介紹過的 content_by_lua 指令,都運行在這個階段。
content
階段屬于一個比較靠后的處理階段,運行在先前介紹過的 rewrite
和 access
這兩個階段之后。當和 rewrite
、access
階段的指令一起使用時,這個階段的指令總是最后運行,例如:
location /test {
# rewrite phase
set $age 1;
rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
# access phase
deny 10.32.168.49;
access_by_lua "ngx.var.age = ngx.var.age * 3";
# content phase
echo "age = $age";
}
這個例子中各個配置指令的執行順序便是它們的書寫順序。測試結果完全符合預期:
$ curl 'http://localhost:8080/test'
age = 6
即使改變它們的書寫順序,也不會影響到執行順序。其中,set 指令來自 ngx_rewrite 模塊,運行于 rewrite
階段;而 rewrite_by_lua 指令來自 ngx_lua 模塊,運行于 rewrite
階段的末尾;接下來,deny 指令來自ngx_access 模塊,運行于 access
階段;再下來,access_by_lua 指令同樣來自 ngx_lua 模塊,運行于access
階段的末尾;最后,我們的老朋友 echo 指令則來自 ngx_echo 模塊,運行在 content
階段。
這個例子展示了通過同時使用多個處理階段的配置指令來實現多個模塊協同工作的效果。在這個過程中,Nginx 變量則經常扮演著在指令間乃至模塊間傳遞(小份)數據的角色。這些配置指令的執行順序,也強烈地受到請求處理階段的影響。
進一步地,在 rewrite
和 access
這兩個階段,多個模塊的配置指令可以同時使用,譬如上例中的 set 指令和 rewrite_by_lua 指令同處 rewrite
階段,而 deny 指令和 access_by_lua 指令則同處 access
階段。但不幸的是,這通常不適用于 content
階段。
絕大多數 Nginx 模塊在向 content
階段注冊配置指令時,本質上是在當前的 location
配置塊中注冊所謂的“內容處理程序”(content handler)。每一個 location
只能有一個“內容處理程序”,因此,當在location
中同時使用多個模塊的 content
階段指令時,只有其中一個模塊能成功注冊“內容處理程序”。考慮下面這個有問題的例子:
? location /test {
? echo hello;
? content_by_lua 'ngx.say("world")';
? }
這里,ngx_echo 模塊的 echo 指令和 ngx_lua 模塊的 content_by_lua 指令同處 content
階段,于是只有其中一個模塊能注冊和運行這個 location
的“內容處理程序”:
$ curl 'http://localhost:8080/test'
world
實際運行結果表明,寫在后面的 content_by_lua 指令反而勝出了,而 echo 指令則完全沒有運行。具體哪一個模塊的指令會勝出是不確定的,例如把上例中的 echo 語句和 content_by_lua 語句交換順序,則輸出就會變成hello
,即 ngx_echo 模塊勝出。所以我們應當避免在同一個 location
中使用多個模塊的 content
階段指令。
將上例中的 content_by_lua 指令替換為 echo 指令就可以如愿了:
location /test {
echo hello;
echo world;
}
測試結果證明了這一點:
$ curl 'http://localhost:8080/test'
hello
world
這里使用多條 echo 指令是沒問題的,因為它們同屬 ngx_echo 模塊,而且 ngx_echo 模塊規定和實現了它們之間的執行順序。值得一提的是,并非所有模塊的指令都支持在同一個 location
中被使用多次,例如content_by_lua 就只能使用一次,所以下面這個例子是錯誤的:
? location /test {
? content_by_lua 'ngx.say("hello")';
? content_by_lua 'ngx.say("world")';
? }
這個配置在 Nginx 啟動時就會報錯:
[emerg] "content_by_lua" directive is duplicate ...
正確的寫法應當是:
location /test {
content_by_lua 'ngx.say("hello") ngx.say("world")';
}
即在 content_by_lua 內聯的 Lua 代碼中調用兩次 ngx.say 函數,而不是在當前 location
中使用兩次content_by_lua 指令。
類似地,ngx_proxy 模塊的 proxy_pass 指令和 echo 指令也不能同時用在一個 location
中,因為它們也同屬 content
階段。不少 Nginx 新手都會犯類似下面這樣的錯誤:
? location /test {
? echo "before...";
? proxy_pass http://127.0.0.1:8080/foo;
? echo "after...";
? }
?
? location /foo {
? echo "contents to be proxied";
? }
這個例子表面上是想在 ngx_proxy 模塊返回的內容前后,通過 ngx_echo 模塊的 ,但其實只有其中一個模塊能在 content
階段運行。測試結果表明,在這個例子中是 ngx_proxy 模塊勝出,而 ngx_echo 模塊的 echo 指令根本沒有運行:
$ curl 'http://localhost:8080/test'
contents to be proxied
要實現這個例子希望達到的效果,需要改用 ngx_echo 模塊提供的 echo_before_body 和 echo_after_body 這兩條配置指令:
location /test {
echo_before_body "before...";
proxy_pass http://127.0.0.1:8080/foo;
echo_after_body "after...";
}
location /foo {
echo "contents to be proxied";
}
測試結果表明這一次我們成功了:
$ curl 'http://localhost:8080/test'
before...
contents to be proxied
after...
配置指令 echo_before_body 和 echo_after_body 之所以可以和其他模塊運行在 content
階段的指令一起工作,是因為它們運行在 Nginx 的“輸出過濾器”中。前面我們在 (一) 中分析 echo 指令產生的“調試日志”時已經知道,Nginx 在輸出響應體數據時都會調用“輸出過濾器”,所以 ngx_echo 模塊才有機會在“輸出過濾器”中對 ngx_proxy 模塊產生的響應體輸出進行修改(即在首尾添加新的內容)。值得一提的是,“輸出過濾器”并不屬于 (一) 中提到的那 11 個請求處理階段(畢竟許多階段都可以通過輸出響應體數據來調用“輸出過濾器”),但這并不妨礙 echo_before_body 和 echo_after_body 指令在文檔中標記下面這一行:
phase: output filter
這一行的意思是,當前配置指令運行在“輸出過濾器”這個特殊的階段。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成