標籤:blog http ar io os 使用 sp java on
在 (一) 中我們提到過,Nginx 變數的值只有一種類型,那就是字串,但是變數也有可能壓根就不存在有意義的值。沒有值的變數也有兩種特殊的值:一種是“不合法”(invalid),另一種是“沒找到”(not found)。
舉例說來,當 Nginx 使用者變數 $foo 建立了卻未被賦值時,$foo 的值便是“不合法”;而如果當前請求的 URL 參數串中並沒有提及 XXX 這個參數,則 $arg_XXX 內建變數的值便是“沒找到”。
無論是“不合法”也好,還是“沒找到”也罷,這兩種 Nginx 變數所擁有的特殊值,和Null 字元串("")這種取值是完全不同的,比如 JavaScript 語言中也有專門的 undefined 和 null 這兩種特殊值,而 Lua 語言中也有專門的 nil 值: 它們既不等同於Null 字元串,也不等同於數字 0,更不是布爾值 false. 其實 SQL 語言中的NULL 也是類似的一種東西。
雖然前面在 (一) 中我們看到,由 set 指令建立的變數未初始化就用在“變數插值”中時,效果等同於Null 字元串,但那是因為 set 指令為它建立的變數自動註冊了一個“取處理常式”,將“不合法”的變數值轉換為空白字串。為了驗證這一點,我們再重新看一下 (一) 中討論過的那個例子:
location /foo {
echo "foo = [$foo]";
}
location /bar {
set $foo 32;
echo "foo = [$foo]";
}
這裡為了簡單起見,省略了原先寫出的外圍 server 配置塊。在這個例子裡,我們在 /bar 介面中用 set 指令隱式地建立了 $foo 變數這個名字,然後我們在 /foo 介面中不對 $foo 進行初始化就直接使用 echo 指令輸出。我們當時測試 /foo 介面的結果是
$ curl ‘http://localhost:8080/foo‘
foo = []
從輸出上看,未初始化的 $foo 變數確實和Null 字元串的效果等同。但細心的讀者當時應該就已經注意到,對於上面這個請求,Nginx 的錯誤記錄檔檔案(一般檔案名稱叫做 error.log)中多出一行類似下面這樣的警告:
[warn] 5765#0: *1 using uninitialized "foo" variable, ...
這一行警告是誰輸出的呢?答案是 set 指令為 $foo 註冊的“取處理常式”。當 /foo 介面中的 echo 指令實際執行的時候,它會對它的參數 "foo = [$foo]" 進行“變數插值”計算。於是,參數串中的 $foo 變數會被讀取,而 Nginx 會首先檢查其值容器裡的取值,結果它看到了“不合法”這個特殊值,於是它這才決定繼續調用$foo 變數的“取處理常式”。於是 $foo 變數的“取處理常式”開始運行,它向 Nginx 的錯誤記錄檔列印出上面那條警告訊息,然後返回一個Null 字元串作為 $foo 的值,並從此緩衝在 $foo 的值容器中。
細心的讀者會注意到剛剛描述的這個過程其實就是那些支援值緩衝的內建變數的工作原理,只不過 set 指令在這裡借用了這套機制來處理未正確初始化的 Nginx 變數。值得一提的是,只有“不合法”這個特殊值才會觸發 Nginx 調用變數的“取處理常式”,而特殊值“沒找到”卻不會。
上面這樣的警告一般會指示出我們的 Nginx 配置中存在變數名拼字錯誤,抑或是在錯誤的場合使用了尚未初始化的變數。因為值緩衝的存在,這條警告在一個請求的生命期中也不會列印多次。當然,ngx_rewrite 模組專門提供了一條 uninitialized_variable_warn 配置指令可用于禁止這條警告日誌。
剛才提到,內建變數 $arg_XXX 在請求 URL 參數 XXX 並不存在時會返回特殊值“找不到”,但遺憾的是在 Nginx 原生配置語言(我們估且這麼稱呼它)中是不能很方便地把它和Null 字元串區分開來的,比如:
location /test {
echo "name: [$arg_name]";
}
這裡我們輸出 $arg_name 變數的值同時故意在請求中不提供 URL 參數 name:
$ curl ‘http://localhost:8080/test‘
name: []
我們看到,輸出特殊值“找不到”的效果和Null 字元串是相同的。因為這一回是 Nginx 的“變數插值”引擎自動把“找不到”給忽略了。
那麼我們究竟應當如何捕捉到“找不到”這種特殊值的蹤影呢?換句話說,我們應當如何把它和Null 字元串給區分開來呢?顯然,下面這個請求中,URL 參數 name 是有值的,而且其值應當是Null 字元串:
$ curl ‘http://localhost:8080/test?name=‘
name: []
但我們卻無法將之和前面完全不提供 name 參數的情況給區分開。
幸運的是,通過第三方模組 ngx_lua,我們可以輕鬆地在 Lua 代碼中做到這一點。請看下面這個例子:
location /test {
content_by_lua ‘
if ngx.var.arg_name == nil then
ngx.say("name: missing")
else
ngx.say("name: [", ngx.var.arg_name, "]")
end
‘;
}
這個例子和前一個例子功能上非常接近,除了我們在 /test 介面中使用了 ngx_lua 模組的 content_by_lua 配置指令,嵌入了一小段我們自己的 Lua 代碼來對 Nginx 變數 $arg_name 的特殊值進行判斷。在這個例子中,當 $arg_name 的值為“沒找到”(或者“不合法”)時,/foo 介面會輸出 name: missing 這一行結果:
curl ‘http://localhost:8080/test‘
name: missing
因為這是我們第一次接觸到 ngx_lua 模組,所以需要先簡單介紹一下。ngx_lua 模組將 Lua 語言解譯器(或者LuaJIT 即時編譯器)嵌入到了 Nginx 核心中,從而可以讓使用者在 Nginx 核心中直接運行 Lua 語言編寫的程式。我們可以選擇在 Nginx 不同的請求處理階段插入我們的 Lua 代碼。這些 Lua 代碼既可以直接內聯在 Nginx 設定檔中,也可以單獨放置在外部 .lua 檔案裡,然後在 Nginx 設定檔中引用 .lua 檔案的路徑。
回到上面這個例子,我們在 Lua 代碼裡引用 Nginx 變數都是通過 ngx.var 這個由 ngx_lua 模組提供的 Lua 介面。比如引用 Nginx 變數 $VARIABLE 時,就在 Lua 代碼裡寫作 ngx.var.VARIABLE 就可以了。當 Nginx 變數 $arg_name 為特殊值“沒找到”(或者“不合法”)時, ngx.var.arg_name 在 Lua 世界中的值就是 nil,即 Lua 語言裡的“空”(不同於 Lua Null 字元串)。我們在 Lua 裡輸出響應體內容的時候,則使用了ngx.say 這個 Lua 函數,也是 ngx_lua 模組提供的,功能上等價於 ngx_echo 模組的 echo 配置指令。
現在,如果我們提供Null 字元串取值的 name 參數,則輸出就和剛才不相同了:
$ curl ‘http://localhost:8080/test?name=‘
name: []
在這種情況下,Nginx 變數 $arg_name 的取值便是Null 字元串,這既不是“沒找到”,也不是“不合法”,因此在 Lua 裡,ngx.var.arg_name 就返回 Lua Null 字元串(""),和剛才的 Lua nil 值就完全區分開了。
這種區分在有些應用情境下非常重要,比如有的 web service 介面會根據 name 這個 URL 參數是否存在來決定是否按 name 屬性對資料集合進行過濾,而顯然提供Null 字元串作為 name 參數的值,也會導致對資料集中取值為空白串的記錄進行篩選操作。
不過,標準的 $arg_XXX 變數還是有一些局限,比如我們用下面這個請求來測試剛才那個 /test 介面:
$ curl ‘http://localhost:8080/test?name‘
name: missing
此時,$arg_name 變數仍然讀出“找不到”這個特殊值,這就明顯有些違反常識。此外,$arg_XXX 變數在請求 URL 中有多個同名 XXX 參數時,就只會返回最先出現的那個 XXX 參數的值,而默默忽略掉其他執行個體:
$ curl ‘http://localhost:8080/test?name=Tom&name=Jim&name=Bob‘
name: [Tom]
要解決這些局限,可以直接在 Lua 代碼中使用 ngx_lua 模組提供的 ngx.req.get_uri_args 函數。
(未完待續)
Nginx 變數漫談(七)