kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

Lua-Nginx-Module常用指令

https://blog.51cto.com/xikder/2331336

https://blog.51cto.com/xikder/2331368

https://blog.51cto.com/xikder/2331504

本章将会讲解基于LuaJIT的Lua-Nginx-Module,它作为Nginx的第三方开源软件,拥有十分丰富的功能,可以轻松完成高并发的业务需求。

注意:本书使用的Lua-Nginx-Module版本是0.10.13。Nginx API for Lua将被简称为Lua API,而Lua-Nginx-Module则被简称为Ngx_lua。后面章节中涉及到的Lua API大部分是包含参数的,如果参数以?结尾,代表这个参数是可选的,如在指令ngx.req.get_headers (max_headers?, raw?)中,max_headers和raw是可选的。

一、Nginx和OpenResty

首先,来认识一下OpenResty,它是一个基于Nginx和Lua开发的高性能的Web平台,包含大量成熟的第三方库,可快速搭建出高性能的Web服务器,支持常用的反向代理、网关系统、Web应用等。

如果在Nginx上使用Ngx_lua,需要先进行编译;而OpenResty已经包含此模块,不需要再进行编译了。读者可以自由选择使用Nginx或OpenResty来搭建服务,如果无法抉择,可参考如下场景。

使用Nginx编译Ngx_Lua的场景

HTTP代理服务器:复杂度较小,只需部分组件即可,且代理服务器一般由运维人员进行维护。使用Nginx的稳定版进行编译,在性能方面会更有保障,而OpenResty是Nginx的主线版,可能会不定期更新。

OpenResty的使用场景

API服务:业务需求多,需要大量组件。
网关系统:需要大量组件和指令来实现动态组件功能。
Web应用服务器:业务服务、页面服务等,如详情页业务的开发。
使用Nginx编写的Lua代码都可以直接迁移到OpenResty上;反之却不一定可行,毕竟OpenResty的组件更多。

二、安装Ngx_lua

请先安装LuaJIT 2.1.0-beta3(详见第6.2节)并需要编译ngx_devel_kit模块。 下面是在Nginx上的安装方式(OpenResty自带此模块,不必安装编译):

1
2
3
4
5
6
7
8
9
10
11
# wget 'http://nginx.org/download/nginx-1.12.2.tar.gz'
# git clone https://github.com/simplresty/ngx_devel_kit.git
# git clone https://github.com/openresty/lua-nginx-module.git
# tar -xzvf nginx-1.12.2.tar.gz
# cd nginx-1.12.2/
# ./configure --prefix=/usr/local/nginx_1.12.2 \
     --add-module=../ngx_devel_kit \
     --add-module=../lua-nginx-module
     --with-ld-opt="-Wl,-rpath,$LUAJIT_LIB"

# make && make install

并不是每个Nginx版本都支持最新的Ngx_lua,目前已知支持最新Ngx_lua的Nginx版本如下:

1
2
3
4
5
6
7
8
1.13.x (last tested: 1.13.6)
1.12.x
1.11.x (last tested: 1.11.2)
1.10.x
1.9.x (last tested: 1.9.15)
1.8.x
1.7.x (last tested: 1.7.10)
1.6.x

如需获取最新版本的支持动态,请参考https://github.com/openresty/lua-nginx-module# nginx-compatibility。

三、牢记context标识

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

1
2
3
4
ngx.var.VARIABLE
语法:ngx.var.VAR_NAME
context(配置环境):set_by_lua*,rewrite_by_lua*,access_by_lua*,content_by_lua*,header_ filter_by_lua*,body_filter_by_lua*,log_by_lua*
context即配置环境,第一次接触Ngx_lua的读者看到这样的配置环境可能会觉得难以理解,因为这还涉及到Ngx_Lua的执行阶段(后面会有介绍)。

四、Hello world

首先,还是来一条经典语句“Hello, world”,在Nginx配置中加入一个server:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
    listen       80;
    server_name  testnginx.com;
    charset koi8-r;
    location = /test {
     #设置文件使用的默认MIME-type,将会增加一个Content-Type:text/plain的响应头
     default_type 'text/plain';    
     -- content_by_lua_block执行阶段
     content_by_lua_block {    
         ngx.say('Hello,world!')
     }
    }
}

访问这个server,输出如下:

1
2
#  curl -I http://testnginx.com/test
Hello,world!    

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

代码中用到了content_by_lua_block这个指令块,它的主要作用是在HTTP的内容处理阶段生成数据,详见第8.6节。

五、避免I/O阻塞

当Nginx和Lua进行读取磁盘操作时会对Nginx的事件循环造成阻塞,所以在请求中应尽量避免操作磁盘,特别是当文件较大时。

如果Lua使用网络I/O,为了避免出现阻塞的情况,请使用基于Lua API开发的指令,并使用子请求(将在7.13节介绍)来发送网络I/O和磁盘I/O。如果需要频繁读取磁盘,请分离磁盘I/O的任务和网络I/O的任务,避免它们相互影响。

六、定义模块搜索路径

在开发过程中,常常需要编写自定义的模块,或者引入第三方的Lua或C模块,通过下面的配置可以定义相关模块的路径以方便快速查找。

6.1 定义Lua模块的搜索路径

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

1
2
3
4
5
http {
    -- lua_package_path在配置中只能出现一次,使用下面的任何一个方法都可以
    lua_package_path "/usr/local/nginx_1.12.2/conf/lua_modules/?.lua;;";
    lua_package_path "conf/lua_modules/?.lua;;";
    lua_package_path "${prefix}conf/lua_modules/?.lua;;";

上述配置中的3种配置方式都指向同一个位置:

第1个是绝对路径;

第2个是相对路径,Nginx编译时用 –prefix=/usr/local/nginx_1.12.2;

第3个也是相对路径,Nginx编译时用 –prefix=/usr/local/nginx_1.12.2 或-p PATH 指定的位置。

第1个配置方式的缺点在于写出了具体文件搜索路径,迁移代码时会比较麻烦。第2个配置方式的缺点在于无法和-p PATH一起使用,如果-p换了位置就会导致这个配置无效。对于第3个配置方式,如果-p的位置换了,${prefix}的值会跟着变换,使用起来比较灵活。所以建议使用第3种配置方式来配置。

lua_package_path可以支持设置多个搜索路径,多个搜索路径之间使用分号分隔就可以了,如下:

1
lua_package_path "${prefix}conf/lua_modules/?.lua;/opt/lua/?.lua;;";

注意:上述配置中搜索路径的最后出现了;;两个半角分号,代表的是LuaJIT安装时的原始搜索路径,如果在前面的搜索路径里面无法搜索到需要的模块,就会依次搜索后面的路径。

6.2 定义C模块的搜索路径

lua_package_cpath:用来设置C模块的搜索路径,并配置在http阶段。使用方式和lua_package_path一样,如下:

1
lua_package_cpath "${prefix}conf/c_md/?.so;/opt/c/?.so;;";

七、读写Nginx的内置变量

如果需要读取Nginx的内置变量可以使用ngx.var.VARIABLE。

1
2
3
语法:ngx.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 set的变量、URL参数,甚至Nginx通过正则表达式捕获的$1、$2等值(获取方式是ngx.var[1]、ngx.var[2],依此类推)。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
    listen       80;
    server_name  testnginx.com;
    location ~ ^/([a-z]+)/var.html {
        set $a '';
        set $b '';
        set $c '';
        set $d '';
        rewrite_by_lua_block {
           local ngx = require "ngx"
           --将1赋值给变量a
           ngx.var.a = '1'
           --获取HTTP请求头中user_agent的值并赋值给变量b
           ngx.var.b = ngx.var.http_user_agent
           --获取参数test的值赋值给变量c
           ngx.var.c = ngx.var.arg_test
           --获取location中正则表达式捕获的$1的值并赋值给变量d
           ngx.var.d = ngx.var[1]
        }
        echo $a;
        echo $b;
        echo $c;
        echo $d;
    }

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
# curl -i 'http://testnginx.com/nginx/var.html?test=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Thu, 07 Jun 2018 07:22:32 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
1
curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
12132
nginx

如果是未定义的Nginx变量,是无法直接在Lua中进行读取的。而且有些变量只能读取,无法进行修改,如querystring、query_string、querys​tring、arg_PARAMETER和$http_NAME。

八、控制请求头

在4.1节中讲了Nginx中控制请求头的指令,在Lua API中也有类似的指令。

8.1 添加请求头

1
2
3
4
5
指令:ngx.req.set_header

语法: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*

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

示例:设置一个名为Test_Ngx_Ver,值为1.12.2的请求头:

1
2
3
ngx.req.set_header("Test_Ngx_Ver", "1.12.2")
ngx.req.set_header支持给同一个请求头设置多个值,用数组的方式添加:
ngx.req.set_header("Test", {"1", "2"})

多个值的输出结果:

1
2
Test: 1
Test: 2

8.2 清除请求头

1
2
3
4
5
指令: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*

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

示例:

1
2
3
4
5
ngx.req.clear_header(“Test_Ngx_Ver”)

还有一种清除请求头的方式:

ngx.req.set_header(“Test_Ngx_Ver”, nil)

8.3 获取请求头

1
2
3
4
5
指令:ngx.req.get_headers

语法: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*

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
    listen       80;
    server_name  testnginx.com;

     location  / {

        content_by_lua_block {
           local ngx = require "ngx";
           local h = ngx.req.get_headers()
           for k, v in pairs(h) do
               ngx.say('Header name: ',k, ' value:',v)
           end
           --因为是table,所以可以使用下面的方式读取单个响应头的值
           ngx.say(h["host"])
        }
    }
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
# curl -i 'http://testnginx.com/test?=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 07:46:38 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

Header name:host value: testnginx.com
Header name:accept value: */*
Header name:user-agent value: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
testnginx.com

九、控制响应头

HTTP响应头需要配置很多重要的信息,例如添加CDN缓存时间、操作set-cookie、标记业务数据类型等。利用Lua的API可以轻松完成这些配置,并且它有丰富的模块可供选择。

9.1 获取响应头

1
2
3
4
5
6
**ngx.resp.get_headers
**
语法: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

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
    listen       80;
    server_name  testnginx.com;
     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    
           --因为是table,所以可以使用下面的方式读取单个响应头的值
           ngx.say(h["content-type"])   
        }
    }
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
# curl -i 'ttp://testnginx.com/test?=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 07:36:35 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

Header name:content-type value: application/octet-stream
Header name:connection value: keep-alive
application/octet-stream

9.2 修改响应头

ngx.header.HEADER

1
2
3
4
5
语法: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*

含义:对响应头进行修改、清除、添加等操作。

此API在输出响应头时,默认会将下划线替换成中横线,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
    listen       80;
    server_name  testnginx.com;

    location / {
        content_by_lua_block {
            local ngx = require "ngx"
            ngx.header.content_type = 'text/plain';
            --在代码里面是下划线,输出时就变成中横线了
            ngx.header.Test_Nginx = 'Lua';
            --下面的代码等同于ngx.header.A_Ver = 'aaa' 
            ngx.header["A_Ver"] = 'aaa';
            --读取响应头,并赋值给变量a
            local a = ngx.header.Test_Nginx;
        }
    }
}

执行代码,下划线都被替换成了中横线,如下所示:

1
2
3
4
5
6
7
8
9
10
# curl -i 'http://testnginx.com/?test=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 03:18:16 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
test-type: ttt
Test-Nginx: Lua
A-Ver: aaa

有时需要在一个响应头中存放多个值,例如,当访问/test 路径时,需要为set-cookie设置两个Cookie:

1
2
3
4
5
6
7
location = /test {
    content_by_lua_block {
       local ngx = require "ngx"
       --以逗号分隔两个Cookie
       ngx.header['Set-Cookie'] = {'test1=1; path=/test', 'test2=2; path=/test'}
    }
}

输出结果如下:

1
2
3
4
5
6
7
8
9
# curl -i 'http://testnginx.com/test?=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 03:21:59 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: test1=1; path=/test
Set-Cookie: test2=2; path=/test

9.3 清除响应头

如果需要清除一个响应头,将它赋值为nil即可,如下所示:

1
ngx.header["X-Test"] = nil;

十、读取请求体

$request_body 表示请求体被读取到内存中的数据,一般由proxy_pass、fastcgi_pass、uwsgi_pass和scgi_pass等指令进行处理。由于Nginx默认不读取请求体的数据,所以当Lua通过ngx.var.request_body的方式获取请求体时会发现数据为空。那么,该如何获得请求体的数据呢?下面将介绍几种可行的方式。

10.1 强制获取请求体

lua_need_request_body

1
2
3
4
5
语法:lua_need_request_body <on|off>

默认:off

配置环境:http,server,location,location if

含义:默认为off,即不读取请求体。如果设置为on,则表示强制读取请求体,此时,可以通过ngx.var.request_body来获取请求体的数据。但需要注意一种情况,requestbody存在于内存中,如果它的字节大小超过Nginx配置的clientbodybuffersize的值,Nginx就会把请求体存放到临时文件中,此时数据就不在内存中了,这会导致request_body存在于内存中,如果它的字节大小超过Nginx配置的client_body_buffer_size的值,Nginx就会把请求体存放到临时文件中,此时数据就不在内存中了,这会导致requestbody存在于内存中,如果它的字节大小超过Nginx配置的clientbodybuffersize的值,Nginx就会把请求体存放到临时文件中,此时数据就不在内存中了,这会导致request_body为空,所以需要设置client_body_buffer_size和client_max_body_size的值相同,避免出现这种情况。 这种配置方式不够灵活,Ngx_lua官网也不推荐使用此方法。下面将介绍一种更合适的方式去获取请求体的数据。

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

ngx.req.read_body

1
2
3
语法:ngx.req.read_body()

环境:rewrite_by_lua,access_by_lua,content_by_lua

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

1
2
3
4
5
ngx.req.get_body_data

语法:data = ngx.req.get_body_data()

配置环境:rewrite_by_lua,access_by_lua,content_by_lua,log_by_lua

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

1
2
3
4
5
ngx.req.get_post_args

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

1
2
3
4
5
ngx.req.get_body_file

语法:file_name = ngx.req.get_body_file()

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

10.3 使用场景示例

下面将对这些指令的使用方式和使用场景进行展示。

获取string类型的请求体

要获取string类型的请求体,可以使用如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
    listen       80;
    server_name  testnginx.com;        
    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
      }
    }

配置好后,重载Nginx配置(重载是指使用HUP信号或reload命令来重新加载配置),先用一个小于1KB的请求体(在Nginx配置中设置client_body_buffer_size为1k)执行请求,输出的是string字符串类型,如下所示:

1
2
3
4
5
6
7
8
9
# curl -i http://testnginx.com/ -d 'test=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 06 Jun 2018 11:03:35 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

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

获取table类型的请求体

要获取table类型的请求体,可以使用如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
server {
    listen       80;
    server_name  testnginx.com;

    location / {
      client_max_body_size 10k;
      client_body_buffer_size 1k;

      content_by_lua_block {
         --开启读取请求体模式
         ngx.req.read_body()
         -- 获取内存中的请求体,返回的结果是Lua的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参数有2个,c参数值为空,d参数连等号都没有。执行结果如下所示:

1
2
3
4
5
6
7
8
#  curl -i http://testnginx.com/ -d 'test=12132&a=2&b=c&dd=1&a=354&c=&d'

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

可以看到参数a的两个值并列显示,并以逗号分隔,参数c显示为空,参数d的结果为布尔值true。

获取临时文件中的请求体

如果使用一个大小在1KB~10KB之间的请求体,会发生什么呢?测试执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
# curl -i http://testnginx.com/ -d 'test=12132&a=2&b=kls204120312saldkk12 easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jesk20312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2je204120312saldkk12easjdiasasd3ej12i3j12io3jeioq2jeskls204120312saldkk12easjdiasasd3ej11'
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 06 Jun 2018 10:14:32 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

body is in file /usr/local/nginx_1.12.2/client_body_temp/0000000051

因为请求体数据的大小大于client_body_buffer_size的值,所以使用了临时文件存储请求体的数据。因此,需要先获取存放数据的临时文件名,再去读取请求体数据。

注意:读取临时文件中的请求体数据是不被推荐的,因此本书不对相关操作进行,有兴趣的读者可以使用io.open完成读取。

10.4 使用建议

在实际应用中,关于读取请求体,有如下几条建议。

1.尽量不要使用lua_need_request_body去获取请求体。

2.获取请求体前,必须执行ngx.req.read_body()。

3.获取请求体数据时尽量不要使用硬盘上的临时文件,否则会对性能有很大影响;务必要确认请求体数字字节大小的范围,并确保client_body_buffer_size和client_max_body_size的值一致,这样只需到内存中去读取数据就可以了。它既提高了Nginx自身的吞吐能力,也提升了Lua的读取性能。

4.如果请求体存放在临时文件中,Nginx会在处理完请求后自动清理临时文件。

5.对ngx.req.get_post_args参数的限制可以灵活控制,但不能关闭限制,以避免被恶意

十一、输出响应体

在Lua中,响应体的输出可以使用ngx.print 和 ngx.say 这两个指令完成。

11.1 异步发送响应体

ngx.print

1
2
3
语法:ok, err = ngx.print(…)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

示例:

1
2
3
4
5
6
7
8
9
10
11
location  / {
    
    content_by_lua_block { 
        local ngx = require "ngx";
        local h = ngx.req.get_headers()
        for k, v in pairs(h) do
            ngx.print('Header name: ',k, ' value: ',v)
        end
     
    }
}

执行结果如下(所有的数据会合并到一起进行发送):

1
2
3
4
5
6
7
8
9
# curl -i 'http://testnginx.com/test?=12132&a=2&b=c&dd'
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 08 Jun 2018 08:11:40 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

Header name:host value: testnginx.comHeader name:accept value: */*Header name:user-agent value: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.

ngx.say

1
2
3
语法:ok, err = ngx.say(…)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

含义:功能和ngx.print一样,只是输出结果多了1个回车符。

11.2 同步发送响应体

ngx.print和ngx.say为异步调用,执行后并不会立即输出响应体,可以通过执行ngx.flush(true)来实现同步输出响应体的功能。

ngx.flush

1
2
3
语法:ok, err = ngx.flush(wait?)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    location /test1 {
        content_by_lua_block {
           ngx.say("test ")
           ngx.say("nginx ")
           ngx.sleep(3)
           ngx.say("ok!")
           ngx.say("666!")
        }
    }

    location /test2 {
        content_by_lua_block {
           ngx.say("test ")
           ngx.say("nginx ")
           ngx.flush(true)
           ngx.sleep(3)
           ngx.say("ok!")
           ngx.say("666!")
        }
    }
}

访问/test1 和 /test2后,从执行结果可以看出,带有ngx.flush(true) 指令的内容会先输出test nginx,然后,等待大约3秒后再输出ok! 666!。如果没有配置ngx.flush(true)指令,请求会在等待3秒后输出完整的一句话。

注意:指令ngx.flush不支持HTTP1.0,可以使用如下方式进行测试:

1
# curl -i 'http://testnginx.com/test2' --http1.0

十二、正则表达式

虽然Lua支持正则匹配且功能齐全,但在Nginx上推荐使用Lua-lua提供的指令。

12.1 单一捕获

ngx.re.match

1
2
3
语法:captures, err = ngx.re.match(subject, regex, options?, ctx?, res_table?)

配置环境:init_worker_by_lua,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.,balancer_by_ lua,ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

    }
}

errlog:

1
2
3
ngx.log(ngx.ERR, "error: ", body)

tail -f /var/log/nginx/error.log

执行结果如下:

1
2
3
4
5
# curl  'http://testnginx.com/test/a123aaa/b456aaa/c'
/test/a123aaa/b456aaa/c---match success---its type: table
/test/a123aaa/b456aaa/c---m[0]---123aaa
/test/a123aaa/b456aaa/c---m[1]---123
/test/a123aaa/b456aaa/c---m[2]---aaa

从执行结果可以看出:

1.ngx.re.match只返回匹配到的第一个结果,所以后面的456aaa并没有被输出。

2.ngx.re.match返回的结果是table类型的。

3.ngx.re.match匹配成功后,m[0] 的值是匹配到的完整数据,而m[1]、m[2] 是被包含在括号内的单个匹配结果。

12.2 全部捕获

ngx.re.match只返回第一次匹配成功的数据,如果想获取所有符合正则表达式的数据,可以使用ngx.re.gmatch。

ngx.re.gmatch

1
2
3
语法:iterator, err = ngx.re.gmatch(subject, regex, options?)

配置环境:init_worker_by_lua,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.,balancer_by_ lua,ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

    }
}

执行结果如下:

1
2
3
4
5
# curl  'http://testnginx.com/test/a123aaa/b456AAA/c'
123aaa
123
456AAA
456

ngx.re.match和ngx.re.gmatch都有一个options参数,用来控制匹配的执行方式,options常用参数说明见表7-1。

表7-1 options常用参数说明

12.3 更高效的匹配和捕获

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

ngx.re.find

1
2
3
语法:from, to, err = ngx.re.find(subject, regex, options?, ctx?, nth?)

配置环境:init_worker_by_lua,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.,balancer_by_ lua,ssl_certificate_by_lua*,ssl_session_fetch_by _lua*,ssl_session_store_by_lua*

含义:与ngx.re.match类似,但只返回匹配结果的开始位置索引和结束位置索引。

因为ngx.re.find不会创建table来存储数据,所以性能上比ngx.re.match和ngx.re.gmatch要好很多。此时,如果需要捕获匹配到的数据,可以使用Lua的函数string.sub。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    }
}

执行结果如下:

1
2
3
4
# curl  'http://testnginx.com/test/a123aaa/b456AAAa/c'
begin:8
end:13
find it: 123aaa

ngx.re.match、ngx.re.gmatch和 ngx.re.find 都支持ctx参数,有关ctx参数的说明如下。

1.ctx是Lua table类型的,是可选的第4个参数,但若用到第5个参数nth,那么,此位置需要用nil作为占位符。

2.当ctx有值(键是pos,如pos=1)时,ngx.re.find将从pos位置开始进行匹配(位置的下标从1开始)。

3.无论ctx表中是否有值,ngx.re.find都会在正则表达式匹配成功后,将ctx值设置为所匹配字符串之后的位置;若匹配失败,ctx表将保持原有的状态。

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],示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
    }
}

执行结果如下:

1
2
3
4
# curl  'http://testnginx.com/test/a123aaa/b456AAAa/c'
begin:10
end:10
find it: 3

因为ctx的位置是10,所以uri前面的“/test/a12”这9个字符被忽略了,匹配到的就只有3aaa,又因为nth为1,所以捕获到的值是3。

12.4 替换数据

Lua API也支持匹配对应数据并对其进行替换的指令。

ngx.re.sub

1
2
3
语法:newstr, n, err = ngx.re.sub(subject, regex, replace, options?)

配置环境:init_worker_by_lua,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.,balancer_by_ lua,ssl_certificate_by_lua*,ssl_session_fetch_by_ lua*,ssl_session_store_by_lua*

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    }
}

执行结果如下:

1
2
3
4
# curl   'http://testnginx.com/test188/x2/1231'
/test188/x2/1231
/testzzzz/x2/1231
1

从结果可以看出,只在第一次匹配成功时进行了替换操作,并且只替换了1次,所以n的结果是1。如果要替换匹配到的全部结果可以使用ngx.re.gsub,示例如下:

1
local n_str, n, err = ngx.re.gsub(uri,"([0-9]+)", 'zzzz')

从执行结果可知,替换了3次:

1
2
3
4
# curl   'http://testnginx.com/test188/x2/1231'
/test188/x2/1231
/testzzzz/xzzzz/zzzz
3

12.5 转义符号

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

1
2
3
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等,同样,子请求也支持这些请求方法。

13.1 请求方法

Lua API中提供了多个指令来实现子请求,Lua API常见的请求方法说明见表7-2。

表7-2 Lua API常见的请求方法说明

13.2 单一子请求

ngx.location.capture

1
2
3
语法:res = ngx.location.capture(uri, options?)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

含义:发出同步但不阻塞Nginx的子请求。可以用来访问指定的location,但不支持访问命名location(如@abc 就是命名location)。location中可以有静态文件,如ngx_proxy、ngx_fastcgi、ngx_memc、ngx_postgres、ngx_drizzle,甚至是Ngx_Lua和Nginx的c模块。

子请求总是会把整个请求体缓存到内存中,如果要处理一个较大的子请求,使用cosockets是最好的选择(cosockets是与ngx.socket.tcp有关的API)。

子请求一般在内部进行访问,建议在被子请求访问的location上配置internal,即只允许内部访问。

子请求返回的结果res,它是一个table类型的数据,包含4个元素:res.status、res.header、res.body和res.truncated,res的元素名及其用途见表7-3。

表7-3 res的元素名及其用途

ngx.location.capture的第2个参数options是可选参数,也可以包含多个参数,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
server {
    listen       80;
    server_name  testnginx.com;
    default_type 'text/plain';
    
    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! ')
        }
    }
}

执行结果如下:

1
2
3
4
5
# curl   'http://testnginx.com/main'
200
request_body:test nginx capture_args:a=1&b=2---  copy_all_vars : helloworld!
table
boolean

从示例中可以看出:

1.ngx.location.capture的第2个参数options可以包含多个table类型的参数。

2.子请求的请求方法由参数method进行配置,示例中的请求方法为GET。

3.子请求通过参数body可以定义新的请求体。

4.子请求通过参数args可以配置新的URL的args,args是table类型的。

5.copy_all_vars = true的作用是将主请求的全部变量传递给子请求,如果没有此配置就不会传递过去。

6.从子请求的返回结果可以获取状态码、响应体、响应头、结果是否被截断。

根据上面的介绍可知,下面两种方式是等价的:

local res = ngx.location.capture(‘/test?a=1&b=2’)

local res = ngx.location.capture(‘/test , args = { a = 1, b = ‘2’ }’)

ngx.location.capture 还支持更丰富的参数操作,具体如下。

1.vars参数,table类型,可以设置子请求中的变量值,前提是该变量在Nginx中被声明过。如果配置copy_all_vars = true,且vars里有和主请求相同的变量,则会使用vars中变量的值;如果vars里是新变量,就会和主请求的变量一起传递过去。

2.share_all_vars参数,用来共享主请求和子请求的变量,如果在子请求中修改了共享变量的值,主请求的变量值也会被改变。不推荐使用此参数,因为可能会导致很多意外问题的出现。

3.always_forward_body参数,默认值为false,此时,如果不设置body参数,且请求方法是PUT或POST,则主请求的请求体可以传给子请求。如果把always_forward_body设置为 true,且不设置body参数,无论请求方法是什么,主请求的请求体都会传给子请求。

4.ctx参数,指定一个table作为子请求的ngx.ctx表,它可以使主请求和子请求共享请求头的上下文环境。

关于参数vars的使用方式,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
location = /main {
    set $m 'hello';
    set $mm '';
    content_by_lua_block {
        local ngx = require "ngx";
        local res = ngx.location.capture(
            '/test',
            { method = ngx.HTTP_POST ,
            vars = {mm = 'MMMMM',m = 'hhhh'}}
        )
        ngx.say(res.body)
    }
}
location = /test {
    content_by_lua_block {
        local ngx = require "ngx";
        ngx.print(ngx.var.m .. ngx.var.mm )
    }
}

执行结果如下:

1
2
# curl   'http://testnginx.com/main'
hhhhMMMMM

主请求的变量在子请求中被修改了,并传给了子请求指定的/test:

注意:使用ngx.location.capture发送子请求时,默认会将主请求的请求头全部传入子请求中,这可能会带来一些不必要的麻烦。例如,如果浏览器发送的压缩头Accept-Encoding:gzip被传入子请求中,且子请求是ngx_proxy的标准模块,则请求的结果会被压缩后再返回,导致Lua无法读取子请求返回的数据。因此应将子请求的 proxy_pass_request_headers设置为off,避免把请求头传递给后端服务器。

13.3 并发子请求

有时需要发送多条子请求去获取信息,这时,就要用到并发操作了。

ngx.location.capture_multi

1
2
3
语法:res1, res2, … = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, … })

配置环境:rewrite_by_lua*,access_by_lua*,content_by_lua*

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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';
    }

}

执行结果如下:

1
2
3
# curl   'http://testnginx.com/main'
test1
test2

主请求需要等到所有的子请求都返回后才会结束子请求的执行,最慢的子请求的执行时间就是整体的消耗时间,所以在实际业务中需要对子请求的超时时间做好限制。

注意:Nginx对子请求有并发数量限制,目前Nginx 1.1以上的版本限制子请求并发数量为200个,老版本是50个。

十四、获取Nginx的环境变量

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

14.1 获取环境所在的模块

ngx.config.subsystem

1
2
3
语法: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

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

14.2 确认调试模式

ngx.config.debug

1
2
3
语法:debug = ngx.config.debug

配置环境: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

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

14.3 获取prefix路径

ngx.config.prefix

1
2
3
语法:prefix = ngx.config.prefix()

配置环境: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时–prefix=的路径,如果启动Nginx时使用了参数-p,就以参数-p的值为准。

14.4 获取Nginx的版本号

ngx.config.nginx_version

1
2
3
语法:ver = ngx.config.nginx_version

配置环境: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的版本号,如本书使用的Nginx版本号是1.12.2。

14.5 获取configure信息

ngx.config.nginx_configure

1
2
3
语法: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

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

14.6 获取Ngx_Lua的版本号

ngx.config.ngx_lua_version

1
2
3
语法:ver = ngx.config.ngx_lua_version

配置环境: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_Lua模块的版本号。可以用来检查Ngx_Lua的版本。例如,当开发某个功能需要使用指定的版本时,可以在代码中进行判断,如果不是指定的版本,可以输出警告信息。

14.7 判断worker进程是否退出

ngx.worker.exiting

1
2
3
语法: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

含义:判断Nginx的worker进程是否退出。

14.8 获取worker进程的ID

ngx.worker.id

1
2
3
语法: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

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

14.9 获取worker进程数量

ngx.worker.count

1
2
3
语法: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 worker进程的数量,即Nginx配置中worker_processes的值。

十五、定时任务

可以使用Nginx执行定时任务,例如,定期获取MySQL数据库中的数据并存放到共享内存中,定时监听某个配置是否发生改变(如果发生改变就重载Nginx),定时将日志远程传输到集中存储上等。 在Lua-0.10.9版本之前,常使用ngx.timer.at来启动定时任务。Ngx_Lua 0.10.9新增了ngx.timer.every,启动定时任务更加方便了。本章中的定时任务都使用ngx.timer.every来创建,后续介绍也会以此命令为主。

15.1 创建定时任务

ngx.timer.every

1
2
3
语法:hdl, err = ngx.timer.every(delay, callback, user_arg1, user_arg2, …)

配置环境:init_worker_by_lua,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.,balancer_by_ lua,ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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进程时就被触发执行,请观察图7-1所示的定时任务输出的日志。

图7-1 定时任务输出的日志

从图7-1可以发现如下规则。

1.每个worker进程都在执行输出操作。

2.都是每3s执行一次。

3.如果没有从Nginx外部进行访问的请求,定时任务会继续执行下去。

参数user_arg1、user_arg2用来给定时任务传递参数,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
}

配置7-1

15.2 性能优化

在配置7-1中,Nginx启动了3个worker进程,所以每3s会执行3次worker进程。但有时只需执行一次即可,例如当前共享内存中存放数据时,因为数据是所有worker进程共享的,所以执行一次就足够了。且被启动的worker进程越多,后端的并发就越多,这会增加后端服务器的负载,那么应该怎么减少worker进程重复执行的次数呢?

其实根据输出的日志可以获得每个worker进程的ID,那么,只需利用ID指定一个worker进程来执行定时任务就可以了,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
init_worker_by_lua_block {
     local delay = 3;
     local ngx = require "ngx";
     local check
     check = function(premature)
         if not premature then
             ngx.log(ngx.ERR, ' ngx.worker.pid: ',ngx.worker.pid(),' ngx.worker.id: ',ngx.worker.id(),"------test nginx !!!")
         end
     end

   --如果worker进程的ID为0就执行定时任务  
   if 0 == ngx.worker.id() then
         local ok, err = ngx.timer.every(delay, check)
         if not ok then
             ngx.log(ngx.ERR, "failed to create timer: ", err)
             return
         end
    end

}

观察日志,会发现每3s worker进程只执行一次。

如果worker进程意外终止,Nginx的master进程会保证在worker进程意外终止后重启新的worker进程,ID保持不变。

如果要求定时任务只在Nginx重载时执行一次,可以使用如下方式:

1
local ok, err = ngx.timer.at(0,func)

这表示立即执行func函数,且由于没有回调ngx.timer.at的指令,只会执行一次。

注意:关于定时任务,需要在在init_worker_by_lua*的执行阶段中执行(详见8.2节)。

定时任务在Nginx后台运行,不直接和客户端请求打交道,因此不会直接影响请求的响应时间,但这并不代表它不会干扰请求的响应时间,如果在同一时间内有大量定时任务执行,也会降低Nginx的整体性能。此时,可以使用如下指令对正在运行的定时任务进行控制。

lua_max_running_timers

1
2
3
4
5
语法:lua_max_running_timers <count>

默认值:lua_max_running_timers 256

配置环境:http

含义:设置被允许的running timers(正在执行回调函数的计时器)的最大数量,如果超过这个数量,就会抛出“N lua_max_running_timers are not enough”,其中N是变量,指的是当前正在运行的running timers的最大数量。

lua_max_pending_timers

1
2
3
4
5
语法:lua_max_pending_timers <count>

默认值:lua_max_pending_timers 1024

配置环境:http

含义:设置允许使用的pending timers(执行挂起的定时器)的最大数量,如果在定时任务中超过这个限制,则会报“too many pending timers”错误。

15.3 禁用的Lua API

ngx.timer.every支持用户操作共享内存、读取数据库数据、获取系统时间等,但在定时任务中有些API是被明确禁止的,例如:

1.子请求ngx.location.capture。

2.向客户端输出的Lua API(如 ngx.say、ngx.print 和 ngx.flush)。

3.以ngx.req.开头的Lua API。

十六、常用指令

Ngx_Lua提供了大量的Lua API指令来实现各种功能,本节会介绍一些常用的指令。

16.1 请求重定向

在Nginx中通过rewrite对请求进行重定向,而在Ngx_Lua里可以使用ngx.redirect、ngx.req.set_uri来完成重定向,并且Ngx_Lua还提供了一个具有强大的扩展能力的ngx.exec指令。

ngx.redirect

1
2
3
语法:ngx.redirect(uri, status?)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

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

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

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

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

也可以自定义参数,如下所示:

1
return ngx.redirect("/test?test=1&a=2" ,301)

支持跳转到其他域名,如http://abc.testnginx.com%EF%BC%9A

1
return ngx.redirect(“ http://abc.testnginx.com”,301)

注意:跳转时都加return指令,其作用是为了强调跳转操作,官方推荐使用这种方式。

ngx.req.set_uri

1
2
3
语法: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

含义:用参数uri来重写当前的URL,和Nginx的rewrite内部重定向功能相似。例如,rewrite的指令rewrite ^ /test last; 与ngx.req.set_uri(“/test”, true)功能相似,而rewrite ^ /test break; 与ngx.req.set_uri(“/foo”, false)功能相似。

如果需要在跳转过程中修改参数,可以使用ngx.req.set_uri_args来完成新的参数配置,操作如下:

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

ngx.exec

1
2
3
语法:ngx.exec(uri, args?)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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指令常用的几种参数设置的示例。

保留之前的参数,如“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(…)的方式。

16.2 日志记录

在使用Lua进行开发的过程中,需要使用日志来输出异常和调试信息,在Lua API中可以使用ngx.log来记录日志。

ngx.log

1
2
3
语法:ngx.log(log_level, …)

配置环境:init_by_lua,init_worker_by_lua,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.,balancer_by_lua,ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

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

log_level的级别及其说明见表7-6(和Nginx的error.log日志级别是一致的)。

表7-6 log_level的级别及其说明

续表

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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')
        }
    }

}

执行结果如下:

1
curl -i 'http://testnginx.com/'

查看error.log 日志,默认在logs/error.log文件中,示例如下:

1
2
3
4
5
6
2018/06/11 11:18:26 [alert] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):4: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"
2018/06/11 11:18:26 [] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):5: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"
2018/06/11 11:18:26 [emerg] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):6: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"
2018/06/11 11:18:26 [alert] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):7: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"
2018/06/11 11:18:26 [crit] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):8: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"
2018/06/11 11:18:26 [error] 1180#1180: *34 [lua] content_by_lua (nginx.conf:66):9: Log Test Nginx, client: 10.19.48.161, server: testnginx.com, request: "GET / HTTP/1.1", host: "testnginx.com"

观察error.log日志可发现,它并没有输出所有级别的日志,这是因为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支持多个字符串合并输出,字符串之间以逗号分隔,示例如下:

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

ngx.log单条日志可输出的最大字节数受Nginx的限制,默认最多是2048个字节,即2K。

Lua提供了print命令来简化INFO级别的日志的输出。下面两条语句的作用是一样的:

1
2
3
print("Log Test Nginx ")

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

注意:ngx.print 和print 是两条命令,不要混淆了。

16.3 请求中断处理

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

1.中断整个请求,则请求不再继续执行,直接返回到客户端。

2.中断当前的执行阶段,请求会继续执行下一个阶段,并继续响应请求。

它们都是通过ngx.exit指令完成的。

ngx.exit

1
2
3
语法: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

含义:参数status的值是HTTP的状态码。当参数status>=200时,请求会被中断,并将status的值作为状态值返回给Nginx。

当参数status==0时,请求会中断当前的执行阶段,继续执行下一个阶段(前提是还有下一个阶段)。

配置环境:init_by_lua,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.,balancer_by lua,ssl_certificate_by_lua,ssl_session_fetch_by_lua,ssl_session_store_by_lua

Ngx_Lua HTTP状态码清单见表7-9。

表7-9 Ngx_Lua HTTP状态码清单

续表

下面是一个HTTP状态码为0的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,或其他异常的状态,表示失败退出当前请求。

继续使用上面的例子,这次以非0的状态码退出当前请求,如下所示:

1
2
3
4
5
6
7
8
location / {
    set $a  '0';           
    rewrite_by_lua_block {
         ngx.var.a = '1';
         ngx.exit(200)  --也可以换成500,数字代表状态码的值
    }
         echo $a;  #没有执行到这一句
}

因为使用了200状态码,所以请求在ngx.exit处被中断后退出了,所以无法执行echo输出的命令。为了强调退出操作,可以在此命令前加上return,如下所示:

1
return ngx.exit(ngx.OK)

十七、提升开发和测试效率

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

1
2
3
4
5
语法:lua_code_cache on | off

默认:lua_code_cache on

配置环境:http,server,location,location if

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

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

17.1 断开客户端连接

对于某些API请求,客户端只管发送并不等待返回结果,例如,触发一个请求通知远程服务端执行某个任务或进行日志推送。此时,可以使用如下指令断开连接。

ngx.eof

1
2
3
语法:ok, err = ngx.eof()

配置环境:rewrite_by_lua,access_by_lua,content_by_lua

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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/%E5%90%8E%EF%BC%8C%E8%AF%B7%E6%B1%82%E4%BC%9A%E7%AB%8B%E5%88%BB%E5%93%8D%E5%BA%94%E4%B8%80%E4%B8%AA200%E7%8A%B6%E6%80%81%EF%BC%8C%E8%A1%A8%E7%A4%BA%E5%93%8D%E5%BA%94%E5%86%85%E5%AE%B9%E5%B7%B2%E8%BF%94%E5%9B%9E%EF%BC%8C%E4%BD%86%E8%AF%B7%E6%B1%82%E7%9A%84%E5%90%8E%E7%BB%AD%E6%93%8D%E4%BD%9C%E4%BB%8D%E5%9C%A8%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E7%BB%A7%E7%BB%AD%E6%89%A7%E8%A1%8C%EF%BC%8C3s%E5%90%8E%E4%BC%9A%E5%B0%86%E6%97%A5%E5%BF%97%E5%86%99%E5%85%A5error.log%E3%80%82

注意:执行完ngx.eof后,如果下一步是发送子请求的指令,那么,子请求会被意外中止,导致无法完成子请求的响应,这是受Nginx中proxy_ignore_client_abort默认值的影响,将proxy_ignore_client_abort设置为on,就可以在执行ngx.eof后继续响应子请求了。

17.2 请求休眠

ngx.sleep

1
2
3
语法:ngx.sleep(seconds)

配置环境:rewrite_by_lua,access_by_lua,content_by_lua,ngx.timer.,ssl_certificate_ by_lua,ssl_session_fetch_by_lua

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

示例:

1
2
3
4
5
6
7
location / {
    content_by_lua_block {
        --5秒后输出ok。
        ngx.sleep(5);
        ngx.say('ok')
    }
}

17.3 获取系统时间

在Ngx_lua中获取系统时间,都是从Nginx的时间缓存中读取的,不涉及系统调用(系统调用的Lua命令类似于通过os.time获取系统时间)。相关指令的配置环境都是相同的,都适用于如下执行阶段。

1
init_worker_by_lua*, 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., balancer_by_lua, ssl_certificate_by_lua*, ssl_session_fetch_ by_lua*, ssl_session_store_by_lua*

在Ngx_lua中获取系统时间的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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())
       }
    }
}

执行结果如下:

ngx.today: 2018-06-11 #返回系统的本地时间,只包含年、月、日

ngx.time: 1528721734 #返回当前时间的Unix时间戳

ngx.now: 1528721734.775 #返回当前时间的Unix时间戳,浮点数类型,小数部分是毫秒级别

ngx.localtime: 2018-06-11 20:55:34 #返回当前时间

ngx.utctime: 2018-06-11 12:55:34 #返回UTC(Coordinated Universal Time,即世界标准世界)时间

ngx.cookie_time: Mon, 11-Jun-18 12:50:05 GMT #返回一个可以让Cookie过期的时间格式,参数是Unix时间戳格式

ngx.http_time: Mon, 11 Jun 2018 12:50:05 GMT #返回一个可以做HTTP头部的时间格式,如expires或last-modified

ngx.parse_http_time: 1528721405 #返回Unix时间戳,和ngx.http_time输出的时间格式不一样

ngx.update_time: #返回空,作用是强行更新Nginx的时间缓存,此操作会增加性能开销,不建议使用

17.4 编码与解码

利用Ngx_Lua的API,可以进行编码和解码的操作。

ngx.escape_uri

1
2
3
语法:newstr = ngx.escape_uri(str)

配置环境:init_by_lua,init_worker_by_lua,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.,balancer_by_lua,ssl_certificate_by_lua*,ssl_session_fetch_by_lua*,ssl_session_store_by_lua* ngx.quote_sql_str

含义:对参数str进行URI编码。

ngx.unescape_uri

1
2
3
语法:newstr = ngx.unescape_uri(str)

配置环境:init_by_lua,init_worker_by_lua,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.,balancer_by_lua,ssl_certificate_by_lua*

含义:对参数str进行URI解码。

ngx.encode_args

1
2
3
语法:str = ngx.encode_args(table)

配置环境: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.,balancer_by_lua,ssl_certificate_ by_lua

含义:按照URI编码规则,将Lua提供的table类型数据编码成一个字符串。

ngx.decode_args

1
2
3
语法:table, err = ngx.decode_args(str, max_args?)

配置环境: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.,balancer_by_lua,ssl_certificate_by_ lua,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

含义:将URI编码的字符串解码为Lua的table类型的数据。

ngx.md5

1
2
3
语法:digest = ngx.md5(str)

配置环境: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.,balancer_by_lua,ssl_certificate_by_ lua,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

含义:对str字符串进行MD5加密,并返回十六进制的数据。

ngx.md5_bin

1
2
3
语法:digest = ngx.md5_bin(str)

配置环境: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.,balancer_by_lua,ssl_certificate_by_ lua,ssl_session_fetch_by_lua*,ssl_session_store_by_lua*

含义:对str字符串进行MD5加密,并返回二进制的数据。

注意:ngx.escape_uri和ngx.unescape_uri作用相反,ngx.encode_args和ngx.decode_args的作用相反。

关于编码、解码操作的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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))
       }
    }
}

执行结果如下:

1
2
3
4
5
6
7
# 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©¨