1. 程式人生 > >OpenResty相關nginx以及lua函式

OpenResty相關nginx以及lua函式

在nginx.conf中,可以執行lua程式碼。lua模組可以看作是將一個class單獨存成檔案。
所以學習OpnenResty要學習兩部分語法,一方面是Nginx內建繫結變數和函式,另一方面是Lua語法和Lua針對Nginx實現的類庫。
所以以下就從這兩方面進行學習和總結。

Nginx語法

關於Nginx的介紹很多,覺得這個挺好:agentzh 的 Nginx 教程,但是我還沒看完。
先介紹Nginx內建繫結變數。

名稱 說明
$arg_name 請求中的name引數
$args 請求中的引數
$binary_remote_addr 遠端地址的二進位制表示
$body_bytes_sent 已傳送的訊息體位元組數
$content_length HTTP請求資訊裡的”Content-Length”
$content_type 請求資訊裡的”Content-Type”
$document_root 針對當前請求的根路徑設定值
$document_uri 與$uri相同; 比如 /test2/test.php
$host 請求資訊中的”Host”,如果請求中沒有Host行,則等於設定的伺服器名
$hostname 機器名使用 gethostname系統呼叫的值
$http_cookie cookie 資訊
$http_referer 引用地址
$http_user_agent 客戶端代理資訊
$http_via 最後一個訪問伺服器的Ip地址。
$http_x_forwarded_for 相當於網路訪問路徑
$is_args 如果請求行帶有引數,返回“?”,否則返回空字串
$limit_rate 對連線速率的限制
$nginx_version 當前執行的nginx版本號
$pid worker程序的PID
$query_string 與$args相同
$realpath_root 按root指令或alias指令算出的當前請求的絕對路徑。其中的符號鏈
$remote_addr 客戶端IP地址
$remote_port 客戶端埠號
$remote_user 客戶端使用者名稱,認證用
$request 使用者請求
$request_body 這個變數(0.7.58+)包含請求的主要資訊。在使用proxy_pass或fastcgi_pass指令的location中比較有意義
$request_body_file 客戶端請求主體資訊的臨時檔名
$request_completion 如果請求成功,設為”OK”;如果請求未完成或者不是一系列請求中最後一部分則設為空
$request_filename 當前請求的檔案路徑名,比如/opt/nginx/www/test.php
$request_method 請求的方法,比如”GET”、”POST”等
$request_uri 請求的URI,帶引數
$scheme 所用的協議,比如http或者是https
$server_addr 伺服器地址,如果沒有用listen指明伺服器地址,使用這個變數將發起一次系統呼叫以取得地址(造成資源浪費)
$server_name 請求到達的伺服器名
$server_port 請求到達的伺服器埠號
$server_protocol 請求的協議版本,”HTTP/1.0”或”HTTP/1.1”
$uri 請求的URI,可能和最初的值有不同,比如經過重定向之類的

如何使用內建的繫結變數,通過一個例子來看

server {
    listen       8080;
    server_name  _;

    location /test {
        default_type "text/html";
        echo $host;
    }
}

當訪問

http://localhost:8080/test  #網頁會輸出"localhost"

其他的繫結變數的使用也是一樣的

Lua程式碼

下面介紹如何使用Lua程式碼。有兩種方式。第一種,直接在server裡的location下,嵌入到

    content_by_lua_block{...}

比如:

location /test{
    default_type "text/html";
    content_by_lua_block {
        local res = ngx.location.capture('/print_param',
        {
            method = ngx.HTTP_POST,
            args = {a = 1, b = '2&'},
            body = 'c=3&d=4%26'
        }
        )
        ngx.say(res.body)
    }
}

在content_by_lua_block下的就是lua語法了。
第二種方式為檔案引入。這種方式更符合模組化的思想,引入方式只需要將

content_by_lua_block{}

替換為

content_by_lua_file /usr/openresty/example/test.lua

然後去看test.lua都有什麼

test.lua
-------
ngx.location.capture('/print_param',
        {
            method = ngx.HTTP_POST,
            args = {a = 1, b = '2&'},
            body = 'c=3&d=4%26'
        }
        )
        ngx.say(res.body)

可以看到就是第一種方式裡的lua程式碼部分,這說明檔案方式引用,其實就是一種插入程式碼的形式。這跟lua的模組思想是一樣的。可以原封不動的放過來,拆出來一個檔案更多的是模組化思想。

我覺得Lua的語法可以去學習Lua簡明教程,這裡只需要介紹與Nginx相關的語法。
ngx.say和ngx.print均是向螢幕輸出內容,知識ngx.say會在內容後多加一個回車符,類似與print與println的區別。
nginx中的set指令,lua中有對應的set_by_lua,可以接收lua程式碼的返回值。

set %name "lisi";
set_by_lua_file $name /usr/openresty/example/lua/test1.lua

Nginx API被封裝ngx和ndk兩個package中。
比方ngx.var.NGX_VAR_NAME能夠訪問Nginx變數。這裡著重介紹一下ngx.location.capture和ngx.location.capture_multi。

ngx.location.capture
語法:res= ngx.location.capture(uri, options?) 用於發出一個同步的,非堵塞的Nginxsubrequest(子請求)。

能夠通過Nginx subrequest向其他location發出非堵塞的內部請求。這些location能夠是配置用於讀取目錄的,也能夠是其他的C模組,比方ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle甚至是ngx_lua自己。 Subrequest僅僅是模擬Http介面,並沒有額外的Http或者Tcp傳輸開銷,它在C層次上執行,很高效。Subrequest不同於Http 301/302重定向,以及內部重定向(通過ngx.redirection)。

#配置:
location =/other {
    ehco 'Hello, world!';
}

# Lua非堵塞IO
location =/lua {
    content_by_lua '
        local res = ngx.location.capture("/other")
        if res.status == 200 then
            ngx.print(res.body)
        end
    ';
}

輸出:

$ curl  'http://localhost/lua'
$ Hello, world!

實際上,location能夠被外部的Http請求呼叫,也能夠被內部的子請求呼叫。每一個location相當於一個函式,而傳送子請求就類似於函式呼叫。並且這樣的呼叫是非堵塞的,這就構造了一個很強大的變成模型,後面我們會看到怎樣通過location和後端的memcached、redis進行非堵塞通訊。
ngx.location.capture_multi

語法:res1,res2, … = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, …}) 與ngx.location.capture功能一樣,能夠並行的、非堵塞的發出多個子請求。這種方法在全部子請求處理完畢後返回。而且整個方法的執行時間取決於執行時間最長的子請求,並非全部子請求的執行時間之和。

配置

# 同一時候傳送多個子請求(subrequest)
location =/moon {
    ehco 'moon';
}
location =/earth {
    ehco 'earth';
}

location =/lua {
    content_by_lua '
        local res1,res2 = ngx.location.capture_multi({ {"/moon"}, {"earth"} })
        if res1.status == 200 then
            ngx.print(res1.body)
        end
        ngx.print(",")
        if res2.status == 200 then
            ngx.print(res2.body)
        end
    ';
}

輸出

$ curl  'http://localhost/lua'
$ moon,earth

在Lua程式碼中的網路IO操作僅僅能通過Nginx Lua API完畢。假設通過標準Lua API會導致Nginx的事件迴圈被堵塞,這樣效能會急劇下降。在進行資料量相當小的磁碟IO時能夠採用標準Lua io庫,可是當讀寫大檔案時這樣是不行的,由於會堵塞整個NginxWorker程序。為了獲得更大的效能。強烈建議將全部的網路IO和磁碟IO託付給Nginx子請求完畢(通過ngx.location.capture)。以下通過訪問/html/index.html這個檔案。來測試將磁碟IO託付給Nginx和通過Lua io直接訪問的效率。通過ngx.location.capture託付磁碟IO:

配置

location / {
    internal;
    root html;
}

location /capture {
    content_by_lua '
        res = ngx.location.capture("/")
        echo res.body
    ';
}

通過標準lua io訪問磁碟檔案:

配置:

location /luaio{
    content_by_lua '
        local io = require("io")
        local chunk_SIZE = 4096
        local f = assert(io.open("html/index.html","r"))
        while true do
            local chunk = f:read(chunk)
            if not chunk then
                break
            end
            ngx.print(chunk)
            ngx.flush(true)
        end
        f:close()
    ';
}

所以,在Lua中進行各種IO時。都要通過ngx.location.capture傳送子請求託付給Nginx事件模型,這樣能夠保證IO是非堵塞的。