一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

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

云服務器|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)。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 性柔术18性13处交 | 亚洲精品综合 | 日本videos有奶水的hd | 国产欧美视频在线观看 | 久久99热在线观看7 久久99精品涩AV毛片观看 | 国产在线观看福利片 | 欧美最新在线 | 精品久久久久久国产91 | 亚洲国产视频网站 | 国产精品免费久久久久影院小说 | 美女张开腿让男人桶的 视频 | 日韩一区二区三区四区五区 | 美女张开腿让我了一夜 | 久久午夜夜伦痒痒想咳嗽P 久久无码AV亚洲精品色午夜麻豆 | 青苹果乐园影院在线播放 | 肉色欧美久久久久久久蜜桃 | 毛片资源 | 日本护士handjob | 五月色婷婷久久综合 | 亚洲欧美日韩国产精品影院 | 亚洲AV无码A片在线观看蜜桃 | 亚飞与亚基国语1080p在线观看 | 小莹的性荡生活45章 | 91成人爽a毛片一区二区 | 日本特黄一级午夜剧场毛片 | 小柔的性放荡羞辱日记动漫 | 免费看国产精品久久久久 | 青春草在线观看视频 | 亚洲欧美在线观看一区二区 | 草草草在线 | 娇妻在床上迎合男人 | 爱色v | 69福利区 | 国产一卡2卡3卡四卡精品网站 | 好大好深视频 | 日韩二区三区 | 精品国产免费第一区二区三区日韩 | 色综合久久中文字幕网 | 欧美艳星julnaann | 午夜精品亚洲 | 羞羞答答免费人成黄页在线观看国产 |