【技術幹貨】聽阿裏雲CDN安防技術專家金九講tengine+lua開發
一、介紹
二、安裝
三、運行
四、開發
1. 介紹
Tengine:輕量級、高性能、高並發、配置化、模塊化、可擴展、可移植的Web和反向代理 服務器,Tengine是nginx超集,但做了很多優化,包含了很多比較有用的模塊,比如直接包含了lua、proc等很有用的模塊。
Lua:一個很輕量級的 腳本,也號稱性能最高的 腳本。代碼總共不到600k,32個C文件,23個頭文件:
root@j9 ~/lua-5.1.5/src# du -sh ./
572K ./
root@j9 ~/lua-5.1.5/src# ls *.c | wc -l
32
root@j9 ~/lua-5.1.5/src# ls *.h | wc -l
23
root@j9 ~/lua-5.1.5/src#
可以非常容易的嵌入C和C++工程中,也比較容易與C和C++互動,這也是目前Lua主要的用法。
ngx_lua:一個nginx很重要的第三方模塊,作者:章亦春(agentzh、春哥),結合了nginx和Lua各自優點,把Lua嵌入nginx中,使其支持Lua來快速開發基於nginx下的業務邏輯。
https://github.com/openresty/lua-nginx-module
2. 安裝
2.1、LuaJIT
wget -c https://luajit.org/download/LuaJIT-2.0.4.tar.gz
tar xzvf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
make install PREFIX=/usr/local/luajit
#注意環境變量!
export LUAJIT_LIB=/usr/local/luajit/lib
export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0
2.2、Tengine
tengine最新代碼中已經包含lua模塊了,直接git clone下來就可以
git clone https://github.com/alibaba/tengine.git
cd tengine
./configure --prefix=/opt/tengine --with-http_lua_module
make
make install
如果是原生nginx的話,得自行下載lua模塊代碼:
wget https://nginx.org/download/nginx-1.7.8.tar.gz
tar xvf nginx-1.7.8.tar.gz
cd nginx-1.7.8
mkdir modules
cd modules
git clone https://github.com/openresty/lua-nginx-module.git
cd ..
./configure --prefix=/opt/nginx --add-module=./modules/lua-nginx-module/
make
make install
3. 運行
修改/opt/tengine/conf/nginx.conf:
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /hello_lua {
content_by_lua '
ngx.say("Lua: hello world!")
';
}
}
}
運行tengine:
root@j9 ~/tengine# /opt/tengine/sbin/nginx
curl訪問一下hello_lua:
root@j9 ~/tengine# curl https://localhost/hello_lua
Lua: hello world!
運行ok。
4、開發
語法
入門
深入
4.1、語法
4.2、入門
4.2.1、API
ngx.print
輸出響應內容體;
例如:ngx.print("a", "b", "c")ngx.say
跟ngx.print的區別隻是最後會多輸出一個換行符;
例如:ngx.say("a", "b", "c")ngx.status
設置響應HTTP狀態碼;
注意,設置狀態碼僅在響應頭發送前有效。當調用ngx.say或者ngx.print時自動發送響應狀態碼(默認為200);可以通ngx.headers_sent來判斷是否發送了響應狀態碼。
例如:ngx.status = 200ngx.exit
設置響應HTTP狀態碼並退出;
注意,設置狀態碼僅在響應頭發送前有效,並且該函數調用之後該函數後麵的lua將被忽略掉,因為已經exit了。
例如:ngx.exit(200)ngx.header
輸出響應頭;
注意,頭部字段中含有橫杠(-)的要轉換成下劃線(_),ngx_lua模塊自動將_轉換成-。
例如:ngx.header["X-Cache"] = "HIT" 或者 ngx.header.X_Cache = "HIT"或者ngx.header.X_Cache = {"AA", "BB"}ngx.redirect
301或者302重定向
例如:ngx.redirect("https://www.taobao.org", 301)ngx.log
打印nginx錯誤日誌,日誌級別有:ngx.STDERR、ngx.EMERG、ngx.ALERT、ngx.CRIT、ngx.ERR、ngx.WARN、ngx.NOTICE、ngx.INFO、ngx.DEBUG
例如:ngx.log(ngx.ERR, "test: ", "ok")
例子:
server {
listen 9898;
location / {
default_type "text/html";
content_by_lua '
local headers_sent_1 = ngx.headers_sent
ngx.header["X-Cache"] = "HIT"
ngx.header.Y_Cache = "MISS"
ngx.header.Z_Cache = {"AA", "BB"}
ngx.status = 602
local headers_sent_2 = ngx.headers_sent
ngx.print("a", "b")
local headers_sent_3 = ngx.headers_sent
ngx.say("c", "d")
ngx.say("e", "f")
ngx.say("headers_sent_1: ", tostring(headers_sent_1))
ngx.say("headers_sent_2: ", tostring(headers_sent_2))
ngx.say("headers_sent_3: ", tostring(headers_sent_3))
ngx.log(ngx.ERR, "ngx.log test ok")
ngx.exit(601)
ngx.say("g", "h")
';
}
location ^~ /redirect {
content_by_lua '
ngx.redirect("https://www.taobao.org", 301)
';
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9898/" -i
HTTP/1.1 602
Server: Tengine/2.2.0
Date: Mon, 19 Oct 2015 16:10:42 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Cache: HIT
Y-Cache: MISS
Z-Cache: AA
Z-Cache: BB
abcd
ef
headers_sent_1: false
headers_sent_2: false
headers_sent_3: true
root@j9 ~# curl "https://127.0.0.1:9898/redirect" -i
HTTP/1.1 301 Moved Permanently
Server: Tengine/2.2.0
Date: Mon, 19 Oct 2015 16:18:16 GMT
Content-Type: text/html
Content-Length: 284
Connection: keep-alive
Location: https://www.taobao.org
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<h1>301 Moved Permanently</h1>
<p>The requested resource has been assigned a new permanent URI.</p>
<hr/>Powered by Tengine/2.2.0</body>
</html>
root@j9 ~#
- ngx.var 讀取nginx變量,如nginx變量為$a,則在Lua中通過ngx.var.a獲取,也可以給nginx變量賦值如ngx.var.a = "aa",前提是該變量在nginx中必須存在,不能在Lua中創建nginx變量。另外,對於nginx location中使用正則捕獲的捕獲組可以使用ngx.var[捕獲組數字]獲取。
例子
server {
listen 9898;
location ~ /var/([^/]*)/([^/]*) {
default_type "text/html";
set $a "aaa";
set $b $host;
content_by_lua '
ngx.say("$a: ", ngx.var.a)
ngx.say("$b: ", ngx.var.b)
ngx.say("$host: ", ngx.var.host)
ngx.say("$arg_id: ", ngx.var.arg_id)
ngx.say("$1: ", ngx.var[1])
ngx.say("$2: ", ngx.var[2])
';
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9898/var/aaaa/bbbb?id=22" -H "Host: www.taobao.org"
$a: aaa
$b: www.taobao.org
$host: www.taobao.org
$arg_id: 22
$1: aaaa
$2: bbbb
root@j9 ~#
ngx.req.raw_header
未解析的請求頭字符串;
例如:ngx.req.raw_header()ngx.req.get_headers
獲取請求頭,默認隻獲取前100個頭部,如果想要獲取所有頭部可以調用ngx.req.get_headers(0);獲取帶中劃線的請求頭時要把中劃線轉換成下劃線使用如headers.user_agent這種方式;如果一個請求頭有多個值,則返回的是table;
例如:ngx.req.get_headers()ngx.req.get_uri_args
獲取url請求參數,其用法與ngx.req.get_headers類似;ngx.req.get_post_args
獲取post請求body參數,其用法與ngx.req.get_uri_args類似,但必須提前調用ngx.req.read_body();ngx.req.read_body
如果要獲取請求的body,則需要調用ngx.req.read_body(),否則獲取不到body數據,(ps:也可以在nginx配置文件中加入指令lua_need_request_body on;來開啟讀取body,但官方不推薦)ngx.req.discard_body
忽略請求的body
注意,如果處理一個包含body的請求且需要ngx.exit時,需要調用此函數來忽略body,否則nginx可能將body當成header來解析,從而導致400錯誤;ngx.req.get_body_data
獲取請求body數據
例子
location ^~ /req {
content_by_lua '
ngx.say("===========ngx.req.raw_header=")
ngx.say(ngx.req.raw_header())
local headers = ngx.req.get_headers()
ngx.say("===========headers============")
ngx.say("Host: ", headers["Host"])
ngx.say("user-agent: ", headers.user_agent)
ngx.say("===========all headers========")
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, ": ", table.concat(v, ","))
else
ngx.say(k, ": ", v)
end
end
ngx.say("===========args===============")
local args = ngx.req.get_uri_args()
for k,v in pairs(args) do
ngx.say(k, ": ", v)
end
ngx.say("===========body===============")
ngx.say("body data: ", ngx.req.get_body_data())
ngx.req.read_body()
local post_args = ngx.req.get_post_args()
for k,v in pairs(post_args) do
ngx.say(k, ": ", v)
end
ngx.say("body data: ", ngx.req.get_body_data())
';
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9898/req?a=11&b=22&c=33" --data "d=11&e=22&f=33"
===========ngx.req.raw_header=
POST /req?a=11&b=22&c=33 HTTP/1.1
User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
Host: 127.0.0.1:9898
Accept: */*
Content-Length: 14
Content-Type: application/x-www-form-urlencoded
===========headers============
Host: 127.0.0.1:9898
user-agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
===========all headers========
host: 127.0.0.1:9898
content-type: application/x-www-form-urlencoded
accept: */*
content-length: 14
user-agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
===========args===============
b: 22
a: 11
c: 33
===========body===============
body data: nil
d: 11
f: 33
e: 22
body data: d=11&e=22&f=33
root@j9 ~#
ngx.escape_uri/ngx.unescape_uri
uri編碼解碼ngx.encode_args/ngx.decode_args
參數編碼解碼ngx.encode_base64/ngx.decode_base64
BASE64編碼解碼ngx.md5
md5加密
例子
location ^~ /code {
content_by_lua '
local request_uri = ngx.var.request_uri
local args = {a=11, b=22}
ngx.say("request uri: ", request_uri)
ngx.say("unescape request uri: ", ngx.unescape_uri(request_uri))
ngx.say("encode args: ", ngx.encode_args(args))
ngx.say("encode base64 request uri: ", ngx.encode_base64(request_uri))
ngx.say("md5(123456): ", ngx.md5("123456"))
';
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9898/code?name=%E9%87%91%E4%B9%9D"
request uri: /code?name=%E9%87%91%E4%B9%9D
unescape request uri: /code?name=金九
encode args: a=11&b=22
encode base64 request uri: L2NvZGU/bmFtZT0lRTklODclOTElRTQlQjklOUQ=
md5(123456): e10adc3949ba59abbe56e057f20f883e
root@j9 ~#
-
ngx.shared.DICT
共享內存接口,其中DICT為共享內存zone名稱,在nginx.conf中通過指令lua_shared_dict配置,而且lua_shared_dict指令配置的共享內存大小最小值為8k。
例子
lua_shared_dict cc_shared_data 16k;
server {
listen 9999;
default_type "text/html";
location ^~ /shared_data {
content_by_lua '
local shared_data = ngx.shared.cc_shared_data
local i = shared_data:get("i")
if not i then
shared_data:set("i", 1)
end
i = shared_data:incr("i", 1)
ngx.say("i: ", i)
';
}
}
測試結果
root@j9 ~# curl "https://127.0.0.1:9999/shared_data"
i: 2
root@j9 ~# curl "https://127.0.0.1:9999/shared_data"
i: 3
root@j9 ~#
ngx.shared.DICT詳細說明:https://wiki.nginx.org/HttpLuaModule
4.2.2、指令
指令 | 階段 | 範圍 | 說明 |
---|---|---|---|
init_by_lua/init_by_lua_file | loading-config | http | nginx master進程加載配置時執行;通常用於初始化全局配置/預加載Lua模塊 |
init_worker_by_lua/init_worker_by_lua_file | starting-worker | http | 每個nginx worker進程啟動時調用的計時器,如果master進程不允許則隻會在init_by_lua之後調用;通常用於定時拉取配置/數據,或者後端服務的健康檢查 |
set_by_lua/set_by_lua_file | rewrite | server,server if,location,location if | 設置nginx變量,可以實現複雜的賦值邏輯;此處是阻塞的,Lua代碼要做到非常快 |
rewrite_by_lua/rewrite_by_lua_file | rewrite tail | http,server,location,location if rewrite | 階段處理,可以實現複雜的轉發/重定向邏輯 |
access_by_lua/access_by_lua_file | access tail | http,server,location,location if | 請求訪問階段處理,用於訪問控製 |
content_by_lua/content_by_lua_file | content | location,location if | 內容處理器,接收請求處理並輸出響應 |
header_filter_by_lua/header_filter_by_lua_file | output-header-filter | http,server,location,location if | 設置header和cookie |
body_filter_by_lua/body_filter_by_lua_file | output-body-filter | http,server,location,location if | 對響應數據進行過濾,比如截斷、替換 |
log_by_lua/log_by_lua_file | log | http,server,location,location if log | 階段處理,比如記錄訪問量/統計平均響應時間 |
更詳細的解釋請參考官網:https://wiki.nginx.org/HttpLuaModule
init_by_lua
每次nginx重新加載配置時執行,可以用它來完成一些耗時模塊的加載,或者初始化一些全局配置;
例子:
init_by_lua '
cjson = require("cjson")
ngx.log(ngx.ERR, "init_by_lua ok")
';
server {
listen 9292;
default_type "text/html";
location / {
content_by_lua '
local arg_json = cjson.decode(ngx.var.arg_json)
ngx.say("aa: ", arg_json.aa)
';
}
}
測試結果:
root@j9 ~# curl 'https://127.0.0.1:9292/?json=\{"aa":111,"bbb":222\}'
aa: 111
root@j9 ~#
init_worker_by_lua
每個worker啟動之後初始化時執行,通常用於每個worker都要做的工作,比如啟動定時任務
例子:
worker_processes 2;
http {
#這裏省略了其他配置
init_worker_by_lua '
ngx.log(ngx.ERR, "test init_worker_by_lua")
-- TODO: 啟動定時任務
';
}
grep一下error.log,會發現兩條包含"test init_worker_by_lua"關鍵字的log,說明每個worker都會執行這個Lua代碼。
set_by_lua
語法:set_by_lua resluascriptstr
arg1 $arg2...; 在Lua代碼中可以實現所有複雜的邏輯,但是要執行速度很快,不要阻塞;
需要注意的是,這個指令需要加入模塊ngx_devel_kit,否則不支持這個指令。
這個指令的Lua代碼中不支持以下API:
1、輸出(ngx.say、ngx.send_headers……)
2、控製(ngx.exit……)
3、子請求(ngx.location.capture、ngx.location.capture_multi……)
4、cosocket(ngx.socket.tcp、ngx.req.socket……)
5、ngx.sleep
例子:
server {
listen 9393;
default_type "text/html";
location /add {
set $diff '';
set $double_c '';
set_by_lua $sum '
local a = ngx.var.arg_a
local b = ngx.var.arg_b
ngx.var.diff = a - b
ngx.var.double_c = 2 * tonumber(ngx.arg[1])
return a + b;
' $arg_c;
return 200 "a + b = $sum, a - b = $diff, 2 * c = $double_c";
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9393/add?a=11&b=22&c=88"
a + b = 33, a - b = -11, 2 * c = 176
root@j9 ~#
rewrite_by_lua
執行內部URL重寫或者外部重定向(301或者302),典型的如偽靜態化的URL重寫。其默認執行在rewrite處理階段的最後。
需要注意的是:
1、在長連接中如果調用了ngx.exit(200)一個請求,則需要調用ngx.req.discard_body(),否則nginx可能會把當前請求的body當成header解析,從而導致400錯誤返回碼並且長連接被關閉。
2、如果該階段調用了ngx.exit(ngx.OK),content_by_lua階段仍然能得到執行。
例子:
server {
listen 9494;
default_type "text/html";
location /rewrite_by_lua {
set $a 11;
rewrite_by_lua '
ngx.var.a = "aa"
if ngx.var.arg_exit == "ok" then
ngx.exit(ngx.OK)
else
ngx.exit(200)
end
';
content_by_lua '
ngx.say("a: ", ngx.var.a)
';
}
}
測試結果
root@j9 ~# curl "https://127.0.0.1:9494/rewrite_by_lua?exit=ok"
a: aa
root@j9 ~# curl "https://127.0.0.1:9494/rewrite_by_lua?exit=200"
root@j9 ~#
access_by_lua
用於訪問控製,比如IP黑白名單限製、鑒權。
例子:
server {
listen 9595;
default_type "text/html";
location / {
access_by_lua '
local auth = ngx.var.arg_auth;
local key = "alicdnj9";
if ngx.md5(key) ~= auth then
return ngx.exit(403)
end
';
content_by_lua '
ngx.say("access ok")
';
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:9595/?auth=xx"
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<h1>403 Forbidden</h1>
<p>You don't have permission to access the URL on this server. Sorry for the inconvenience.<br/>
Please report this message and include the following information to us.<br/>
Thank you very much!</p>
<table>
<tr>
<td>URL:</td>
<td>https://127.0.0.1:9595/?auth=xx</td>
</tr>
<tr>
<td>Server:</td>
<td>j9</td>
</tr>
<tr>
<td>Date:</td>
<td>2015/10/27 16:47:20</td>
</tr>
</table>
<hr/>Powered by Tengine/2.2.0</body>
</html>
root@j9 ~# echo -n alicdnj9 | md5sum
50652c84270d22210593318f5d3016a1 -
root@j9 ~# curl "https://127.0.0.1:9595/?auth=50652c84270d22210593318f5d3016a1"
access ok
root@j9 ~#
注意,如果在access_by_lua中調用ngx.exit(ngx.OK),content階段仍然能得到執行。
content_by_lua
content階段,注意在同一個Location中不要和其他content階段指令一起使用,比如proxy_pass。
例子:略
header_filter_by_lua和body_filter_by_lua
分別為header_filter階段和body_filter階段,其中body_filter可能會被執行多次。
不支持以下API:
- 輸出 (ngx.say、ngx.send_headers)
- 控製 (ngx.exit、ngx.exec)
- 子請求 (ngx.location.capture、ngx.location.capture_multi)
- Cosocket (ngx.socket.tcp、ngx.req.socket).
比如對後端chunked長度做限製:
server {
listen 9696;
default_type "text/html";
set $content_len 0;
location / {
header_filter_by_lua '
-- 先去掉Content-Length頭部,轉成Chunked傳輸
ngx.header.content_length = nil
';
body_filter_by_lua '
local content_length = #ngx.arg[1]
content_length = ngx.var.content_len + content_length
ngx.var.content_len = content_length
-- 最多隻能傳輸10字節的body,否則直接關掉連接
if content_length > 10 then
return ngx.ERROR
end
';
content_by_lua '
for i=1, ngx.var.arg_len do
ngx.print("a")
end
';
}
}
測試結果
root@j9 ~# curl "https://127.0.0.1:9696/?len=10" -i
HTTP/1.1 200 OK
Server: Tengine/2.2.0
Date: Mon, 26 Oct 2015 01:48:23 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
aaaaaaaaaa
root@j9 ~# curl "https://127.0.0.1:9696/?len=11" -i
curl: (52) Empty reply from server
root@j9 ~#
可以看出當參數len為11時,服務器就直接不返回數據了。
4.3、深入
1、content_by_lua中的代碼一定要注意單引號或者雙引號,一般用法是外單內雙,或者外雙內單。
2、在nginx_lua中值為nil的變量不能與字符串或者數字相加,否則nginx會報500錯誤。
3、lua調試: ngx.log(ngx.ERR,xx)。(tail -f logs/error.log)
4、*_by_lua_file指令指定的文件支持絕對路徑和相對路徑,其中相對路徑是相對nginx工作目錄。
5、lua文件的require函數指定的lua模塊路徑查找順序,可以從出錯信息中看出來:
no file '/opt/libs/lua/a.lua'
no file './a.lua'
no file '/usr/local/luajit/share/luajit-2.0.4/a.lua'
no file '/usr/local/share/lua/5.1/a.lua'
no file '/usr/local/share/lua/5.1/a/init.lua'
no file '/usr/local/luajit/share/lua/5.1/a.lua'
no file '/usr/local/luajit/share/lua/5.1/a/init.lua'
no file './a.so'
no file '/usr/local/lib/lua/5.1/a.so'
no file '/usr/local/luajit/lib/lua/5.1/a.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
其中,第一個/opt/libs/lua/a.lua為lua_package_path指定的路徑:lua_package_path '/opt/libs/lua/?.lua;;';
第二個./a.lua為相對路徑,相對於nginx.conf配置文件,而非包含它的lua文件。
so模塊查找順序類似,但是先查找.lua再查找.so,查找.so時先在lua_package_cpah指定的路徑查找:lua_package_cpath '/opt/libs/lua_shared/?.so;;';
可以從出錯信息中看出來:
no field package.preload['a']
no file '/opt/libs/lua/a.lua'
no file './a.lua'
no file '/usr/local/luajit/share/luajit-2.0.4/a.lua'
no file '/usr/local/share/lua/5.1/a.lua'
no file '/usr/local/share/lua/5.1/a/init.lua'
no file '/usr/local/luajit/share/lua/5.1/a.lua'
no file '/usr/local/luajit/share/lua/5.1/a/init.lua'
no file '/opt/libs/lua_shared/a.so'
no file './a.so'
no file '/usr/local/lib/lua/5.1/a.so'
no file '/usr/local/luajit/lib/lua/5.1/a.so'
no file '/usr/local/lib/lua/5.1/loadall.so'
6、lua代碼一定要健壯,否則不管lua產生什麼錯,nginx都會返回500錯誤,這時可以從error.log中查看錯誤信息來定位。
7、編寫lua代碼時最好用local局部變量,不要用全局變量。
8、實現worker級別的全局變量:
server {
listen 9797;
default_type "text/html";
location / {
content_by_lua '
local a = 1
local b = {b = 1}
local status = require("status")
ngx.say("a: ", a, ", b: ", b.b, " counter: ", status.counter)
a = a + 1
b.b = b.b + 1
status.counter = (status.counter or 0) + 1
';
}
}
其中status.lua為:
local m = {}
m.counter = 1
return m
測試結果:
root@j9 ~# curl "https://127.0.0.1:9797/"
a: 1, b: 1 counter: 1
root@j9 ~# curl "https://127.0.0.1:9797/"
a: 1, b: 1 counter: 2
root@j9 ~# curl "https://127.0.0.1:9797/"
a: 1, b: 1 counter: 3
root@j9 ~#
可以看出status.counter的值一直是累加的,這是因為require一個模塊隻load第一次,後續require該模塊都會先看全局表中是否已經load過,load過則就不需要再load了,所以status.counter累加其實是累加m.counter。
9、定時任務
API: ok, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)
例子:
local delay = 5
local handler
handler = function (premature)
-- do some routine job in Lua just like a cron job
if premature then
return
end
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
end
local ok, err = ngx.timer.at(delay, handler)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
注意:在timer處理函數的上下文中不能調用ngx.var.*、ngx.req.*、子請求API、輸出API,因為這些API隻能在請求上下文中生效。
10、子請求
API:res = ngx.location.capture(uri, options?)
上下文:rewrite_by_lua*, access_by_lua*, content_by_lua*
例子:
正向代理,當源站返回301或者302時代理客戶端跳轉
server {
listen 8181;
default_type "text/html";
location /test {
content_by_lua '
local res = ngx.location.capture("/get" .. ngx.var.request_uri, { method = ngx.HTTP_HEAD })
if res.status == 200 then
ngx.exec("/get" .. ngx.var.request_uri)
elseif res.status == 301 or res.status == 302 then
location = res.header["Location"]
local m, err = ngx.re.match(location, "https://([^/]+)(/.*)")
if not m then
ngx.exit(500)
end
host = m[1]
uri = m[2]
ngx.exec("/redirect/" .. host .. "/" .. ngx.var.request_uri)
else
ngx.exit(res.status)
end
';
}
location ~ /redirect/([^/]*)/([^/]*) {
proxy_pass https://$1/$2?$args;
}
location /get {
if ($arg_tag = "1") {
return 302 "https://127.0.0.1:8282/$request_uri";
}
return 200 "ok";
}
}
server {
listen 8282;
default_type "text/html";
location / {
return 200 "redirect ok, args: $args";
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:8181/test?tag=0" -i
HTTP/1.1 200 OK
Server: Tengine/2.2.0
Date: Mon, 26 Oct 2015 15:17:10 GMT
Content-Type: text/html
Content-Length: 2
Connection: keep-alive
ok
root@j9 ~# curl "https://127.0.0.1:8181/test?tag=1" -i
HTTP/1.1 200 OK
Server: Tengine/2.2.0
Date: Mon, 26 Oct 2015 15:17:14 GMT
Content-Type: text/html
Content-Length: 19
Connection: keep-alive
redirect ok, args: tag=1
root@j9 ~#
可見,當傳tag為1時,返回的值就是想要的值,不需要再302重定向了。
注意,子請求隻能請求本server的非@location。
另外一個需要注意的是,發起子請求之前修改的變量在子請求的location中是獲取不到的,這是因為變量的上下文是在請求結構體r中,而子請求是掛在主請求下麵,是兩個不同的請求。
實驗:
server {
listen 8383;
default_type "text/html";
set $cc "cc";
location /test {
content_by_lua '
ngx.var.cc = "11"
local res = ngx.location.capture("/get" .. ngx.var.request_uri)
if res.status == 200 then
ngx.say(res.body)
ngx.say("test cc: ", ngx.var.cc)
else
ngx.exit(res.status)
end
';
}
location /get {
content_by_lua '
ngx.say("get cc: ", ngx.var.cc)
ngx.var.cc = "22"
';
}
}
結果:
root@j9 ~# curl "https://127.0.0.1:8383/test"
get cc: cc
test cc: 11
root@j9 ~#
11、location @xx
server {
listen 8484;
default_type "text/html";
set $cc "2";
location / {
content_by_lua '
ngx.var.cc = "5";
if ngx.var.arg_location == "at" then
ngx.exec("@cc")
else
ngx.exec("/cc")
end
';
}
location @cc {
return 200 "this is @cc location, cc: $cc";
}
location /cc {
return 200 "this is /cc location, cc: $cc";
}
}
測試結果:
root@j9 ~# curl "https://127.0.0.1:8484/?location=at"
this is @cc location, cc: 5
root@j9 ~# curl "https://127.0.0.1:8484/"
this is /cc location, cc: 2
root@j9 ~#
在ngx.exec跳轉之前已經把變量cc的值改成5了,但可以看出這兩種跳轉方式變量cc的值不一樣,這是因為ngx.exec跳轉到@cc這個location時,從location rewrite階段開始執行,而跳轉到/cc這個location時是從server rewrite階段開始執行,而set指令是在server塊,就是在這個階段得到執行的,所以$cc又被賦值成2了。
最後更新:2017-08-13 22:31:13