【技術乾貨】聽阿里雲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下的業務邏輯。
[]()
2. 安裝
2.1、LuaJIT
wget -c 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 cd tengine ./configure --prefix=/opt/tengine --with-http_lua_module make make install
如果是原生nginx的話,得自行下載lua模組程式碼:
wget tar xvf nginx-1.7.8.tar.gz cd nginx-1.7.8 mkdir modules cd modules git clone .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 Lua: hello world!
執行ok。
4、開發
> 語法
> 入門
> 深入
4.1、語法
參考:
[Lua簡明教程]()
[Lua線上lua學習教程]()
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狀態碼;
<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("[]()", 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("", 301) '; } }
測試結果:
root@j9 ~# curl "" -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 "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: <!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. <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 "var/aaaa/bbbb?id=22" -H "Host: " $a: aaa $b: $host: $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 "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 "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 "" i: 2 root@j9 ~# curl "" i: 3 root@j9 ~#
ngx.shared.DICT詳細說明:[]()
**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 ifrewrite | 階段處理,可以實現複雜的轉發/重定向邏輯 |
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 iflog | 階段處理,比如記錄訪問量/統計平均響應時間 |
更詳細的解釋請參考官網:
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 '\{"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 "" a + b = 33, a - b = -11, 2 * c = 176
rewrite_by_lua
執行內部URL重寫或者外部重定向(301或者302),典型的如偽靜態化的URL重寫。其預設執行在rewrite處理階段的最後。
<u>需要注意的是:
1、在長連線中如果呼叫了ngx.exit(200)一個請求,則需要呼叫ngx.req.discard_body(),否則nginx可能會把當前請求的body當成header解析,從而導致400錯誤返回碼並且長連線被關閉。
2、如果該階段呼叫了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 "" a: aa root@j9 ~# curl "" 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 "" <!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! <table><td>URL: <td> <td>Server: <td>j9 <td>Date: <td>2015/10/27 16:47:20 <hr/>Powered by Tengine/2.2.0</body> </html> root@j9 ~# echo -n alicdnj9 | md5sum 50652c84270d22210593318f5d3016a1 - root@j9 ~# curl "" 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:
1. 輸出 (ngx.say、ngx.send_headers)
2. 控制 (ngx.exit、ngx.exec)
3. 子請求 (ngx.location.capture、ngx.location.capture_multi)
4. 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 "" -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 "" -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 "" a: 1, b: 1 counter: 1 root@j9 ~# curl "" a: 1, b: 1 counter: 2 root@j9 ~# curl "" 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
<u>注意:在timer處理函式的上下文中不能呼叫ngx.var.*、ngx.req.*、子請求API、輸出API,因為這些API只能在請求上下文中生效。</u>
**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, "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 } location /get { if ($arg_tag = "1") { return 302 ""; } return 200 "ok"; } } server { listen 8282; default_type "text/html"; location / { return 200 "redirect ok, args: $args"; } }
測試結果:
root@j9 ~# curl "" -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 "" -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 "" 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 "" this is @cc location, cc: 5 root@j9 ~# curl "" 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了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29779867/viewspace-2143408/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【技術乾貨】聽阿里雲CDN安防技術專家金九講SystemTap使用技巧阿里
- 來聽聽達摩院技術專家是怎麼講智慧外呼機器人技術機器人
- 技術乾貨收集
- [乾貨分享]1000篇乾貨好文!量子技術——專家觀點篇
- CDN技術發展
- 阿里雲高階技術專家空見: CDN的資料化之路阿里
- 【北京】Golang技術專家--螞蟻金服Golang
- 技術乾貨 | WebRTC 技術解析之 Android VDMWebAndroid
- 阿里技術精華乾貨整理阿里
- 聊聊技術管理(一)入行之技術管理和技術專家
- 阿里雲技術專家解讀:2021 年六大容器技術發展趨勢阿里
- 【同行說技術】教你玩轉iOS的5篇技術乾貨iOS
- 技術乾貨 | 阿里雲資料庫PostgreSQL 13大版本揭秘阿里資料庫SQL
- 掘金 AMA:聽螞蟻金服 mPaaS 團隊技術專家--凝睇講客戶端推送 & 997 那些事客戶端
- 技術乾貨:RabbitMQ面試題及答案MQ面試題
- 技術乾貨:ActiveMQ面試題及答案MQ面試題
- 技術乾貨| MongoDB時間序列集合MongoDB
- 乾貨 | 大型網站專案架構技術一覽網站架構
- 乾貨 | TechWorld2021技術嘉年華演講PPT下載
- 招募TensorFlow領域的Google開發技術專家Go
- 什麼是cdn技術
- 乾貨 | Dubbo 介面測試技術,測試開發進階必備
- 技術乾貨:Hadoop面試題及答案Hadoop面試題
- 2020文章合集 技術乾貨
- 技術乾貨 | 漫遊Linux塊IOLinux
- 乾貨!天翼雲DPU技術解碼
- [乾貨分享]1000篇乾貨好文!量子技術——資訊篇
- 與50位技術專家連線(贈技術全景圖)
- 英雄帖 | 深圳&廣州&珠海招募“機器學習Google開發技術專家”機器學習Go
- 從小白到專家 PG技術大講堂 - Part 2:PG原始碼安裝原始碼
- 1269道Java技術答疑,阿里技術專家幫你Java技術進階Java阿里
- CSDN社群乾貨技術分享:探尋技術進階之道(Python和AI)PythonAI
- [乾貨分享]1000篇乾貨好文!量子技術——進階篇
- 從 0 開始搭建一個技術部落格,私藏乾貨~
- 什麼是CDN加速技術
- 技術乾貨:spring boot面試題及答案Spring Boot面試題
- 「技術乾貨」Pontus-用友雲限流服務
- 技術乾貨 | WebRTC ADM 原始碼流程分析Web原始碼