openresty开发系列24--openresty中lua的引入及使用
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)
轉載于:https://www.cnblogs.com/reblue520/p/11434252.html
總結
以上是生活随笔為你收集整理的openresty开发系列24--openresty中lua的引入及使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: openresty开发系列23--lua
- 下一篇: openresty开发系列25--ope