Nginx 配置指令的執行順序(二)
我們前面已經知道,當 set 指令用在 location
配置塊中時,都是在當前請求的 rewrite
階段運行的。事實上,在此上下文中,ngx_rewrite 模塊中的幾乎全部指令,都運行在 rewrite
階段,包括 Nginx 變量漫談(二) 中介紹過的 rewrite 指令。不過,值得一提的是,當這些指令使用在 server
配置塊中時,則會運行在一個我們尚未提及的更早的處理階段,server-rewrite
階段。
Nginx 變量漫談(二) 中介紹過的 ngx_set_misc 模塊的 set_unescape_uri 指令同樣也運行在 rewrite
階段。特別地,ngx_set_misc 模塊的指令還可以和 ngx_rewrite 的指令混合在一起依次執行。我們來看這樣的一個例子:
location /test {
set $a "hello%20world";
set_unescape_uri $b $a;
set $c "$b!";
echo $c;
}
訪問這個接口可以得到:
$ curl 'http://localhost:8080/test'
hello world!
我們看到,set_unescape_uri 語句前后的 set 語句都按書寫時的順序一前一后地執行了。
為了進一步確認這一點,我們不妨再檢查一下 Nginx 的“調試日志”(如果你還不清楚如何開啟“調試日志”的話,可以參考 (一) 中的步驟):
grep -E 'http script (value|copy|set)' t/servroot/logs/error.log
過濾出來的調試日志信息如下所示:
[debug] 11167#0: *1 http script value: "hello%20world"
[debug] 11167#0: *1 http script set $a
[debug] 11167#0: *1 http script value (post filter): "hello world"
[debug] 11167#0: *1 http script set $b
[debug] 11167#0: *1 http script copy: "!"
[debug] 11167#0: *1 http script set $c
開頭的兩行信息
[debug] 11167#0: *1 http script value: "hello%20world"
[debug] 11167#0: *1 http script set $a
就對應我們的配置語句
set $a "hello%20world";
而接下來的兩行
[debug] 11167#0: *1 http script value (post filter): "hello world"
[debug] 11167#0: *1 http script set $b
則對應配置語句
set_unescape_uri $b $a;
我們看到第一行信息與 這個標記,而且最后顯示出 URI 解碼操作確實如我們期望的那樣工作了,即 "hello%20world"
在這里被成功解碼為 "hello world"
.
而最后兩行調試信息
[debug] 11167#0: *1 http script copy: "!"
[debug] 11167#0: *1 http script set $c
則對應最后一條 set 語句:
set $c "$b!";
注意,因為這條指令在為 $c
變量賦值時使用了“變量插值”功能,所以第一行調試信息是以 http script copy
起始的,后面則是拼接到最終取值的字符串常量 "!"
.
把這些調試信息聯系起來看,我們不難發現,這些配置指令的實際執行順序是:
set $a "hello%20world";
set_unescape_uri $b $a;
set $c "$b!";
這與它們在配置文件中的書寫順序完全一致。
我們在 Nginx 變量漫談(七) 中初識了第三方模塊 ngx_lua,它提供的 set_by_lua 配置指令也和ngx_set_misc 模塊的指令一樣,可以和 ngx_rewrite 模塊的指令混合使用。set_by_lua 指令支持通過一小段用戶 Lua 代碼來計算出一個結果,然后賦給指定的 Nginx 變量。和 set 指令相似,set_by_lua 指令也有自動創建不存在的 Nginx 變量的功能。
下面我們就來看一個 set_by_lua 指令與 set 指令混合使用的例子:
location /test {
set $a 32;
set $b 56;
set_by_lua $c "return ngx.var.a + ngx.var.b";
set $equation "$a + $b = $c";
echo $equation;
}
這里我們先將 $a
和 $b
變量分別初始化為 32
和 56
,然后利用 set_by_lua 指令內聯一行我們自己指定的 Lua 代碼,計算出 Nginx 變量 $a
和 $b
的“代數和”(sum),并賦給變量 $c
,接著利用“變量插值”功能,把變量 $a
、 $b
和 $c
的值拼接成一個字符串形式的等式,賦予變量 $equation
,最后再用 echo 指令輸出 $equation
的值。
這個例子值得注意的地方是:首先,我們在 Lua 代碼中是通過 ngx.var.VARIABLE 接口來讀取 Nginx 變量$VARIABLE
的;其次,因為 Nginx 變量的值只有字符串這一種類型,所以在 Lua 代碼里讀取 ngx.var.a
和ngx.var.b
時得到的其實都是 Lua 字符串類型的值 "32"
和 "56"
;接著,我們對兩個字符串作加法運算會觸發 Lua 對加數進行自動類型轉換(Lua 會把兩個加數先轉換為數值類型再求和);然后,我們在 Lua 代碼中把最終結果通過 return
語句返回給外面的 Nginx 變量 $c
;最后,ngx_lua 模塊在給 $c
實際賦值之前,也會把return
語句返回的數值類型的結果,也就是 Lua 加法計算得出的“和”,自動轉換為字符串(這同樣是因為 Nginx 變量的值只能是字符串)。
這個例子的實際運行結果符合我們的期望:
$ curl 'http://localhost:8080/test'
32 + 56 = 88
于是這驗證了 set_by_lua 指令確實也可以和 set 這樣的 ngx_rewrite 模塊提供的指令混合在一起工作。
還有不少第三方模塊,例如 Nginx 變量漫談(八) 中介紹過的 ngx_array_var 以及后面即將接觸到的用于加解密用戶會話(session)的 ngx_encrypted_session,也都可以和 ngx_rewrite 模塊的指令無縫混合工作。
標準 ngx_rewrite 模塊的應用是如此廣泛,所以能夠和它的配置指令混合使用的第三方模塊是幸運的。事實上,上面提到的這些第三方模塊都采用了特殊的技術,將它們自己的配置指令“注入”到了 ngx_rewrite 模塊的指令序列中(它們都借助了 Marcus Clyne 編寫的第三方模塊 ngx_devel_kit)。換句話說,更多常規的在 Nginx 的 rewrite
階段注冊和運行指令的第三方模塊就沒那么幸運了。這些“常規模塊”的指令雖然也運行在rewrite
階段,但其配置指令和 ngx_rewrite 模塊(以及同一階段內的其他模塊)都是分開獨立執行的。在運行時,不同模塊的配置指令集之間的先后順序一般是不確定的(嚴格來說,一般是由模塊的加載順序決定的,但也有例外的情況)。比如 A
和 B
兩個模塊都在 rewrite
階段運行指令,于是要么是 A
模塊的所有指令全部執行完再執行 B
模塊的那些指令,要么就是反過來,把 B
的指令全部執行完,再去運行 A
的指令。除非模塊的文檔中有明確的交待,否則用戶一般不應編寫依賴于此種不確定順序的配置。