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是非堵塞的。