Nginx Lua的执行阶段

栏目: Lua · 发布时间: 7年前

内容简介:对刚接触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指令

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所示为健康检查数据结果。

Nginx Lua的执行阶段

图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所示。

Nginx Lua的执行阶段

图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进行重定向、读取 MySQLRedis 数据、发送子请求、控制请求头等。

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中的执行顺序。下面举一个例子来验证一下这个执行顺序。

Nginx Lua的执行阶段

图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所示)可以看得更清晰。

Nginx Lua的执行阶段

图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的执行阶段进行了讲解,掌握每个执行阶段的用途会让大家在实际开发中更加得心应手。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design for Hackers

Design for Hackers

David Kadavy / Wiley / 2011-10-18 / USD 39.99

Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具