Nginx 變量漫談(三)
也有一些內建變量是支持改寫的,其中一個例子是 $args. 這個變量在讀取時返回當前請求的 URL 參數串(即請求 URL 中問號后面的部分,如果有的話 ),而在賦值時可以直接修改參數串。我們來看一個例子:
location /test {
set $orig_args $args;
set $args "a=3&b=4";
echo "original args: $orig_args";
echo "args: $args";
}
這里我們把原始的 URL 參數串先保存在 $orig_args
變量中,然后通過改寫 $args 變量來修改當前的 URL 參數串,最后我們用 echo 指令分別輸出 $orig_args
和 $args 變量的值。接下來我們這樣來測試這個 /test
接口:
$ curl 'http://localhost:8080/test'
original args:
args: a=3&b=4
$ curl 'http://localhost:8080/test?a=0&b=1&c=2'
original args: a=0&b=1&c=2
args: a=3&b=4
在第一次測試中,我們沒有設置任何 URL 參數串,所以輸出 $orig_args
變量的值時便得到空。而在第一次和第二次測試中,無論我們是否提供 URL 參數串,參數串都會在 location /test
中被強行改寫成 a=3&b=4
.
需要特別指出的是,這里的 $args 變量和 $arg_XXX 一樣,也不再使用屬于自己的存放值的容器。當我們讀取 $args 時,Nginx 會執行一小段代碼,從 Nginx 核心中專門存放當前 URL 參數串的位置去讀取數據;而當我們改寫 $args 時,Nginx 會執行另一小段代碼,對相同位置進行改寫。Nginx 的其他部分在需要當前 URL 參數串的時候,都會從那個位置去讀數據,所以我們對 $args 的修改會影響到所有部分的功能。我們來看一個例子:
location /test {
set $orig_a $arg_a;
set $args "a=5";
echo "original a: $orig_a";
echo "a: $arg_a";
}
這里我們先把內建變量 $arg_a
的值,即原始請求的 URL 參數 a
的值,保存在用戶變量 $orig_a
中,然后通過對內建變量 $args 進行賦值,把當前請求的參數串改寫為 a=5
,最后再用 echo 指令分別輸出 $orig_a
和$arg_a
變量的值。因為對內建變量 $args 的修改會直接導致當前請求的 URL 參數串發生變化,因此內建變量$arg_XXX 自然也會隨之變化。測試的結果證實了這一點:
$ curl 'http://localhost:8080/test?a=3'
original a: 3
a: 5
我們看到,因為原始請求的 URL 參數串是 a=3
, 所以 $arg_a
最初的值為 3
, 但隨后通過改寫 $args 變量,將 URL 參數串又強行修改為 a=5
, 所以最終 $arg_a
的值又自動變為了 5
.
我們再來看一個通過修改 $args
變量影響標準的 HTTP 代理模塊 ngx_proxy 的例子:
server {
listen 8080;
location /test {
set $args "foo=1&bar=2";
proxy_pass http://127.0.0.1:8081/args;
}
}
server {
listen 8081;
location /args {
echo "args: $args";
}
}
這里我們在 http
配置塊中定義了兩個虛擬主機。第一個虛擬主機監聽 8080 端口,其 /test
接口自己通過改寫 $args 變量,將當前請求的 URL 參數串無條件地修改為 foo=1&bar=2
. 然后 /test
接口再通過 ngx_proxy模塊的 proxy_pass 指令配置了一個反向代理,指向本機的 8081 端口上的 HTTP 服務 /args
. 默認情況下,ngx_proxy 模塊在轉發 HTTP 請求到遠方 HTTP 服務的時候,會自動把當前請求的 URL 參數串也轉發到遠方。
而本機的 8081 端口上的 HTTP 服務正是由我們定義的第二個虛擬主機來提供的。我們在第二個虛擬主機的location /args
中利用 echo 指令輸出當前請求的 URL 參數串,以檢查 /test
接口通過 ngx_proxy 模塊實際轉發過來的 URL 請求參數串。
我們來實際訪問一下第一個虛擬主機的 /test
接口:
$ curl 'http://localhost:8080/test?blah=7'
args: foo=1&bar=2
我們看到,雖然請求自己提供了 URL 參數串 blah=7
,但在 location /test
中,參數串被強行改寫成了foo=1&bar=2
. 接著經由 proxy_pass 指令將我們被改寫掉的參數串轉發給了第二個虛擬主機上配置的 /args
接口,然后再把 /args
接口的 URL 參數串輸出。事實證明,我們對 $args 變量的賦值操作,也成功影響到了ngx_proxy 模塊的行為。
在讀取變量時執行的這段特殊代碼,在 Nginx 中被稱為“取處理程序”(get handler);而改寫變量時執行的這段特殊代碼,則被稱為“存處理程序”(set handler)。不同的 Nginx 模塊一般會為它們的變量準備不同的“存取處理程序”,從而讓這些變量的行為充滿魔法。
其實這種技巧在計算世界并不鮮見。比如在面向對象編程中,類的設計者一般不會把類的成員變量直接暴露給類的用戶,而是另行提供兩個方法(method),分別用于該成員變量的讀操作和寫操作,這兩個方法常常被稱為“存取器”(accessor)。下面是 C++ 語言中的一個例子:
#include <string>
using namespace std;
class Person {
public:
const string get_name() {
return m_name;
}
void set_name(const string name) {
m_name = name;
}
private:
string m_name;
};
在這個名叫 Person
的 C++ 類中,我們提供了 get_name
和 set_name
這兩個公共方法,以作為私有成員變量m_name
的“存取器”。
這樣設計的好處是顯而易見的。類的設計者可以在“存取器”中執行任意代碼,以實現所需的業務邏輯以及“副作用”,比如自動更新與當前成員變量存在依賴關系的其他成員變量,抑或是直接修改某個與當前對象相關聯的數據庫表中的對應字段。而對于后一種情況,也許“存取器”所對應的成員變量壓根就不存在,或者即使存在,也頂多扮演著數據緩存的角色,以緩解被代理數據庫的訪問壓力。
與面向對象編程中的“存取器”概念相對應,Nginx 變量也是支持綁定“存取處理程序”的。Nginx 模塊在創建變量時,可以選擇是否為變量分配存放值的容器,以及是否自己提供與讀寫操作相對應的“存取處理程序”。
不是所有的 Nginx 變量都擁有存放值的容器。擁有值容器的變量在 Nginx 核心中被稱為“被索引的”(indexed);反之,則被稱為“未索引的”(non-indexed)。
我們前面在 (二) 中已經知道,像 $arg_XXX 這樣具有無數變種的變量群,是“未索引的”。當讀取這樣的變量時,其實是它的“取處理程序”在起作用,即實時掃描當前請求的 URL 參數串,提取出變量名所指定的 URL 參數的值。很多新手都會對 $arg_XXX 的實現方式產生誤解,以為 Nginx 會事先解析好當前請求的所有 URL 參數,并且把相關的 $arg_XXX 變量的值都事先設置好。然而事實并非如此,Nginx 根本不會事先就解析好 URL 參數串,而是在用戶讀取某個 $arg_XXX 變量時,調用其“取處理程序”,即時去掃描 URL 參數串。類似地,內建變量 $cookie_XXX 也是通過它的“取處理程序”,即時去掃描 Cookie
請求頭中的相關定義的。
(未完待續)
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成