Nginx 變量漫談(一)

   Nginx 的配置文件使用的就是一門微型的編程語言,許多真實世界裏的 Nginx 配置文件其實就是一個一個的小程序。固然,是否是「圖靈徹底的」暫且不論,它在設計上受 Perl 和 Bourne Shell 這兩種語言的影響很大。在這一點上,相比 Apache 和 Lighttpd 等其餘 Web 服務器的配置記法,不能不說算是 Nginx 的一大特點了。既然是編程語言,通常也就少不了「變量」這種東西(固然,Haskell 這樣奇怪的函數式語言除外了)。html

  熟悉 Perl、Bourne Shell、C/C++ 等命令式編程語言的朋友確定知道,變量說白了就是存放「值」的容器。而所謂「值」,在許多編程語言裏,既能夠是 3.14 這樣的數值,也能夠是 hello world 這樣的字符串,甚至能夠是像數組、哈希表這樣的複雜數據結構。然而,在 Nginx 配置中,變量只能存放一種類型的值,由於也只存在一種類型的值,那就是字符串。nginx

  好比咱們的 nginx.conf 文件中有下面這一行配置:程序員

    set $a "hello world";

  咱們使用了標準 ngx_rewrite 模塊的 set 配置指令對變量 $a 進行了賦值操做。特別地,咱們把字符串 hello world 賦給了它。編程

  咱們看到,Nginx 變量名前面有一個 $ 符號,這是記法上的要求。全部的 Nginx 變量在 Nginx 配置文件中引用時都須帶上 $ 前綴。這種表示方法和 Perl、PHP 這些語言是類似的。小程序

  雖然 $ 這樣的變量前綴修飾會讓正統的 Java 和 C# 程序員不舒服,但這種表示方法的好處也是顯而易見的,那就是能夠直接把變量嵌入到字符串常量中以構造出新的字符串:數組

    set $a hello;
    set $b "$a, $a";服務器

  裏咱們經過已有的 Nginx 變量 $a 的值,來構造變量 $b 的值,因而這兩條指令順序執行完以後,$a 的值是 hello,而 $b 的值則是 hello, hello. 這種技術在 Perl 世界裏被稱爲「變量插值」(variable interpolation),它讓專門的字符串拼接運算符變得再也不那麼必要。咱們在這裏也不妨採用此術語。數據結構

  咱們來看一個比較完整的配置示例:curl

  

server { listen 8080; location /test { set $foo hello; echo "foo: $foo"; } }

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

  $ curl 'http://localhost:8080/test'
    foo: hello

  這裏咱們使用第三方 ngx_echo 模塊的 echo 配置指令將 $foo 變量的值做爲當前請求的響應體輸出。
  咱們看到,echo 配置指令的參數也支持「變量插值」。不過,須要說明的是,並不是全部的配置指令都支持「變量插值」。事實上,指令參數是否容許「變量插值」,取決於該指令的實現模塊。

  咱們看到,echo 配置指令的參數也支持「變量插值」。不過,須要說明的是,並不是全部的配置指令都支持「變量插值」。事實上,指令參數是否容許「變量插值」,取決於該指令的實現模塊。

  若是咱們想經過 echo 指令直接輸出含有「美圓符」($)的字符串,那麼有沒有辦法把特殊的$字符給轉義掉呢?答案是否認的。不過幸運的是,咱們能夠繞過這個限制,好比經過不支持「變量插值」的模塊配置指令專門構造出取值爲$ 的 Nginx 變量,而後再在 echo 中使用這個變量。看下面這個例子:
  
geo $dollar { default "$"; } server { listen 8080; location /test { echo "This is a dollar sign: $dollar"; } }

  測試結果以下:

   $ curl 'http://localhost:8080/test'

    This is a dollar sign: $

  
這裏用到了標準模塊 ngx_geo 提供的配置指令 geo 來爲變量 $dollar 賦予字符串 "$",這樣咱們在下面須要使用美圓符的地方,就直接引用咱們的$dollar變量就能夠了。其實 ngx_geo 模塊最常規的用法是根據客戶端的 IP 地址對指定的 Nginx 變量進行賦值,這裏只是借用它以便「無條件地」對咱們的$dollar 變量賦予「美圓符」這個值。

  在「變量插值」的上下文中,還有一種特殊狀況,即當引用的變量名以後緊跟着變量名的構成字符時(好比後跟字母、數字以及下劃線),咱們就須要使用特別的記法來消除歧義,例如:
  
server { listen 8080; location /test { set $first "hello "; echo "${first}world"; } }

  

  這裏,咱們在echo 配置指令的參數值中引用變量 $first 的時候,後面緊跟着 world 這個單詞,因此若是直接寫做 "$firstworld" 則 Nginx 「變量插值」計算引擎會將之識別爲引用了變量 $firstworld. 爲了解決這個難題,Nginx 的字符串記法支持使用花括號在 $ 以後把變量名圍起來,好比這裏的 ${first}. 上面這個例子的輸出是:

    $ curl 'http://localhost:8080/test
    hello world

 set 指令(以及前面提到的 geo 指令)不只有賦值的功能,它還有建立 Nginx 變量的反作用,即看成爲賦值對象的變量尚不存在時,它會自動建立該變量。好比在上面這個例子中,若是$a 這個變量還沒有建立,則 set指令會自動建立$a這個用戶變量。若是咱們不建立就直接使用它的值,則會報錯。例如
  
server { listen 8080; location /bad { echo $foo; } }

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

    [emerg] unknown "foo" variable

  是的,咱們甚至都沒法啓動服務!

  

  有趣的是,Nginx 變量的建立和賦值操做發生在全然不一樣的時間階段。Nginx 變量的建立只能發生在 Nginx 配置加載的時候,或者說 Nginx 啓動的時候;而賦值操做則只會發生在請求實際處理的時候。這意味着不建立而直接使用變量會致使啓動失敗,同時也意味着咱們沒法在請求處理時動態地建立新的 Nginx 變量。  

     Nginx 變量一旦建立,其變量名的可見範圍就是整個 Nginx 配置,甚至能夠跨越不一樣虛擬主機的 server配置塊。咱們來看一個例子:

  
server { listen 8080; location /foo { echo "foo = [$foo]"; } location /bar { set $foo 32; echo "foo = [$foo]"; } }

  這裏咱們在 location /bar 中用 set 指令建立了變量 $foo,因而在整個配置文件中這個變量都是可見的,所以咱們能夠在 location /foo 中直接引用這個變量而不用擔憂 Nginx 會報錯。

  

  下面是在命令行上用 curl 工具訪問這兩個接口的結果:

    $ 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 變量的生命期是不可能跨越請求邊界的。

相關文章
相關標籤/搜索