Context配置环境

Ngx_Lua API 指令和Nginx的指令一样,都存在配置环境的约束问题,因此在使用过程中要确保指令的环境符合预期。

  • 指令:ngx.var.VARIABLE
  • 语法:nginx.var.VAR_NAME

读-写Nginx的内置变量

语法:nginx.var.VAR_NAME
环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • og_by_lua*

作用:读/写Nginx的变量值。例如HTTP请求头、Nginx创建的变量、URL参数、Nginx通过正则表达式捕获的1、2等值(获取方式为nginx.var[1]、nginx.var[2])
示例:

location ~ ^/([a-z]+)/var.html {
        #配置变量,如果时未定义的Nginx变量,就无法直接在Lua中进行读取        
    set $a '';
    set $b '';
    set $c '';
    set $d '';
        #rewrite_by_lua_block指令块作为rewrite阶段的处理,为每个请求执行指定的lua代码。注意这个处理是在标准HtpRewriteModule之后进行的
    rewrite_by_lua_block {
        -- 通过require加载ngx模块并将结果赋值给ngx,在后面的操作用就可以通过ngx直接调用ngx模块的功能了。         
local ngx = require "ngx"
ngx.var.a = '1'
ngx.var.b = ngx.var.http_user_agent
        -- 获取参数test的值赋值给变量c
        ngx.var.c = ngx.var.arg_test
ngx.var.d = ngx.var[1]
    }
    echo $a;
    echo $b;
    echo $c;
    echo $d;
}

返回结果如下:

curl -i 'http://127.0.0.1/nginx/var.html?test=123&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Tue, 05 Jul 2022 14:31:31 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive

1
curl/7.29.0
123
nginx

控制请求头

lua_package_path

作用:

设置默认的Lua模块的搜索路径,并配置在http阶段。它支持配置相对路径和绝对路径,其中相对路径是在Nginx启动时由-p PATH决定的。如果在启动Nginx时没有配置-p PATH,就会使用编译时--prefix的值,此值一般存放在Nginx的$prefix(也可以用${prefix}来表示)变量中。

配置示例:

http {
    -- lua_package_path在配置中只能出现一次,下方三种方式任选一种
    -- 下方路径结尾的两个分号,代表的是LuaJIT安装时的原始搜索路径,如果在前面的搜索路径里面无法搜索到需要的模块,就会依次搜索后面的路径
    lua_package_path "/usr/local/nginx/conf/lua_modules/?.lua;;";
    lua_package_path "conf/lua_modules/?.lua;;";
    lua_package_path "${prefix}conf/lua_modules/?.lua;;";   #推荐使用此种方式
    -- lua_package_path也支持设置多个搜索路径,多个搜索路径之间使用分号进行分隔,如下:
    lua_package_path "${prefix}conf/lua_modules/?.lua;/opt/lua/?.lua;;";
}

ngx.req.set_header

作用:

添加或修改当前HTTP的请求头。如果请求头已经存在,则会被替换成新的值。通过此方式设置的请求头会被继承到子请求中。

语法:

ngx.req.set_header(header_name,header_value)

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*

配置示例1

添加请求头:

        location ~ ^/([a-z]+)/var.html {
            rewrite_by_lua_block {
                local ngx = require "ngx"
                ngx.req.set_header("Test_Ngx_ver","1.12.2")
            }
            echo $http_Test_Ngx_ver;
        }


# 请求及返回:
curl -i 'http://127.0.0.1/nginx/var.html'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Tue, 05 Jul 2022 15:00:33 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive

1.12.2

ngx.req.clear_header

作用:

清除当前请求中指定的请求头。清除后,如果存在未执行的子请求,则子请求会继承清除后的请求头。

语法:

ngx.req.clear_header(header_name)

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*

配置示例:

        location ~ ^/([a-z]+)/var.html {
            rewrite_by_lua_block {
                local ngx = require "ngx"
                ngx.req.set_header("Test_Ngx_ver","1.12.2")
                ngx.req.clear_header("Test_Ngx_ver")
            }
            echo $http_Test_Ngx_ver;
        }
# 请求及返回,可以看到调用Test_Ngx_ver输出了一个空值并换行
curl -i 'http://127.0.0.1/nginx/var.html'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Tue, 05 Jul 2022 15:10:47 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive

ngx.req.get_headers

作用:

获取当前请求的全部请求头,并返回一个Lua的table类型的数据。

语法:

headers = ngx.req.get_headers(max_headers?,raw?)

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*

配置示例:

        location /test {
            default_type 'text/plain';
            content_by_lua_block {
                local headers = ngx.req.get_headers()
                ngx.say("Host : ", headers["Host"], "<br/>")
                ngx.say("user-agent : ", headers["user-agent"], "<br/>")
                ngx.say("user-agent : ", headers.user_agent, "<br/>")
                # lua无法直接输出table的值,需要通过循环来输出
                for k,v in pairs(headers) do
                    if type(v) == "table" then
                        ngx.say(k, " : ", table.concat(v, ","), "<br/>")
                    else
                        ngx.say(k, " : ", v, "<br/>")
                    end
                end
            }
        }
#请求及返回
[root@Test22 conf]# curl -i 'http://127.0.0.1/test'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 01:51:45 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

Host : 127.0.0.1<br/>
user-agent : curl/7.29.0<br/>
user-agent : curl/7.29.0<br/>
host : 127.0.0.1<br/>
accept : */*<br/>
user-agent : curl/7.29.0<br/>

控制响应头/体

ngx.resp.get_headers

作用:

读取当前请求的响应头,并返回一个Lua的table类型的数据。

语法:

headers = ngx.resp.get_headers(max_headers?,raw?)

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • balancer_by_lua*

配置示例:

        location / {
            content_by_lua_block {
                local ngx = require "ngx"
                local h = ngx.resp.get_headers()
                for k,v in pairs(h) do
                    ngx.say('Header name: ',k,' value: ',v)
                end
                ngx.say(h["connection"])
            }
        }

# 请求及返回,此处返回只有Connection,具体是何原因不清楚
curl -i 'http://127.0.0.1/test?=12314&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 13:45:12 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

Header name: connection value: keep-alive
keep-alive

ngx.header.HEADER

作用:

对响应头进行修改、清除、添加等操作,此API在输出响应头时,默认会将"_"替换为"-"。

语法:

ngx.header.HEADER = VALUE或者 value = ngx.header.HEADER

环境:

rewrite_by_lua*
access_by_lua*
content_by_lua*
header_filter_by_lua*
body_filter_by_lua*
log_by_lua*

配置示例:

        location / {
            content_by_lua_block {
                local ngx = require "ngx"
                -- 修改
                ngx.header.content_type = 'text/plain'
                -- 修改一个没有的key则为添加
                ngx.header.Test_Nginx = 'Lua'
                -- 下方代码等同于 ngx.header.A_Ver = 'aaa'
                ngx.header["A_Ver"] = 'aaa'
                -- 读取响应头,并赋值给变量a
                local a = ngx.header.Test_Nginx
                -- 清除/删除响应头,将响应头的值赋值为nil即可
            }
        }
# 请求及返回
curl -i 'http://127.0.0.1/test?=12314&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 13:58:17 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Test-Nginx: Lua
A-Ver: aaa

异步发送响应体

ngx.say

作用:

将数据作为响应体输出,返回给客户端,并在末尾加上一个回车符

语法:

ok, err = ngx.say(...)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

配置示例:

        location /test {
            default_type 'text/plain';
            content_by_lua_block {   #作用是在http的内容处理阶段生成数据
                ngx.say('Hello,World!')
            }
        }

请求及返回:
curl -i http://127.0.0.1/test
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Tue, 05 Jul 2022 13:49:45 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

Hello,World!

ngx.print

作用:

用来输出内容,输出的内容会和其它输出合并,然后发送给客户端,如果响应头还未发送,发送前会优先将响应头发送出去。

语法:

ok, err = nginx.print(...)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

同步发送响应体

ngx.flush

作用:

在默认情况下会发起一个异步调用,即不等后续的数据到达缓冲区就直接将内容输出到客户端。如果将wait的参数值设置为true,则表示同步执行,即会等内存全部输出到缓冲区后再输出到客户端。该指令不支持http1.0

语法:

ok, err = ngx.flush(wait?)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

控制请求体

强制获取请求体

lua_need_request_body

作用:

强制获取请求体。默认为off,即不读取请求体。如果设置为on,则表示强制读取请求体,此时,可以通过ngx.var.request_body来获取请求体的数据。需要注意的是,request_body存在于内存中,如果它的字节大小超过Nginx配置中的client_body_buffer_size的值,Nginx就会把请求体存放到临时文件中。此时数据就不再内存中了,这会导致request_body为空。所以需要设置client_body_buffer_size和client_max_body_size的值相同来避免这种情况。

语法:

lua_need_request_body <on|off>

环境:

  • http
  • server
  • location
  • location if

用同步非阻塞方式获取请求体

注意

  1. 在实际使用中,读取请求体,建议如下:
  2. 尽量不要使用lua_need_request_body获取请求体
  3. 获取请求体前,必须执行ngx.req.read_body()
  4. 获取请求体数据时今年不要使用硬盘上的临时文件,否则会对性能有很大影响;务必确认请求体数据的字节大小,并确保client_body_buffer_size和client_max_body_size的值一致,这样只需到内存中读取数据就可以了。它既提高了Nginx自身的吞吐能力,也提升了Lua的读取性能。
  5. 如果请求体存放在临时文件中,Nginx会在处理完请求后自动清理临时文件。
  6. 使用ngx.req.get_post_args可以对请求的参数进行灵活控制,但不能关闭限制,以避免被恶意攻击。

ngx.req.read_body

作用:

用同步非阻塞方式获取请求体。同步读取客户端请求体,且不会阻塞Nginx的事件循环。使用此指令后,就可以通过ngx.req.get_body_data来获取请求体的数据了。但如果使用临时文件来存放请求体,就需要先使用函数ngx.req.get_body_file来获取临时文件名,再读取临时文件中的请求体数据。

语法:

ngx.req.read_body()

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

ngx.req.get_body_data

作用:

执行ngx.req.read_body指令后,可以使用本指令在内存中获取请求体数据,结果会返回一个Lua的字符串类型的数据。如果要获取Lua的table类型的数据,则需要使用ngx.req.get_post_args。

语法:

data = ngx.req.get_body_data()

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • log_by_lua*

ngx.req.get_post_args

作用:

在执行ngx.req.read_body指令后,可以使用本指令读取包含当前请求在内的所有POST请求的查询参数,返回一个Lua的table类型的数据。max_args参数的作用是限制参数的数量。为了服务的安全,最多支持使用100个参数(包括重复的参数),超过限制的参数会被忽略。如果max_args为0,则表示关闭此限制;但为了避免被无穷多的参数攻击,不要将max_args设置为0.如果最多支持使用10个参数,则应配置为ngx.req.get_post_args(10)。

语法:

args, err = ngx.req.get_post_args(max_args?)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*

ngx.req.get_body_file

作用:

在执行ngx.req.read_body指令后,可以使用本指令获取存放请求体的临时文件名(绝对路径)。如果请求体被存放在内存中,获取的值就是nil。通过本指令获取的文件是只读的,不可以被修改,且会在被Nginx读取后删除。

语法:

file_name = ngx.req.get_body_file()

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

获取请求体配置示例

获取string类型的请求体

        location / {
            client_max_body_size 10k;
            client_body_buffer_size 1k;

            content_by_lua_block {
                local ngx = require "ngx"
                -- 启用读取请求体模式
                ngx.req.read_body()
                -- 获取内存中的请求体
                local data = ngx.req.get_body_data()
                if data then
                    ngx.print('ngx.req.get_body_data: ',data,'  ---- type is ',type(data))
                    return
                else
                    -- 如果没有获取到内存中的请求体数据,则到临时文件中读取
                    local file = ngx.req.get_body_file()
                    if file then
                        ngx.say("body is in file ", file)
                    else
                        ngx.say("no body found")
                    end
                end
            }

        }
#请求及返回
#先用一个小于1KB的请求体执行请求,因为Nginx配置中设置了client_body_buffer_size为1K
curl -i http://127.0.0.1/ -d 'test=213123&a=2&bn=c&dd'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 14:47:56 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

ngx.req.get_body_data: test=213123&a=2&bn=c&dd  ---- type is string

获取table类型的请求体

        location / {
            client_max_body_size 10k;
            client_body_buffer_size 1k;
            
            content_by_lua_block {
                local ngx = require "ngx"
                ngx.req.read_body()
                -- 获取内存中的请求体,返回的结果时table类型的数据
                local args, err = ngx.req.get_post_args()
                if args then 
                    for k, v in pairs(args) do
                        if type(v) == "table" then
                            -- 如果存在相同的参数名,就会将相同的参数并列再一起,以逗号分隔
                            ngx.say(k,": ", table.concat(v, ", "))
                        else
                            ngx.say(k, ": ", v)
                        end
                    end
                else
                    -- 如果没有获取到内存中的请求体,则到临时文件中读取
                    local file = ngx.req.get_body_file()
                    if file then
                        ngx.say("body is in file ", file)
                    else
                        ngx.say("no body found")
                    end
                end
            }
        }
#请求及返回
#a参数有两个,c参数值为空,d参数连等号都没有
curl -i http://127.0.0.1/ -d 'test=1234&a=2&b=c&dd=1&a=354&c=&d'
HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 15:13:59 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

b: c
dd: 1
d: true
c: 
a: 2, 354
test: 1234

获取临时文件中的请求体

        location / {
            client_max_body_size 10k;
            client_body_buffer_size 1k;

            content_by_lua_block {
                local ngx = require "ngx"
                -- 启用读取请求体模式
                ngx.req.read_body()
                -- 获取内存中的请求体
                local data = ngx.req.get_body_data()
                if data then
                    ngx.print('ngx.req.get_body_data: ',data,'  ---- type is ',type(data))
                    return
                else
                    -- 如果没有获取到内存中的请求体数据,则到临时文件中读取
                    local file = ngx.req.get_body_file()
                    if file then
                        ngx.say("body is in file ", file)
                    else
                        ngx.say("no body found")
                    end
                end
            }

        }
#请求及返回
#使用一个大于1KB小于10KB的请求体执行请求
#因为请求体的数据大小大于client_body_buffer_size的值,所以使用了临时文件存储请求体的数据
#所以需要先获取存放数据的临时文件名,再读取请求体的数据
#但读取临时文件中的请求体数据是不被推荐的,如有需要可以使用io.open来完成
curl -i http://127.0.0.1/ -d 'giuabsigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbioigbasibgioupasbguiopsabguiopsbuipgbsauipdbgpuibasdguipbuipbuipbpuiybuipybuipiuiiubuipbbbbbbbbbbbbbbbbbbbbbbbibikbihbihbihbibibiubibibibibibbivbiuvbyuihovbivbiovbio&dd'
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Server: openresty/1.15.8.3
Date: Wed, 06 Jul 2022 14:49:57 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

body is in file /usr/local/openresty/nginx/client_body_temp/0000000001

正则表达式

单一捕获

ngx.re.match

作用:

使用Perl兼容的正则表达式来匹配subject参数,只返回匹配到的第一个结果。如果匹配失败,则返回nil;如果有异常,则返回nil和一个描述错误信息的err。

语法:

captures, err = ngx.re.match(subject,regex,options?,ctx?,res_table?)

环境:

基本都支持

配置示例:

        location / {
            content_by_lua_block {
                local ngx = require "ngx";
                -- 匹配多个数字+aaa的正则表达式      
                local m, err = ngx.re.match(ngx.var.uri, "([0-9]+)(aaa)");
                if m then
                   -- 匹配成功后输出的信息
                   ngx.say(ngx.var.uri, '---match success---', 'its type: ',type(m))
                   ngx.say(ngx.var.uri, '---m[0]--- ', m[0])
                   ngx.say(ngx.var.uri, '---m[1]--- ', m[1])
                   ngx.say(ngx.var.uri, '---m[2]--- ', m[2])
                else
                   if err then
                       ngx.log(ngx.ERR, "error: ", err)
                       return
                   end
                   ngx.say("match not found")
                end

            }
        }
# 请求及返回
curl http://127.0.0.1/test/a123aaa/b4556aaa/c
/test/a123aaa/b4556aaa/c---match success---its type: table
/test/a123aaa/b4556aaa/c---m[0]--- 123aaa
/test/a123aaa/b4556aaa/c---m[1]--- 123
/test/a123aaa/b4556aaa/c---m[2]--- aaa

#从执行结果可以得到如下几点
1.ngx.re.match只返回匹配到的第一个结果,所以后面的456aaa并没有被输出。
2.ngx.re.match返回的结果是table类型的。
3.ngx.re.match匹配成功后,m[0] 的值是匹配到的完整数据,而m[1]、m[2] 是被包含在括号内的单个匹配结果

全部捕获

ngx.re.gmatch

作用:

和ngx.re.match功能相似,但返回的是一个Lua迭代器,可以通过迭代的方式获取匹配到的全部数据

语法:

iteratior, err = ngx.re.gmatch(subject,regex,options?)

环境:

基本都支持

配置示例:

        location / {
           content_by_lua_block {
              local ngx = require "ngx";
              -- 参数i表示忽略大小写
              local m_table, err = ngx.re.gmatch(ngx.var.uri, "([0-9]+)(aaa)", "i");
              if not m_table then
                  ngx.log(ngx.ERR,  err)
                  return
              end
              while true do
                 local m, err = m_table()
                 if err then
                    ngx.log(ngx.ERR,  err)
                    return
                 end
                 if not m then
                      break
                 end
                 ngx.say(m[0])
                 ngx.say(m[1])
              end

            }
        }
#请求及返回
curl  'http://127.0.0.1/test/a123aaa/b456AAA/c'
123aaa
123
456AAA
456

更高效的匹配及捕获

ngx.re.match和ngx.re.gmatch在使用过程中都会生成Lua table,如果只需要确认正则表达式是否可以匹配成功,可以使用如下指令

ngx.re.find

作用:

与ngx.re.match类似,但只返回匹配结果的开始位置索引和结束位置索引。因为ngx.re.find不会创建table来存储数据,所以性能上比ngx.re.match和ngx.re.gmatch要好很多。此时,如果需要捕获匹配到的数据,可以使用Lua的函数string.sub。

语法:

from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)

环境:

基本都支持

配置示例:

        location / {
           content_by_lua_block {
              local ngx = require "ngx";
              local uri = ngx.var.uri
              -- 使用o、j两个参数进行匹配,以提升性能
              local find_begin,find_end,err = ngx.re.find(uri, "([0-9]+)(aaa)","oj");
              if find_begin then
                  ngx.say('begin: ',find_begin)
                  ngx.say('end: ',find_end)
                  -- 利用Lua的string.sub函数来获取数据
                  ngx.say('find it: ' ,string.sub(uri, find_begin,find_end))
                  return
              end
            }
        }
#请求及返回
curl  'http://127.0.0.1/test/a123aaa/b456AAA/c'
begin: 8
end: 13
find it: 123aaa

小结

options

ngx.re.match和ngx.re.gmatch都支持options参数,用来控制匹配的执行方式,常用参数列表如下:

参数说明
i忽略大小写
o仅编译一次,启动worker进程级别的正则表达式进行缓存
m多行模式(类似于perl的/m修饰符
j启用pcre jit编译,要求pcre版本在8.21以上,且必须在Nginx编译时指定--enable-jit参数。为了使性能更佳,此参数应与参数o一起使用

ctx

上述三个指令都支持ctx参数,说明如下:

  • ctx是Lua table类型的,是可选的第四个参数,但若用到第5个参数nth,那么,此位置需要用nil作为占位符
  • 但ctx有值(键是pos,如pos=1)时,ngx.re.find将从pos位置开始进行匹配(位置的下标从1开始)
  • 无论ctx表中是否有值,ngx.re.find都会在正则表达式匹配成功后,将ctx值设置为所匹配字符串之后的位置;若匹配失败,ctx表将保持原有的状态

nth

nth是ngx.re.find的第5个参数,是在Lua-Nginx-Module 0.9.3版本之后新增加的参数,它的作用和ngx.re.match中的m[1]、m[2]类似。当nth等于1时,获取的结果等同于ngx.re.match中的m[1],示例如下:

        location / {
           content_by_lua_block {
              local ngx = require "ngx";
              local uri = ngx.var.uri

              --从uri位置为10的地方开始进行匹配,下标默认从1开始,只匹配nth是1的数据,即([0-9]+)的值
              local ctx = { pos = 10 }
              local find_begin,find_end,err = ngx.re.find(uri, "([0-9]+)(aaa)","oji",ctx,1);
              if find_begin then
                  ngx.say('begin: ',find_begin)
                  ngx.say('end: ',find_end)
                  ngx.say('find it: ' ,string.sub(uri, find_begin,find_end))
                  return
              end
            }
        }
#请求及返回
#因为ctx的位置是10,所以uri前面的“/test/a12”这9个字符被忽略了,匹配到的就只有3aaa,又因为nth为1,所以捕获到的值是3
curl http://127.0.0.1/test/a123aaa/b456AAAa/c
begin: 10
end: 10
find it: 3

替换数据

ngx.re.sub

作用:

若subject中含有参数regex的值,则将之替换为参数replace的值。options为可选参数。替换后的内容将赋值给newstr,n表示匹配到的次数。此命令只匹配一次。

语法:

newstr, n, err = ngx.re.sub(subject, regex, replace, options?)

环境:

基本都支持

配置示例:

        location / {
            content_by_lua_block {
                local ngx = require "ngx";
                local uri = ngx.var.uri
                local n_str, n, err = ngx.re.sub(uri,"([0-9]+)", 'zzzz')
                if n_str then
                    ngx.say(uri)
                    ngx.say(n_str)
                    ngx.say(n)
                else
                    ngx.log(ngx.ERR, "error: ", err)
                    return
                end
            }
        }
#请求及返回
curl http://127.0.0.1/test/a123aaa/b456AAAa/c
/test/a123aaa/b456AAAa/c
/test/azzzzaaa/b456AAAa/c
1

ngx.re.gsub

  • 作用:和ngx.re.sub一样,不同之处在于此指令可以替换所有匹配到的结果
  • 语法:newstr, n, err = ngx.re.gsub(subject, regex, replace, options?)
  • 环境:基本都支持

转义符号

正则表达式包括\d、\s、\w 等匹配方式,但在Ngx_Lua中使用时,反斜线 \ 会被Lua处理掉,从而导致匹配异常。所以需要对带有 \ 的字符进行转义,转义方式和其他语言有些区别,转义后的格式为\\\\d、\\\\s、\\\\w,因为反斜线会被Nginx和Lua各处理一次,所以\\\\会先变成\\,再变成\。
还可以通过[[]]的方式将正则表达式直接传入匹配指令中,以避免被转义,如下所示:

local find_regex = [[\d+]]
local m = ngx.re.match("xxx,43", find_regex)
ngx.say(m[0])   --输出 43

通常建议使用[[]]的方式。

子请求

Nginx一般分两种请求类型,一种是主请求;一种是子请求,即subrequest。主请求从Nginx的外部进行访问,而子请求则在Nginx内部进行访问。子请求不是HTTP请求,不会增加网络开销。它的主要作用是将一个主请求分解为多个子请求,用子请求去访问指定的location服务,最后汇总到一起完成主请求的任务。
Nginx的请求方法有很多种,如GET、POST、 PUT 、DELETE等,同样,子请求也支持这些请求方法。

请求方法

Lua API中提供了多个指令来实现子请求,Lua API常见的请求方法如下:

Nginx的请求方法Lua API中的请求方法说明
GETngx.HTTP_GETGET请求,主要用于获取数据
HEADngx.HTTP_HEAD和GET请求一样,但是没有响应体
PUTngx.HTTP_PUT向服务器传送数据并替换指定文档的内容
DELETEngx.HTTP_DELETE删除指定的资源
POSTngx.HTTP_POST将请求体提交到服务器上
OPTIONSngx.HTTP_OPTIONS允许客户端请求查看服务器的性能

单一子请求

ngx.location.capture

作用:

发出同步但不阻塞Nginx的子请求。可以用来访问指定的location,但不支持访问命名location(如@abc就是命名location)。location中可以有静态文件,如ngx_proxy,ngx_fastcig、ngxmemc、ngx_postgres、ngx_drizzle,甚至Ngx_Lua和Nginx的c模块。
子请求总是会把整个请求体缓存到内存中,如果要处理一个较大的子请求,使用cosokets是最好的选择(cosockets是与ngx.socket.tcp有关的API)。
子请求一般在内部进行访问,建议在被子请求访问的location上配置internal,即只允许内部访问。
子请求返回的结果res,它是一个table类型的数据,包含4个元素:res.status、res.header、res.body和res.truncated。res的元素名及其用途见下表:

元素名用途
res.status存储子请求的HTTP返回状态
res.header存储子请求返回的所有响应头,table类型
res.body保存子请求的响应体,数据有可能会被截断,table类型
res.truncated记录请求是否被截断,一般是由于客户端过早断开连接或子请求超时导致的,布尔类型

语法:

res = ngx.location.capture(uri,options?)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

配置示例:

        location = /main {
            set $m 'hello';
            content_by_lua_block {
                local ngx = require "ngx";
                --发起子请求,访问/test,请求方式是GET,请求体是test nginx,子请求的URL参数是a=1&b=2,并使用copy_all_vars将主请求的Nginx变量($m)全部复制到子>请求中
                local res = ngx.location.capture(
                '/test',  { method = ngx.HTTP_GET , body = 'test nginx',
                args = { a = 1, b = 2 },copy_all_vars = true }
                )
                ngx.say(res.status)
                ngx.say(res.body)
                ngx.say(type(res.header))
                ngx.say(type(res.truncated))
            }
        }
        location = /test {
        #只能在Nginx内部进行访问 
            internal;
            content_by_lua_block {
                local ngx = require "ngx";
                --获取请求体,在这里是获取主请求的请求体
                ngx.req.read_body()
                local body_args = ngx.req.get_body_data()
                --输出请求的参数,获取主请求的m变量的值,并与world进行字符串拼接
                ngx.print('request_body: ' ,body_args, ' capture_args: ', ngx.var.args, '---  copy_all_vars : ', ngx.var.m .. 'world! ')
            }
        }
#请求及返回
#第二个参数options是可选的 也可以如上带有很多参数
[root@Test22 conf]# curl http://127.0.0.1/main
200
request_body: test nginx capture_args: a=1&b=2---  copy_all_vars : helloworld! 
table
boolean

结论

  • ngx.location.capture的第二个参数options可以包含多个table类型的参数
  • 子请求的请求方法由参数method进行配置
  • 子请求通过参数body可以定义新的请求体。
  • 子请求通过参数args配置新的URL的args,args是table类型的。
  • copy_all_vars = true的作用是将著请求的全部变量传递给子请求,如果没有此配置就不会传递过去
  • 从子请求的返回结果中可以获取状态码、响应体、响应头、结果是否被截断

并发子请求

ngx.location.capture_multi

作用:

与ngx.location.capture相似,但可以支持多个子请求并行访问,并按照配置顺序返回数据。返回的数据也是多个结果集。

语法:

res1, res2, ... = ngx.location.capture_multi({ {uri,options?}, {uri, options?}, ...})

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

配置示例:

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location = /main {
        set $m 'hello';
        set $mm '';
        content_by_lua_block {
            local ngx = require "ngx";
            --发送两个子请求,会返回两个结果集
            local res1, res2 = ngx.location.capture_multi{
                { "/test1?a=1&b=2" },
                { "/test2",{ method = ngx.HTTP_POST},body = "test nginx" },
            }
            --返回的body的方式和ngx.location.capture一样
            if res1.status == ngx.HTTP_OK then
                ngx.say(res1.body)
            end

            if res2.status == ngx.HTTP_OK then
                ngx.say(res2.body)
            end
        }
    }
    location = /test1 {
         echo 'test1';
    }
    location = /test2 {
         echo 'test2';
    }

}

#请求及返回
curl   'http://testnginx.com/main'
test1
test2

获取Nginx的环境变量

通过Lua API可以获取Nginx的环境变量,用来提升某些业务处理流程,例如有些定时任务只需要在一个worker进程上执行,不需要执行多次,因此可以获取环境变量中worker的ID,在指定ID上执行任务即可;或者获取Nginx的worker进程是否正在shutdown,以决定是否对数据进行备份操作。

ngx.config.subsystem

作用:

获取当前请求的Nginx子环境(http或stream)。如果在http模块下,就返回字符串http,如果在stream模块下,则返回字符串stream。

语法:

subsystem = ngx.config.subsystem

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • ngx.timer.*
  • init_by_lua*
  • init_worker_by_lua*

ngx.config.debug

作用:

判断请求是否在Debug模式下执行。例如,但需要在Debug模式下打印某些数据或执行某些代码时,可以通过这个判断,区分线下测试环境和线上环境。

语法: debug = ngx.config.debug

环境:

同上

ngx.config.prefix

作用:

获取编译Nginx时--prefix=的路径,如果启动Nginx时使用了参数-p,就以参数-p的值为准

语法:

prefix = nginx.config.prefix()

环境:

同上

ngx.config.nginx_version

作用:

获取Nginx的版本号

语法

ver = ngx.config.nginx_version

环境:

同上

ngx.config.nginx_configure

作用:

获取编译Nginx时./configure命令的信息,返回的是一个字符串

语法:

str = ngx.config.nginx_configure()

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • ngx.timer.*
  • init_by_lua*

ngx.config.ngx_lua_version

作用:

获取Ngx_Lua模块的版本号。可以用来检查Ngx_Lua的版本。例如,当开发某个功能需要使用指定版本的是否,可以在代码中进行判断,如果不是指定的版本,可以输出警告信息。

语法:

ver = ngx.config.ngx_lua_verison

环境:

同上

ngx.worker.exiting

作用:

判断Nginx的worker进程是否退出

语法:

exiting = ngx.worker.exiting

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • ngx.timer.*
  • init_by_lua*
  • init_worker_by_lua*

ngx.worker.id

作用:

获取当前执行的worker进程的ID。worker进程的ID从0开始,依次递增,最大值是worker总数的值减1.

语法:

count = ngx.worker.id()

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • ngx.timer.*
  • init_by_lua*

ngx.worker.count

作用:

获取当前Nginx worker的进程的数量,即Nginx配置中worker_processes的值

语法:

count = ngx.worker.count()

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*
  • log_by_lua*
  • ngx.timer.*
  • init_by_lua*
  • init_worker_by_lua*

定时任务

可以使用Nginx执行定时任务,例如,定期获取Mysql数据库中的数据并存放到共享内存中,定时监听某个配置是否发生了改变(如果发生改变就重载Nginx),定时将日志远程传输到集中存储上等。
在Lua_Nginx_Module 0.10.9版本之前,常使用ngx.timer.at来启动定时任务,0.10.9版本新增了ngx.timer.every,启动定时任务更加方便。

创建定时任务

ngx.timer.every

作用:

创建一个定时任务,delay指延迟时间,表示每隔多久执行一次,支持配置0.001s,不支持配置0s;callback是需要执行的Lua行数,当Nginx退出时,定时任务会被关闭。

语法:

hd1, err = ngx.timer.every(delay,callback,user_arg1,user_arg2,...)

环境:

基本都支持

配置示例:

init_worker_by_lua_block {
    local delay = 3;
    local ngx = require "ngx";
    local check
    check = function(premature)
        if not premature then
         --输出当前worker进程的PID和ID。
             ngx.log(ngx.ERR, ' ngx.worker.pid: ',ngx.worker.pid(),' ngx.worker.id: ',ngx.worker.id(),"------test nginx !!!")
        end
    end
    --每隔3s执行一次check函数
    local ok, err = ngx.timer.every(delay, check)
    if not ok then
         ngx.log(ngx.ERR, "failed to create timer: ", err)
         return
    end
}

#重载Nginx配置后,定时任务会在启动worker进程时被触发执行,可以通过日志进行查看
#每个worker进程都在执行输出操作
#都是3s执行一次
#如果没有从Nginx外部进行访问的请求,定时任务会继续执行下去。
#参数user_arg1、user_arg2用来给定时任务传递参数,配置如下:
init_worker_by_lua_block {
    local delay = 3;
    local ngx = require "ngx";
    local check
    --新增一个u_arg1参数,是对下面定时任务的'test nginx'进行填充
    check = function(premature,u_arg1)
        if not premature then
            ngx.log(ngx.ERR, ' ngx.worker.pid: ',ngx.worker.pid(),' ngx.worker.id: ',ngx.worker.id(),'------', u_arg1)
        end
    end

    --新增参数'test nginx' 
    local ok, err = ngx.timer.every(delay, check, 'test nginx')
    if not ok then
        ngx.log(ngx.ERR, "failed to create timer: ", err)
        return
    end
}

常用的指令

请求重定向

ngx.redirect

作用:

发出一个HTTP状态码为301或302的重定向请求到指定的URI。

语法:

ngx.redirect(uri,status?)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

参数status的可选值有301、302、303、307和308,默认值时302,下面时ngx.redirect重定向和rewrite重定向的对比:

location / {
    # 等同于 rewrite ^/ http://testnginx.com/test? redirect;
    rewrite_by_lua_block {
        return ngx.redirect("/test")
    }
}

上述配置使用了默认状态码302.如果在跳转过程中需要保留请求的参数,可以使用如下配置:

location / {
    #  等同于 rewrite ^/ http://testnginx.com/test permanent;
    rewrite_by_lua_block {
        local ngx = require "ngx";
        return ngx.redirect("/test?" ..  ngx.var.args  ,301)
    }
}

#也可以自定义参数如下:
return ngx.redirect("/test?test=1&a=2" ,301)
#支持跳转到其他域名如下:
return ngx.redirect("http://abc.testnginx.com",301)
#跳转时都需要加return指令,其作用是为了强跳转操作,官方推荐这种方式

ngx.req.set_uri

作用:

用参数uri来重写当前的URL,和Nginx的rewrite内部重定向功能相似。

语法:

ngx.req.set_uri(uri,jump?)

环境:

  • set_by_lua*
  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • body_filter_by_lua*

例如,rewrite的指令rewrite ^ /test last; 与ngx.req.set_uri(“/test”, true)功能相似,而rewrite ^ /test break; 与ngx.req.set_uri(“/foo”, false)功能相似。
如果需要在跳转过程中修改参数,可以使用ngx.req.set_uri_args来完成新的参数配置,操作如下:

ngx.req.set_uri_args("a=1&b=2&c=3")
ngx.req.set_uri("/test", true)

ngx.exec

作用:

使用uri、args参数来完成内部重定向,类似于echo-nginx-module的echo_exec指令。

语法:

ngx.exec(uri.args?)

环境:

  • rewrite_by_lua
  • access_by_lua*
  • content_by_lua*

配置示例:

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
        content_by_lua_block {
            return ngx.exec('/test');
        }
    }
    location /test {
        content_by_lua_block {
            ngx.say(ngx.var.args);
        }        
    }
}
常用配置示例
  • 保留之前的参数:ngx.exec('/test',ngx.var.args)
  • 保留之前的参数,并新增参数:ngx.exec('/test',ngx.var.args .. 'd=4')
  • 去掉之前的参数,并新增参数:ngx.exec('/test','d=4')

ngx_exec是一个内部重定向指令,不涉及外部的HTTP请求。在使用过程中推荐采用return ngx.exec(...)的方式。

日志记录

ngx.log

作用:

根据log_level的等级,将内容记录到error.log的日志文件中

语法:

ngx.log(log_level, ...)

环境:

基本都支持
log_level的级别及说明如下:

级别说明
ngx.STDERR标准输出
ngx.EMERG紧急错误,需要引起重视
ngx.ALERT警告,需要引起重视
ngx.CRIT严重问题,需要引起重视
ngx.ERR错误,需要引起重视
ngx.WARN告警,一般可以忽略
ngx.NOTICE通知性信息
ngx.INFO各种打印信息,一般不会用于线上环境
ngx.DEBUG调试日志,开发环境使用

配置示例

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
        content_by_lua_block {
            ngx.say("test ")
            ngx.say("nginx ")
            ngx.log(ngx.ALERT, 'Log Test Nginx')
            ngx.log(ngx.STDERR, 'Log Test Nginx')
            ngx.log(ngx.EMERG, 'Log Test Nginx')
            ngx.log(ngx.ALERT, 'Log Test Nginx')
            ngx.log(ngx.CRIT, 'Log Test Nginx')
            ngx.log(ngx.ERR, 'Log Test Nginx')
            ngx.log(ngx.WARN, 'Log Test Nginx')
            ngx.log(ngx.NOTICE, 'Log Test Nginx')
            ngx.log(ngx.INFO, 'Log Test Nginx')
            ngx.log(ngx.DEBUG, 'Log Test Nginx')
        }
    }
}

通过观察日志可以发现,并没有输出所有级别的日志,这是因为Nginx中error.log的日志级别会影响到Lua日志的级别。如果将error.log的级别修改如下:

error_log /usr/local/nginx_1.12.2/logs/error.log info;

这样Lua的日志就可以打印到info级别了,如果需要debug级别的日志,重新编译Nginx并开启Debug模式即可。
ngx.log支持多个字符串合并输出,字符串之间以逗号分隔,如下:

ngx.log(ngx.ERR, ‘Log Test Nginx’, ‘a’, ‘b’, ‘c’)

ngx.log单条日志可输出的最大字节受Nginx的限制,默认是2048字节,即2KB.
Lua提供了print命令来简化info级别日志的输出。下方两个语句的作用一样:

print("Log Test Nginx ")
ngx.log(ngx.INFO, ‘Log Test Nginx’)

请求中断处理

在Lua中可以对请求进行中断处理,有两种情况:

  • 中断整个请求,则请求不在继续执行,直接返回给客户端
  • 中断当前的执行阶段,请求会继续执行下一个阶段,并继续相应请求。

ngx.exit

作用:

参数status的值是HTTP的状态码。当参数status>=200时,请求会被中断,并将status的值作为状态码返回给Nginx;当参数status=0时,请求会中断当前的执行阶段,继续执行下一个阶段(前提是还有下一个阶段)

语法:

ngx.exit(status)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • header_filter_by_lua*
  • ngx.timer.*
  • balancer_by_lua*
  • ssl_certificate_by_lua*
  • ssl_session_fetch_by_lua*
  • ssl_session_store_by_lua*

配置示例:

#HTTP状态码为0
server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
        set $a  '0';
        rewrite_by_lua_block {
             ngx.var.a = '1';
             --等价于 ngx.exit(0), 0即HTTP状态码
             ngx.exit(ngx.OK)
        }
        echo $a;  #执行结果等于1 
    }
}
#ngx.exit(ngx.OK)可以让Nginx退出当前的rewrite_by_lua_block阶段,继续执行下面的阶段,如上方代码中的echo
#如果想终端当前的请求,不再继续后面的执行阶段,可以设置两种退出状态
#设置状态码大于200且小于300,表示成功退出当前请求。
#设置状态码大于或等于500,或其它异常的状态,表示失败退出当前请求。
#HTTP状态码为非0
location / {
    set $a  '0';           
    rewrite_by_lua_block {
         ngx.var.a = '1';
         ngx.exit(200)  --也可以换成500,数字代表状态码的值
    }
         echo $a;  #没有执行到这一句
}
#因为使用了200状态码,所以请求再ngx.exit处被中断后推出了,所以无法执行echo输出的命令。为了强调退出操作,可以在此命令前加上return,如下所示
return ngx.exit(ngx.OK)

提升开发和测试效率

在使用Lua进行开发的过程中,可能需要频繁修改Lua代码,默认情况都是需要重启Nginx才能使修改生效,使用lua_code_cache指令可以对其进行重新配置,以此提升开发效率。

lua_code_cache

作用:

打开或关闭_by_lua_file指定的Lua代码及Lua模块的缓存。如果设置为off,则代码缓存会被关闭,在_by_lua_file修改的代码不需要再重载Nginx配置就可以生效。

语法:

**lua_code_cache on | off

默认:

**lua_code_cache on

环境:

  • **http
  • server
  • location
  • location if

注意:此指令只适用于_by_lua_file中的代码,不适用于_by_lua_block和*_by_lua中的代码,因为这两种指令的代码都是内嵌到Nginx配置文件中的,必须要通过reload配置文件才可以使修改生效。把lua_code_cache设置为on只适合在开发环境中使用,不适合在线上环境中使用

ngx.eof

作用:

显示指定响应的输出结束,会告知客户端主动关闭连接,并在服务器端继续执行剩下的操作。

语法:

of,err = ngx.eof()

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*

配置示例:

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
        set $a '0';
        content_by_lua_block {
            ngx.var.a = '1';
            --告知客户端主动断开连接
            ngx.eof()
            ngx.sleep(3);  --让请求休眠3s。
            ngx.log(ngx.ERR, 'Test Nginx---',ngx.var.a)
        }
    }
}
#执行curl -i http://testnginx.com/后,请求会立刻响应一个200的状态,表示响应内容已返回,但请求的后续操作仍在服务器端继续执行,3s后会将日志写入error.log
#注意:执行完ngx.eof后,如果下一步是发送子请求的指令,那么,子请求会被意外中止,导致无法完成子请求的响应,这是受Nginx中proxy_ignore_client_abort默认值的影响,将proxy_ignore_client_abort设置为on,就可以在执行ngx.eof后继续响应子请求了。

ngx.sleep

作用:

通过ngx.sleep命令可以在不阻塞Nginx worker进程的情况下,让当前请求休眠指定时间(seconds),最小值为0.001s。

语法:

ngx.sleep(seconds)

环境:

  • rewrite_by_lua*
  • access_by_lua*
  • content_by_lua*
  • ngx.timer*
  • ssl_certificate_by_lua*
  • ssl_session_fetch_by_lua*

配置示例:

location / {
    content_by_lua_block {
        --5秒后输出ok。
        ngx.sleep(5);
        ngx.say('ok')
    }
}

获取系统时间

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
       content_by_lua_block {
            ngx.say('ngx.today: ',ngx.today())
            ngx.say('ngx.time: ',ngx.time())
            ngx.say('ngx.now: ',ngx.now())
            ngx.say('ngx.localtime: ',ngx.localtime())
            ngx.say('ngx.utctime: ',ngx.utctime())
            ngx.say('ngx.cookie_time: ',ngx.cookie_time(1528721405))
            ngx.say('ngx.parse_http_time: ',ngx.parse_http_time('Mon, 11-Jun-18 12:50:05 GMT'))
            ngx.say('ngx.update_time: ',ngx.update_time())
       }
    }
}

编码及解码

server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location / {
       content_by_lua_block {
            local ngx = require "ngx";
            --对URI进行编码
            ngx.say(ngx.var.uri, '---ngx.escape_uri---',ngx.escape_uri(ngx.var.uri))

            --对已经编码过的URI进行解码
            ngx.say('%2Ftest%2Fa%2Fb%2Fc', '---ngx.unescape_uri---',ngx.unescape_uri('%2Ftest%2Fa%2Fb%2Fc'))

            --将Lua的table类型数据编码成字符串
            local args_table_new =  ngx.encode_args({a = 1, b = 2, c = 3 })
            ngx.say('{a = 1, b = 2, c = 3 }', '---ngx.encode_args---' ,args_table_new)

            --对URI编码的字符串进行解码,解码成table类型的数据
            local args = ngx.var.args
            local args_table = ngx.decode_args(args)
            ngx.say(args, '---ngx.decode_args---', 'a=',args_table["a"])  --获取table中的a的值
            --对URI进行MD5编码,返回十六进制数据
            ngx.say(ngx.var.uri, '---ngx.md5---',ngx.md5(ngx.var.uri))
            --对URI进行MD5编码,返回二进制数据   
            ngx.say(ngx.var.uri, '---ngx.md5_bin---',ngx.md5_bin(ngx.var.uri))
       }
    }
}
#请求及返回
curl  'http://testnginx.com/test/a/b/c?a=1&b=2&c=3'
/test/a/b/c---ngx.escape_uri---%2Ftest%2Fa%2Fb%2Fc
%2Ftest%2Fa%2Fb%2Fc---ngx.unescape_uri---/test/a/b/c
{a = 1, b = 2, c = 3 }---ngx.encode_args---b=2&a=1&c=3
a=1&b=2&c=3---ngx.decode_args---a=1
/test/a/b/c---ngx.md5---dfa371a9a8f52c9aadd016bda535fa43
/test/a/b/c---ngx.md5_bin---ߣq©¨

星霜荏苒 居诸不息