Nginx Lua的執行階段
對剛接觸Ngx_lua的讀者來說,可能會存在下面兩個困惑。
1、Lua在Nginx的哪些階段可以執行程式碼?
Lua在Nginx的每個階段可以執行哪些操作?
2、只有理解了這兩個問題,才能在業務中巧妙地利用Ngx_Lua來完成各項需求。
Nginx的11個執行階段,每個階段都有自己能夠執行的指令,並可以實現不同的功能。Ngx_Lua的功能大部分是基於Nginx這11個執行階段開發和配置的,Lua程式碼在這些指令塊中執行,並依賴於它們的執行順序。本章將對Ngx_Lua的執行階段進行一一講解。
一、 init_by_lua_block
init_by_lua_block是init_by_lua的替代版本,在OpenResty 1.9.3.1或Lua-Nginx-Modulev 0.9.17之前使用的都是init_by_lua。init_by_lua_block比init_by_lua更靈活,所以建議優先選用init_by_lua_block。
本章中的執行階段都採用*_block格式的指令,後續不再說明。
1.1 階段說明
語法:init_by_lua_block {lua-script-str}
配置環境:http
階段:loading-config
含義:當Nginx的master程序載入Nginx配置檔案(載入或重啟Nginx程序)時,會在全域性的Lua VM(Virtual Machine,虛擬機器)層上執行<lua-script-str> 指定的程式碼,每次當Nginx獲得HUP(即Hangup)過載訊號載入程序時,程式碼都會被重新執行。
1.2 初始化配置
在loading-config階段一般會執行如下操作。
1.初始化Lua全域性變數,特別適合用來處理在啟動master程序時就要求存在的資料,對CPU消耗較多的功能也可以在此處處理。
2.預載入模組。
3.初始化lua_shared_dict共享記憶體的資料(關於共享記憶體詳見第10章)。
示例如下:
user webuser webuser;
worker_processes 1;
worker_rlimit_nofile 10240;
events {
use epoll;
worker_connections 10240;
}
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" "$request_time" "$upstream_addr $upstream_status $upstream_response_time" "upstream_time_sum:$upstream_time_sum" "jk_uri:$jk_uri"';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
lua_package_path "/usr/local/nginx_1.12.2/conf/lua_modules/?.lua;;";
lua_package_cpath "/usr/local/nginx_1.12.2/conf/lua_modules/c_package/?.so;;";
lua_shared_dict dict_a 100k; --宣告一個Lua共享記憶體,dict_a為100KB
init_by_lua_block {
-- cjson.so檔案需要手動存放在lua_package_cpath搜尋路徑下,如果是OpenResty,就不必了,因為它預設支援該操作
cjson = require "cjson";
local dict_a = ngx.shared.dict_a;
dict_a:set("abc", 9)
}
server {
listen 80;
server_name testnginx.com;
location / {
content_by_lua_block {
ngx.say(cjson.encode({a = 1, b = 2}))
local dict_a = ngx.shared.dict_a;
ngx.say("abc=",dict_a:get("abc"))
}
}
}
執行結果如下:
# curl -I http://testnginx.com/
{"a":1,"b":2}
abc=9
1.3 控制初始值
在init_by_lua_block階段設定的初始值,即使在其他執行階段被修改過,當Nginx過載配置時,這些值就又會恢復到初始狀態。如果在過載Nginx配置時不希望再次改動這些初始值,可以在程式碼中做如下調整。
init_by_lua_block {
local cjson = require "cjson";
local dict_a = ngx.shared.dict_a;
local v = dict_a:get("abc"); --判斷初始值是否已經被set
if not v then --如果沒有,就執行初始化操作
dict_a:set("abc", 9)
end
}
1.4 init_by_lua_file
init_by_lua_file和init_by_lua_block的作用一樣,主要用於將init_by_lua_block的內容轉存到指定的檔案中,這樣Lua程式碼就不必全部寫在Nginx配置裡了,易讀性更強。
init_by_lua_file支援配置絕對路徑和相對路徑。相對路徑是在啟動Nginx時由-p PATH 決定的,如果啟動Nginx時沒有配置-p PATH,就會使用編譯時--prefix的值,該值一般存放在Nginx的$prefix(也可以用${prefix}來表示)變數中。init_by_lua_file和Nginx的include指令的相對路徑一致。
舉例如下:
init_by_lua_file conf/lua/init.lua; --相對路徑
init_by_lua_file /usr/local/nginx/conf/lua/init.lua; --絕對路徑
init.lua檔案的內容如下:
cjson = require "cjson"
local dict_a = ngx.shared.dict_a
local v = dict_a:get("abc")
if not v then
dict_a:set("abc", 9)
end
1.5 可使用的Lua API指令
init_by_lua是Nginx配置載入的階段,很多Nginx API for Lua命令是不支援的。目前已知支援的Nginx API for Lua的命令有ngx.log、ngx.shared.DICT、print。
注意:init_by_lua中的表示萬用字元,init_by_lua即所有以init_by_lua開頭的API。後續的萬用字元亦是如此,不再另行說明。
二、init_worker_by_lua_block
2.1 階段說明
語法:init_worker_by_lua_block {lua-script-str}
配置環境:http
階段:starting-worker
含義:當master程序被啟動後,每個worker程序都會執行Lua程式碼。如果Nginx禁用了master程序,init_by_lua*將會直接執行。
2.2 啟動Nginx的定時任務
在init_worker_by_lua_block執行階段最常見的功能是執行定時任務。示例如下:
user webuser webuser;
worker_processes 3;
worker_rlimit_nofile 10240;
events {
use epoll;
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test_12 {
server 127.0.0.1:81 weight=20 max_fails=300000 fail_timeout=5s;
server 127.0.0.1:82 weight=20 max_fails=300000 fail_timeout=5s;
}
lua_package_path "${prefix}conf/lua_modules/?.lua;;";
lua_package_cpath "${prefix}conf/lua_modules/c_package/?.so;;";
init_worker_by_lua_block {
local delay = 3 --3秒
local cron_a
--定時任務的函式
cron_a = function (premature)
if not premature then --如果執行函式時沒有傳參,則該任務會一直被觸發執行
ngx.log(ngx.ERR, "Just do it !")
end
end
--每隔delay引數值的時間,就執行一次cron_a函式
local ok, err = ngx.timer.every(delay, cron_a)
if not ok then
ngx.log(ngx.ERR, "failed to create the timer: ", err)
return
end
}
2.3 動態進行後端健康檢查
在init_worker_by_lua_block階段,也可以實現後端健康檢查的功能,用於檢查後端HTTP服務是否正常,類似於Nginx商業版中的health_check功能。
如果使用OpenResty 1.9.3.2及以上的版本,預設已支援此模組;如果使用Nginx,則首先需要安裝此模組,安裝方式如下:
# git clone https://github.com/openresty/lua-upstream-nginx-module.git
# cd nginx-1.12.2
# ./configure --prefix=/opt/nginx \
--with-ld-opt="-Wl,-rpath,$LUAJIT_LIB" \
--add-module=/path/to/lua-nginx-module \
--add-module=/path/to/lua-upstream-nginx-module
# make && make install
注意:安裝完成後,重新編譯Nginx時,請先確認之前安裝模組的數量,避免遺忘某個模組。
然後,將lua-resty-upstream-healthcheck模組中的lib檔案複製到lua_package_path指定的位置,示例如下:
# git clone https://github.com/openresty/lua-resty-upstream-healthcheck.git
# cp lua-resty-upstream-healthcheck/lib/resty/upstream/healthcheck.lua /usr/local/nginx_1.12.2/conf/lua_modules/resty/
實現動態進行後端健康檢查的功能,配置如下:
user webuser webuser;
worker_processes 3;
worker_rlimit_nofile 10240;
events {
use epoll;
worker_connections 10240;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test_12 {
server 127.0.0.1:81 weight=20 max_fails=10 fail_timeout=5s;
server 127.0.0.1:82 weight=20 max_fails=10 fail_timeout=5s;
server 127.0.0.1:8231 weight=20 max_fails=10 fail_timeout=5s;
}
lua_shared_dict healthcheck 1m; # 存放upstream servers的共享記憶體,後端伺服器組越多,配置就越大
lua_socket_log_errors off; # 當TCP傳送失敗時,會發送error日誌到error.log中,該過程會增加效能開銷,建議關閉,以避免在健康檢查過程中出現多臺伺服器宕機的情況,異常情況請使用ngx.log來記錄
lua_package_path "${prefix}conf/lua_modules/?.lua;;";
lua_package_cpath "${prefix}conf/lua_modules/c_package/?.so;;";
init_worker_by_lua_block {
local hc = require "resty.upstream.healthcheck"
local ok, err = hc.spawn_checker{
shm = "healthcheck", -- 使用共享記憶體
upstream = "test_12", -- 進行健康檢查的upstream名字
type = "http", -- 檢查型別是http
http_req = "GET /status HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",
-- 用來發送HTTP請求的格式和資料,核實服務是否正常
interval = 3000, -- 設定檢查的頻率為每3s一次
timeout = 1000, -- 設定請求的超時時間為1s
fall = 3, --設定連續失敗3次後就把後端服務改為down
rise = 2, --設定連續成功2次後就把後端服務改為up
valid_statuses = {200, 302}, --設定請求成功的響應狀態碼是200和302
concurrency = 10, --設定傳送請求的併發等級
}
if not ok then
ngx.log(ngx.ERR, "failed to spawn health checker: ", err)
return
end
}
server {
listen 80;
server_name testnginx.com;
location / {
proxy_pass http://test_12;
}
# /status 定義了後端健康檢查結果的輸出頁面
location = /status {
default_type text/plain;
content_by_lua_block {
local hc = require "resty.upstream.healthcheck"
--輸出當前檢查結果是哪個worker程序的
ngx.say("Nginx Worker PID: ", ngx.worker.pid())
--status_page()輸出後端伺服器的詳細情況
ngx.print(hc.status_page())
}
}
}
訪問http://testnginx.com/status檢視檢查的結果,圖8-1所示為健康檢查資料結果。
圖8-1 健康檢查資料結果
如果要檢查多個upstream,則配置如下(只有黑色加粗位置的配置有變化):
local ok, err = hc.spawn_checker{
shm = "healthcheck",
upstream = "test_12",
type = "http",
http_req = "GET /status HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",
interval = 3000,
timeout = 1000,
fall = 3,
rise = 2,
valid_statuses = {200, 302},
concurrency = 10,
}
local ok, err = hc.spawn_checker{
shm = "healthcheck",
upstream = "test_34",
type = "http",
http_req = "GET /test HTTP/1.0\r\nHost: testnginx.com\r\n\r\n",
interval = 3000,
timeout = 1000,
fall = 3,
rise = 2,
valid_statuses = {200, 302},
concurrency = 10,
如果把lua_socket_log_errors設定為on,那麼當有異常出現時,例如出現了超時,就會往error.log裡寫日誌,日誌記錄如圖8-2所示。
圖8-2 日誌記錄
經過lua-resty-upstream-healthcheck的健康檢查發現異常的伺服器後,Nginx會動態地將異常伺服器在upstream中禁用,以實現更精準的故障轉移。
三、set_by_lua_block
3.1 階段說明
語法:set_by_lua_block $res {lua-script-str}
配置環境:server,server if,location,location if
階段:rewrite
含義:執行<lua-script-str>程式碼,並將返回的字串賦值給$res。
3.2 變數賦值
本指令一次只能返回一個值,並賦值給變數$res(即只有一個$res被賦值),示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
set $a '';
set_by_lua_block $a {
local t = 'tes'
return t
}
return 200 $a;
}
執行結果如下:
# curl http://testnginx.com/
test
那如果希望返回多個變數,該怎麼辦呢?可以使用ngx.var.VARIABLE,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
#使用ngx.var.VARIABLE前需先定義變數
set $b '';
set_by_lua_block $a {
local t = 'test'
ngx.var.b = 'test_b'
return t
}
return 200 $a,$b;
}
}
執行結果如下:
# curl http://testnginx.com/test
test,test_b
3.2 Rewrite階段的混用模式
因為set_by_lua_block處在rewrite階段,所以它可以和ngx_http_rewrite_module、set-misc-nginx-module,以及array-var-nginx-module一起使用,在程式碼執行時,按照配置檔案的順序從上到下執行,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
set $a '123';
set_by_lua_block $b {
local t = 'bbb'
return t
}
set_by_lua_block $c {
local t = 'ccc' .. ngx.var.b
return t
}
set $d "456$c";
return 200 $a,$b,$c,$d;
}
}
從執行結果可以看出資料是從上到下執行的,如下所示:
# curl http://testnginx.com/test
123,bbb,cccbbb,456cccbbb
3.3 阻塞事件
set_by_lua_block指令塊在Nginx中執行的指令是阻塞型操作,因此應儘量在這個階段執行輕、快、短、少的程式碼,以避免耗時過多。set_by_lua_block不支援非阻塞I/O,所以不支援yield當前Lua的輕執行緒。
3.4 被禁用的Lua API指令
在set_by_lua_block階段的上下文中,下面的Lua API是被禁止的(只羅列部分)。
輸出型別的API函式(如ngx.say和ngx.send_headers)。
控制型別的API函式(如ngx.exit)。
子請求的API函式(如ngx.location.capture和ngx.location.capture_multi)。
Cosocket API函式(如ngx.socket.tcp和ngx.req.socket)。
休眠API函式ngx.sleep。
四、rewrite_by_lua_block
4.1 階段說明
語法:rewrite_by_lua_block {lua-script-str}
配置環境:http,server,location,location if
階段:rewrite tail
含義:作為一個重寫階段的處理程式,對每個請求執行<lua-script-str>指定的Lua程式碼。
這些Lua程式碼可以呼叫所有的Lua API,並且執行在獨立的全域性環境(類似於沙盒)中,以新的協程來執行。因為可以呼叫所有的Lua API,所以此階段可以實現很多功能,例如對請求的URL進行重定向、讀取MySQL或Redis資料、傳送子請求、控制請求頭等。
4.2 利用rewrite_by_lua_no_postpone改變執行順序
rewrite_by_lua_block命令預設在ngx_http_rewrite_module之後執行,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
set $b '1';
rewrite_by_lua_block { ngx.var.b = '2'}
set $b '3';
echo $b;
}
}
從程式碼來看,預計應輸出的結果是3,但輸出的結果卻是2,如下所示:
# curl http://testnginx.com/test
2
上述配置的操作結果說明rewrite_by_lua_block始終都在rewrite階段的後面執行,如果要改變這個順序,需使用rewrite_by_lua_no_postpone指令。
語法:rewrite_by_lua_no_postpone on|off
預設:rewrite_by_lua_no_postpone off
配置環境:http
含義:在rewrite請求處理階段,控制rewrite_by_lua*的所有指令是否被延遲執行,預設為off,即延遲到最後執行;如果設定為on,則會根據配置檔案的順序從上到下執行。
示例:
rewrite_by_lua_no_postpone on; #只能在http階段配置
server {
listen 80;
server_name testnginx.com;
location / {
set $b '1';
rewrite_by_lua_block { ngx.var.b = '2'}
set $b '3';
echo $b;
}
}
執行結果如下:
# curl -i http://testnginx.com/test
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 28 May 2018 12:47:37 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
3
注意:if 語句是在rewrite_by_lua_block階段之前執行的,所以在運用if語句時要特別留意執行順序,避免出現意想不到的結果。
4.3 階段控制
在rewrite_by_lua_block階段,當呼叫ngx.exit(ngx.OK)之後,請求會退出當前的執行階段,繼續下一階段的內容處理(如果想了解更多關於ngx.exit的使用方式,請參考7.16.3節)。
五、access_by_lua_block
5.1 階段說明
語法:access_by_lua_block {lua-script-str}
配置環境:http,server,location,location if
階段:access tail
含義:在Nginx的access階段,對每個請求執行<lua-script-str>的程式碼,和rewrite_bylua block一樣,這些Lua程式碼可以呼叫所有的Lua API,並且執行在獨立的全域性環境(類似於沙盒)中,以新的協程來執行。此階段一般用來進行許可權檢查和黑白名單配置。
5.2 利用access_by_lua_no_postpone改變執行順序
access_by_lua_block預設在ngx_http_access_module之後。但把access_by_lua_no_postpone設定為on可以改變執行順序,變成根據配置檔案的順序從上到下執行。access_by_luano postpone的用法和rewrite_by_lua_no_postpone類似。
5.3 階段控制
access_by_lua_block和rewrite_by_lua_block一樣,都可以通過ngx.exit來結束當前的執行階段。
5.4 動態配置黑白名單
Nginx提供了allow、deny等指令來控制IP地址訪問服務的許可權,但如果每次新增的IP地址都需要過載配置檔案就不太靈活了,而且反覆過載Nginx也會導致服務不穩定。此時,可以通過access_by_lua_block來動態新增黑白名單。
下面是兩種常見的動態配置黑白名單的方案。
1.將黑白名單存放在Redis中,然後使用Nginx+Lua讀取Redis的資料,通過修改Redis中的資料來動態配置黑白名單。這種方案的缺點是增加了對網路I/O的操作,相比使用共享記憶體的方式,效能稍微差了些。
2.將黑白名單存放在Ngx_Lua提供的共享記憶體中,每次請求時都去讀取共享記憶體中的黑白名單,通過修改共享記憶體的資料達到動態配置黑白名單的目的。本方案的缺點是在Nginx 重啟的過程中資料會丟失。
8.6 content_by_lua_block
6.1 階段說明
語法:content_by_lua_block {lua-script-str}
配置環境:location,location if
階段:content
含義:content_by_lua_block 作為內容處理階段,對每個請求執行<lua-script-str>的程式碼。和rewrite_by_lua_block一樣,這些Lua程式碼可以呼叫所有的Lua API,並且執行在獨立的全域性環境(類似於沙盒)中,以新的協程來執行。
content_by_lua_block指令不可以和其他內容處理階段的模組如echo、return、proxy_pass等放在一起,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
content_by_lua_block {
ngx.say("content_by_lua_block")
}
echo 'ok';
}
}
輸出結果只有ok,並未執行content_by_lua_block指令。
6.2 動態調整執行檔案的路徑
指令content_by_lua_file可以用來動態地調整執行檔案的路徑,它後面跟的Lua執行檔案路徑可以是變數,該變數可以是不同的引數或URL等,示例如下:
location / {
#content_by_lua_file可以直接獲取URL中引數file_name的值
content_by_lua_file conf/lua/$arg_file_name;
}
#七、balancer_by_lua_block
7.1 階段說明
語法:balancer_by_lua_block { lua-script }
配置環境:upstream
階段:content
含義:在upstream的配置中執行並控制後端伺服器的地址,它會忽視原upstream中預設的配置。
示例:
upstream foo {
server 127.0.0.1; #會忽視這個配置
balancer_by_lua_block {
#真實的IP地址在這裡配置
}
}
7.2 被禁用的Lua API指令
在balancer_by_lua_block階段,Lua程式碼的執行環境不支援yield,因此需禁用可能會產生yield的Lua API指令(如cosockets和light threads)。但可以利用ngx.ctx建立一個擁有上下文的變數,在本階段前面的某個執行階段(如rewrite_by_lua*階段)將資料生成後傳入upstream中。
八、header_filter_by_lua_block
8.1 階段說明
語法:header_filter_by_lua_block { lua-script }
配置環境:http,server,location,location if
階段:output-header-filter
含義:在header_filter_by_lua_block階段,對每個請求執行<lua-script-str>的程式碼,以此對響應頭進行過濾。常用於對響應頭進行新增、刪除等操作。
例如,新增一個響應頭test,值為nginx-lua,示例如下:
location / {
header_filter_by_lua_block {
ngx.header.test = "nginx-lua";
}
echo 'ok';
}
8.2 被禁用的Lua API指令
在header_filter_by_lua_block階段中,下列Lua API是被禁止使用的。
輸出型別的API函式(如ngx.say和ngx.send_headers)。
控制型別的API函式(如ngx.redirect和ngx.exec)。
子請求的API函式(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函式(如ngx.socket.tcp和ngx.req.socket)。
九、body_filter_by_lua_block
9.1 階段說明
語法:body_filter_by_lua_block { lua-script-str }
配置環境:http,server,location,location if
階段:output-body-filter
含義:在body_filter_by_lua_block階段執行<lua-script-str>的程式碼,用於設定輸出響應體的過濾器。在此階段可以修改響應體的內容,如修改字母的大小寫、替換字串等。
9.2 控制響應體資料
通過ngx.arg[1](ngx.arg[1]是Lua的字串型別的資料)輸入資料流,結束標識eof是響應體資料的最後一位ngx.arg[2](ngx.arg[2]是Lua的布林值型別的資料)。
由於Nginx chain緩衝區的存在,資料流不一定都是一次性完成的,可能需要傳送多次。在這種情況下,結束標識eof僅僅是Nginx chain緩衝區的last_buf(主請求)或last_in_chain(子請求),因此Nginx輸出過濾器在一個單獨的請求中會被多次呼叫,此階段的Lua指令也會被執行多次,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
#將響應體全部轉換為大寫
body_filter_by_lua_block { ngx.arg[1] = string.upper(ngx. arg[1]) }
echo 'oooKkk';
echo 'oooKkk';
echo 'oooKkk';
}
}
執行結果如下:
# curl -i http://testnginx.com/?file_name=1.lua
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Tue, 29 May 2018 11:36:54 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
OOOKKK
OOOKKK
OOOKKK
使用return ngx.ERROR可以截斷響應體,但會導致資料不完整,請求無效:
location / {
body_filter_by_lua_block {
ngx.arg[1] = string.upper(ngx.arg[1]);
return ngx.ERROR
}
echo 'oooKkk';
echo 'oooKkk';
echo 'oooKkk';
}
執行結果如下:
# curl -i http://testnginx.com/
curl: (52) Empty reply from server
ngx.arg[2]是一個布林值,如果把它設為true,可以用來截斷響應體的資料,和return ngx.ERROR不一樣,此時被截斷的資料仍然可以輸出內容,是有效的請求,示例如下:
server {
listen 80;
server_name testnginx.com;
location / {
body_filter_by_lua_block {
local body_chunk = ngx.arg[1]
--如果響應體匹配到了2ooo,就讓ngx.arg[2]=true
if string.match(body_chunk, "2ooo") then
ngx.arg[2] = true
return
end
--設定ngx.arg[1] = nil,表示此響應體不會出現在輸出內容中
ngx.arg[1] = nil
}
echo '1oooKkk';
echo '2oooKkk';
echo '3oooKkk';
}
}
執行結果如下:
# curl -i http://testnginx.com/?file_name=1.lua
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Tue, 29 May 2018 11:48:52 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
2oooKkk
從輸出結果可以得出如下結論。
1.沒有輸出1oooKkk,是因為它在if語句中匹配不成功,設定 ngx.arg[1] = nil將沒有匹配成功的資料遮蔽了。
2.沒有輸出3oooKkk,是因為2oooKkk被匹配成功了,使用ngx.arg[2] = true終止了後續的操作,剩下的內容就不會再被輸出了。
9.3 被禁用的Lua API指令
在body_filter_by_lua_block階段中,下列Lua API是被禁止的。
輸出型別的API函式(如ngx.say和ngx.send_headers)。
控制型別的API函式(如ngx.redirect和ngx.exec)。
子請求的API函式(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函式(如ngx.socket.tcp和ngx.req.socket)。
#十、log_by_lua_block
10.1 階段說明
語法:log_by_lua_block { lua-script }
配置環境:http,server,location,location if
階段:log
含義:在日誌請求處理階段執行的{ lua-script }程式碼。它不會替換當前access請求的日誌,而會執行在access的前面。
log_by_lua_block階段非常適合用來對日誌進行定製化處理,且可以實現日誌的叢集化維護(詳見第12章)。另外,此階段屬於log階段,這時,請求已經返回到了客戶端,對Ngx_Lua程式碼的異常影響要小很多。
示例:
server {
listen 80;
server_name testnginx.com;
location / {
log_by_lua_block {
local ngx = require "ngx";
xxxsada --一個明顯的語法錯誤
ngx.log(ngx.ERR, 'x');
}
echo 'ok';
}
}
上述程式碼的error_log雖有報錯,但執行結果仍然會輸出“ok”。
10.2 被禁用的Lua API指令
在log_by_lua_block階段中,下列Lua API是被禁止的。
輸出型別的API函式(如ngx.say和ngx.send_headers)。
控制型別的API函式(如ngx.redirect和ngx.exec)。
子請求的API函式(如ngx.location.capture和ngx.location.capture_multi)。
cosocket API函式(如ngx.socket.tcp和ngx.req.socket)。
休眠API函式ngx.sleep。
#十一、Lua和ssl
Lua API還可以對HTTPS協議進行處理,可以使用ssl_certificate_by_lua_block、ssl_session_fetch_by_lua_block、ssl_session_store_by_lua_block這3個指令塊進行配置,由於都涉及lua-resty-core模組的ngx.ssl指令(這已經超出了本章的內容),有興趣的讀者可以去檢視lua-resty-core的相關資料。
#十二、Ngx_Lua執行階段
通過前面章節的學習,讀者瞭解了Ngx_lua模組各個執行階段的作用,現在將這些執行階段彙總到一起來觀察一下整體的執行流程。
圖8-3所示為Ngx_lua的執行順序,它引用自Lua-Nginx-Module的官方Wiki,很清晰地展示了Ngx_lua在Nginx中的執行順序。下面舉一個例子來驗證一下這個執行順序。
圖8-3 Ngx_Lua的執行順序
1.測試程式碼使用 *_by_lua_file指令來配置,首先,建立如下的test.lua檔案:
# vim /usr/local/nginx_1.12.2/conf/lua/test.lua
local ngx = require "ngx"
-- ngx.get_phase()可以獲取Lua程式碼在執行時屬於哪個執行階段
local phase = ngx.get_phase()
ngx.log(ngx.ERR, phase, ': Hello,world! ')
2.在Nginx配置中載入如下的檔案:
# init_by_lua_file 和 init_worker_by_lua_file只能在http指令塊執行
init_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
init_worker_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
server {
listen 80;
server_name testnginx.com;
location / {
#檔案的順序是隨意擺放的,觀察日誌,留意檔案順序是否對執行順序有干擾
body_filter_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
header_filter_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
rewrite_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
access_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
set_by_lua_file $test /usr/local/nginx_1.12.2/conf/lua/test.lua;
log_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
# content_by_lua_file 和 balancer_by_lua_block 不能存在於同一個location,所以只用content_by_lua_file示例
content_by_lua_file /usr/local/nginx_1.12.2/conf/lua/test.lua;
}
}
3.過載Nginx配置讓程式碼生效,此時不要訪問Nginx,可以觀察到error.log在沒有請求訪問的情況下也會有內容輸出。
如果輸出“init”,表示此階段屬於init_by_lua_block階段;如果輸出“init_worker”,表示此階段屬於init_worker_by_lua_block階段。
有些讀者可能會發現init_worker的輸出有3行,那是因為此階段是在Nginx的worker程序上執行的,每個worker程序都會獨立執行一次Lua程式碼,因此可以看出此時Nginx啟動了3個worker程序,如下所示:
2018/06/04 19:00:23 [error] 12034#12034: [lua] test.lua:3: init: Hello,world!
2018/06/04 19:00:23 [error] 21019#21019: *914 [lua] test.lua:3: init_worker: Hello,world!, context: init_worker_by_lua*
2018/06/04 19:00:23 [error] 21020#21020: *915 [lua] test.lua:3: init_worker: Hello,world!, context: init_worker_by_lua*
2018/06/04 19:00:23 [error] 21018#21018: *916 [lua] test.lua:3: init_worker: Hello,world!, context: init_worker_by_lua*
4.傳送請求:
curl -i http://testnginx.com/
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 04 Jun 2018 11:00:29 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
5.觀察access.log日誌,能夠看到每個執行階段的優先順序順序:
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: set: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: rewrite: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: access: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: content: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: header_filter: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: body_filter: Hello,world!, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
2018/06/04 19:16:20 [error] 21019#21019: *920 [lua] test.lua:3: log: Hello,world! while logging request, client: 10.19.64.210, server: testnginx.com, request: "GET /test?ss HTTP/1.1", host: "testnginx.com"
通過access.log日誌截圖(如圖8-4所示)可以看得更清晰。
圖8-4 access.log日誌截圖
觀察Ngx_Lua整體的執行流程,可得出如下結論。
Ngx_Lua執行順序如下:
init_by_lua_block
init_worker_by_lua_block
set_by_lua_block
rewrite_by_lua_block
access_by_lua_block
content_by_lua_block
header_filter_by_lua_block
body_filter_by_lua_block
log_by_lua_block
指令在配置檔案中的順序不會影響執行的順序,這一點和Nginx一樣。
http階段的init_by_lua_block和init_worker_by_lua_block只會在配置過載時執行,只進行HTTP請求時,不會被執行。
注意:init_by_lua_file 在lua_code_cache 為off 的情況下,每次執行HTTP請求都會執行過載配置階段的Lua程式碼。
13、小結
本章對Ngx_Lua的執行階段進行了講解,掌握每個執行階段的用途會讓大家在實際開發中更加得心應手。