1. 程式人生 > >Lua-Nginx-Module常用指令(上)

Lua-Nginx-Module常用指令(上)

常用 get contex 單個 配置 含義 發的 3.6 odin

本章將會講解基於LuaJIT的Lua-Nginx-Module,它作為Nginx的第三方開源軟件,擁有十分豐富的功能,可以輕松完成高並發的業務需求。

註意:本書使用的Lua-Nginx-Module版本是0.10.13。Nginx API for Lua將被簡稱為Lua API,而Lua-Nginx-Module則被簡稱為Ngx_lua。後面章節中涉及到的Lua API大部分是包含參數的,如果參數以?結尾,代表這個參數是可選的,如在指令ngx.req.get_headers (max_headers?, raw?)中,max_headers和raw是可選的。

一、Nginx和OpenResty

首先,來認識一下OpenResty,它是一個基於Nginx和Lua開發的高性能的Web平臺,包含大量成熟的第三方庫,可快速搭建出高性能的Web服務器,支持常用的反向代理、網關系統、Web應用等。

如果在Nginx上使用Ngx_lua,需要先進行編譯;而OpenResty已經包含此模塊,不需要再進行編譯了。讀者可以自由選擇使用Nginx或OpenResty來搭建服務,如果無法抉擇,可參考如下場景。

  1. 使用Nginx編譯Ngx_Lua的場景

HTTP代理服務器:復雜度較小,只需部分組件即可,且代理服務器一般由運維人員進行維護。使用Nginx的穩定版進行編譯,在性能方面會更有保障,而OpenResty是Nginx的主線版,可能會不定期更新。

  1. OpenResty的使用場景
    API服務:業務需求多,需要大量組件。
    網關系統:需要大量組件和指令來實現動態組件功能。
    Web應用服務器:業務服務、頁面服務等,如詳情頁業務的開發。
    使用Nginx編寫的Lua代碼都可以直接遷移到OpenResty上;反之卻不一定可行,畢竟OpenResty的組件更多。

二、安裝Ngx_lua

請先安裝LuaJIT 2.1.0-beta3(詳見第6.2節)並需要編譯ngx_devel_kit模塊。
下面是在Nginx上的安裝方式(OpenResty自帶此模塊,不必安裝編譯):

# wget ‘http://nginx.org/download/nginx-1.12.2.tar.gz‘
# git clone https://github.com/simplresty/ngx_devel_kit.git
# git clone https://github.com/openresty/lua-nginx-module.git
# tar -xzvf nginx-1.12.2.tar.gz
# cd nginx-1.12.2/
# ./configure --prefix=/usr/local/nginx_1.12.2      --add-module=../ngx_devel_kit      --add-module=../lua-nginx-module
     --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB"

# make && make install

並不是每個Nginx版本都支持最新的Ngx_lua,目前已知支持最新Ngx_lua的Nginx版本如下:

1.13.x (last tested: 1.13.6)
1.12.x
1.11.x (last tested: 1.11.2)
1.10.x
1.9.x (last tested: 1.9.15)
1.8.x
1.7.x (last tested: 1.7.10)
1.6.x

如需獲取最新版本的支持動態,請參考https://github.com/openresty/lua-nginx-module# nginx-compatibility。

三、牢記context標識

Ngx_lua API指令和Nginx的指令一樣,都存在配置環境的約束問題,因此在使用過程中要確保指令的環境符合預期,例如:

ngx.var.VARIABLE
語法:ngx.var.VAR_NAME
context(配置環境):set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,header_ filter_by_lua,body_filter_by_lua,log_by_lua*
context即配置環境,第一次接觸Ngx_lua的讀者看到這樣的配置環境可能會覺得難以理解,因為這還涉及到Ngx_Lua的執行階段(後面會有介紹)。

四、Hello world

首先,還是來一條經典語句“Hello, world”,在Nginx配置中加入一個server:

server {
    listen       80;
    server_name  testnginx.com;
    charset koi8-r;
    location = /test {
     #設置文件使用的默認MIME-type,將會增加一個Content-Type:text/plain的響應頭
     default_type ‘text/plain‘;    
     -- content_by_lua_block執行階段
     content_by_lua_block {    
         ngx.say(‘Hello,world!‘)
     }
    }
}

訪問這個server,輸出如下:

#  curl -I http://testnginx.com/test
Hello,world!    

ngx.say將數據作為響應體輸出,返回給客戶端,並在末尾加上一個回車符。
代碼中用到了content_by_lua_block這個指令塊,它的主要作用是在HTTP的內容處理階段生成數據,詳見第8.6節。

五、避免I/O阻塞

當Nginx和Lua進行讀取磁盤操作時會對Nginx的事件循環造成阻塞,所以在請求中應盡量避免操作磁盤,特別是當文件較大時。
如果Lua使用網絡I/O,為了避免出現阻塞的情況,請使用基於Lua API開發的指令,並使用子請求(將在7.13節介紹)來發送網絡I/O和磁盤I/O。如果需要頻繁讀取磁盤,請分離磁盤I/O的任務和網絡I/O的任務,避免它們相互影響。

六、定義模塊搜索路徑

在開發過程中,常常需要編寫自定義的模塊,或者引入第三方的Lua或C模塊,通過下面的配置可以定義相關模塊的路徑以方便快速查找。

6.1 定義Lua模塊的搜索路徑

lua_package_path用來設置默認的Lua模塊的搜索路徑,並配置在http階段。它支持配置相對路徑和絕對路徑,其中相對路徑是在Nginx啟動時由-p PATH 決定的,如果在啟動Nginx時沒有配置-p PATH,就會使用編譯時--prefix的值,此值一般存放在Nginx的$prefix(也可以用${prefix}來表示)變量中。使用lua_package_path設置Lua模塊搜索路徑的示例如下:

http {
    -- lua_package_path在配置中只能出現一次,使用下面的任何一個方法都可以
    lua_package_path "/usr/local/nginx_1.12.2/conf/lua_modules/?.lua;;";
    lua_package_path "conf/lua_modules/?.lua;;";
    lua_package_path "${prefix}conf/lua_modules/?.lua;;";

上述配置中的3種配置方式都指向同一個位置:
第1個是絕對路徑;
第2個是相對路徑,Nginx編譯時用 --prefix=/usr/local/nginx_1.12.2;
第3個也是相對路徑,Nginx編譯時用 --prefix=/usr/local/nginx_1.12.2 或-p PATH 指定的位置。

第1個配置方式的缺點在於寫出了具體文件搜索路徑,遷移代碼時會比較麻煩。第2個配置方式的缺點在於無法和-p PATH一起使用,如果-p換了位置就會導致這個配置無效。對於第3個配置方式,如果-p的位置換了,${prefix}的值會跟著變換,使用起來比較靈活。所以建議使用第3種配置方式來配置。

lua_package_path可以支持設置多個搜索路徑,多個搜索路徑之間使用分號分隔就可以了,如下:

lua_package_path "${prefix}conf/lua_modules/?.lua;/opt/lua/?.lua;;";

註意:上述配置中搜索路徑的最後出現了;;兩個半角分號,代表的是LuaJIT安裝時的原始搜索路徑,如果在前面的搜索路徑裏面無法搜索到需要的模塊,就會依次搜索後面的路徑。

6.2 定義C模塊的搜索路徑
lua_package_cpath:用來設置C模塊的搜索路徑,並配置在http階段。使用方式和lua_package_path一樣,如下:

lua_package_cpath "${prefix}conf/c_md/?.so;/opt/c/?.so;;";

七、讀寫Nginx的內置變量

如果需要讀取Nginx的內置變量可以使用ngx.var.VARIABLE。

語法:ngx.var.VAR_NAME

配置環境:set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,headerfilter by_lua,body_filter_by_lua,og_by_lua*

含義:讀寫Nginx的變量值。例如HTTP請求頭、Nginx set的變量、URL參數,甚至Nginx通過正則表達式捕獲的$1、$2等值(獲取方式是ngx.var[1]、ngx.var[2],依此類推)。
示例如下:

server {
    listen       80;
    server_name  testnginx.com;
    location ~ ^/([a-z]+)/var.html {
        set $a ‘‘;
        set $b ‘‘;
        set $c ‘‘;
        set $d ‘‘;
        rewrite_by_lua_block {
           local ngx = require "ngx"
           --將1賦值給變量a
           ngx.var.a = ‘1‘
           --獲取HTTP請求頭中user_agent的值並賦值給變量b
           ngx.var.b = ngx.var.http_user_agent
           --獲取參數test的值賦值給變量c
           ngx.var.c = ngx.var.arg_test
           --獲取location中正則表達式捕獲的$1的值並賦值給變量d
           ngx.var.d = ngx.var[1]
        }
        echo $a;
        echo $b;
        echo $c;
        echo $d;
    }

執行結果如下:

# curl -i ‘http://testnginx.com/nginx/var.html?test=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 07 Jun 2018 07:22:32 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
1
curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
12132
nginx

如果是未定義的Nginx變量,是無法直接在Lua中進行讀取的。而且有些變量只能讀取,無法進行修改,如$query_string、$arg_PARAMETER和$http_NAME。

八、控制請求頭
在4.1節中講了Nginx中控制請求頭的指令,在Lua API中也有類似的指令。

8.1 添加請求頭

指令:ngx.req.set_header

語法:ngx.req.set_header(header_name, header_value)

配置環境:set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,headerfilter by_lua,body_filter_by_lua

含義:添加或修改當前HTTP的請求頭,如果請求頭已經存在,則會被替換成新的值。通過此方式設置的請求頭會被繼承到子請求中。

示例:設置一個名為Test_Ngx_Ver,值為1.12.2的請求頭:

ngx.req.set_header("Test_Ngx_Ver", "1.12.2")
ngx.req.set_header支持給同一個請求頭設置多個值,用數組的方式添加:
ngx.req.set_header("Test", {"1", "2"})

多個值的輸出結果:

Test: 1
Test: 2

8.2 清除請求頭

指令:ngx.req.clear_header

語法:ngx.req.clear_header(header_name)

配置環境:set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,headerfilter by_lua,body_filter_by_lua

含義:清除當前請求中指定的請求頭。清除後,如果存在未執行的子請求,則子請求會繼承清除後的請求頭。

示例:
ngx.req.clear_header("Test_Ngx_Ver")
還有一種清除請求頭的方式:
ngx.req.set_header("Test_Ngx_Ver", nil)

8.3 獲取請求頭

指令:ngx.req.get_headers

語法:headers = ngx.req.get_headers(max_headers?, raw?)

配置環境:set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,headerfilter by_lua,body_filter_by_lua,log_by_lua*

含義:獲取當前請求的全部請求頭,並返回一個Lua的table類型的數據:

示例:

server {
    listen       80;
    server_name  testnginx.com;

     location  / {

        content_by_lua_block {
           local ngx = require "ngx";
           local h = ngx.req.get_headers()
           for k, v in pairs(h) do
               ngx.say(‘Header name: ‘,k, ‘ value:‘,v)
           end
           --因為是table,所以可以使用下面的方式讀取單個響應頭的值
           ngx.say(h["host"])
        }
    }
}

輸出結果如下:

# curl -i ‘http://testnginx.com/test?=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 07:46:38 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

Header name:host value: testnginx.com
Header name:accept value: */*
Header name:user-agent value: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
testnginx.com

Lua-Nginx-Module常用指令(上)