国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

云服務器|WEB服務器|FTP服務器|郵件服務器|虛擬主機|服務器安全|DNS服務器|服務器知識|Nginx|IIS|Tomcat|

服務器之家 - 服務器技術 - Nginx - 淺析Nginx配置文件中的變量的編寫使用

淺析Nginx配置文件中的變量的編寫使用

2019-11-06 12:10agentzh Nginx

這篇文章主要介紹了Nginx配置文件中的變量的編寫使用,包括從常用的rewrite等方面來深入變量的相關定義,需要的朋友可以參考下

nginx 的配置文件使用的就是一門微型的編程語言,許多真實世界里的 Nginx 配置文件其實就是一個一個的小程序。當然,是不是“圖靈完全的”暫且不論,至少據我觀察,它在設計上受 Perl 和 Bourne shell 這兩種語言的影響很大。在這一點上,相比 Apache 和 Lighttpd 等其他 Web 服務器的配置記法,不能不說算是 Nginx 的一大特色了。既然是編程語言,一般也就少不了“變量”這種東西(當然,Haskell 這樣奇怪的函數式語言除外了)。
熟悉 Perl、Bourne shell、C/C++ 等命令式編程語言的朋友肯定知道,變量說白了就是存放“值”的容器。而所謂“值”,在許多編程語言里,既可以是 3.14 這樣的數值,也可以是 hello world 這樣的字符串,甚至可以是像數組、哈希表這樣的復雜數據結構。然而,在 Nginx 配置中,變量只能存放一種類型的值,因為也只存在一種類型的值,那就是字符串。
比如我們的 nginx.conf 文件中有下面這一行配置:

?
1
set $a "hello world";

我們使用了標準 ngx_rewrite 模塊的 set 配置指令對變量 $a 進行了賦值操作。特別地,我們把字符串 hello world 賦給了它。
我們看到,Nginx 變量名前面有一個 $ 符號,這是記法上的要求。所有的 Nginx 變量在 Nginx 配置文件中引用時都須帶上 $ 前綴。這種表示方法和 Perl、PHP 這些語言是相似的。
雖然 $ 這樣的變量前綴修飾會讓正統的 Java 和 C# 程序員不舒服,但這種表示方法的好處也是顯而易見的,那就是可以直接把變量嵌入到字符串常量中以構造出新的字符串:

?
1
2
set $a hello; 
set $b "$a, $a";

這里我們通過已有的 Nginx 變量 $a 的值,來構造變量 $b 的值,于是這兩條指令順序執行完之后,$a 的值是 hello,而 $b 的值則是 hello, hello. 這種技術在 Perl 世界里被稱為“變量插值”(variable interpolation),它讓專門的字符串拼接運算符變得不再那么必要。我們在這里也不妨采用此術語。
我們來看一個比較完整的配置示例:

?
1
2
3
4
5
6
7
8
server { 
  listen 8080; 
 
  location /test { 
    set $foo hello; 
    echo "foo: $foo"; 
  
}

 
這個例子省略了 nginx.conf 配置文件中最外圍的 http 配置塊以及 events 配置塊。使用 curl 這個 HTTP 客戶端在命令行上請求這個 /test 接口,我們可以得到

?
1
2
$ curl 'http://localhost:8080/test' 
foo: hello

這里我們使用第三方 ngx_echo 模塊的 echo 配置指令將 $foo 變量的值作為當前請求的響應體輸出。
我們看到,echo 配置指令的參數也支持“變量插值”。不過,需要說明的是,并非所有的配置指令都支持“變量插值”。事實上,指令參數是否允許“變量插值”,取決于該指令的實現模塊。
如果我們想通過 echo 指令直接輸出含有“美元符”($)的字符串,那么有沒有辦法把特殊的 $ 字符給轉義掉呢?答案是否定的(至少到目前最新的 Nginx 穩定版 1.0.10)。不過幸運的是,我們可以繞過這個限制,比如通過不支持“變量插值”的模塊配置指令專門構造出取值為 $ 的 Nginx 變量,然后再在 echo 中使用這個變量??聪旅孢@個例子:

?
1
2
3
4
5
6
7
8
9
10
11
geo $dollar { 
  default "$"; 
 
server { 
  listen 8080; 
 
  location /test { 
    echo "This is a dollar sign: $dollar"; 
  
}

測試結果如下:

?
1
2
$ curl 'http://localhost:8080/test' 
This is a dollar sign: $

這里用到了標準模塊 ngx_geo 提供的配置指令 geo 來為變量 $dollar 賦予字符串 "$",這樣我們在下面需要使用美元符的地方,就直接引用我們的 $dollar 變量就可以了。其實 ngx_geo 模塊最常規的用法是根據客戶端的 IP 地址對指定的 Nginx 變量進行賦值,這里只是借用它以便“無條件地”對我們的 $dollar 變量賦予“美元符”這個值。
在“變量插值”的上下文中,還有一種特殊情況,即當引用的變量名之后緊跟著變量名的構成字符時(比如后跟字母、數字以及下劃線),我們就需要使用特別的記法來消除歧義,例如:

?
1
2
3
4
5
6
7
8
server { 
  listen 8080; 
 
  location /test { 
    set $first "hello "; 
    echo "${first}world"; 
  
}

 
這里,我們在 echo 配置指令的參數值中引用變量 $first 的時候,后面緊跟著 world 這個單詞,所以如果直接寫作 "$firstworld" 則 Nginx “變量插值”計算引擎會將之識別為引用了變量 $firstworld. 為了解決這個難題,Nginx 的字符串記法支持使用花括號在 $ 之后把變量名圍起來,比如這里的 ${first}. 上面這個例子的輸出是:

?
1
2
$ curl 'http://localhost:8080/test 
hello world

set 指令(以及前面提到的 geo 指令)不僅有賦值的功能,它還有創建 Nginx 變量的副作用,即當作為賦值對象的變量尚不存在時,它會自動創建該變量。比如在上面這個例子中,如果 $a 這個變量尚未創建,則 set 指令會自動創建 $a 這個用戶變量。如果我們不創建就直接使用它的值,則會報錯。例如

?
1
2
3
4
5
6
7
server { 
 listen 8080; 
 
 location /bad { 
   echo $foo; 
 
}

此時 Nginx 服務器會拒絕加載配置:

?
1
[emerg] unknown "foo" variable

是的,我們甚至都無法啟動服務!
有趣的是,Nginx 變量的創建和賦值操作發生在全然不同的時間階段。Nginx 變量的創建只能發生在 Nginx 配置加載的時候,或者說 Nginx 啟動的時候;而賦值操作則只會發生在請求實際處理的時候。這意味著不創建而直接使用變量會導致啟動失敗,同時也意味著我們無法在請求處理時動態地創建新的 Nginx 變量。
Nginx 變量一旦創建,其變量名的可見范圍就是整個 Nginx 配置,甚至可以跨越不同虛擬主機的 server 配置塊。我們來看一個例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
server { 
  listen 8080; 
 
  location /foo { 
    echo "foo = [$foo]"; 
  
 
  location /bar { 
    set $foo 32; 
    echo "foo = [$foo]"; 
  
}

 
這里我們在 location /bar 中用 set 指令創建了變量 $foo,于是在整個配置文件中這個變量都是可見的,因此我們可以在 location /foo 中直接引用這個變量而不用擔心 Nginx 會報錯。
下面是在命令行上用 curl 工具訪問這兩個接口的結果:

?
1
2
3
4
5
6
$ curl 'http://localhost:8080/foo' 
foo = [] 
$ curl 'http://localhost:8080/bar' 
foo = [32] 
$ curl 'http://localhost:8080/foo' 
foo = []

從這個例子我們可以看到,set 指令因為是在 location /bar 中使用的,所以賦值操作只會在訪問 /bar 的請求中執行。而請求 /foo 接口時,我們總是得到空的 $foo 值,因為用戶變量未賦值就輸出的話,得到的便是空字符串。
從這個例子我們可以窺見的另一個重要特性是,Nginx 變量名的可見范圍雖然是整個配置,但每個請求都有所有變量的獨立副本,或者說都有各變量用來存放值的容器的獨立副本,彼此互不干擾。比如前面我們請求了 /bar 接口后,$foo 變量被賦予了值 32,但它絲毫不會影響后續對 /foo 接口的請求所對應的 $foo 值(它仍然是空的?。驗楦鱾€請求都有自己獨立的 $foo 變量的副本。
對于 Nginx 新手來說,最常見的錯誤之一,就是將 Nginx 變量理解成某種在請求之間全局共享的東西,或者說“全局變量”。而事實上,Nginx 變量的生命期是不可能跨越請求邊界的。

關于 nginx 變量的另一個常見誤區是認為變量容器的生命期,是與 location 配置塊綁定的。其實不然。我們來看一個涉及“內部跳轉”的例子:

?
1
2
3
4
5
6
7
8
9
10
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 這行輸出。測試證實了這一點:

?
1
2
$ curl localhost:8080/foo 
a = [hello]

但如果我們從客戶端直接訪問 /bar 接口,就會得到空的 $a 變量的值,因為它依賴于 location /foo 來對 $a 進行初始化。從上面這個例子我們看到,一個請求在其處理過程中,即使經歷多個不同的 location 配置塊,它使用的還是同一套 Nginx 變量的副本。這里,我們也首次涉及到了“內部跳轉”這個概念。值得一提的是,標準 ngx_rewrite 模塊的 rewrite 配置指令其實也可以發起“內部跳轉”,例如上面那個例子用 rewrite 配置指令可以改寫成下面這樣的形式:

?
1
2
3
4
5
6
7
8
9
10
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 (未經解碼,并且包含請求參數)。請看下面這個例子:

?
1
2
3
4
location /test { 
  echo "uri = $uri"; 
  echo "request_uri = $request_uri"; 
}

這里為了簡單起見,連 server 配置塊也省略了,和前面所有示例一樣,我們監聽的依然是 8080 端口。在這個例子里,我們把 $uri 和 $request_uri 的值輸出到響應體中去。下面我們用不同的請求來測試一下這個 /test 接口:

?
1
2
3
4
5
6
7
8
9
$ 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 參數的值,而且還是未解碼的原始形式的值。我們來看一個比較完整的示例:

?
1
2
3
4
location /test { 
  echo "name: $arg_name"; 
  echo "class: $arg_class"; 
}

 
然后在命令行上使用各種參數組合去請求這個 /test 接口:

?
1
2
3
4
5
6
7
8
9
$ 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,等等:

?
1
2
3
4
5
6
$ 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 配置指令:

?
1
2
3
4
5
6
location /test { 
  set_unescape_uri $name $arg_name; 
  set_unescape_uri $class $arg_class; 
  echo "name: $name"; 
  echo "class: $class"; 
}

現在我們再看一下效果:

?
1
2
3
$ 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. 對只讀變量進行賦值是應當絕對避免的,因為會有意想不到的后果,比如:

?
1
2
3
4
location /bad { 
 set $uri /blah; 
 echo $uri; 
}

這個有問題的配置會讓 Nginx 在啟動的時候報出一條令人匪夷所思的錯誤:

?
1
[emerg] the duplicate "uri" variable in ...

如果你嘗試改寫另外一些只讀的內建變量,比如 $arg_XXX 變量,在某些 Nginx 的版本中甚至可能導致進程崩潰。
也有一些內建變量是支持改寫的,其中一個例子是 $args. 這個變量在讀取時返回當前請求的 URL 參數串(即請求 URL 中問號后面的部分,如果有的話 ),而在賦值時可以直接修改參數串。我們來看一個例子:

?
1
2
3
4
5
6
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 接口:

?
1
2
3
4
5
6
$ 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 的修改會影響到所有部分的功能。我們來看一個例子:

?
1
2
3
4
5
6
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 自然也會隨之變化。測試的結果證實了這一點:

?
1
2
3
$ 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 的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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 接口:

?
1
2
$ 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)。

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 欧美成人二区 | 国产一区二区影院 | 99国产精品99久久久久久 | av片在线观看 | 欧美成人精品欧美一级私黄 | 国产精品久久久999 一区二区三区视频免费在线观看 | 国产精品一区二区三区在线播放 | 午夜资源 | 成人网av| 人人插人人 | 国产成人久久精品一区二区三区 | 日韩亚洲视频 | 欧美在线国产 | 日韩激情一区二区三区 | 成人午夜性a一级毛片免费看 | 亚洲精品二区 | 一本色道精品久久一区二区三区 | 亚洲成a人v欧美综合天堂麻豆 | 一区中文字幕 | 精品国产三级 | 亚洲热av| 久热官网| 日韩在线不卡一区 | 国产欧美日韩 | 日韩成人在线电影 | 伊人干 | 99精品热| 国产精品久久一区二区三区 | 久久久免费看 | 成人亚洲| 国产一区二区三区在线视频观看 | 精品无码久久久久国产 | 亚洲 欧美 日韩在线 | 成年人黄色影院 | 午夜四虎 | 国产精品久久久久永久免费观看 | 亚洲精品乱码久久久久久金桔影视 | 日韩欧美一区二区在线视频 | 国产在线视频网站 | 高清免费av| 91精品国产高清一区二区三区 |