Lua-Nginx-Module常用指令(中)
HTTP響應頭需要配置很多重要的信息,例如添加CDN緩存時間、操作set-cookie、標記業務數據類型等。利用Lua的API可以輕松完成這些配置,並且它有豐富的模塊可供選擇。
9.1 獲取響應頭
ngx.resp.get_headers
語法:headers = ngx.resp.get_headers(max_headers?, raw?)
配置環境:set_by_lua,rewrite_by_lua,access_by_lua,content_by_lua,headerfilter by_lua,body_filter_by_lua,log_by_lua,balancer_by_lua
含義:讀取當前請求的響應頭,並返回一個Lua的table類型的數據。
示例:
server { listen 80; server_name testnginx.com; location / { content_by_lua_block { local ngx = require "ngx"; local h = ngx.resp.get_headers() for k, v in pairs(h) do ngx.say(‘Header name: ‘,k, ‘ value: ‘,v) end --因為是table,所以可以使用下面的方式讀取單個響應頭的值 ngx.say(h["content-type"]) } } }
執行結果如下:
# curl -i ‘ttp://testnginx.com/test?=12132&a=2&b=c&dd‘ HTTP/1.1 200 OK Server: nginx/1.12.2 Date: Fri, 08 Jun 2018 07:36:35 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive Header name:content-type value: application/octet-stream Header name:connection value: keep-alive application/octet-stream
9.2 修改響應頭
ngx.header.HEADER
語法:ngx.header.HEADER = VALUE
語法:value = ngx.header.HEADER
配置環境:rewrite_by_lua,access_by_lua,content_by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua
含義:對響應頭進行修改、清除、添加等操作。
此API在輸出響應頭時,默認會將下劃線替換成中橫線,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
content_by_lua_block {
local ngx = require "ngx"
ngx.header.content_type = ‘text/plain‘;
--在代碼裏面是下劃線,輸出時就變成中橫線了
ngx.header.Test_Nginx = ‘Lua‘;
--下面的代碼等同於ngx.header.A_Ver = ‘aaa‘
ngx.header["A_Ver"] = ‘aaa‘;
--讀取響應頭,並賦值給變量a
local a = ngx.header.Test_Nginx;
}
}
}
執行代碼,下劃線都被替換成了中橫線,如下所示:
# curl -i ‘http://testnginx.com/?test=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 03:18:16 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
test-type: ttt
Test-Nginx: Lua
A-Ver: aaa
有時需要在一個響應頭中存放多個值,例如,當訪問/test 路徑時,需要為set-cookie設置兩個Cookie:
location = /test {
content_by_lua_block {
local ngx = require "ngx"
--以逗號分隔兩個Cookie
ngx.header[‘Set-Cookie‘] = {‘test1=1; path=/test‘, ‘test2=2; path=/test‘}
}
}
輸出結果如下:
# curl -i ‘http://testnginx.com/test?=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 03:21:59 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: test1=1; path=/test
Set-Cookie: test2=2; path=/test
9.3 清除響應頭
如果需要清除一個響應頭,將它賦值為nil即可,如下所示:
ngx.header["X-Test"] = nil;
十、讀取請求體
$request_body表示請求體被讀取到內存中的數據,一般由proxy_pass、fastcgi_pass、uwsgi_pass和scgi_pass等指令進行處理。由於Nginx默認不讀取請求體的數據,所以當Lua通過ngx.var.request_body的方式獲取請求體時會發現數據為空。那麽,該如何獲得請求體的數據呢?下面將介紹幾種可行的方式。
10.1 強制獲取請求體
lua_need_request_body
語法:lua_need_request_body <on|off>
默認:off
配置環境:http,server,location,location if
含義:默認為off,即不讀取請求體。如果設置為on,則表示強制讀取請求體,此時,可以通過ngx.var.request_body來獲取請求體的數據。但需要註意一種情況,$request_body存在於內存中,如果它的字節大小超過Nginx配置的client_body_buffer_size的值,Nginx就會把請求體存放到臨時文件中,此時數據就不在內存中了,這會導致$request_body為空,所以需要設置client_body_buffer_size和client_max_body_size的值相同,避免出現這種情況。
這種配置方式不夠靈活,Ngx_lua官網也不推薦使用此方法。下面將介紹一種更合適的方式去獲取請求體的數據。
10.2 用同步非阻塞方式獲取請求體
ngx.req.read_body
語法:ngx.req.read_body()
環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:同步讀取客戶端請求體,且不會阻塞Nginx的事件循環。使用此指令後,就可以通過ngx.req.get_body_data來獲取請求體的數據了。但如果是使用臨時文件來存放請求體的話,就需要先使用函數ngx.req.get_body_file來獲取臨時文件名,再去讀取臨時文件中的請求體數據了。
ngx.req.get_body_data
語法:data = ngx.req.get_body_data()
配置環境:rewrite_by_lua,access_by_lua,content_by_lua,log_by_lua
含義:執行ngx.req.read_body指令後,可以使用本指令在內存中獲取請求體數據,結果會返回一個Lua的字符串類型的數據。如果要獲取Lua 的table類型的數據,則需要使用ngx.req.get_post_args。
ngx.req.get_post_args
語法: args, err = ngx.req.get_post_args(max_args?)
配置環境:rewrite_by_lua,access_by_lua,content_by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua
含義:在執行ngx.req.read_body指令後,可以使用本指令讀取包含當前請求在內的所有POST請求的查詢參數,返回一個Lua的table類型。max_args參數的作用是限制參數的數量,為了服務的安全,最多支持使用100個參數(包括重復的參數),超過限制的參數會被忽略。如果max_args為0,則表示關閉此限制,但為了避免被無窮多的參數***,不要設置max_args為0。如果最多支持使用10個參數,則應配置為ngx.req.get_post_args(10)。
ngx.req.get_body_file
語法:file_name = ngx.req.get_body_file()
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:在執行ngx.req.read_body指令後,可以使用本指令獲取存放請求體的臨時文件名(絕對路徑),如果請求體被存放在內存中,獲取的值就是nil。通過本指令獲取的文件是只讀的,不可以被修改,且會在被Nginx讀取後被刪除掉。
10.3 使用場景示例
下面將對這些指令的使用方式和使用場景進行展示。
獲取string類型的請求體
要獲取string類型的請求體,可以使用如下配置:
server {
listen 80;
server_name testnginx.com;
location / {
client_max_body_size 10k;
client_body_buffer_size 1k;
content_by_lua_block {
local ngx = require "ngx"
--開啟讀取請求體模式
ngx.req.read_body()
--獲取內存中的請求體
local data = ngx.req.get_body_data()
if data then
ngx.print(‘ngx.req.get_body_data: ‘,data, ‘ ---- type is ‘, type(data))
return
else
--如果沒有獲取到內存中的請求體數據,則去臨時文件中讀取
local file = ngx.req.get_body_file()
if file then
ngx.say("body is in file ", file)
else
ngx.say("no body found")
end
end
}
}
配置好後,重載Nginx配置(重載是指使用HUP信號或reload命令來重新加載配置),先用一個小於1KB的請求體(在Nginx配置中設置client_body_buffer_size為1k)執行請求,輸出的是string字符串類型,如下所示:
# curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 06 Jun 2018 11:03:35 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
ngx.req.get_body_data: test=12132&a=2&b=c&dd ---- type is string
獲取table類型的請求體
要獲取table類型的請求體,可以使用如下配置:
server {
listen 80;
server_name testnginx.com;
location / {
client_max_body_size 10k;
client_body_buffer_size 1k;
content_by_lua_block {
--開啟讀取請求體模式
ngx.req.read_body()
-- 獲取內存中的請求體,返回的結果是Lua的table類型的數據
local args, err = ngx.req.get_post_args()
if args then
for k, v in pairs(args) do
if type(v) == "table" then
--如果存在相同的參數名,就會將相同的參數並列在一起,以逗號分隔
ngx.say(k, ": ", table.concat(v, ", "))
else
ngx.say(k, ": ", v)
end
end
else
--如果沒有獲取到內存中的請求體數據,則去臨時文件中讀取
local file = ngx.req.get_body_file()
if file then
ngx.say("body is in file ", file)
else
ngx.say("no body found")
end
end
}
}
}
發送測試請求,其中a參數有2個,c參數值為空,d參數連等號都沒有。執行結果如下所示:
# curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=c&dd=1&a=354&c=&d‘
b: c
dd: 1
d: true
c:
test: 12132
a: 2, 354
可以看到參數a的兩個值並列顯示,並以逗號分隔,參數c顯示為空,參數d的結果為布爾值true。
獲取臨時文件中的請求體
如果使用一個大小在1KB~10KB之間的請求體,會發生什麽呢?測試執行結果如下:
# curl -i http://testnginx.com/ -d ‘test=12132&a=2&b=kls204120312saldkk12 easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jesk20312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej11‘
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 06 Jun 2018 10:14:32 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
body is in file /usr/local/nginx_1.12.2/client_body_temp/0000000051
因為請求體數據的大小大於client_body_buffer_size的值,所以使用了臨時文件存儲請求體的數據。因此,需要先獲取存放數據的臨時文件名,再去讀取請求體數據。
註意:讀取臨時文件中的請求體數據是不被推薦的,因此本書不對相關操作進行,有興趣的讀者可以使用io.open完成讀取。
10.4 使用建議
在實際應用中,關於讀取請求體,有如下幾條建議。
1.盡量不要使用lua_need_request_body去獲取請求體。
2.獲取請求體前,必須執行ngx.req.read_body()。
3.獲取請求體數據時盡量不要使用硬盤上的臨時文件,否則會對性能有很大影響;務必要確認請求體數字字節大小的範圍,並確保client_body_buffer_size和client_max_body_size的值一致,這樣只需到內存中去讀取數據就可以了。它既提高了Nginx自身的吞吐能力,也提升了Lua的讀取性能。
4.如果請求體存放在臨時文件中,Nginx會在處理完請求後自動清理臨時文件。
5.對ngx.req.get_post_args參數的限制可以靈活控制,但不能關閉限制,以避免被惡意***。
十一、輸出響應體
在Lua中,響應體的輸出可以使用ngx.print 和 ngx.say 這兩個指令完成。
11.1 異步發送響應體
ngx.print
語法:ok, err = ngx.print(...)
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:用來輸出內容,輸出的內容會和其他的輸出合並,然後再發送給客戶端。如果響應頭還未發送的話,發送前會優先將響應頭發送出去。
示例:
location / {
content_by_lua_block {
local ngx = require "ngx";
local h = ngx.req.get_headers()
for k, v in pairs(h) do
ngx.print(‘Header name: ‘,k, ‘ value: ‘,v)
end
}
}
執行結果如下(所有的數據會合並到一起進行發送):
# curl -i ‘http://testnginx.com/test?=12132&a=2&b=c&dd‘
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 08:11:40 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
Header name:host value: testnginx.comHeader name:accept value: */*Header name:user-agent value: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.
ngx.say
語法:ok, err = ngx.say(...)
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:功能和ngx.print一樣,只是輸出結果多了1個回車符。
11.2 同步發送響應體
ngx.print和ngx.say為異步調用,執行後並不會立即輸出響應體,可以通過執行ngx.flush(true)來實現同步輸出響應體的功能。
ngx.flush
語法:ok, err = ngx.flush(wait?)
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:在默認情況下會發起一個異步調用,即不等後續的數據到達緩沖區就會直接將內容輸出到客戶端。如果將wait的參數值設置為true,表示同步執行,即會等內容全部輸出到緩沖區後再輸出到客戶端。
server {
listen 80;
server_name testnginx.com;
default_type ‘text/plain‘;
location /test1 {
content_by_lua_block {
ngx.say("test ")
ngx.say("nginx ")
ngx.sleep(3)
ngx.say("ok!")
ngx.say("666!")
}
}
location /test2 {
content_by_lua_block {
ngx.say("test ")
ngx.say("nginx ")
ngx.flush(true)
ngx.sleep(3)
ngx.say("ok!")
ngx.say("666!")
}
}
}
訪問/test1 和 /test2後,從執行結果可以看出,帶有ngx.flush(true) 指令的內容會先輸出test nginx,然後,等待大約3秒後再輸出ok! 666!。如果沒有配置ngx.flush(true)指令,請求會在等待3秒後輸出完整的一句話。
註意:指令ngx.flush不支持HTTP1.0,可以使用如下方式進行測試:# curl -i ‘http://testnginx.com/test2‘ --http1.0
十二、正則表達式
雖然Lua支持正則匹配且功能齊全,但在Nginx上推薦使用Lua-lua提供的指令。
12.1 單一捕獲
ngx.re.match
語法:captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)
配置環境:init_worker_by_lua,set_by_lua,rewrite_by_lua,access_by_lua,content_ by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua,ngx.timer.,balancerby lua,ssl_certificate_by_lua,ssl_session_fetch_by_lua,ssl_session_store_by_lua*
含義:使用Perl兼容的正則表達式來匹配subject參數,只返回匹配到的第一個結果。如果匹配失敗,則返回nil;如果有異常,則返回nil和一個描述錯誤信息的err。
示例:
location / {
content_by_lua_block {
local ngx = require "ngx";
--匹配多個數字+aaa的正則表達式
local m, err = ngx.re.match(ngx.var.uri, "([0-9]+)(aaa)");
if m then
--匹配成功後輸出的信息
ngx.say(ngx.var.uri, ‘---match success---‘, ‘its type: ‘,type(m))
ngx.say(ngx.var.uri, ‘---m[0]--- ‘, m[0])
ngx.say(ngx.var.uri, ‘---m[1]--- ‘, m[1])
ngx.say(ngx.var.uri, ‘---m[2]--- ‘, m[2])
else
if err then
ngx.log(ngx.ERR, "error: ", err)
return
end
ngx.say("match not found")
end
}
}
執行結果如下:
# curl ‘http://testnginx.com/test/a123aaa/b456aaa/c‘
/test/a123aaa/b456aaa/c---match success---its type: table
/test/a123aaa/b456aaa/c---m[0]---123aaa
/test/a123aaa/b456aaa/c---m[1]---123
/test/a123aaa/b456aaa/c---m[2]---aaa
從執行結果可以看出:
1.ngx.re.match只返回匹配到的第一個結果,所以後面的456aaa並沒有被輸出。
2.ngx.re.match返回的結果是table類型的。
3.ngx.re.match匹配成功後,m[0] 的值是匹配到的完整數據,而m[1]、m[2] 是被包含在括號內的單個匹配結果。
12.2 全部捕獲
ngx.re.match只返回第一次匹配成功的數據,如果想獲取所有符合正則表達式的數據,可以使用ngx.re.gmatch。
ngx.re.gmatch
語法:iterator, err = ngx.re.gmatch(subject, regex, options?)
配置環境:init_worker_by_lua,set_by_lua,rewrite_by_lua,access_by_lua,content_ by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua,ngx.timer.,balancerby lua,ssl_certificate_by_lua,ssl_session_fetch_by_lua,ssl_session_store_by_lua*
含義:和ngx.re.match功能相似,但返回的是一個Lua叠代器,可以通過叠代的方式獲取匹配到的全部數據。
location / {
content_by_lua_block {
local ngx = require "ngx";
--參數i表示忽略大小寫
local m_table, err = ngx.re.gmatch(ngx.var.uri, "([0-9]+)(aaa)", "i");
if not m_table then
ngx.log(ngx.ERR, err)
return
end
while true do
local m, err = m_table()
if err then
ngx.log(ngx.ERR, err)
return
end
if not m then
break
end
ngx.say(m[0])
ngx.say(m[1])
end
}
}
執行結果如下:
# curl ‘http://testnginx.com/test/a123aaa/b456AAA/c‘
123aaa
123
456AAA
456
ngx.re.match和ngx.re.gmatch都有一個options參數,用來控制匹配的執行方式,options常用參數說明見表7-1。
表7-1 options常用參數說明
12.3 更高效的匹配和捕獲
ngx.re.match和ngx.re.gmatch在使用過程中都會生成Lua table,如果只需確認正則表達式是否可以匹配成功,推薦使用如下指令。
ngx.re.find
語法:from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)
配置環境:init_worker_by_lua,set_by_lua,rewrite_by_lua,access_by_lua,content_ by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua,ngx.timer.,balancerby lua,ssl_certificate_by_lua,ssl_session_fetch_by _lua,ssl_session_store_by_lua*
含義:與ngx.re.match類似,但只返回匹配結果的開始位置索引和結束位置索引。
因為ngx.re.find不會創建table來存儲數據,所以性能上比ngx.re.match和ngx.re.gmatch要好很多。此時,如果需要捕獲匹配到的數據,可以使用Lua的函數string.sub。
location / {
content_by_lua_block {
local ngx = require "ngx";
local uri = ngx.var.uri
--使用o、j兩個參數進行匹配,以提升性能
local find_begin,find_end,err = ngx.re.find(uri, "([0-9]+)(aaa)","oj");
if find_begin then
ngx.say(‘begin: ‘,find_begin)
ngx.say(‘end: ‘,find_end)
--利用Lua的string.sub函數來獲取數據
ngx.say(‘find it: ‘ ,string.sub(uri, find_begin,find_end))
return
end
}
}
執行結果如下:
# curl ‘http://testnginx.com/test/a123aaa/b456AAAa/c‘
begin:8
end:13
find it: 123aaa
ngx.re.match、ngx.re.gmatch和 ngx.re.find 都支持ctx參數,有關ctx參數的說明如下。
1.ctx是Lua table類型的,是可選的第4個參數,但若用到第5個參數nth,那麽,此位置需要用nil作為占位符。
2.當ctx有值(鍵是pos,如pos=1)時,ngx.re.find將從pos位置開始進行匹配(位置的下標從1開始)。
3.無論ctx表中是否有值,ngx.re.find都會在正則表達式匹配成功後,將ctx值設置為所匹配字符串之後的位置;若匹配失敗,ctx表將保持原有的狀態。
nth是ngx.re.find的第5個參數,是在Lua-Nginx-Module 0.9.3版本之後新增加的參數,它的作用和ngx.re.match中的m[1]、m[2]類似。當nth等於1時,獲取的結果等同於ngx.re.match中的m[1],示例如下:
location / {
content_by_lua_block {
local ngx = require "ngx";
local uri = ngx.var.uri
--從uri位置為10的地方開始進行匹配,下標默認從1開始,只匹配nth是1的數據,即([0-9]+)的值
local ctx = { pos = 10 }
local find_begin,find_end,err = ngx.re.find(uri, "([0-9]+)(aaa)","oji",ctx,1);
if find_begin then
ngx.say(‘begin: ‘,find_begin)
ngx.say(‘end: ‘,find_end)
ngx.say(‘find it: ‘ ,string.sub(uri, find_begin,find_end))
return
end
}
}
執行結果如下:
# curl ‘http://testnginx.com/test/a123aaa/b456AAAa/c‘
begin:10
end:10
find it: 3
因為ctx的位置是10,所以uri前面的“/test/a12”這9個字符被忽略了,匹配到的就只有3aaa,又因為nth為1,所以捕獲到的值是3。
12.4 替換數據
Lua API也支持匹配對應數據並對其進行替換的指令。
ngx.re.sub
語法:newstr, n, err = ngx.re.sub(subject, regex, replace, options?)
配置環境:init_worker_by_lua,set_by_lua,rewrite_by_lua,access_by_lua,content_ by_lua,header_filter_by_lua,body_filter_by_lua,log_by_lua,ngx.timer.,balancerby lua,ssl_certificate_by_lua,ssl_session_fetchby lua,ssl_session_store_by_lua*
含義:若subject中含有參數regex的值,則將之替換為參數replace的值。options為可選參數。替換後的內容將賦值給newstr,n表示匹配到的次數。
示例:
location / {
content_by_lua_block {
local ngx = require "ngx";
local uri = ngx.var.uri
local n_str, n, err = ngx.re.sub(uri,"([0-9]+)", ‘zzzz‘)
if n_str then
ngx.say(uri)
ngx.say(n_str)
ngx.say(n)
else
ngx.log(ngx.ERR, "error: ", err)
return
end
}
}
執行結果如下:
# curl ‘http://testnginx.com/test188/x2/1231‘
/test188/x2/1231
/testzzzz/x2/1231
1
從結果可以看出,只在第一次匹配成功時進行了替換操作,並且只替換了1次,所以n的結果是1。如果要替換匹配到的全部結果可以使用ngx.re.gsub,示例如下:
local n_str, n, err = ngx.re.gsub(uri,"([0-9]+)", ‘zzzz‘)
從執行結果可知,替換了3次:
# curl ‘http://testnginx.com/test188/x2/1231‘
/test188/x2/1231
/testzzzz/xzzzz/zzzz
3
12.5 轉義符號
正則表達式包括\d、\s、\w 等匹配方式,但在Ngx_Lua中使用時,反斜線?\?會被Lua處理掉,從而導致匹配異常。所以需要對帶有?\?的字符進行轉義,轉義方式和其他語言有些區別,轉義後的格式為\\d、\\s、\\w,因為反斜線會被Nginx和Lua各處理一次,所以\\會先變成\,再變成\。
還可以通過[[]]的方式將正則表達式直接傳入匹配指令中,以避免被轉義,如下所示:
local find_regex = [[\d+]]
local m = ngx.re.match("xxx,43", find_regex)
ngx.say(m[0]) --輸出 43
通常建議使用[[]]的方式。
十三、子請求
Nginx一般分兩種請求類型,一種是主請求;一種是子請求,即subrequest。主請求從Nginx的外部進行訪問,而子請求則在Nginx內部進行訪問。子請求不是HTTP請求,不會增加網絡開銷。它的主要作用是將一個主請求分解為多個子請求,用子請求去訪問指定的location服務,最後匯總到一起完成主請求的任務。
Nginx的請求方法有很多種,如GET、POST、 PUT 、DELETE等,同樣,子請求也支持這些請求方法。
13.1 請求方法
Lua API中提供了多個指令來實現子請求,Lua API常見的請求方法說明見表7-2。
表7-2 Lua API常見的請求方法說明
13.2 單一子請求
ngx.location.capture
語法:res = ngx.location.capture(uri, options?)
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:發出同步但不阻塞Nginx的子請求。可以用來訪問指定的location,但不支持訪問命名location(如@abc 就是命名location)。location中可以有靜態文件,如ngx_proxy、ngx_fastcgi、ngx_memc、ngx_postgres、ngx_drizzle,甚至是Ngx_Lua和Nginx的c模塊。
子請求總是會把整個請求體緩存到內存中,如果要處理一個較大的子請求,使用cosockets是最好的選擇(cosockets是與ngx.socket.tcp有關的API)。
子請求一般在內部進行訪問,建議在被子請求訪問的location上配置internal,即只允許內部訪問。
子請求返回的結果res,它是一個table類型的數據,包含4個元素:res.status、res.header、res.body和res.truncated,res的元素名及其用途見表7-3。
表7-3 res的元素名及其用途
ngx.location.capture的第2個參數options是可選參數,也可以包含多個參數,示例如下:
server {
listen 80;
server_name testnginx.com;
default_type ‘text/plain‘;
location = /main {
set $m ‘hello‘;
content_by_lua_block {
local ngx = require "ngx";
--發起子請求,訪問/test,請求方式是GET,請求體是test nginx,子請求的URL參數是a=1&b=2,並使用copy_all_vars將主請求的Nginx變量($m)全部復制到子請求中
local res = ngx.location.capture(
‘/test‘, { method = ngx.HTTP_GET , body = ‘test nginx‘,
args = { a = 1, b = 2 },copy_all_vars = true }
)
ngx.say(res.status)
ngx.say(res.body)
ngx.say(type(res.header))
ngx.say(type(res.truncated))
}
}
location = /test
{
#只能在Nginx內部進行訪問
internal;
content_by_lua_block {
local ngx = require "ngx";
--獲取請求體,在這裏是獲取主請求的請求體
ngx.req.read_body()
local body_args = ngx.req.get_body_data()
--輸出請求的參數,獲取主請求的m變量的值,並與world進行字符串拼接
ngx.print(‘request_body: ‘ ,body_args, ‘ capture_args: ‘, ngx.var.args, ‘--- copy_all_vars : ‘, ngx.var.m .. ‘world! ‘)
}
}
}
執行結果如下:
# curl ‘http://testnginx.com/main‘
200
request_body:test nginx capture_args:a=1&b=2--- copy_all_vars : helloworld!
table
boolean
從示例中可以看出:
1.ngx.location.capture的第2個參數options可以包含多個table類型的參數。
2.子請求的請求方法由參數method進行配置,示例中的請求方法為GET。
3.子請求通過參數body可以定義新的請求體。
4.子請求通過參數args可以配置新的URL的args,args是table類型的。
5.copy_all_vars = true的作用是將主請求的全部變量傳遞給子請求,如果沒有此配置就不會傳遞過去。
6.從子請求的返回結果可以獲取狀態碼、響應體、響應頭、結果是否被截斷。
根據上面的介紹可知,下面兩種方式是等價的:
local res = ngx.location.capture(‘/test?a=1&b=2‘)
local res = ngx.location.capture(‘/test , args = { a = 1, b = ‘2‘ }‘)
ngx.location.capture 還支持更豐富的參數操作,具體如下。
1.vars參數,table類型,可以設置子請求中的變量值,前提是該變量在Nginx中被聲明過。如果配置copy_all_vars = true,且vars裏有和主請求相同的變量,則會使用vars中變量的值;如果vars裏是新變量,就會和主請求的變量一起傳遞過去。
2.share_all_vars參數,用來共享主請求和子請求的變量,如果在子請求中修改了共享變量的值,主請求的變量值也會被改變。不推薦使用此參數,因為可能會導致很多意外問題的出現。
3.always_forward_body參數,默認值為false,此時,如果不設置body參數,且請求方法是PUT或POST,則主請求的請求體可以傳給子請求。如果把always_forward_body設置為 true,且不設置body參數,無論請求方法是什麽,主請求的請求體都會傳給子請求。
4.ctx參數,指定一個table作為子請求的ngx.ctx表,它可以使主請求和子請求共享請求頭的上下文環境。
關於參數vars的使用方式,示例如下:
location = /main {
set $m ‘hello‘;
set $mm ‘‘;
content_by_lua_block {
local ngx = require "ngx";
local res = ngx.location.capture(
‘/test‘,
{ method = ngx.HTTP_POST ,
vars = {mm = ‘MMMMM‘,m = ‘hhhh‘}}
)
ngx.say(res.body)
}
}
location = /test {
content_by_lua_block {
local ngx = require "ngx";
ngx.print(ngx.var.m .. ngx.var.mm )
}
}
執行結果如下:
# curl ‘http://testnginx.com/main‘
hhhhMMMMM
主請求的變量在子請求中被修改了,並傳給了子請求指定的/test:
註意:使用ngx.location.capture發送子請求時,默認會將主請求的請求頭全部傳入子請求中,這可能會帶來一些不必要的麻煩。例如,如果瀏覽器發送的壓縮頭Accept-Encoding:gzip被傳入子請求中,且子請求是ngx_proxy的標準模塊,則請求的結果會被壓縮後再返回,導致Lua無法讀取子請求返回的數據。因此應將子請求的 proxy_pass_request_headers設置為off,避免把請求頭傳遞給後端服務器。
13.3 並發子請求
有時需要發送多條子請求去獲取信息,這時,就要用到並發操作了。
ngx.location.capture_multi
語法:res1, res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ... })
配置環境:rewrite_by_lua,access_by_lua,content_by_lua*
含義:與ngx.location.capture相似,但可以支持多個子請求並行訪問,並按配置順序返回數據。返回的數據也是多個結果集。
示例:
server {
listen 80;
server_name testnginx.com;
default_type ‘text/plain‘;
location = /main {
set $m ‘hello‘;
set $mm ‘‘;
content_by_lua_block {
local ngx = require "ngx";
--發送兩個子請求,會返回兩個結果集
local res1, res2 = ngx.location.capture_multi{
{ "/test1?a=1&b=2" },
{ "/test2",{ method = ngx.HTTP_POST},body = "test nginx" },
}
--返回的body的方式和ngx.location.capture一樣
if res1.status == ngx.HTTP_OK then
ngx.say(res1.body)
end
if res2.status == ngx.HTTP_OK then
ngx.say(res2.body)
end
}
}
location = /test1 {
echo ‘test1‘;
}
location = /test2 {
echo ‘test2‘;
}
}
執行結果如下:
# curl ‘http://testnginx.com/main‘
test1
test2
主請求需要等到所有的子請求都返回後才會結束子請求的執行,最慢的子請求的執行時間就是整體的消耗時間,所以在實際業務中需要對子請求的超時時間做好限制。
註意:Nginx對子請求有並發數量限制,目前Nginx 1.1以上的版本限制子請求並發數量為200個,老版本是50個。
Lua-Nginx-Module常用指令(中)