Nginx Lua的执行阶段

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_alocal 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.0Host: testnginx.com",                    -- 用来发送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.0Host: testnginx.com",    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.0Host: testnginx.com",    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/testtest,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/test123,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/test2

上述配置的操作结果说明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/testHTTP/1.1 200 OKServer: nginx/1.12.2Date: Mon, 28 May 2018 12:47:37 GMTContent-Type: application/octet-streamTransfer-Encoding: chunkedConnection: 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.luaHTTP/1.1 200 OKServer: nginx/1.12.2Date: Tue, 29 May 2018 11:36:54 GMTContent-Type: application/octet-streamTransfer-Encoding: chunkedConnection: keep-aliveOOOKKKOOOKKKOOOKKK使用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.luaHTTP/1.1 200 OKServer: nginx/1.12.2Date: Tue, 29 May 2018 11:48:52 GMTContent-Type: application/octet-streamTransfer-Encoding: chunkedConnection: keep-alive2oooKkk

从输出结果可以得出如下结论。
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 OKServer: nginx/1.12.2Date: Mon, 04 Jun 2018 11:00:29 GMTContent-Type: application/octet-streamTransfer-Encoding: chunkedConnection: 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_blockinit_worker_by_lua_blockset_by_lua_blockrewrite_by_lua_blockaccess_by_lua_blockcontent_by_lua_blockheader_filter_by_lua_blockbody_filter_by_lua_blocklog_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的执行阶段进行了讲解,掌握每个执行阶段的用途会让大家在实际开发中更加得心应手。

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部