[code.openresty] Openresty框架-Nginx的lua模块

openresty

OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
-- Openresty中文官方站

nginx的http lua模块

Openresty的ngx_http_lua_module将lua的功能嵌入到nginx http服务中。这个模块不是和原始的nginx服务分离的,而是打包在一起的,需要安装包含有nginx完整功能和各个lua模块的openresty框架。

概要

    
    # 设置纯lua外部函数库的搜索路径(';;'代表默认的路径)
    lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';
    
    # 设置用C语言编写的lua外部函数库的搜索路径(也可以使用';;')
    lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';

    server {
        location /lua_content{
            #使用default_type确定MIME的类型
            default_type 'text/plain';
            
            content_by_lua_block{
                ngx.say('Hello,world!')
            }
            
        }
        
        location /nginx_var {
            #使用default_type确定MIME的类型
            default_type 'text/plain';
            
            #尝试访问 /nginx_var?a=hello,world
            content_by_lua_block {
                ngx.say(ngx.var.arg_a)
            }
        }
        
        location = /request_body {
            client_max_body_size 50k;
            client_body_buffer_size 50k;
            
            content_by_lua_blick {
                ngx.req.read_body() -- 明确要读取请求的body
                local data = ngx.req.get_body_data()
                if data then
                    ngx.say("body data")
                    ngx.print(data)
                    return
                end
                
                -- body有可能缓存到一个临时文件中
                local file = ngx.req.get_body_file()
                if file then
                    ngx.say("body is in file ",file)
                else
                    ngx.say("no body found")
                end
            }
        }
        
        # 在lua中非阻塞IO的子请求
        # (当然,一个更好的方式是使用cosockets)
        location = /lua {
            # 使用default_type来确定MIME的类型
            default_type 'text/plain';
            
            content_by_lua_block {
                local res = ngx.location.capture("/some_other_location")
                if res then
                    ngx.say("status:",res.status)
                    ngx.say("body:")
                    ngx.print(res.body)
                end
            }
            
        }
        
        location = /foo {
            rewrite_by_lua_block {
                res = ngx.location.capture("/memc",
                    { args = { cmd = "incr",key = ngx.var.uri }}
                )
            }
            
            proxy_pass http://blah.blah.com
        }
        
        location = /mixed {
            rewrite_by_lua_file /path/to/rewrite.lua;
            access_by_lua_file /path/to/access.lua;
            content_by_lua_file /path/to/content.lua;
        }
        
        # 在代码路径中使用nginx变量
        # 警告:nginx变量中的内容必须被小心的过滤出来
        # 否则这里会有严重的安全风险
        location ~ ^/app/([-_a-zA-Z0-9/]+) {
            set $path $1;
            content_by_lua_file /path/to/lua/app/root/$path.lua;
        }
        
        location / {
            client_max_body_size 100k;
            client_body_buffer_size 100k;
            
            access_by_lua_block {
                -- 检查客户端的IP地址是否在我们的黑名单中
                if ngx.var.remote_addr == "132.5.72.3" then
                    ngx.exit(ngx.HTTP_FORBIDDEN)
                end
                
                -- 检查URI中是否含有不良的词语
                if ngx.var.uri and 
                    string.match(nax.var.request_body,"evil")
                then
                    return ngx.redirect("/terms_of_use.html")
                end
                
                -- 测试通过
            }
            
            # proxy_pass/fastcgi_pass/etc settings
        }
        
    }

描述

lua-nginx-module嵌入了lua,把标准的lua 5.1解释器或者LuaJIT 2.0/2.1嵌入到Nginx中并且通过利用Nginx的子请求,允许集成强大的lua线程 (Lua coroutines) 到Nginx的事件模型中。

和Apache's mod_lua、Lighttpd's mod_magnet不同的是,Lua代码通过这个模块执行可以在网络通信中实现100%非阻塞,只要使用openresty的lua模特提供的Nginx API for lua去handle请求、upstream服务如MySQL、PostgreSQL、Memcached、Redis或者upstream HTTP web services。
至少如下的Lua函数库和Nginx模块能被此ngx_lua模块使用到:

  • lua-resty-memcached
  • lua-resty-mysql
  • lua-resty-redis
  • lua-resty-dns
  • lua-resty-upload
  • lua-resty-websocket
  • lua-resty-lock
  • lua-resty-logger-socket
  • lua-resty-lrucache
  • lua-resty-string
  • ngx_memc
  • ngx_postgres
  • ngx_redis2
  • ngx_redis
  • ngx_proxy
  • ngx_fastcgi

几乎上面所有的Nginx模块可以用ngx.location.capture or ngx.location.capture_multi来调用,但是这里推荐使用这些lua-resty-*函数库来替代创建子请求访问Nginx upstream模块,因为前者通常来说更加灵活和节省内存。

Lua解释器或者LuaJIT实例可以在一个单独的nginx工作进程中被所有的请求共享。但是在使用轻量级的Lua coroutines序时请求上下文是被隔离的。
留存在Nginx的工作进程级别的那些已经被加载的Lua模块只会导致一个很小的内存占用,即使是在沉重的负载下。
本lua-nginx模块已经被嵌入到NGINX的http子系统中,所以他只能识别HTTP家族中的downstream通信协议(HTTP 0.9/1.0/1.1/2.0,WebSockets等等)。如果你想通过downstream客户端完成一个一般的TCP通信,那么你应该使用ngx_stream_lua模块来代替,此模块也提供一个兼容的Lua API。

典型使用

只列举出一部分:

  • 通过Lua来混合和加工各种nginx upstream输出(proxy,drizzle,postgres,redis,memcached等等),
  • 通过Lua在请求实际到达upstream后端之前,做任意的复杂访问控制和安全监测,
  • 通过Lua任意的操纵返回的headers
  • 从外部存取后端中获取后端信息(如:reids,memcached,mysql,postgresql),并且使用这些信息在传输过程中选择使用哪个upstream后端,
  • 编写任意的复杂web应用程序,使用同步的但是仍然非阻塞的后端数据库或者其他存储设备来处理内容。
  • 在rewrite阶段通过Lua做一个非常复杂的URL dispatch,
  • 使用Lua为Nginx的子请求和任意的location实现高级的缓存机制。

此模块有无限的发展潜力,因为此模块允许在Nginx中结合各种模块同时也在使用者面前展示了Lua的能力。这个模块提供了完整的脚本语言的灵活性,同时,在CUP时间和内存占用方面提供了可以与C语言程序相比的性能水平。特别是LuaJIT 2.x被开启时。

其他的脚本语言实现通常很难匹配到这个性能水平。

Lua状态(Lua VM实例)在一个nginx工作进程中被所有的请求共享来达到最少的内存使用。

安装

强烈推荐使用OpenResty releases,这个集成了Nginx,ngx_lua,LuaJIT2.1,同时也有其他的功能强大的Nginx模块和Lua函数库。不要自己通过nginx去构造这个模块因为很难能够完全配置正确。同时,nginx的内核有各种各样的局限性和长期存在的bug会导致一些模块的特性变得有缺陷,不能正常的工作或者运行缓慢。

作为一种选择,ngx_lua可以被手动的编译到Nginx中:

  1. 安装LuaJIT 2.0或2.1(建议)或者Lua5.1(Lua 5.2目前被支持)。LuaJIT能够从LuaJIT project website上下载下来,Lua 5.1从 Lua project website。一些发布包管理器同样可以分发LuaJIT和/或Lua。
  2. 下载最新版的ngx_devel_kit(NDK)模块 HERE。
  3. 下载最新版的ngx_luaHERE。
  4. 下载最新版本的的NginxHERE (See Nginx Compatibility)

通过这个模块来构建源:


    wget 'http://nginx.org/download/nginx-1.11.2.tar.gz'
    tar -xzvf nginx-1.11.2.tar.gz
    cd nginx-1.11.2/
 
    # 告诉nginx的构建系统从哪里去寻找LuaJIT 2.0:
    export LUAJIT_LIB=/path/to/luajit/lib
    export LUAJIT_INC=/path/to/luajit/include/luajit-2.0
 
    # 告诉nigix的构建系统从哪里去寻找LuaJIT 2.1:
    export LUAJIT_LIB=/path/to/luajit/lib
    export LUAJIT_INC=/path/to/luajit/include/luajit-2.1
    
    # 或者要是使用Lua替代的话告诉从哪去找lua
    #export LUA_LIB=/path/to/lua/lib
    #export LUA_INC=/path/to/lua/include
    
    # 这里我们假设Nginx被安装在/opt/nginx/目录中
    ./configure --prefix=/opt/nginx \
            --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \
            --add-module=/path/to/ngx_devel_kit \
            --add-module=/path/to/lua-nginx-module
    
    # 注意,你可能还想添加在你当前nginx构建时使用到的`./configure`选项
    # 你可以通过命令nginx -V来得到这些选项
    
    # 你可以改变下面平行结构里面的数字2来适应你机器上的CPU核心数
    make -j2
    make install
 

作为一个动态模块构建

从Nginx 1.9.11开始,你也可以将此模块编译成为一个动态模块,通过在上面的./configure命令行中使用--add-dynamic-module=PATH选项来替代--add-module=PATH。然后你可以在你的nginx.conf中通过直接使用load_module来明确的加载该模块。如:


    load_module /path/to/modules/ndk_http_module.so; # 假设NDK也是作为一个动态模块构建
    load_module /path/to/modules/ngx_http_lua_module.so;
    

C语言宏定义

无论是通过OpenResty还是通过Nginx内核来构建这个模块,你都可以通过C编译选项定义下面的C语言宏。

  • NGX_LUA_USE_ASSERT
    当定义了这个 ,会在ngix_lua的C代码库开启断言。在排除错误或者测试构建时推荐定义。当开启后会有一些小的时间开销。这个宏在v0.9.10版本时被首先引进。

  • NGX_LUA_ABORT_AT_PANIC
    当Lua/LuaJIT VM极度匆忙时,ngx_lua会在默认状况下通知当前的nginx工作进程优雅的退出。通过指定这个C语言宏,ngx_lua会立即终止当前的nginx工作进程(通常会导致一个核心的转储文件)。这个现象通常在调试VM极度匆忙是有用。这个宏在v0.9.8版本时被首先引进。

  • NGX_LUA_NO_FFI_API
    排除Nginx的FFI-based Lua API中的纯C API(例如,在lua-resty-core需要)。开启这个宏可以使结果二进制代码体积更小。

要启用一个或者多个这些宏,只需要在NGINX或者OpenResty的./configure脚本中通过额外的c编译选项。例如:


./configure --with-cc-opt="-DNGX_LUA_USE_ASSERT -DNGX_LUA_ABORT_AT_PANIC"

在Ubuntu 11.10上进行安装

注意,只要有可能,推荐使用LuaJIT 2.0或者LuaJIT 2.1来代替标准的Lua 5.1。

无论如何,如果需要标准的Lua 5.1解释器,执行下面的命令来从Ubuntu库中安装它:


    apt-get install -y lua5.1 liblua5.1-0 liblua5.1-0-dev
    

除了一个小调整外,一切都应该被正确安装。

库名称liblua.so在liblua5.1 包中被改变了,它仅提供了liblua5.1.so,需要符号连接到/usr/lib从而使其可以在配置过程中被发现。


 ln -s /usr/lib/x86_64-linux-gnu/liblua5.1.so /usr/lib/liblua.so

Lua/LuaJIT字节码支持

v0.5.0rc32版本开始,所有的*_by_lua_file配置指令(例如content_by_lua_file)支持直接加载Lua5.1和LuaJIT 2.0/2.1原始字节码文件。

请注意LuaJIT 2.0/2.1使用的字节码格式和标准的Lua 5.1解释器使用的不兼容。所以如果通过ngx_lua使用LuaJIT 2.0/2.1,LuaJIT 兼容字节码文件必须通过这样生成:


    /path/to/luajit/bin/luajit -b /path/to/input_file.lua /path/to/output_file.ljbc

可以使用-bg选项来在LuaJIT字节码文件中包含debug信息:


    /path/to/luajit/bin/luajit -bg /path/to/input_file.lua /path/to/output_file.ljbc
 

请参阅官方LuaJIT文档查看-b选项的更多细节:

http://luajit.org/running.html#opt_b

另外,通过LuaJIT 2.1生成的字节码文件并兼容LuaJIT 2.0,反之亦然。对LuaJIT 2.1字节码的支持在ngx_lua v0.9.3版本时被首先支持。

同样的,如果在ngx_lua中使用标准的Lua 5.1解释器,Lua兼容的字节码文件必须使用luac命令工具生成,如下所示:


    luac -o /path/to/output_file.luac /path/to/input_file.lua

和LuaJIT不同的是,debug信息在标准的Lua5.1字节码文件中默认被包含。这个能够指定-s选项来剥离,如下所示:


    lua -s -o /path/to/output_file.luac /path/to/input_file.lua
    

尝试将标准的Lua5.1字节码文件加载到链接到 LuaJIT 2.0/2.1的ngx_lua实例,会导致一个错误信息,反之亦然。错误如下所示,被记录到Nginx的error.log文件中:


[error] 13909#0: *1 failed to load Lua inlined code: bad byte-code header in /path/to/test_file.luac

通过Lua原语如likedofile来加载字节码文件,会一直按照预期工作。

系统环境变量支持

如果你想访问系统变量,如:foo,在Lua中通过标准的Lua API os.getenv,然后你还应该在你的nginx.conf文件中通过env directive列出这个环境变量的名字。例如:


 env foo;
 

HTTP 1.0 支持

HTTP 1.0 协议不支持分块输出并且在响应体不为空时需要一个明确的Content-Length header 从而支持HTTP 1.0长连接。所以当一个HTTP 1.0请求了,并且lua_http10_buffering指令是on,ngx_lua会缓冲ngx.say 和 ngx.print调用的输出,并且推迟发送响应headers直到所有的响应body被接收到。在那个时候ngx_lua可以计算出body的总长度并且构建一个合适的Content-Length header来返回给HTTP 1.0客户端。如果Content-Length响应header在运行Lua代码中被设置了,然而,这种缓冲将被禁用,即使lua_http10_buffering设置被至为on

对于大型流输出响应,重要的是禁用lua_http10_buffering设置来使内存使用降到最低。

注意,常见的HTTP基准工具例如abhttp_load默认发出HTTP 1.0请求,要强制curl发出HTTP 1.0请求,使用-0选项。

静态链接纯lua模块

当使用的是LuaJIT 2.X版本时,可以静态链接纯Lua模块到Nginx可执行文件。

基本上你使用luajit可执行文件来编译.lua Lua模块文件成.o对象文件包含导出的字节码数据,并且在你Nginx构建时直接链接.o文件。

下面是一个简单的例子来展示这一点。考虑到我们有以下.lua文件命名为foo.lua:

-- foo.lua
local _M  = {}

function _M.go()
    print("Hello from foo")
end

return _M

然后我们编译这个.lua文件成为foo.o文件:

/path/to/luajit/bin/luajit -bg foo.lua foo.o

这里重要的是.lua文件的名称,这个决定了之后你在Lua里面怎么使用这个模块。这个foo.o的文件名称并不重要,除了这个.o的文件拓展(告诉luajit什么使用什么输出格式)。如果你想从结果字节码中剥离Lua的调试信息,就可以在上面中指定-b选项来替代-bg

然后当构建Nginx或者OpenResty时,通过这个--with-ld-opt="foo.o"选项来配置./configure脚本:

  ./configure --with-ld-opt="/path/to/foo.o" ...

最终,你可以在ngx_lua模块中在任何Lua代码中向下面这样做:

  local foo = require "foo"
  foo.go()

这段代码不再依赖于外部的foo.lua文件了,因为他已经被编译进ngix可执行部分了。

如果你想在调用require的时候在Lua模块名称中使用点,例如:

    local foo = require "resty.foo"

那么你需要在命令行工具luajit将文件编译成.o之前,先重新命名那个foo.lua文件成resty_foo.lua

非常重要的一点是,当你编译.lua文件成为.o文件和编译ngix + ngx_lua时,要使用相同版本的LuaJIT。这是因为不同版本的LuaJIT字节码文件可能是不兼容的。当字节码是不兼容的时候,你会看到一个Lua运行时错误说没有找到Lua模块。

当你有多个.lua文件要编译和链接时,只需要在--with-ld-opt选项中同时指定他们的.o文件。例如:

  ./configure --with-ld-opt="/path/to/foo.o /path/to/bar.o" ...

如果你有太多的.o文件,那么将他们在单条命令中都列出来它们是不可行的。在这种情况下,你可以将你的那些.o文件构建一个静态library(或者archive),例如:

 ar rcus libmyluafiles.a *.o

然后你可以连接整个myluafiles archive到你的nginx可执行部分。

  ./configure \
        --with-ld-opt="-L/path/to/lib -Wl,--whole-archive -libmyluafiles
        --Wl,--no-whole-archive"

其中/path/to/lib是包含libmyluafiles.a文件的路径。应该注意的是链接选项--whole-archive在这里是必须的否则我们的archive会被忽略,这是因为在nginx可执行文件的主要部分,我们archive里的符号没有被提到。

在同一个Nginx Worker中数据共享

为了全局共享在同一个nginx worker进程中处理的所有requests中的数据,封装数据到一个Lua模块中,使用Lua的内建指令require来导入这个模块,然后在Lua中操纵这些共享数据。这是可行的,因为这个模块只被加载一次,而且所有的协同程序(coroutines )会共享这个模块的同样的副本(代码和数据)。但是要注意,Lua全局变量(注意,不是模块级变量)不会在request之间存在,这是由于one-coroutine-per-request的孤立设计。

这里是一个完整的小例子:


-- mydata.lua
local _M = {}

local data = {
    dog = 3,
    cat = 4,
    pig = 5,
}

function _M.get_age(name)
    return data[name]
end

return _M

然后从nginx.conf中访问它


    location /lua {
        content_by_lua_block {
            local mydata = require "mydata"
            ngx.say(mydata.get_age("dog"))
        }
    }

这个例子中的mydata模块只会在运行对定位/lua的第一次请求中加载和运行,并且所有对相同的nginx工作进程的后续请求将会使用已经被加载的实例模块以及相同的数据副本,直到一个HUP信号被发送到nginx主进程中去强制重载。这个数据共享技术对基于这个模块的Lua应用程序的高性能表现是至关重要的。

要注意这个数据共享是基于每个进程基础上而不是每个服务基础上的,也就是说,在一个nginx master下有多个nginx工作进程,数据共享不能穿越这些工作进程的边界。

通常建议以这种方式共享只读数据。你也可以在每个nginx工作进程的所有并发请求中共享可变数据,只要在你计算期间这里没有非阻塞I/O操作(包括ngx.sleep)。只要你不给nginx事件循环和ngx_lua的线程调度程序(甚至是隐式的)控制返回,这里永远不会有竞争条件。出于这个原因,当你想在进程级别共享可变数据时一定要小心。错误的优化可以很容易地导致难以调试的竞态条件。

如果需要服务器范围的数据共享,那么使用下列一个或多个方法:

  1. 使用本模块提供的ngx.shared.DICT API。
  2. 仅仅使用一个nginx进程和一个server(但是当这里在一个机器上有多个CPU核心或者多个CPU时不推荐这么做)
  3. 使用数据存储机制例如memcached,redis,MySQL或者PostgreSQL。与这个模块关联的OpenResty包(http://openresty.org)提供了于这些数据存储机制进行接口调用的一组相伴的nginx模块和lua库。

已知的问题

TCP套接字连接操作的问题

这个tcpsock:connect方法可能表示成功(success)尽管出现连接错误如Connection Refused错误。

然而,后面试图操纵cosocket会失败并且返回连接失败操作生成的实际错误状态信息。

这个问题是由于Nginx事件模型的局限性并且只出现影响 Mac OS X.

Lua 协同程序(Coroutine) 挂起(Yielding)/恢复(Resuming)

  • 因为目前Lua的dofilerequire内置指令目前在Lua 5.1和LuaJIT 2.0/2.1中是作为C语言函数实现的 。如果在要被dofile或者require加载的Lua文件中在lua文件顶级范围里面调用ngx.location.capture*, ngx.exec, ngx.exit,或者其他需要挂起(yielding )的API方法,那么将会引起“attempt to yield across C-call boundary”的错误。为了避免这个问题,将这些需要挂起(yielding )的调用放在lua文件中你自己的lua方法中,而不是放在文件的顶级范围内。
  • 由于标准的Lua 5.1解释器的VM并非完全可恢复的,这些 ngx.location.capture, ngx.location.capture_multi, ngx.redirect, ngx.exec, 和 ngx.exit方法,不能在Lua的pcall() 或者 xpcall()的上下文中被使用,当标准的Lua 5.1 解释器被使用时也不能在for ... in ...声明的第一行中被使用。否则会产生attempt to yield across metamethod/C-call boundary的错误。请使用支持完全恢复VM的LuaJIT 2.x来避免这个错误。

Lua变量作用域

必须注意引入模块的时候需要使用下面这形式:


  local xxx = require('xxx')

而不是使用旧的弃用的形式:


  require('xxx')

这是原因:在设计的时候,全局变量和与之关联的Nginx请求处理(request handler)拥有完全相同的生命周期,每一个请求处理都有他自己的一组Lua全局变量并且这就是请求隔离的概念。这个Lua模块实际上是由第一个Nginx请求处理加载的并且被require()缓存,构建在package.loaded table里以便后续的引用,并且在一些Lua模块里使用内置的module()函数会有一些副作用也就是在加载了模块的表中设置了而一些全局变量。但是这些全局变量会在这个请求处理结束后被清除掉,并且每一个后续的请求处理都有他自己的(干净的)全局变量。所以其中一个会得到访问到nil值的Lua异常。

在ngx_lua的上下文中使用Lua全局变量通常是不明智的,因为:

  1. 在并发请求中滥用Lua的全局变量有有害的副作用,而这些变量应该在local范围中被使用。
  2. 在Lua的全局环境变量表中查找Lua全局变量会引起一次昂贵的计算。
  3. 一些Lua全局变量的引用可能包含输入(typing)错误,而这些错误很难被debug

因此强烈建议总是要用一个适当的local作用域来声明。


  -- Avoid
  foo = 123
  -- Recommended
  local foo = 123

  -- Avoid
  function foo() return 123 end
  -- Recommended
  local function foo() return 123 end

要在你的Lua代码里面找到所有的Lua全局变量的实例,可以在所有.lua源文件里运行lua-releng tool命令。

$ lua-releng
  Checking use of Lua global variables in file lib/foo/bar.lua ...
          1       [1489]  SETGLOBAL       7 -1    ; contains
          55      [1506]  GETGLOBAL       7 -3    ; setvar
          3       [1545]  GETGLOBAL       3 -4    ; varexpand

输出表明在文件lib/foo/bar.lua的第1489行写入了一个名为contains的全局变量,在1506行读取了全局变量contains,在1545读取了全局变量varexpand

这个工具可以确保在Lua模块的方法中定义的局部变量都声明了local关键字,否则抛出一个运行时的错误。它阻止了访问这些变量时的不良竞争条件。查看[在同一个Nginx Worker中数据共享]了解背后的原因

由其他模块的子请求规则配置的Location

ngx.location.capture 和 ngx.location.capture_multi 指令不能capture包含add_before_body, add_after_body, auth_request, echo_location, echo_location_async, echo_subrequest, 或者 echo_subrequest_async 指令的location。


location /foo {
   content_by_lua_block{
     res = ngx.location.capture("/bar")
   }
}
location /bar {
   echo_location /blah;
}
location /blah {
   echo "Success!";
}
  $ curl -i http://example.com/foo

将不能按照预期正常工作。

Cosockets 不是在每个地方都有效

由于nginx核心的内部限制,cosocket API 在下面这些上下文中是被禁用的:set_by_lua*, log_by_lua*, header_filter_by_lua*, 和 body_filter_by_lua

Cosockets目前同样在init_by_lua* 和 init_worker_by_lua*指令上下文中是被禁用的但是我们有可能在未来支持这些上下文因为在nginx核心里面没有限制(或者限制有可能工作)

这里存在一个变通的方法,无论怎样,当原始的上下文 需要等待cosocket结果。就是说,通过ngx.timer.at的API创建以一个零延时的计时器并且在定时器处理程序中处理cosocket结果,从而为了在原始上下文中创建定时器来异步执行。

特殊的转义序列

注意v0.9.17版本之后,这个缺陷,可以通过使用*_by_lua_block {}配置指令来避免。

PCRE序列例如\d\s,或者\w,需要特别注意因为在字符串常量中,反斜杠字符,\,在Lua语言解析器和nginx配置文件解析器中(如果不是在一个*_by_lua_block {}指令中)处理之前都被剔除了。所以下面的片段不会按照预期的那样工作:


  #nginx.conf
  ? location /test {
  ?     content_by_lua '
  ?         local regex = "\d+"  -- THIS IS WRONG OUTSIDE OF A *_by_lua_block DIRECTIVE
  ?         local m = ngx.re.match("hello, 1234", regex)
  ?         if m then ngx.say(m[0]) else ngx.say("not matched!") end
  ?     ';
  ? }
  # 计算结果为 "not matched!"

为了避免这种情况,两次 转义那个反斜线:


 # nginx.conf
 location /test {
     content_by_lua '
         local regex = "\\\\d+"
         local m = ngx.re.match("hello, 1234", regex)
         if m then ngx.say(m[0]) else ngx.say("not matched!") end
     ';
 }
 # 计算结果为 "1234"

这里,\\\\d+ 会被Nginx配置文件解析器精简为\\d+ 并且在运行时被Lua语言解析器进一步精简为 \d+

作为另一种选择,这个正则表达式模式可以在Lua中可以通过一个长方括号字符串的方式展示,通过把它放到“长方括号里”,[[...]],在这种情况下,反斜线只需要在nginx配置文件解析器中被转义一次。


# nginx.conf
location /test{
    content_by_lua '
        local regex = [[\\d+]]
        local m = ngx.re.match("hello,1234",regex)
        if m then ngx.say(m[0]) else ngx.say("not matched!") end
    ';
}
# 计算结果为 “1234”

这里, [[\\d+]]在Nginx配置文件解析器里面被精简为[[\d+]]并且这是能够被正确处理的。

注意如果正则表表达式里面包含[...]序列的话,需要使用一种更长的形式的长方括号, [=[...]=]。如果需要的话,[=[...]=]可以作为默认的形式。

# nginx.conf
location /test {
      content_by_lua '
          local regex = [=[[0-9]+]=]
          local m = ngx.re.match("hello,12345",regex)
          if m then ngx.say(m[0]) else ngx.say("not matched!") end
      ';
}
# 计算结果为“1234”

另外一种转义PCRE序列的方法是确保Lua代码被放置在外部的脚本文件里,并且使用各种*_by_lua_file指令执行。使用这种方法,反斜杠只会给Lua语言解析器精简并且每个只需要被转义一次。


-- test.lua
local regex = "\\d+"
local m = ngx.re.match("hello,1234",regex)
if m then ngx.say(m[0]) else ngx.say("not matched!") end
-- 计算结果是“1234”

在外部脚本文件里,PCRE序列以长方括号的Lua字符常量提供则不需要修改。


-- test.lua
local regex = [[\d+]]
local m = ngx.re.match("hello,1234",regex)
if m then ngx.say(m[0]) else ngx.say("not matched!") end
-- 计算结果为“1234”

正如上面所提到的,PCRE序列在*_by_lua_block{}指令中提供的不需要修改(从v0.9.17版本之后可用)。


#nginx.conf
location /test {
    content_by_lua_block {
        local regex = "\d+"
        local m = ngx.re.match("hello,1234",regex)
        if m then ngx.say(m[0]) else ngx.say("not matched!") end
    }
}  
#计算结果为“1234”

不支持混合SSI

不支持在同一个Nginx请求中混合ngx_lua和SSI。只单一的使用ngx_lua。你在SSI上能做的任何事都可以在ngx_lua上实现并且当使用ngx_lua的时候会更有效率。

不完全支持SPDY模式

某些ngx_lua提供的Lua API在Nginx的SPDY模式下不能工作:ngx.location.capture, ngx.location.capture_multi, 和 ngx.req.socket.

在短路请求中缺失数据

Nginx可能提前终止一些请求(至少):

  • 400 (Bad Request)
  • 405 (Not Allowed)
  • 408 (Request Timeout)
  • 414 (Request URI Too Large)
  • 494 (Request Headers Too Large)
  • 499 (Client Closed Request)
  • 500 (Internal Server Error)
  • 501 (Not Implemented)

这意味着会跳过正常执行的阶段,例如rewrite或access阶段。这同时意味着不管后面阶段的执行,例如log_by_lua,将不会获得通常在这些阶段设置的信息。

测试套件

在运行测试套件时需要下面的依赖:

  • Nginx 版本 >= 1.4.2

  • Perl 模块:

    • Test::Nginx: https://github.com/openresty/test-nginx
  • Nginx 模块:

    • ngx_devel_kit
    • ngx_set_misc
    • ngx_auth_request (this is not needed if you're using Nginx 1.5.4+.
    • ngx_echo
    • ngx_memc
    • ngx_srcache
    • ngx_lua (i.e., this module)
    • ngx_lua_upstream
    • ngx_headers_more
    • ngx_drizzle
    • ngx_rds_json
    • ngx_coolkit
    • ngx_redis2
      这些模考中添加配置的顺序是很重要的,因为过滤链中任何过滤模块的位置决定了最终的输出。例如:上面就是正确的添加顺序。
  • 第三方Lua库:

    • lua-cjson
  • 应用程序:

    • mysql: 创建数据库 'ngx_test', 赋给用户 'ngx_test' 所有权限, 密码是 'ngx_test'
    • memcached: 监听默认端口, 11211.
    • redis: 监听默认端口, 6379.

在设置测试环境是也可以查看developer build script的更多细节。

在默认的测试模式下运行整个测试套件:


    cd /path/to/lua-nginx-module
    export PATH=/path/to/your/nginx/sbin:$path
    prove -I/path/to/test-nginx/lib -r t
  
``

要执行特殊的测试文件:

```bash

    cd /path/to/lua-nginx-module
    export PATH = /path/to/your/nginx/sbin:$PATH
    prove -I/path/to/test-nginx/lib t/002-connect.t t/003-errors.t

要在一个特殊的测试文件里面运行一个特定的测试模块,添加 --- ONLY 这行到你想运行的测试模块中,并且使用prove工具运行那个.t文件。

你可能感兴趣的:([code.openresty] Openresty框架-Nginx的lua模块)