openresty開發系列24--openresty中lua的引入及使用
openresty 引入 lua
一)openresty中nginx引入lua方式
1)xxx_by_lua --->字符串編寫方式
2) xxx_by_lua_block ---->代碼塊方式
3) xxx_by_lua_file ---->直接引用一個lua腳本文件
我們案例中使用內容處理階段,用content_by_lua演示
-----------------編輯nginx.conf-----------------------
第一種:content_by_lua
location /testlua {
content_by_lua "ngx.say('hello world')";
}
輸出了hello world
content_by_lua 方式,參數為字符串,編寫不是太方便。
----------------------------------------
第二種:content_by_lua_block
location /testlua {
content_by_lua_block {
ngx.say("hello world");
}
}
content_by_lua_block {} 表示內部為lua塊,里面可以應用lua語句
----------------------------------------
第三種:content_by_lua_file
location /testlua {
content_by_lua_file /usr/local/lua/test.lua;
}
content_by_lua_file 就是引用外部lua文件
# vi test.lua
ngx.say("hello world");
二)openresty使用lua打印輸出案例
location /testsay {
content_by_lua_block {
--寫響應頭
ngx.header.a = "1"
ngx.header.b = "2"
--輸出響應
ngx.say("a", "b", "<br/>")
ngx.print("c", "d", "<br/>")
--200狀態碼退出
return ngx.exit(200)
}
}
ngx.header:輸出響應頭;
ngx.print:輸出響應內容體;
ngx.say:通ngx.print,但是會最后輸出一個換行符;
ngx.exit:指定狀態碼退出。
三)介紹一下openresty使用lua常用的api
1)ngx.var : 獲取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,帶參數; 比如http://localhost:88/test1/
$scheme 所用的協議,比如http或者是https
$server_addr 服務器地址,如果沒有用listen指明服務器地址,使用這個變量將發起一次系統調用以取得地址(造成資源浪費)
$server_name 請求到達的服務器名
$server_port 請求到達的服務器端口號
$server_protocol 請求的協議版本,"HTTP/1.0"或"HTTP/1.1"
$uri 請求的URI,可能和最初的值有不同,比如經過重定向之類的
ngx.var.xxx
location /var {
set $c 3;
#處理業務
content_by_lua_block {
local a = tonumber(ngx.var.arg_a) or 0
local b = tonumber(ngx.var.arg_b) or 0
local c = tonumber(ngx.var.c) or 0
ngx.say("sum:", a + b + c )
}
}
注意:ngx.var.c 此變量必須提前聲明;
另外對於nginx location中使用正則捕獲的捕獲組可以使用ngx.var[捕獲組數字]獲取;
location ~ ^/var/([0-9]+) {
content_by_lua_block {
ngx.say("var[1]:", ngx.var[1] )
}
}
2)ngx.req請求模塊的常用api
ngx.req.get_headers:獲取請求頭,
獲取帶中划線的請求頭時請使用如headers.user_agent這種方式;如果一個請求頭有多個值,則返回的是table;
-----------test.lua-------------------
local headers = ngx.req.get_headers()
ngx.say("============headers begin===============", "<br/>")
ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("headers['user-agent'] : ", headers["user-agent"], "<br/>")
ngx.say("headers.user_agent : ", headers.user_agent, "<br/>")
ngx.say("-------------遍歷headers-----------", "<br/>")
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), "<br/>")
else
ngx.say(k, " : ", v, "<br/>")
end
end
ngx.say("===========headers end============", "<br/>")
ngx.say("<br/>")
3)獲取請求參數
ngx.req.get_uri_args:獲取url請求參數,其用法和get_headers類似;
ngx.req.get_post_args:獲取post請求內容體,其用法和get_headers類似,
但是必須提前調用ngx.req.read_body()來讀取body體
(也可以選擇在nginx配置文件使用lua_need_request_body on;開啟讀取body體,
但是官方不推薦);
ngx.req.get_body_data:為解析的請求body體內容字符串。
---------------test.lua---------------
--get請求uri參數
ngx.say("===========uri get args begin==================", "<br/>")
local uri_args = ngx.req.get_uri_args()
for k, v in pairs(uri_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
else
ngx.say(k, ": ", v, "<br/>")
end
end
ngx.say("===========uri get args end==================", "<br/>")
--post請求參數
ngx.req.read_body()
ngx.say("=================post args begin====================", "<br/>")
local post_args = ngx.req.get_post_args()
for k, v in pairs(post_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
else
ngx.say(k, ": ", v, "<br/>")
end
end
ngx.say("================post args end=====================", "<br/>")
4) ngx.req其他常用的api
--請求的http協議版本
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
--請求方法
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")
--原始的請求頭內容
ngx.say("ngx.req.raw_header : ", ngx.req.raw_header(), "<br/>")
--請求的body內容體
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")
ngx.say("<br/>")
ngx.req.raw_header()這個函數返回值為字符串
5)編碼解碼
ngx.escape_uri/ngx.unescape_uri : uri編碼解碼;
ngx.encode_args/ngx.decode_args:參數編碼解碼;
ngx.encode_base64/ngx.decode_base64:BASE64編碼解碼;
-------test.lua
--未經解碼的請求uri
local request_uri = ngx.var.request_uri;
ngx.say("request_uri : ", request_uri, "<br/>");
--編碼
local escape_uri = ngx.escape_uri(request_uri)
ngx.say("escape_uri : ", escape_uri, "<br/>");
--解碼
ngx.say("decode request_uri : ", ngx.unescape_uri(escape_uri), "<br/>");
--參數編碼
local request_uri = ngx.var.request_uri;
local question_pos, _ = string.find(request_uri, '?')
if question_pos>0 then
local uri = string.sub(request_uri, 1, question_pos-1)
ngx.say("uri sub=",string.sub(request_uri, question_pos+1),"<br/>");
--對字符串進行解碼
local args = ngx.decode_args(string.sub(request_uri, question_pos+1))
for k,v in pairs(args) do
ngx.say("k=",k,",v=", v, "<br/>");
end
if args and args.userId then
args.userId = args.userId + 10000
ngx.say("args+10000 : ", uri .. '?' .. ngx.encode_args(args), "<br/>");
end
end
6)md5加密api
--MD5
ngx.say("ngx.md5 : ", ngx.md5("123"), "<br/>")
7)nginx獲取時間
之前介紹的os.time()會涉及系統調用,性能比較差,推薦使用nginx中的時間api
ngx.time() --返回秒級精度的時間戳
ngx.now() --返回毫秒級精度的時間戳
就是通過這兩種方式獲取到的只是nginx緩存起來的時間戳,不是實時的。
所以有時候會出現一些比較奇怪的現象,比如下面代碼:
local t1 = ngx.now()
for i=1,1000000 do
end
local t2 = ngx.now()
ngx.say(t1, ",", t2) -- t1和t2的值是一樣的,why?
ngx.exit(200)
正常來說,t2應該大於t1才對,但由於nginx沒有及時更新(緩存的)時間戳,所以導致t2和t1獲取到的時間戳是一樣的。
那么怎樣才能強迫nginx更新緩存呢?調用多一個ngx.update_time()函數即可:
local t1 = ngx.now()
for i=1,1000000 do
end
ngx.update_time()
local t2 = ngx.now()
ngx.say(t1, ",", t2)
ngx.exit(200)
8)ngx.re模塊中正則表達式相關的api
ngx.re.match
ngx.re.sub
ngx.re.gsub
ngx.re.find
ngx.re.gmatch
我們這里只簡單的介紹 ngx.re.match,詳細用法可以自行去網上學習
ngx.re.match
只有第一次匹配的結果被返回,如果沒有匹配,則返回nil;或者匹配過程中出現錯誤時,
也會返回nil,此時錯誤信息會被保存在err中。
當匹配的字符串找到時,一個Lua table captures會被返回,
captures[0]中保存的就是匹配到的字串,
captures[1]保存的是用括號括起來的第一個子模式(捕獲分組)的結果,
captures[2]保存的是第二個子模式(捕獲分組)的結果,依次類似。
---------------------
local m, err = ngx.re.match("hello, 1234", "[0-9]+")
if m then
ngx.say(m[0])
else
if err then
ngx.log(ngx.ERR, "error: ", err)
return
end
ngx.say("match not found")
end
上面例子中,匹配的字符串是1234,因此m[0] == "1234",
--------------
local m, err = ngx.re.match("hello, 1234", "([0-9])[0-9]+")
ngx.say(m[0],"<br/>")
ngx.say(m[1])
---------------------------------------------------------
備注:有沒有注意到,我們每次修改都要重啟nginx,這樣太過於麻煩,我們可以用
content_by_lua_file 引入外部lua,這樣的話 只要修改外部的lua,就可以了,不需要重啟nginx了。
注意需要把lua_code_cache 設置為off,實際生產環境是需要設置為on的
語法:lua_code_cache on | off
默認: on
適用上下文:http、server、location、location if
這個指令是指定是否開啟lua的代碼編譯緩存,開發時可以設置為off,以便lua文件實時生效,
如果是生產線上,為了性能,建議開啟。
最終nginx.conf修改為
以后我們只要修改test.lua 文件就可以了。
**********生產環境不建議修改
9)標准日志輸出
ngx.log(log_level, ...)
日志輸出級別
ngx.STDERR -- 標准輸出
ngx.EMERG -- 緊急報錯
ngx.ALERT -- 報警
ngx.CRIT -- 嚴重,系統故障,觸發運維告警系統
ngx.ERR -- 錯誤,業務不可恢復性錯誤
ngx.WARN -- 告警,業務中可忽略錯誤
ngx.NOTICE -- 提醒,業務比較重要信息
ngx.INFO -- 信息,業務瑣碎日志信息,包含不同情況判斷等
ngx.DEBUG -- 調試
-------------------------------------
#user nobody;
worker_processes 1;
error_log logs/error.log error; # 日志級別
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
content_by_lua_block {
local num = 55
local str = "string"
local obj
ngx.log(ngx.ERR, "num:", num)
ngx.log(ngx.INFO, " string:", str)
print([[i am print]])
ngx.log(ngx.ERR, " object:", obj)
}
}
}
}
日志輸出級別使用的 error,只有等於或大於這個級別的日志才會輸出
ngx.DEBUG
ngx.WARN
對於應用開發,一般使用 ngx.INFO 到 ngx.CRIT 就夠了。生產中錯誤日志開啟到 error 級別就夠了
10)重定向 ngx.redirect
-----重定向
location = /bar {
content_by_lua_block {
ngx.say([[I am bar]])
}
}
location = /foo {
rewrite_by_lua_block {
return ngx.redirect('/bar');
}
}
11)不同階段共享變量
ngx.ctx 全局共享變量
在 OpenResty 的體系中,可以通過共享內存的方式完成不同工作進程的數據共享,
本地內存方式 去讓不同的工作進程共享數據
openresty有不同處理階段,后面的課程會介紹。在不同的處理階段,如何共享數據
可以通過 Lua 模塊方式完成單個進程內不同請求的數據共享。如何完成單個請求內不同階段的數據共享呢?
ngx.ctx 表就是為了解決這類問題而設計的。參考下面例子:
location /test {
rewrite_by_lua_block {
ngx.ctx.foo = 76
}
access_by_lua_block {
ngx.ctx.foo = ngx.ctx.foo + 3
}
content_by_lua_block {
ngx.say(ngx.ctx.foo)
}
}
ngx.ctx.xxxxx
首先 ngx.ctx 是一個表,所以我們可以對他添加、修改。它用來存儲基於請求的 Lua 環境數據,
其生存周期與當前請求相同 (類似 Nginx 變量)。它有一個最重要的特性:
單個請求內的 rewrite (重寫),access (訪問),和 content (內容) 等各處理階段是保持一致的。
額外注意,每個請求,包括子請求,都有一份自己的 ngx.ctx 表。例如:
location /sub {
content_by_lua_block {
ngx.say("sub pre: ", ngx.ctx.blah)
ngx.ctx.blah = 32
ngx.say("sub post: ", ngx.ctx.blah)
}
}
location /main {
content_by_lua_block {
ngx.ctx.blah = 73
ngx.say("main pre: ", ngx.ctx.blah)
local res = ngx.location.capture("/sub")
ngx.print(res.body)
ngx.say("main post: ", ngx.ctx.blah)
}
}
ngx.ctx 表查詢需要相對昂貴的元方法調用,這比通過用戶自己的函數參數直接傳遞基於請求的數據要慢得多。
所以不要為了節約用戶函數參數而濫用此 API,因為它可能對性能有明顯影響。
由於 ngx.ctx 保存的是指定請求資源,所以這個變量是不能直接共享給其他請求使用的。
更多api使用 https://www.nginx.com/resources/wiki/modules/lua/#nginx-api-for-lua
操作指令 說明
ngx.arg 指令參數,如跟在content_by_lua_file后面的參數
ngx.var 變量,ngx.var.VARIABLE引用某個變量
ngx.ctx 請求的lua上下文
ngx.header 響應頭,ngx.header.HEADER引用某個頭
ngx.status 響應碼
API 說明
ngx.log 輸出到error.log
print 等價於 ngx.log(ngx.NOTICE, ...)
ngx.send_headers 發送響應頭
ngx.headers_sent 響應頭是否已發送
ngx.resp.get_headers 獲取響應頭
ngx.timer.at 注冊定時器事件
ngx.is_subrequest 當前請求是否是子請求
ngx.location.capture 發布一個子請求
ngx.location.capture_multi 發布多個子請求
ngx.exec
ngx.redirect
ngx.print 輸出響應
ngx.say 輸出響應,自動添加'n'
ngx.flush 刷新響應
ngx.exit 結束請求
ngx.eof
ngx.sleep 無阻塞的休眠(使用定時器實現)
ngx.get_phase
ngx.on_abort 注冊client斷開請求時的回調函數
ndk.set_var.DIRECTIVE
ngx.req.start_time 請求的開始時間
ngx.req.http_version 請求的HTTP版本號
ngx.req.raw_header 請求頭(包括請求行)
ngx.req.get_method 請求方法
ngx.req.set_method 請求方法重載
ngx.req.set_uri 請求URL重寫
ngx.req.set_uri_args
ngx.req.get_uri_args 獲取請求參數
ngx.req.get_post_args 獲取請求表單
ngx.req.get_headers 獲取請求頭
ngx.req.set_header
ngx.req.clear_header
ngx.req.read_body 讀取請求體
ngx.req.discard_body 扔掉請求體
ngx.req.get_body_data
ngx.req.get_body_file
ngx.req.set_body_data
ngx.req.set_body_file
ngx.req.init_body
ngx.req.append_body
ngx.req.finish_body
ngx.req.socket
ngx.escape_uri 字符串的url編碼
ngx.unescape_uri 字符串url解碼
ngx.encode_args 將table編碼為一個參數字符串
ngx.decode_args 將參數字符串編碼為一個table
ngx.encode_base64 字符串的base64編碼
ngx.decode_base64 字符串的base64解碼
ngx.crc32_short 字符串的crs32_short哈希
ngx.crc32_long 字符串的crs32_long哈希
ngx.hmac_sha1 字符串的hmac_sha1哈希
ngx.md5 返回16進制MD5
ngx.md5_bin 返回2進制MD5
ngx.sha1_bin 返回2進制sha1哈希值
ngx.quote_sql_str SQL語句轉義
ngx.today 返回當前日期
ngx.time 返回UNIX時間戳
ngx.now 返回當前時間
ngx.update_time 刷新時間后再返回
ngx.localtime
ngx.utctime
ngx.cookie_time 返回的時間可用於cookie值
ngx.http_time 返回的時間可用於HTTP頭
ngx.parse_http_time 解析HTTP頭的時間
ngx.re.match
ngx.re.find
ngx.re.gmatch
ngx.re.sub
ngx.re.gsub
ngx.shared.DICT
ngx.shared.DICT.get
ngx.shared.DICT.get_stale
ngx.shared.DICT.set
ngx.shared.DICT.safe_set
ngx.shared.DICT.add
ngx.shared.DICT.safe_add
ngx.shared.DICT.replace
ngx.shared.DICT.delete
ngx.shared.DICT.incr
ngx.shared.DICT.flush_all
ngx.shared.DICT.flush_expired
ngx.shared.DICT.get_keys
ngx.socket.udp
udpsock:setpeername
udpsock:send
udpsock:receive
udpsock:close
udpsock:settimeout
ngx.socket.tcp
tcpsock:connect
tcpsock:sslhandshake
tcpsock:send
tcpsock:receive
tcpsock:receiveuntil
tcpsock:close
tcpsock:settimeout
tcpsock:setoption
tcpsock:setkeepalive
tcpsock:getreusedtimes
ngx.socket.connect
ngx.thread.spawn
ngx.thread.wait
ngx.thread.kill
coroutine.create
coroutine.resume
coroutine.yield
coroutine.wrap
coroutine.running
coroutine.status
ngx.config.debug 編譯時是否有 --with-debug選項
ngx.config.prefix 編譯時的 --prefix選項
ngx.config.nginx_version 返回nginx版本號
ngx.config.nginx_configure 返回編譯時 ./configure的命令行選項
ngx.config.ngx_lua_version 返回ngx_lua模塊版本號
ngx.worker.exiting 當前worker進程是否正在關閉(如reload、shutdown期間)
ngx.worker.pid 返回當前worker進程的pid
常量說明
ngx.OK (0)
ngx.ERROR (-1)
ngx.AGAIN (-2)
ngx.DONE (-4)
ngx.DECLINED (-5)
ngx.nil
HTTP 請求方式
ngx.HTTP_GET
ngx.HTTP_HEAD
ngx.HTTP_PUT
ngx.HTTP_POST
ngx.HTTP_DELETE
ngx.HTTP_OPTIONS
ngx.HTTP_MKCOL
ngx.HTTP_COPY
ngx.HTTP_MOVE
ngx.HTTP_PROPFIND
ngx.HTTP_PROPPATCH
ngx.HTTP_LOCK
ngx.HTTP_UNLOCK
ngx.HTTP_PATCH
ngx.HTTP_TRACE
HTTP 返回狀態
ngx.HTTP_OK (200)
ngx.HTTP_CREATED (201)
ngx.HTTP_SPECIAL_RESPONSE (300)
ngx.HTTP_MOVED_PERMANENTLY (301)
ngx.HTTP_MOVED_TEMPORARILY (302)
ngx.HTTP_SEE_OTHER (303)
ngx.HTTP_NOT_MODIFIED (304)
ngx.HTTP_BAD_REQUEST (400)
ngx.HTTP_UNAUTHORIZED (401)
ngx.HTTP_FORBIDDEN (403)
ngx.HTTP_NOT_FOUND (404)
ngx.HTTP_NOT_ALLOWED (405)
ngx.HTTP_GONE (410)
ngx.HTTP_INTERNAL_SERVER_ERROR (500)
ngx.HTTP_METHOD_NOT_IMPLEMENTED (501)
ngx.HTTP_SERVICE_UNAVAILABLE (503)
ngx.HTTP_GATEWAY_TIMEOUT (504)
