前面在 (二) 中我們已經(jīng)了解到變量值容器的生命期是與請(qǐng)求綁定的,但是我當(dāng)時(shí)有意避開(kāi)了“請(qǐng)求”的正式定義。大家應(yīng)當(dāng)一直默認(rèn)這里的“請(qǐng)求”都是指客戶(hù)端發(fā)起的 HTTP 請(qǐng)求。其實(shí)在 Nginx 世界里有兩種類(lèi)型的“請(qǐng)求”,一種叫做“主請(qǐng)求”(main request),而另一種則叫做“子請(qǐng)求”(subrequest)。我們先來(lái)介紹一下它們。 所謂“主請(qǐng)求”,就是由 HTTP 客戶(hù)端從 Nginx 外部發(fā)起的請(qǐng)求。
成都創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),南開(kāi)企業(yè)網(wǎng)站建設(shè),南開(kāi)品牌網(wǎng)站建設(shè),網(wǎng)站定制,南開(kāi)網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,南開(kāi)網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶(hù)成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。我們前面見(jiàn)到的所有例子都只涉及到“主請(qǐng)求”,包括 (二) 中那兩個(gè)使用 echo_exec 和 rewrite 指令發(fā)起“內(nèi)部跳轉(zhuǎn)”的例子。 而“子請(qǐng)求”則是由 Nginx 正在處理的請(qǐng)求在 Nginx 內(nèi)部發(fā)起的一種級(jí)聯(lián)請(qǐng)求。“子請(qǐng)求”在外觀上很像 HTTP 請(qǐng)求,但實(shí)現(xiàn)上卻和 HTTP 協(xié)議乃至網(wǎng)絡(luò)通信一點(diǎn)兒關(guān)系都沒(méi)有。它是 Nginx 內(nèi)部的一種抽象調(diào)用,目的是為了方便用戶(hù)把“主請(qǐng)求”的任務(wù)分解為多個(gè)較小粒度的“內(nèi)部請(qǐng)求”,并發(fā)或串行地訪問(wèn)多個(gè) location 接口,然后由這些 location 接口通力協(xié)作,共同完成整個(gè)“主請(qǐng)求”。當(dāng)然,“子請(qǐng)求”的概念是相對(duì)的,任何一個(gè)“子請(qǐng)求”也可以再發(fā)起更多的“子子請(qǐng)求”,甚至可以玩遞歸調(diào)用(即自己調(diào)用自己)。當(dāng)一個(gè)請(qǐng)求發(fā)起一個(gè)“子請(qǐng)求”的時(shí)候,按照 Nginx 的術(shù)語(yǔ),習(xí)慣把前者稱(chēng)為后者的“父請(qǐng)求”(parent request)。值得一提的是,Apache 服務(wù)器中其實(shí)也有“子請(qǐng)求”的概念,所以來(lái)自 Apache 世界的讀者對(duì)此應(yīng)當(dāng)不會(huì)感到陌生。 下面就來(lái)看一個(gè)使用了“子請(qǐng)求”的例子:
location /main { echo_location /foo; echo_location /bar; } location /foo { echo foo; } location /bar { echo bar; } 這里在 location /main 中,通過(guò)第三方 ngx_echo 模塊的 echo_location 指令分別發(fā)起到 /foo 和 /bar 這兩個(gè)接口的 GET 類(lèi)型的“子請(qǐng)求”。由 echo_location 發(fā)起的“子請(qǐng)求”,其執(zhí)行是按照配置書(shū)寫(xiě)的順序串行處理的,即只有當(dāng) /foo 請(qǐng)求處理完畢之后,才會(huì)接著處理 /bar 請(qǐng)求。這兩個(gè)“子請(qǐng)求”的輸出會(huì)按執(zhí)行順序拼接起來(lái),作為 /main 接口的最終輸出:
$ curl \'http://localhost:8080/main\' foo bar 我們看到,“子請(qǐng)求”方式的通信是在同一個(gè)虛擬主機(jī)內(nèi)部進(jìn)行的,所以 Nginx 核心在實(shí)現(xiàn)“子請(qǐng)求”的時(shí)候,就只調(diào)用了若干個(gè) C 函數(shù),完全不涉及任何網(wǎng)絡(luò)或者 UNIX 套接字(socket)通信。我們由此可以看出“子請(qǐng)求”的執(zhí)行效率是極高的。 回到先前對(duì) Nginx 變量值容器的生命期的討論,我們現(xiàn)在依舊可以說(shuō),它們的生命期是與當(dāng)前請(qǐng)求相關(guān)聯(lián)的。每個(gè)請(qǐng)求都有所有變量值容器的獨(dú)立副本,只不過(guò)當(dāng)前請(qǐng)求既可以是“主請(qǐng)求”,也可以是“子請(qǐng)求”。即便是父子請(qǐng)求之間,同名變量一般也不會(huì)相互干擾。讓我們來(lái)通過(guò)一個(gè)小實(shí)驗(yàn)證明一下這個(gè)說(shuō)法:
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"; } 在這個(gè)例子中,我們分別在 /main,/foo 和 /bar 這三個(gè) location 配置塊中為同一名字的變量,$var,分別設(shè)置了不同的值并予以輸出。特別地,我們?cè)?/main 接口中,故意在調(diào)用過(guò) /foo 和 /bar 這兩個(gè)“子請(qǐng)求”之后,再輸出它自己的 $var 變量的值。請(qǐng)求 /main 接口的結(jié)果是這樣的:
$ curl \'http://localhost:8080/main\' foo: foo bar: bar main: main 顯然,/foo 和 /bar 這兩個(gè)“子請(qǐng)求”在處理過(guò)程中對(duì)變量 $var 各自所做的修改都絲毫沒(méi)有影響到“主請(qǐng)求” /main. 于是這成功印證了“主請(qǐng)求”以及各個(gè)“子請(qǐng)求”都擁有不同的變量 $var 的值容器副本。 不幸的是,一些 Nginx 模塊發(fā)起的“子請(qǐng)求”卻會(huì)自動(dòng)共享其“父請(qǐng)求”的變量值容器,比如第三方模塊 ngx_auth_request. 下面是一個(gè)例子:
location /main { set $var main; auth_request /sub; echo "main: $var"; } location /sub { set $var sub; echo "sub: $var"; } 這里我們?cè)?/main 接口中先為 $var 變量賦初值 main,然后使用 ngx_auth_request 模塊提供的配置指令 auth_request,發(fā)起一個(gè)到 /sub 接口的“子請(qǐng)求”,最后利用 echo 指令輸出變量 $var 的值。而我們?cè)?/sub 接口中則故意把 $var 變量的值改寫(xiě)成 sub. 訪問(wèn) /main 接口的結(jié)果如下:
$ curl \'http://localhost:8080/main\' main: sub 我們看到,/sub 接口對(duì) $var 變量值的修改影響到了主請(qǐng)求 /main. 所以 ngx_auth_request 模塊發(fā)起的“子請(qǐng)求”確實(shí)是與其“父請(qǐng)求”共享一套 Nginx 變量的值容器。 對(duì)于上面這個(gè)例子,相信有讀者會(huì)問(wèn):“為什么‘子請(qǐng)求’ /sub 的輸出沒(méi)有出現(xiàn)在最終的輸出里呢?”答案很簡(jiǎn)單,那就是因?yàn)?auth_request 指令會(huì)自動(dòng)忽略“子請(qǐng)求”的響應(yīng)體,而只檢查“子請(qǐng)求”的響應(yīng)狀態(tài)碼。當(dāng)狀態(tài)碼是 2XX 的時(shí)候,auth_request 指令會(huì)忽略“子請(qǐng)求”而讓 Nginx 繼續(xù)處理當(dāng)前的請(qǐng)求,否則它就會(huì)立即中斷當(dāng)前(主)請(qǐng)求的執(zhí)行,返回相應(yīng)的出錯(cuò)頁(yè)。在我們的例子中,/sub “子請(qǐng)求”只是使用 echo 指令作了一些輸出,所以隱式地返回了指示正常的 200 狀態(tài)碼。 如 ngx_auth_request 模塊這樣父子請(qǐng)求共享一套 Nginx 變量的行為,雖然可以讓父子請(qǐng)求之間的數(shù)據(jù)雙向傳遞變得極為容易,但是對(duì)于足夠復(fù)雜的配置,卻也經(jīng)常導(dǎo)致不少難于調(diào)試的詭異 bug. 因?yàn)橛脩?hù)時(shí)常不知道“父請(qǐng)求”的某個(gè) Nginx 變量的值,其實(shí)已經(jīng)在它的某個(gè)“子請(qǐng)求”中被意外修改了。諸如此類(lèi)的因共享而導(dǎo)致的不好的“副作用”,讓包括 ngx_echo,ngx_lua,以及 ngx_srcache 在內(nèi)的許多第三方模塊都選擇了禁用父子請(qǐng)求間的變量共享。