openResty-----nginx基礎

nginx基礎變量

變量賦值

在nginx中變量的類型只有一種,字符串

比如我們在nginx.conf中進行定義:

set $a "hello world"

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

nginx變量名前面有nginx 符號,類似於PHP。

這樣做的好處在於我們可以直接把變量嵌入到字符串中:

set $a hello;
set $b "$a, $a";

#在上述代碼中,則b變量爲hello,hello

例如我們添加location塊:
這裏寫圖片描述

然後openResty 啓動nginx ,

curl http://localhost/test
#輸出:a:hello,world

我們使用第三方 ngx_echo 模塊的 echo 配置指令將 $foo 變量的值作爲當前請求的響應體輸出。

在echo我們仍然可以進行變量插值,但是如何直接輸入$符號,在nginx中我們沒有辦法對$符號進行轉義。

不過我們可以通過沒有辦法進行變量插入的模塊進行構造值爲$的變量

 geo $dollar {
        default "$";
    }

    server {
        listen 8080;
        location /test {
            echo "This is a dollar sign: $dollar";
        }
    }

比如我們可以通過上面的匹配來繞開,$無法轉義的情況

用到了標準模塊 ngx_geo 提供的配置指令 geo 來爲變量$dollar 賦予字符串 “$”,這樣我們在下面需要使用美元符的地方,就直接引用我們的 $dollar 變量就可以了。其實 ngx_geo 模塊最常規的用法是根據客戶端的 IP 地址對指定的 Nginx 變量進行賦值。

在特殊的情況變量插入的情況,變量與字符創需要進行分割:

server {
        listen 8080;
        location /test {
            set $first "hello ";
            echo "${first}world";
            #在這種情況下變量與字符串沒有分割,必須採用{}將變量與字符創進行分割
        }
    }

上面我們使用的set以及geo模塊,如果變量不存在都會自動創建變量,但是nginx的變量創建與賦值發生在不同的階段,當nginx啓動的時候回根據配置文件進行變量創建,但是變量的賦值發生在請求實際處理的時候。

Nginx 變量一旦創建,其變量名的可見範圍就是整個 Nginx 配置,甚至可以跨越不同虛擬主機的 server 配置塊。

 server {
        listen 8080;

        location /foo {
            echo "foo = [$foo]";
        }

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

在上面的配置中,都定義了foo變量,因此配置檢查可以通過,但是當我們訪問:

    $ curl 'http://localhost:8080/foo'
    foo = []

    $ curl 'http://localhost:8080/bar'
    foo = [32]

    $ curl 'http://localhost:8080/foo'
    foo = []

我們發現,當我們第一次訪問/foo的時候變量爲空,然後訪問/bar變量進行賦值因此是32,再一次訪問/bar時,變量爲空,但是在nginx中變量是全局的這不矛盾嗎?嘿嘿,這裏並不矛盾,nginx變量雖然是全局的,但是每個請求都會有自己獨立的變量副本,請求與請求之間互不影響。

內部跳轉

 server {
        listen 8080;

        location /foo {
            set $a hello;
            echo_exec /bar;
        }

        location /bar {
            echo "a = [$a]";
        }
    }

上面使用第三方模塊 ngx_echo 提供的 echo_exec 配置指令,發起了到/bar的內部跳轉。

    curl localhost:8080/foo
    a = [hello]

因此我們可以判斷這是一個請求,不同於301、302,進行的跳轉。但是如果我們直接訪問/bar,那麼我們會得到空的變量。

 server {
        listen 8080;

        location /foo {
            set $a hello;
            rewrite ^ /bar;
        }

        location /bar {
            echo "a = [$a]";
        }
    }

在上面的配置,我們依然採用的是內部跳轉,使用ngx_rewrite模塊。實現相同的功能。

結論:nginx中變量雖然時全局的,但是每個請求都會有自己的副本,互不影響,各個模塊之間也不影響。

上面我們使用set指令創建的變量稱爲隱式變量或者用戶自定義變量,在nginx核心以及各個模塊中存在預定義變量。

Nginx 內建變量最常見的用途就是獲取關於請求或響應的各種信息。例如由 ngx_http_core 模塊提供的內建變量 $uri,可以用來獲取當前請求的 URI(經過解碼,並且不含請求參數),而 $request_uri 則用來獲取請求最原始的 URI (未經解碼,並且包含請求參數).

 location /test {
        echo "uri = $uri";
        echo "request_uri = $request_uri";
    }

    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

另一個常用的內建變量是一個族羣,我們稱爲args_XXX變量羣:

location /test {
        echo "name: $arg_name";
        echo "class: $arg_class";
    }

我們開始進行訪問:

    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

args_XXX實則就是拿取請求參數,同時注意XXX不區分大小寫哦。

但是我們拿到的參數是經過編碼的,name: hello%20world其中%20實則爲空格,我們可以採用:第三方 ngx_set_misc 模塊提供的 set_unescape_uri 配置指令

location /test {
        set_unescape_uri $name $arg_name;
        set_unescape_uri $class $arg_class;

        echo "name: $name";
        echo "class: $class";
    }

curl 'http://localhost:8080/test?name=hello%20world&class=9'
    name: hello world
    class: 9

這樣我們便將url參數成功進行解碼

類似 $arg_XXX 的內建變量還有不少,比如用來取 cookie 值的 $cookie_XXX 變量羣,用來取請求頭的 $http_XXX 變量羣,以及用來取響應頭的 $sent_http_XXX 變量羣.

需要注意的是:

許多內建變量都是隻讀的,比如我們剛纔介紹的 uri request_uri。

但是也有例外,對於args參數,就是可以被修改的:

location /test {
        set $orig_args $args;
        set $args "a=3&b=4";

        echo "original args: $orig_args";
        echo "args: $args";
    }

    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

上面我們先把args賦值給original ,然後在修改args變量的值,需要特別指出的是,向args與args_XXX變量都不是實現進行計算好的,而是使用的時候對url進行掃描,然後進行賦值操作。

變量緩存

上面我們提到,在nginx中,args_XXX等變量都是在用到的時候,對請求的URL進行掃描,然後進行取值操作,那麼如果我們多次用到這個值,每次都去取就會很浪費,因此存在變量緩存,Nginx 變量也可以選擇將其值容器用作緩存,這樣在多次讀取變量的時候,就只需要調用“取處理程序”計算一次。

map $args $foo {
        default     0;
        debug       1;
    }

    server {
        listen 8080;

        location /test {
            set $orig_foo $foo;
            set $args debug;

            echo "original foo: $orig_foo";
            echo "foo: $foo";
        }
    }

再上面我們用到了標準的ngx_map模塊的,map配置指令,map在此處我們理解爲映射,或者理解爲y=f(x),在上面我們認爲args 爲自變量x,foo 爲因變量也就是y,y隨着x的變化而變化。

映射規則:

default  默認匹配條件,還記得swich裏面的default操作符吧,它們很相似,都是在其他條件不匹配的情況下,默認的匹配,將變量foo賦值爲0。
debug    也就是當args爲debug的時候,他們則匹配,然後將變量賦值foo爲1

因此當args不是debug的情況下,foo都爲0,否則爲1。

    curl 'http://localhost:8080/test'
    original foo: 0
    foo: 0

在訪問中,匹配將args改爲debug,然後在輸入foo值,x變換y應該跟着變化,應該輸出1纔對。其實原因很簡單,那就是 $foo 變量在第一次讀取時,根據映射規則計算出的值被緩存住了。剛纔我們說過,Nginx 模塊可以爲其創建的變量選擇使用值容器,作爲其“取處理程序”計算結果的緩存。顯然, ngx_map 模塊認爲變量間的映射計算足夠昂貴,需要自動將因變量的計算結果緩存下來,這樣在當前請求的處理過程中如果再次讀取這個因變量,Nginx 就可以直接返回緩存住的結果,而不再調用該變量的“取處理程序”再行計算了。

請求

在nginx中我們將請求分爲:

主請求:
     HTTP 客戶端從 Nginx 外部發起的請求。
子請求:
    由 Nginx 正在處理的請求在 Nginx 內部發起的一種級聯請求。
location /main {
        echo_location /foo;
        echo_location /bar;
    }

    location /foo {
        echo foo;
    }

    location /bar {
        echo bar;
    }

在上面的配置中,http請求/main,然後利用第三方 ngx_echo 模塊的 echo_location 指令分別發起到 /foo 和 /bar 這兩個接口的 GET 類型的“子請求”執行是按照配置書寫的順序串行處理的,即只有當 /foo 請求處理完畢之後,纔會接着處理 /bar 請求。這兩個“子請求”的輸出會按執行順序拼接起來,作爲 /main 接口的最終輸出。

子請求”方式的通信是在同一個虛擬主機內部進行的,所以 Nginx 核心在實現“子請求”的時候,就只調用了若干個 C 函數,完全不涉及任何網絡或者 UNIX 套接字(socket)通信。我們由此可以看出“子請求”的執行效率是極高的。

     location /test {
            set $a hello,world;
            echo_location /bar;
            echo $a;
        }


        location /bar {
            echo $a;
            set $a xixi;
        }

在上述請求過程中,我們首相將a設置爲hello,world,然後發送自請求到/bar中,打印a,然後在賦值爲xixi,回到主請求再次打印a。

curl http://localhost/test
xixi
hello,world

我們發現,在主請求中對a進行賦值,但是在子請求中沒有打印a的值,然後將a進行賦值爲xixi,主請求依然打印hello,world。

因此我們得到結論:

主請求與子請求各自擁有自己的獨立變量副本,互補影響。

特例:

不幸的是,這不是所有情況的結論,在ngx_auth_request 模塊發起的“子請求”確實是與其“父請求”共享一套 Nginx 變量的值容器。

nginx變量爲空

在nginx中變量,當我們聲明瞭變量但是沒有賦值,以及我們採用args_XXX訪問變量變量不存在的情況下。

  location /foo {
        echo "foo = [$foo]";
    }

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

例如我們訪問/foo,foo變量雖然在配置文件中被聲明,但是並沒有被賦值,我們插入nginx的error.log日誌[warn] 5765#0: *1 using uninitialized “foo” variable, …

當 /foo 接口中的 echo 指令實際執行的時候,它會對它的參數 “foo = $foo]” 進行“變量插值”計算。於是,參數串中的 $foo 變量會被讀取,而 Nginx 會首先檢查其值容器裏的取值,結果它看到了“不合法”這個特殊值,於是它這才決定繼續調用$foo 變量的“取處理程序”。於是$foo 變量的“取處理程序”開始運行,它向 Nginx 的錯誤日誌打印出上面那條警告消息,然後返回一個空字符串作爲 $foo 的值,並從此緩存在 $foo 的值容器中.

細心的讀者會注意到剛剛描述的這個過程其實就是那些支持值緩存的內建變量的工作原理,只不過 set 指令在這裏借用了這套機制來處理未正確初始化的 Nginx 變量。值得一提的是,只有“不合法”這個特殊值纔會觸發 Nginx 調用變量的“取處理程序”,而特殊值“沒找到”卻不會

但是當我們採用args_XXX進行參數尋找的時候,如果url中沒有相關參數,則認爲爲空字符,nginx直接忽略了這種情況,不過我們可以採用lua進行查找:

 location /test {
        content_by_lua '
            if ngx.var.arg_name == nil then
                ngx.say("name: missing")
            else
                ngx.say("name: [", ngx.var.arg_name, "]")
            end
        ';
    }

上面我們採用了ngx_lua模塊, ngx_lua 模塊可以讓nginx直接運行lua語言,我們在 Lua 代碼裏引用 Nginx 變量都是通過 ngx.var 這個由 ngx_lua 模塊提供的 Lua 接口。比如引用 Nginx 變量 $VARIABLE 時,就在 Lua 代碼裏寫作 ngx.var.VARIABLE 就可以了。

不過,標準的 $arg_XXX 變量還是有一些侷限,比如我們用下面這個請求來測試剛纔那個 /test 接口:

    curl 'http://localhost:8080/test?name'
    name: missing

此時,argname arg_XXX 變量在請求 URL 中有多個同名 XXX 參數時,就只會返回最先出現的那個 XXX 參數的值,而默默忽略掉其他實例:

    curl 'http://localhost:8080/test?name=Tom&name=Jim&name=Bob'
    name: [Tom]

要解決這些侷限,可以直接在 Lua 代碼中使用 ngx_lua 模塊提供的 ngx.req.get_uri_args 函數。

引用:

https://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章