内容简介:对刚接触Ngx_lua的读者来说,可能会存在下面两个困惑。1、Lua在Nginx的哪些阶段可以执行代码?Lua在Nginx的每个阶段可以执行哪些操作?
对刚接触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.4init_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指令
是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.2Rewrite阶段的混用模式
因为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_by lua 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_lua no 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.6content_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-3Ngx_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-4access.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的执行阶段进行了讲解,掌握每个执行阶段的用途会让大家在实际开发中更加得心应手。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Nginx执行阶段详细解析
- MyBatis执行流程的各阶段介绍
- JS引擎线程的执行过程的三个阶段
- javascript系列--javascript引擎执行的过程的理解--语法分析和预编译阶段
- 金色观察丨以太坊2.0从“零阶段”到“一阶段”比想象中简单?
- 技术人员发展四阶段
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
京东平台运营攻略(全彩)
京东商学院 / 电子工业出版社 / 2015-5 / 69.00元
2014 年年末,京东POP 开放平台的入驻商家已超过6 万,京东平台被广泛关注和认可的同时,在电商江湖中仍颇具神秘色彩。面对碎片化的信息,京东的店铺经营者及希望入驻京东的准商家们,对于在京东如何利用丰富的各类平台资源,搭建并运营京东店铺,一直很难找到全面而系统的资料。 《京东平台运营攻略(全彩)》由京东官方出品,动员了京东内部涉及第三方店铺业务线的众多部门,由多位业务精英参与撰写,保证了内......一起来看看 《京东平台运营攻略(全彩)》 这本书的介绍吧!