1、介紹
2、安裝
3、運行
4、開發javascript
Tengine:輕量級、高性能、高併發、配置化、模塊化、可擴展、可移植的Web和反向代理 服務器,Tengine是nginx超集,但作了不少優化,包含了不少比較有用的模塊,好比直接包含了lua、proc等頗有用的模塊。html
Lua:一個很輕量級的 腳本,也號稱性能最高的 腳本。代碼總共不到600k,32個C文件,23個頭文件:java
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-modulelinux
2.一、LuaJITnginx
wget -c http://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.二、Tenginegit
tengine最新代碼中已經包含lua模塊了,直接git clone下來就能夠github
git clone https://github.com/alibaba/tengine.git cd tengine ./configure --prefix=/opt/tengine --with-http_lua_module make make install
若是是原生nginx的話,得自行下載lua模塊代碼:shell
wget http://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
修改/opt/tengine/conf/nginx.conf:json
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:c#
root@j9 ~/tengine# /opt/tengine/sbin/nginx
curl訪問一下hello_lua:
root@j9 ~/tengine# curl http://localhost/hello_lua Lua: hello world!
運行ok。
語法
入門
深刻
4.一、語法
4.二、入門
4.2.一、API
ngx.print
輸出響應內容體;
例如:ngx.print("a", "b", "c")
ngx.say
跟ngx.print的區別只是最後會多輸出一個換行符;
例如:ngx.say("a", "b", "c")
ngx.status
設置響應HTTP狀態碼;
<u>注意,設置狀態碼僅在響應頭髮送前有效。當調用ngx.say或者ngx.print時自動發送響應狀態碼(默認爲200);能夠通ngx.headers_sent來判斷是否發送了響應狀態碼。</u>
例如:ngx.status = 200
ngx.exit
設置響應HTTP狀態碼並退出;
<u>注意,設置狀態碼僅在響應頭髮送前有效,而且該函數調用以後該函數後面的lua將被忽略掉,由於已經exit了。</u>
例如:ngx.exit(200)
ngx.header
輸出響應頭;
<u>注意,頭部字段中含有橫槓(-)的要轉換成下劃線(_),ngx_lua模塊自動將_轉換成-。</u>
例如:ngx.header["X-Cache"] = "HIT" 或者 ngx.header.X_Cache = "HIT"或者ngx.header.X_Cache = {"AA", "BB"}
ngx.redirect
301或者302重定向
例如:ngx.redirect("http://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("http://www.taobao.org", 301) '; } }
測試結果:
root@j9 ~# curl "http://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 "http://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: http://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 "http://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 "http://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 "http://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 "http://127.0.0.1:9999/shared_data" i: 2 root@j9 ~# curl "http://127.0.0.1:9999/shared_data" i: 3 root@j9 ~#
ngx.shared.DICT詳細說明:http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT
4.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 | 階段處理,好比記錄訪問量/統計平均響應時間 |
更詳細的解釋請參考官網:http://wiki.nginx.org/HttpLua...
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 'http://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:
一、輸出(ngx.say、ngx.send_headers……)
二、控制(ngx.exit……)
三、子請求(ngx.location.capture、ngx.location.capture_multi……)
四、cosocket(ngx.socket.tcp、ngx.req.socket……)
五、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 "http://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處理階段的最後。
<u>須要注意的是:
一、在長鏈接中若是調用了ngx.exit(200)一個請求,則須要調用ngx.req.discard_body(),不然nginx可能會把當前請求的body當成header解析,從而致使400錯誤返回碼而且長鏈接被關閉。
二、若是該階段調用了ngx.exit(ngx.OK),content_by_lua階段仍然能獲得執行。</u>
例子:
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 "http://127.0.0.1:9494/rewrite_by_lua?exit=ok" a: aa root@j9 ~# curl "http://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 "http://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>http://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 "http://127.0.0.1:9595/?auth=50652c84270d22210593318f5d3016a1" access ok root@j9 ~#
<u>注意,若是在access_by_lua中調用ngx.exit(ngx.OK),content階段仍然能獲得執行。</u>
content_by_lua
content階段,<u>注意在同一個Location中不要和其餘content階段指令一塊兒使用,好比proxy_pass。</u>
例子:略
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 "http://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 "http://127.0.0.1:9696/?len=11" -i curl: (52) Empty reply from server root@j9 ~#
能夠看出當參數len爲11時,服務器就直接不返回數據了。
4.三、深刻
一、content_by_lua中的代碼必定要注意單引號或者雙引號,通常用法是外單內雙,或者外雙內單。
二、在nginx_lua中值爲nil的變量不能與字符串或者數字相加,不然nginx會報500錯誤。
三、lua調試: ngx.log(ngx.ERR,xx)。(tail -f logs/error.log)
四、*_by_lua_file指令指定的文件支持絕對路徑和相對路徑,其中相對路徑是相對nginx工做目錄。
五、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'
六、lua代碼必定要健壯,不然無論lua產生什麼錯,nginx都會返回500錯誤,這時能夠從error.log中查看錯誤信息來定位。
七、編寫lua代碼時最好用local局部變量,不要用全局變量。
八、實現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 "http://127.0.0.1:9797/" a: 1, b: 1 counter: 1 root@j9 ~# curl "http://127.0.0.1:9797/" a: 1, b: 1 counter: 2 root@j9 ~# curl "http://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。
九、定時任務
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
<u>注意:在timer處理函數的上下文中不能調用ngx.var.、ngx.req.、子請求API、輸出API,由於這些API只能在請求上下文中生效。</u>
十、子請求
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, "http://([^/]+)(/.*)") 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 http://$1/$2?$args; } location /get { if ($arg_tag = "1") { return 302 "http://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 "http://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 "http://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 "http://127.0.0.1:8383/test" get cc: cc test cc: 11 root@j9 ~#
十一、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 "http://127.0.0.1:8484/?location=at" this is @cc location, cc: 5 root@j9 ~# curl "http://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了。