標籤:blog http ar io os 使用 sp on 資料
Nginx 內建變數用在“子請求”的上下文中時,其行為也會變得有些微妙。
前面在 (三) 中我們已經知道,許多內建變數都不是簡單的“存放值的容器”,它們一般會通過註冊“存取處理常式”來表現得與眾不同,而它們即使有存放值的容器,也只是用於緩衝“存取處理常式”的計算結果。我們之前討論過的 $args 變數正是通過它的“取處理常式”來返回當前請求的 URL 參數串。因為當前請求也可以是“子請求”,所以在“子請求”中讀取 $args,其“取處理常式”會很自然地返回當前“子請求”的參數串。我們來看這樣的一個例子:
location /main {
echo "main args: $args";
echo_location /sub "a=1&b=2";
}
location /sub {
echo "sub args: $args";
}
這裡在 /main 介面中,先用 echo 指令輸出當前請求的 $args 變數的值,接著再用 echo_location 指令發起子請求 /sub. 這裡值得注意的是,我們在 echo_location 語句中除了通過第一個參數指定“子請求”的 URI 之外,還提供了第二個參數,用以指定該“子請求”的 URL 參數串(即 a=1&b=2)。最後我們定義了 /sub 介面,在裡面輸出了一下 $args 的值。請求 /main 介面的結果如下:
$ curl ‘http://localhost:8080/main?c=3‘
main args: c=3
sub args: a=1&b=2
顯然,當 $args 用在“主請求” /main 中時,輸出的就是“主請求”的 URL 參數串,c=3;而當用在“子請求” /sub 中時,輸出的則是“子請求”的參數串,a=1&b=2。這種行為正符合我們的直覺。
與 $args 類似,內建變數 $uri 用在“子請求”中時,其“取處理常式”也會正確返回當前“子請求”解析過的 URI:
location /main {
echo "main uri: $uri";
echo_location /sub;
}
location /sub {
echo "sub uri: $uri";
}
請求 /main 的結果是
$ curl ‘http://localhost:8080/main‘
main uri: /main
sub uri: /sub
這依然是我們所期望的。
但不幸的是,並非所有的內建變數都作用於當前請求。少數內建變數只作用於“主請求”,比如由標準模組ngx_http_core 提供的內建變數 $request_method.
變數 $request_method 在讀取時,總是會得到“主請求”的要求方法,比如 GET、POST 之類。我們來測試一下:
location /main {
echo "main method: $request_method";
echo_location /sub;
}
location /sub {
echo "sub method: $request_method";
}
在這個例子裡,/main 和 /sub 介面都會分別輸出 $request_method 的值。同時,我們在 /main 介面裡利用echo_location 指令發起一個到 /sub 介面的 GET “子請求”。我們現在利用 curl 命令列工具來發起一個到/main 介面的 POST 請求:
$ curl --data hello ‘http://localhost:8080/main‘
main method: POST
sub method: POST
這裡我們利用 curl 程式的 --data 選項,指定 hello 作為我們的請求體資料,同時 --data 選項會自動讓發送的請求使用 POST 要求方法。測試結果證明了我們先前的預言,$request_method 變數即使在 GET “子請求” /sub 中使用,得到的值依然是“主請求” /main 的要求方法,POST.
有的讀者可能覺得我們在這裡下的結論有些草率,因為上例是先在“主請求”裡讀取(並輸出)$request_method 變數,然後才發“子請求”的,所以這些讀者可能認為這並不能排除 $request_method在進入子請求之前就已經把第一次讀到的值給緩衝住,從而影響到後續子請求中的輸出結果。不過,這樣的顧慮是多餘的,因為我們前面在 (五) 中也特別提到過,緩衝所依賴的變數的值容器,是與當前請求綁定的,而由ngx_echo 模組發起的“子請求”都禁用了父子請求之間的變數共用,所以在上例中,$request_method 內建變數即使真的使用了值容器作為緩衝(事實上它也沒有),它也不可能影響到 /sub 子請求。
為了進一步消除這部分讀者的疑慮,我們不妨稍微修改一下剛才那個例子,將 /main 介面輸出$request_method 變數的時間延遲到“子請求”執行完畢之後:
location /main {
echo_location /sub;
echo "main method: $request_method";
}
location /sub {
echo "sub method: $request_method";
}
讓我們重新測試一下:
$ curl --data hello ‘http://localhost:8080/main‘
sub method: POST
main method: POST
可以看到,再次以 POST 方法請求 /main 介面的結果與原先那個例子完全一致,除了父子請求的輸出順序顛倒了過來(因為我們在本例中交換了 /main 介面中那兩條輸出配置指令的先後次序)。
由此可見,我們並不能通過標準的 $request_method 變數取得“子請求”的要求方法。為了達到我們最初的目的,我們需要求助於第三方模組 ngx_echo 提供的內建變數 $echo_request_method:
location /main {
echo "main method: $echo_request_method";
echo_location /sub;
}
location /sub {
echo "sub method: $echo_request_method";
}
此時的輸出終於是我們想要的了:
$ curl --data hello ‘http://localhost:8080/main‘
main method: POST
sub method: GET
我們看到,父子請求分別輸出了它們各自不同的要求方法,POST 和 GET.
類似 $request_method,內建變數 $request_uri 一般也返回的是“主請求”未經解析過的 URL,畢竟“子請求”都是在 Nginx 內部發起的,並不存在所謂的“未解析的”原始形式。
如果真如前面那部分讀者所擔心的,內建變數的值緩衝在共用變數的父子請求之間起了作用,這無疑是災難性的。我們前面在 (五) 中已經看到 ngx_auth_request 模組發起的“子請求”是與其“父請求”共用一套變數的。下面是一個這樣的可怕例子:
map $uri $tag {
default 0;
/main 1;
/sub 2;
}
server {
listen 8080;
location /main {
auth_request /sub;
echo "main tag: $tag";
}
location /sub {
echo "sub tag: $tag";
}
}
這裡我們使用久違了的 map 指令來把內建變數 $uri 的值對應到使用者變數 $tag 上。當 $uri 的值為 /main時,則賦予 $tag 值 1,當 $uri 取值 /sub 時,則賦予 $tag 值 2,其他情況都賦 0. 接著,我們在 /main介面中先用 ngx_auth_request 模組的 auth_request 指令發起到 /sub 介面的子請求,然後再輸出變數 $tag的值。而在 /sub 介面中,我們直接輸出變數 $tag. 猜猜看,如果我們提供者 /main,將會得到什麼樣的輸出呢?
$ curl ‘http://localhost:8080/main‘
main tag: 2
咦?我們不是分明把 /main 這個值對應到 1 上的嗎?為什麼實際輸出的是 /sub 映射的結果 2 呢?
其實道理很簡單,因為我們的 $tag 變數在“子請求” /sub 中首先被讀取,於是在那裡計算出了值 2(因為 $uri 在那裡取值 /sub,而根據 map 映射規則,$tag 應當取值 2),從此就被 $tag 的值容器給緩衝住了。而 auth_request 發起的“子請求”又是與“父請求”共用一套變數的,於是當 Nginx 的執行流回到“父請求”輸出 $tag 變數的值時,Nginx 就直接返回緩衝住的結果 2 了。這樣的結果確實太意外了。
從這個例子我們再次看到,父子請求間的變數共用,實在不是一個好主意。
(未完待續)
Nginx 變數漫談(六)