Nginx 變量漫談(二)
關于 Nginx 變量的另一個常見誤區是認為變量容器的生命期,是與 location
配置塊綁定的。其實不然。我們來看一個涉及“內部跳轉”的例子:
server {
listen 8080;
location /foo {
set $a hello;
echo_exec /bar;
}
location /bar {
echo "a = [$a]";
}
}
這里我們在 location /foo
中,使用第三方模塊 ngx_echo 提供的 echo_exec 配置指令,發起到 location /bar
的“內部跳轉”。所謂“內部跳轉”,就是在處理請求的過程中,于服務器內部,從一個 location
跳轉到另一個 location
的過程。這不同于利用 HTTP 狀態碼 301
和 302
所進行的“外部跳轉”,因為后者是由 HTTP 客戶端配合進行跳轉的,而且在客戶端,用戶可以通過瀏覽器地址欄這樣的界面,看到請求的 URL 地址發生了變化。內部跳轉和 Bourne Shell
(或 Bash
)中的 exec
命令很像,都是“有去無回”。另一個相近的例子是 C
語言中的 goto
語句。
既然是內部跳轉,當前正在處理的請求就還是原來那個,只是當前的 location
發生了變化,所以還是原來的那一套 Nginx 變量的容器副本。對應到上例,如果我們請求的是 /foo
這個接口,那么整個工作流程是這樣的:先在 location /foo
中通過 set 指令將 $a
變量的值賦為字符串 hello
,然后通過 echo_exec 指令發起內部跳轉,又進入到 location /bar
中,再輸出 $a
變量的值。因為 $a
還是原來的 $a
,所以我們可以期望得到 hello
這行輸出。測試證實了這一點:
$ curl localhost:8080/foo
a = [hello]
但如果我們從客戶端直接訪問 /bar
接口,就會得到空的 $a
變量的值,因為它依賴于 location /foo
來對 $a
進行初始化。
從上面這個例子我們看到,一個請求在其處理過程中,即使經歷多個不同的 location
配置塊,它使用的還是同一套 Nginx 變量的副本。這里,我們也首次涉及到了“內部跳轉”這個概念。值得一提的是,標準ngx_rewrite 模塊的 rewrite 配置指令其實也可以發起“內部跳轉”,例如上面那個例子用 rewrite 配置指令可以改寫成下面這樣的形式:
server {
listen 8080;
location /foo {
set $a hello;
rewrite ^ /bar;
}
location /bar {
echo "a = [$a]";
}
}
其效果和使用 echo_exec 是完全相同的。后面我們還會專門介紹這個 rewrite 指令的更多用法,比如發起 301
和 302
這樣的“外部跳轉”。
從上面這個例子我們看到,Nginx 變量值容器的生命期是與當前正在處理的請求綁定的,而與 location
無關。
前面我們接觸到的都是通過 set 指令隱式創建的 Nginx 變量。這些變量我們一般稱為“用戶自定義變量”,或者更簡單一些,“用戶變量”。既然有“用戶自定義變量”,自然也就有由 Nginx 核心和各個 Nginx 模塊提供的“預定義變量”,或者說“內建變量”(builtin variables)。
Nginx 內建變量最常見的用途就是獲取關于請求或響應的各種信息。例如由 ngx_http_core 模塊提供的內建變量 $uri,可以用來獲取當前請求的 URI(經過解碼,并且不含請求參數),而 $request_uri 則用來獲取請求最原始的 URI (未經解碼,并且包含請求參數)。請看下面這個例子:
location /test {
echo "uri = $uri";
echo "request_uri = $request_uri";
}
這里為了簡單起見,連 server
配置塊也省略了,和前面所有示例一樣,我們監聽的依然是 8080
端口。在這個例子里,我們把 $uri 和 $request_uri 的值輸出到響應體中去。下面我們用不同的請求來測試一下這個 /test
接口:
$ curl 'http://localhost:8080/test'
uri = /test
request_uri = /test
$ curl 'http://localhost:8080/test?a=3&b=4'
uri = /test
request_uri = /test?a=3&b=4
$ curl 'http://localhost:8080/test/hello%20world?a=3&b=4'
uri = /test/hello world
request_uri = /test/hello%20world?a=3&b=4
另一個特別常用的內建變量其實并不是單獨一個變量,而是有無限多變種的一群變量,即名字以 arg_
開頭的所有變量,我們估且稱之為 $arg_XXX 變量群。一個例子是 $arg_name
,這個變量的值是當前請求名為 name
的 URI 參數的值,而且還是未解碼的原始形式的值。我們來看一個比較完整的示例:
location /test {
echo "name: $arg_name";
echo "class: $arg_class";
}
然后在命令行上使用各種參數組合去請求這個 /test
接口:
$ curl 'http://localhost:8080/test'
name:
class:
$ curl 'http://localhost:8080/test?name=Tom&class=3'
name: Tom
class: 3
$ curl 'http://localhost:8080/test?name=hello%20world&class=9'
name: hello%20world
class: 9
其實 $arg_name
不僅可以匹配 name
參數,也可以匹配 NAME
參數,抑或是 Name
,等等:
$ curl 'http://localhost:8080/test?NAME=Marry'
name: Marry
class:
$ curl 'http://localhost:8080/test?Name=Jimmy'
name: Jimmy
class:
Nginx 會在匹配參數名之前,自動把原始請求中的參數名調整為全部小寫的形式。
如果你想對 URI 參數值中的 %XX
這樣的編碼序列進行解碼,可以使用第三方 ngx_set_misc 模塊提供的set_unescape_uri 配置指令:
location /test {
set_unescape_uri $name $arg_name;
set_unescape_uri $class $arg_class;
echo "name: $name";
echo "class: $class";
}
現在我們再看一下效果:
$ curl 'http://localhost:8080/test?name=hello%20world&class=9'
name: hello world
class: 9
空格果然被解碼出來了!
從這個例子我們同時可以看到,這個 set_unescape_uri 指令也像 set 指令那樣,擁有自動創建 Nginx 變量的功能。后面我們還會專門介紹到 ngx_set_misc 模塊。
像 $arg_XXX 這種類型的變量擁有無窮無盡種可能的名字,所以它們并不對應任何存放值的容器。而且這種變量在 Nginx 核心中是經過特別處理的,第三方 Nginx 模塊是不能提供這樣充滿魔法的內建變量的。
類似 $arg_XXX 的內建變量還有不少,比如用來取 cookie 值的 $cookie_XXX 變量群,用來取請求頭的$http_XXX 變量群,以及用來取響應頭的 $sent_http_XXX 變量群。這里就不一一介紹了,感興趣的讀者可以參考 ngx_http_core 模塊的官方文檔。
需要指出的是,許多內建變量都是只讀的,比如我們剛才介紹的 $uri 和 $request_uri. 對只讀變量進行賦值是應當絕對避免的,因為會有意想不到的后果,比如:
? location /bad {
? set $uri /blah;
? echo $uri;
? }
這個有問題的配置會讓 Nginx 在啟動的時候報出一條令人匪夷所思的錯誤:
[emerg] the duplicate "uri" variable in ...
如果你嘗試改寫另外一些只讀的內建變量,比如 $arg_XXX 變量,在某些 Nginx 的版本中甚至可能導致進程崩潰。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成