Nginx 變量漫談(五)
前面在 (二) 中我們已經了解到變量值容器的生命期是與請求綁定的,但是我當時有意避開了“請求”的正式定義。大家應當一直默認這里的“請求”都是指客戶端發起的 HTTP 請求。其實在 Nginx 世界里有兩種類型的“請求”,一種叫做“主請求”(main request),而另一種則叫做“子請求”(subrequest)。我們先來介紹一下它們。
所謂“主請求”,就是由 HTTP 客戶端從 Nginx 外部發起的請求。我們前面見到的所有例子都只涉及到“主請求”,包括 (二) 中那兩個使用 echo_exec 和 rewrite 指令發起“內部跳轉”的例子。
而“子請求”則是由 Nginx 正在處理的請求在 Nginx 內部發起的一種級聯請求。“子請求”在外觀上很像 HTTP 請求,但實現上卻和 HTTP 協議乃至網絡通信一點兒關系都沒有。它是 Nginx 內部的一種抽象調用,目的是為了方便用戶把“主請求”的任務分解為多個較小粒度的“內部請求”,并發或串行地訪問多個 location
接口,然后由這些 location
接口通力協作,共同完成整個“主請求”。當然,“子請求”的概念是相對的,任何一個“子請求”也可以再發起更多的“子子請求”,甚至可以玩遞歸調用(即自己調用自己)。當一個請求發起一個“子請求”的時候,按照 Nginx 的術語,習慣把前者稱為后者的“父請求”(parent request)。值得一提的是,Apache 服務器中其實也有“子請求”的概念,所以來自 Apache 世界的讀者對此應當不會感到陌生。
下面就來看一個使用了“子請求”的例子:
location /main {
echo_location /foo;
echo_location /bar;
}
location /foo {
echo foo;
}
location /bar {
echo bar;
}
這里在 location /main
中,通過第三方 ngx_echo 模塊的 echo_location 指令分別發起到 /foo
和 /bar
這兩個接口的 GET
類型的“子請求”。由 echo_location 發起的“子請求”,其執行是按照配置書寫的順序串行處理的,即只有當 /foo
請求處理完畢之后,才會接著處理 /bar
請求。這兩個“子請求”的輸出會按執行順序拼接起來,作為 /main
接口的最終輸出:
$ curl 'http://localhost:8080/main'
foo
bar
我們看到,“子請求”方式的通信是在同一個虛擬主機內部進行的,所以 Nginx 核心在實現“子請求”的時候,就只調用了若干個 C 函數,完全不涉及任何網絡或者 UNIX 套接字(socket)通信。我們由此可以看出“子請求”的執行效率是極高的。
回到先前對 Nginx 變量值容器的生命期的討論,我們現在依舊可以說,它們的生命期是與當前請求相關聯的。每個請求都有所有變量值容器的獨立副本,只不過當前請求既可以是“主請求”,也可以是“子請求”。即便是父子請求之間,同名變量一般也不會相互干擾。讓我們來通過一個小實驗證明一下這個說法:
location /main {
set $var main;
echo_location /foo;
echo_location /bar;
echo "main: $var";
}
location /foo {
set $var foo;
echo "foo: $var";
}
location /bar {
set $var bar;
echo "bar: $var";
}
在這個例子中,我們分別在 /main
,/foo
和 /bar
這三個 location
配置塊中為同一名字的變量,$var
,分別設置了不同的值并予以輸出。特別地,我們在 /main
接口中,故意在調用過 /foo
和 /bar
這兩個“子請求”之后,再輸出它自己的 $var
變量的值。請求 /main
接口的結果是這樣的:
$ curl 'http://localhost:8080/main'
foo: foo
bar: bar
main: main
顯然,/foo
和 /bar
這兩個“子請求”在處理過程中對變量 $var
各自所做的修改都絲毫沒有影響到“主請求” /main
. 于是這成功印證了“主請求”以及各個“子請求”都擁有不同的變量 $var
的值容器副本。
不幸的是,一些 Nginx 模塊發起的“子請求”卻會自動共享其“父請求”的變量值容器,比如第三方模塊ngx_auth_request. 下面是一個例子:
location /main {
set $var main;
auth_request /sub;
echo "main: $var";
}
location /sub {
set $var sub;
echo "sub: $var";
}
這里我們在 /main
接口中先為 $var
變量賦初值 main
,然后使用 ngx_auth_request 模塊提供的配置指令auth_request
,發起一個到 /sub
接口的“子請求”,最后利用 echo 指令輸出變量 $var
的值。而我們在/sub
接口中則故意把 $var
變量的值改寫成 sub
. 訪問 /main
接口的結果如下:
$ curl 'http://localhost:8080/main'
main: sub
我們看到,/sub
接口對 $var
變量值的修改影響到了主請求 /main
. 所以 ngx_auth_request 模塊發起的“子請求”確實是與其“父請求”共享一套 Nginx 變量的值容器。
對于上面這個例子,相信有讀者會問:“為什么‘子請求’ /sub
的輸出沒有出現在最終的輸出里呢?”答案很簡單,那就是因為 auth_request
指令會自動忽略“子請求”的響應體,而只檢查“子請求”的響應狀態碼。當狀態碼是 2XX
的時候,auth_request
指令會忽略“子請求”而讓 Nginx 繼續處理當前的請求,否則它就會立即中斷當前(主)請求的執行,返回相應的出錯頁。在我們的例子中,/sub
“子請求”只是使用 echo指令作了一些輸出,所以隱式地返回了指示正常的 200
狀態碼。
如 ngx_auth_request 模塊這樣父子請求共享一套 Nginx 變量的行為,雖然可以讓父子請求之間的數據雙向傳遞變得極為容易,但是對于足夠復雜的配置,卻也經常導致不少難于調試的詭異 bug. 因為用戶時常不知道“父請求”的某個 Nginx 變量的值,其實已經在它的某個“子請求”中被意外修改了。諸如此類的因共享而導致的不好的“副作用”,讓包括 ngx_echo,ngx_lua,以及 ngx_srcache 在內的許多第三方模塊都選擇了禁用父子請求間的變量共享。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成