对刚接触Ngx_lua的读者来说,可能会存在下面两个困惑。
1、Lua在Nginx的哪些阶段可以执行代码?
2、Lua在Nginx的每个阶段可以执行哪些操作?只有理解了这两个问题,才能在业务中巧妙地利用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的执行阶段进行了讲解,掌握每个执行阶段的用途会让大家在实际开发中更加得心应手。
转载于:https://blog.51cto.com/xikder/2331649