跳转至

lua: Lua 脚本支持 NGINX

安装

您可以在任何基于 RHEL 的发行版中安装此模块,包括但不限于:

  • RedHat Enterprise Linux 7、8、9 和 10
  • CentOS 7、8、9
  • AlmaLinux 8、9
  • Rocky Linux 8、9
  • Amazon Linux 2 和 Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-lua
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-lua

通过在 /etc/nginx/nginx.conf 顶部添加以下内容来启用该模块:

load_module modules/ngx_http_lua_module.so;

本文档描述了 nginx-module-lua v0.10.29.post2,于 2025 年 12 月 16 日发布。


欢迎订阅我们的 官方 YouTube 频道,OpenResty

概述

 # 设置纯 Lua 外部库的搜索路径(';;' 是默认路径):

 # 设置用 C 编写的 Lua 外部库的搜索路径(也可以使用 ';;'):

 server {
     location /lua_content {
         # MIME 类型由 default_type 决定:
         default_type 'text/plain';

         content_by_lua_block {
             ngx.say('Hello,world!')
         }
     }

     location /nginx_var {
         # MIME 类型由 default_type 决定:
         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_block {
             ngx.req.read_body()  -- 明确读取请求体
             local data = ngx.req.get_body_data()
             if data then
                 ngx.say("body data:")
                 ngx.print(data)
                 return
             end

             -- 请求体可能会缓存在临时文件中:
             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 中的透明非阻塞 I/O
     # (更好的方法是使用 cosockets)
     location = /lua {
         # MIME 类型由 default_type 决定:
         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(ngx.var.request_body, "evil")
            then
                return ngx.redirect("/terms_of_use.html")
            end

            -- 测试通过
        }

        # proxy_pass/fastcgi_pass/etc 设置
     }
 }

描述

该模块将 LuaJIT 2.0/2.1 嵌入到 Nginx 中。 它是 OpenResty 的核心组件。如果您使用此模块,则实际上是在使用 OpenResty。

自该模块的 v0.10.16 版本以来,不再支持标准 Lua 解释器(也称为 "PUC-Rio Lua")。本文档交替使用 "Lua" 和 "LuaJIT" 来指代 LuaJIT 解释器。

通过利用 Nginx 的子请求,该模块允许将强大的 Lua 线程(称为 Lua "协程")集成到 Nginx 事件模型中。

Apache 的 mod_luaLighttpd 的 mod_magnet 不同,使用此模块执行的 Lua 代码可以在网络流量上 100% 非阻塞,只要使用本模块提供的 Nginx API for Lua 来处理对上游服务(如 MySQL、PostgreSQL、Memcached、Redis 或上游 HTTP Web 服务)的请求。

至少可以与此模块一起使用以下 Lua 库和 Nginx 模块:

几乎所有 Nginx 模块都可以通过 ngx.location.capturengx.location.capture_multi 与此 ngx_lua 模块一起使用,但建议使用这些 lua-resty-* 库,而不是创建子请求来访问 Nginx 上游模块,因为前者通常更加灵活且内存效率更高。

Lua 解释器(也称为 "Lua 状态" 或 "LuaJIT VM 实例")在单个 Nginx 工作进程中的所有请求之间共享,以最小化内存使用。请求上下文通过轻量级 Lua 协程进行隔离。

加载的 Lua 模块在 Nginx 工作进程级别持久存在,即使在高负载下,Lua 的内存占用也很小。

该模块插入到 Nginx 的 "http" 子系统中,因此只能使用 HTTP 系列的下游通信协议(HTTP 0.9/1.0/1.1/2.0、WebSockets 等)。如果您想与下游客户端进行通用 TCP 通信,则应使用 ngx_stream_lua 模块,该模块提供兼容的 Lua API。

典型用法

仅举几例:

  • 在 Lua 中混合和处理各种 Nginx 上游输出(代理、drizzle、postgres、redis、memcached 等)的输出,
  • 在请求实际到达上游后,在 Lua 中进行任意复杂的访问控制和安全检查,
  • 以任意方式操作响应头(通过 Lua)
  • 从外部存储后端(如 redis、memcached、mysql、postgresql)获取后端信息,并使用该信息动态选择要访问的上游后端,
  • 在内容处理程序中编写任意复杂的 Web 应用程序,使用同步但仍然非阻塞的方式访问数据库后端和其他存储,
  • 在重写阶段使用 Lua 进行非常复杂的 URL 调度,
  • 使用 Lua 实现 Nginx 子请求和任意位置的高级缓存机制。

可能性是无限的,因为该模块允许将 Nginx 中的各种元素结合在一起,并向用户暴露 Lua 语言的强大功能。该模块提供了完整的脚本灵活性,同时在 CPU 时间和内存占用方面提供了与本地 C 语言程序相当的性能水平,这要归功于 LuaJIT 2.x。

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

Nginx 兼容性

该模块的最新版本与以下版本的 Nginx 兼容:

  • 1.29.x (最后测试:1.29.2)
  • 1.27.x (最后测试:1.27.1)
  • 1.25.x (最后测试:1.25.1)
  • 1.21.x (最后测试:1.21.4)
  • 1.19.x (最后测试:1.19.3)
  • 1.17.x (最后测试:1.17.8)
  • 1.15.x (最后测试:1.15.8)
  • 1.14.x
  • 1.13.x (最后测试:1.13.6)
  • 1.12.x
  • 1.11.x (最后测试:1.11.2)
  • 1.10.x
  • 1.9.x (最后测试:1.9.15)
  • 1.8.x
  • 1.7.x (最后测试:1.7.10)
  • 1.6.x

Nginx 1.6.0 之前的核心版本不支持。

代码仓库

该项目的代码仓库托管在 GitHub 上,地址为 openresty/lua-nginx-module

LuaJIT 字节码支持

观看 YouTube 视频 "Measure Execution Time of Lua Code Correctly in OpenResty"

Precompile Lua Modules into LuaJIT Bytecode to Speedup OpenResty Startup

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

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

-bg 选项可用于在 LuaJIT 字节码文件中包含调试信息:

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

有关 -b 选项的更多详细信息,请参阅官方 LuaJIT 文档:

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

请注意,LuaJIT 2.1 生成的字节码文件与 LuaJIT 2.0 不兼容,反之亦然。LuaJIT 2.1 字节码的支持首次在 ngx_lua v0.9.3 中添加。

尝试将标准 Lua 5.1 字节码文件加载到链接到 LuaJIT 2.0/2.1 的 ngx_lua 实例中(或反之)将导致 Nginx 错误消息,如下所示:

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

通过 Lua 原语(如 requiredofile)加载字节码文件应该始终按预期工作。

系统环境变量支持

如果您想通过标准 Lua API os.getenv 在 Lua 中访问系统环境变量,例如 foo,则应在 nginx.conf 文件中通过 env 指令 列出此环境变量名称。例如,

 env foo;

HTTP 1.0 支持

HTTP 1.0 协议不支持分块输出,并且在响应体不为空时需要显式的 Content-Length 头以支持 HTTP 1.0 keep-alive。 因此,当发出 HTTP 1.0 请求并且 lua_http10_buffering 指令设置为 on 时,ngx_lua 将缓冲 ngx.sayngx.print 调用的输出,并且在接收到所有响应体输出之前推迟发送响应头。 此时,ngx_lua 可以计算主体的总长度并构造适当的 Content-Length 头返回给 HTTP 1.0 客户端。 然而,如果在运行的 Lua 代码中设置了 Content-Length 响应头,则即使 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 对象文件,然后将 .o 文件直接链接到您的 Nginx 构建中。

以下是一个简单示例。假设我们有以下名为 foo.lua.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 文件,因为它已经被编译到 nginx 可执行文件中。

如果您希望在调用 require 时在 Lua 模块名称中使用点,例如

 local foo = require "resty.foo"

那么您需要在编译为 .o 文件之前将 foo.lua 文件重命名为 resty_foo.lua

在编译 .lua 文件为 .o 文件时,使用与构建 nginx + 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 文件构建一个静态库(或归档),如下所示:

 ar rcus libmyluafiles.a *.o

然后您可以将 myluafiles 库作为一个整体链接到您的 nginx 可执行文件中:

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

其中 /path/to/lib 是包含 libmyluafiles.a 文件的目录的路径。需要注意的是,这里需要链接器选项 --whole-archive,否则我们的归档将被跳过,因为在 nginx 可执行文件的主要部分中没有提到我们的归档中的符号。

在 Nginx 工作进程中共享数据

要在同一 Nginx 工作进程处理的所有请求之间全局共享数据,请将共享数据封装到 Lua 模块中,使用 Lua require 内置函数导入模块,然后在 Lua 中操作共享数据。这是可行的,因为所需的 Lua 模块只会加载一次,所有协程将共享模块的同一副本(包括其代码和数据)。

请注意,强烈不建议使用全局 Lua 变量,因为这可能导致并发请求之间出现意外的竞争条件。

以下是通过 Lua 模块在 Nginx 工作进程中共享数据的小示例:

 -- 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 主进程下有多个工作进程时,数据共享无法跨越这些工作进程之间的进程边界。

通常建议以这种方式共享只读数据。您还可以在每个 Nginx 工作进程的所有并发请求之间共享可更改的数据,只要在计算过程中没有 非阻塞 I/O 操作(包括 ngx.sleep)。只要您不将控制权交还给 Nginx 事件循环和 ngx_lua 的轻量线程调度器(即使是隐式的),就不会出现竞争条件。因此,在工作级别共享可更改数据时,请始终非常小心。错误的优化很容易在负载下导致难以调试的竞争条件。

如果需要全服务器范围的数据共享,则可以使用以下一种或多种方法:

  1. 使用本模块提供的 ngx.shared.DICT API。
  2. 仅使用单个 Nginx 工作进程和单个服务器(但在单台机器上有多核 CPU 或多个 CPU 时不推荐这样做)。
  3. 使用数据存储机制,如 memcachedredisMySQLPostgreSQLOpenResty 官方版本 附带一组伴随的 Nginx 模块和 Lua 库,提供与这些数据存储机制的接口。

已知问题

TCP 套接字连接操作问题

tcpsock:connect 方法可能会在连接失败时指示 success,例如 Connection Refused 错误。

然而,稍后尝试操作 cosocket 对象将失败并返回实际的错误状态消息,由失败的连接操作生成。

此问题是由于 Nginx 事件模型的限制,仅在 Mac OS X 上出现。

Lua 协程的让步/恢复

  • 由于 Lua 的 dofilerequire 内置函数当前作为 C 函数在 LuaJIT 2.0/2.1 中实现,如果通过 dofilerequire 加载的 Lua 文件在 Lua 文件的 顶层 范围内调用 ngx.location.capture*ngx.execngx.exit 或其他需要让步的 API 函数,则会引发 Lua 错误 "attempt to yield across C-call boundary"。为避免这种情况,请将这些需要让步的调用放入 Lua 文件中的自定义 Lua 函数中,而不是放在文件的顶层范围内。

Lua 变量作用域

导入模块时必须小心,应使用以下形式:

 local xxx = require('xxx')

而不是旧的已弃用形式:

 require('xxx')

原因如下:根据设计,全局环境的生命周期与与其相关联的 Nginx 请求处理程序完全相同。每个请求处理程序都有自己的一组 Lua 全局变量,这就是请求隔离的理念。Lua 模块实际上是由第一个 Nginx 请求处理程序加载的,并由 require() 内置函数在 package.loaded 表中缓存以供后续引用,而一些 Lua 模块使用的 module() 内置函数会产生副作用,将全局变量设置为加载的模块表。但是,这个全局变量将在请求处理程序结束时被清除,每个后续请求处理程序都有自己(干净的)全局环境。因此,访问 nil 值时会引发 Lua 异常。

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

  1. 错误使用 Lua 全局变量会对并发请求产生不利影响,而这些变量应该在作用域内是局部的,
  2. Lua 全局变量需要在全局环境中进行 Lua 表查找,这在计算上是昂贵的,并且
  3. 一些 Lua 全局变量引用可能包含打字错误,这使得调试变得困难。

因此,强烈建议始终在适当的局部作用域内声明这些变量。

 -- 避免
 foo = 123
 -- 推荐
 local foo = 123

 -- 避免
 function foo() return 123 end
 -- 推荐
 local function foo() return 123 end

要查找 Lua 代码中的所有 Lua 全局变量实例,可以在所有 .lua 源文件中运行 lua-releng 工具

$ 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 行读取了全局变量 setvar,第 1545 行读取了全局变量 varexpand

此工具将确保 Lua 模块函数中的局部变量都使用 local 关键字声明,否则将抛出运行时异常。它防止在访问这些变量时出现不必要的竞争条件。有关此背后的原因,请参见 在 Nginx 工作进程中共享数据

由其他模块的子请求指令配置的位置

ngx.location.capturengx.location.capture_multi 指令无法捕获包含 add_before_bodyadd_after_bodyauth_requestecho_locationecho_location_asyncecho_subrequestecho_subrequest_async 指令的位置。

 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+"  -- 这是在 *_by_lua_block 指令外部错误
 ?         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"

在外部脚本文件中,作为长括号 Lua 字符串字面量呈现的 PCRE 序列不需要修改。

 -- 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"

如前所述,在 *_by_lua_block {} 指令中呈现的 PCRE 序列(自 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"

注意 当 Lua 代码非常长时,建议使用 by_lua_file

不支持与 SSI 混合

在同一 NGINX 请求中完全不支持将 SSI 与 ngx_lua 混合使用。只需独占使用 ngx_lua。您可以使用 ngx_lua 完成与 SSI 相同的所有操作,并且在使用 ngx_lua 时可能更高效。

SPDY 模式不完全支持

某些由 ngx_lua 提供的 Lua API 在 Nginx 的 SPDY 模式下尚不工作:ngx.location.capturengx.location.capture_multingx.req.socket

短路请求缺失数据

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

  • 400(错误请求)
  • 405(不允许)
  • 408(请求超时)
  • 413(请求实体过大)
  • 414(请求 URI 过大)
  • 494(请求头过大)
  • 499(客户端关闭请求)
  • 500(内部服务器错误)
  • 501(未实现)

这意味着通常运行的阶段会被跳过,例如重写或访问阶段。这也意味着无论如何运行的后续阶段,例如 log_by_lua,将无法访问通常在这些阶段中设置的信息。

变更

此模块每个版本中所做的更改列在 OpenResty 捆绑包的变更日志中:

https://openresty.org/#Changes

测试套件

运行测试套件所需的依赖项:

这些模块在配置期间添加的顺序很重要,因为过滤链中任何过滤器模块的位置决定了最终输出。例如,正确的添加顺序如上所示。

  • 第三方 Lua 库:

  • 应用程序:

    • mysql:创建数据库 'ngx_test',授予用户 'ngx_test' 所有权限,密码为 'ngx_test'
    • memcached:监听默认端口 11211。
    • redis:监听默认端口 6379。

有关设置测试环境的更多详细信息,请参阅 开发者构建脚本

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

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

要运行特定测试文件:

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

要在特定测试文件中运行特定测试块,请在要运行的测试块中添加行 --- ONLY,然后使用 prove 工具运行该 .t 文件。

还有各种基于 mockeagain、valgrind 等的测试模式。有关各种高级测试模式的更多详细信息,请参阅 Test::Nginx 文档。另请参阅在 Amazon EC2 上运行的 Nginx 测试集群的测试报告:https://qa.openresty.org

另请参阅

博客文章:

其他相关模块和库:

指令

脚本 Nginx 与 Lua 的基本构建块是指令。指令用于指定用户 Lua 代码的运行时间以及结果将如何使用。下面是一个图表,显示了指令执行的顺序。

Lua Nginx 模块指令

lua_load_resty_core

语法: lua_load_resty_core on|off

默认值: lua_load_resty_core on

上下文: http

自该模块的 v0.10.16 版本以来,此指令已被弃用。resty.core 模块来自 lua-resty-core 现在在 Lua VM 初始化期间强制加载。指定此指令将无效。

此指令首次在 v0.10.15 版本中引入,用于可选加载 resty.core 模块。

返回目录

lua_capture_error_log

语法: lua_capture_error_log size

默认值:

上下文: http

启用指定 size 的缓冲区以捕获所有 Nginx 错误日志消息数据(不仅仅是由此模块或 Nginx http 子系统生成的,而是所有内容),而不接触文件或磁盘。

您可以在 size 值中使用单位,如 km,例如:

 lua_capture_error_log 100k;

作为经验法则,4KB 的缓冲区通常可以容纳大约 20 条典型的错误日志消息。所以请计算一下!

此缓冲区永远不会增长。如果已满,新错误日志消息将替换缓冲区中的最旧消息。

缓冲区的大小必须大于单个错误日志消息的最大长度(在 OpenResty 中为 4K,在标准 NGINX 中为 2K)。

您可以通过 get_logs() 函数在 Lua 中读取缓冲区中的消息,该函数来自 ngx.errlog 模块的 lua-resty-core 库。此 Lua API 函数将返回捕获的错误日志消息,并将这些已读取的消息从全局捕获缓冲区中删除,为任何新错误日志数据腾出空间。因此,如果用户读取缓冲的错误日志数据足够快,则不应将此缓冲区配置得太大。

请注意,标准 error_log 指令中指定的日志级别 确实 对此捕获功能有影响。它仅捕获日志级别不低于 error_log 指令中指定的日志级别的日志消息。用户仍然可以选择通过 Lua API 函数 errlog.set_filter_level 动态设置更高的过滤日志级别。因此,它比静态的 error_log 指令更灵活。

值得注意的是,无法捕获调试日志,除非使用 ./configure 选项 --with-debug 构建 OpenResty 或 Nginx。并且在生产构建中强烈不建议启用调试日志,因为开销很高。

此指令首次在 v0.10.9 版本中引入。

返回目录

lua_use_default_type

语法: lua_use_default_type on | off

默认值: lua_use_default_type on

上下文: http、server、location、location if

指定是否使用 default_type 指令指定的 MIME 类型作为 Content-Type 响应头的默认值。如果不希望 Lua 请求处理程序的默认 Content-Type 响应头,则禁用此指令。

此指令默认开启。

此指令首次在 v0.9.1 版本中引入。

返回目录

lua_malloc_trim

语法: lua_malloc_trim

默认值: lua_malloc_trim 1000

上下文: http

请求底层 libc 运行时库在 Nginx 核心处理的每 N 个请求后将其缓存的空闲内存释放回操作系统。默认情况下,N 为 1000。您可以使用自己的数字配置请求计数。较小的数字意味着更频繁的释放,这可能会导致更高的 CPU 时间消耗和较小的内存占用,而较大的数字通常会导致较小的 CPU 时间开销和相对较大的内存占用。只需根据您的用例调整数字即可。

将参数配置为 0 实际上会完全关闭定期内存修整。

 lua_malloc_trim 0;  # 完全关闭修整

当前实现使用 Nginx 日志阶段处理程序进行请求计数。因此,nginx.conf 中出现的 log_subrequest on 指令可能会在涉及子请求时加快计数。默认情况下,仅“主请求”计数。

请注意,此指令 影响 LuaJIT 自己基于 mmap 系统调用的分配的内存。

此指令首次在 v0.10.7 版本中引入。

返回目录

lua_code_cache

语法: lua_code_cache on | off

默认值: lua_code_cache on

上下文: http、server、location、location if

启用或禁用 Lua 代码缓存,用于 *_by_lua_file 指令中的 Lua 代码(如 set_by_lua_filecontent_by_lua_file)和 Lua 模块。

关闭时,由 ngx_lua 提供的每个请求将在单独的 Lua VM 实例中运行,自 0.9.3 版本起。因此,在 set_by_lua_filecontent_by_lua_fileaccess_by_lua_file 等中引用的 Lua 文件将不会被缓存,所有使用的 Lua 模块将从头加载。这样,开发人员可以采用编辑和刷新的方法。

但是请注意,内联在 nginx.conf 中编写的 Lua 代码(如通过 set_by_luacontent_by_luaaccess_by_luarewrite_by_lua 指定的代码)在您编辑 nginx.conf 文件中的内联 Lua 代码时不会更新,因为只有 Nginx 配置文件解析器可以正确解析 nginx.conf 文件,唯一的方法是通过发送 HUP 信号重新加载配置文件或重新启动 Nginx。

即使启用代码缓存,通过 dofileloadfile 加载的 Lua 文件在 *_by_lua_file 中也无法被缓存(除非您自己缓存结果)。通常,您可以使用 init_by_luainit_by_lua_file 指令加载所有这些文件,或者仅将这些 Lua 文件作为真正的 Lua 模块并通过 require 加载它们。

ngx_lua 模块不支持 Apache mod_lua 模块中可用的 stat 模式(尚未)。

禁用 Lua 代码缓存在生产中强烈不建议使用,仅在开发期间使用,因为这会对整体性能产生显著的负面影响。例如,"hello world" Lua 示例的性能在禁用 Lua 代码缓存后可能会下降一个数量级。

返回目录

lua_thread_cache_max_entries

语法: lua_thread_cache_max_entries

默认值: lua_thread_cache_max_entries 1024

上下文: http

指定工作进程级别 Lua 线程对象缓存中允许的最大条目数。

此缓存在所有“轻线程”之间回收 Lua 线程 GC 对象。

<num> 的零值禁用缓存。

请注意,此功能需要 OpenResty 的 LuaJIT 和新的 C API lua_resetthread

此功能首次在 v0.10.9 版本中引入。

返回目录

lua_regex_cache_max_entries

语法: lua_regex_cache_max_entries

默认值: lua_regex_cache_max_entries 1024

上下文: http

指定工作进程级别编译的正则表达式缓存中允许的最大条目数。

ngx.re.matchngx.re.gmatchngx.re.subngx.re.gsub 中使用的正则表达式将在此缓存中缓存,如果指定了正则表达式选项 o(即编译一次标志)。

允许的默认条目数为 1024,当达到此限制时,将不再缓存新的正则表达式(就像未指定 o 选项一样),并且在 error.log 文件中将记录一条警告:

2011/08/27 23:18:26 [warn] 31997#0: *1 lua exceeding regex cache max entries (1024), ...

如果您使用的是通过加载 resty.core.regex 模块(或仅加载 resty.core 模块)实现的 lua-resty-core,则在此处使用的正则表达式缓存将使用 LRU 缓存。

请勿为动态生成的正则表达式(和/或 ngx.re.subngx.re.gsubreplace 字符串参数)启用 o 选项,以避免达到指定限制。

返回目录

lua_regex_match_limit

语法: lua_regex_match_limit

默认值: lua_regex_match_limit 0

上下文: http

指定 PCRE 库在执行 ngx.re API 时使用的“匹配限制”。引用 PCRE 手册,“限制...会限制可以发生的回溯量。”

当达到限制时,错误字符串 "pcre_exec() failed: -8" 将由 ngx.re API 函数返回。

将限制设置为 0 时,将使用编译 PCRE 库时的默认“匹配限制”。这是此指令的默认值。

此指令首次在 v0.8.5 版本中引入。

返回目录

lua_package_path

语法: lua_package_path

默认值: LUA_PATH 环境变量的内容或 Lua 的编译默认值。

上下文: http

设置 Lua 模块搜索路径,由 set_by_luacontent_by_lua 等指定。路径字符串采用标准 Lua 路径形式,;; 可以用于表示原始搜索路径。

v0.5.0rc29 版本起,搜索路径字符串中可以使用特殊符号 $prefix${prefix} 来指示由启动 Nginx 服务器时的 -p PATH 命令行选项确定的 server prefix 的路径。

返回目录

lua_package_cpath

语法: lua_package_cpath

默认值: LUA_CPATH 环境变量的内容或 Lua 的编译默认值。

上下文: http

设置 Lua C 模块搜索路径,由 set_by_luacontent_by_lua 等指定。cpath 字符串采用标准 Lua cpath 形式,;; 可以用于表示原始 cpath。

v0.5.0rc29 版本起,搜索路径字符串中可以使用特殊符号 $prefix${prefix} 来指示由启动 Nginx 服务器时的 -p PATH 命令行选项确定的 server prefix 的路径。

返回目录

init_by_lua

语法: init_by_lua

上下文: http

阶段: 加载配置

注意v0.9.17 发布以来,不建议使用此指令。请改用 init_by_lua_block 指令。

init_by_lua_block 指令类似,但直接在 Nginx 字符串字面量中接受 Lua 源代码(这需要特殊字符转义)。

例如,

 init_by_lua '
     print("I need no extra escaping here, for example: \r\nblah")
 '

此指令首次在 v0.5.5 版本中引入。

返回目录

init_by_lua_block

语法: init_by_lua_block { lua-script }

上下文: http

阶段: 加载配置

当 Nginx 接收到 HUP 信号并开始重新加载配置文件时,Lua VM 也将重新创建,init_by_lua_block 将在新的 Lua VM 上再次运行。如果 lua_code_cache 指令关闭(默认开启),则 init_by_lua_block 处理程序将在每个请求上运行,因为在这种特殊模式下,每个请求始终创建一个独立的 Lua VM。

通常,您可以通过此钩子在服务器启动时预加载 Lua 模块,并利用现代操作系统的写时复制(COW)优化。以下是预加载 Lua 模块的示例:

 # 这在分叉出 Nginx 工作进程之前运行:
 init_by_lua_block { require "cjson" }

 server {
     location = /api {
         content_by_lua_block {
             -- 以下 require() 将仅返回
             --  package.loaded 中加载的模块:
             ngx.say(require "cjson".encode{dog = 5, cat = 6})
         }
     }
 }

您还可以在此阶段初始化 lua_shared_dict shm 存储。以下是此示例:

 lua_shared_dict dogs 1m;

 init_by_lua_block {
     local dogs = ngx.shared.dogs
     dogs:set("Tom", 56)
 }

 server {
     location = /api {
         content_by_lua_block {
             local dogs = ngx.shared.dogs
             ngx.say(dogs:get("Tom"))
         }
     }
 }

但请注意,经过配置重新加载(例如通过发送 HUP 信号)时,lua_shared_dict 的 shm 存储不会被清除。因此,如果您不希望在这种情况下在 init_by_lua_block 代码中重新初始化 shm 存储,则只需在 shm 存储中设置一个自定义标志,并始终在 init_by_lua_block 代码中检查该标志。

由于此上下文中的 Lua 代码在 Nginx 分叉其工作进程之前运行(如果有),因此在所有工作进程之间共享的任何数据或代码都将享受许多操作系统提供的 写时复制(COW) 特性,从而节省大量内存。

请勿在此上下文中初始化自己的 Lua 全局变量,因为使用 Lua 全局变量会产生性能损失,并可能导致全局命名空间污染(有关详细信息,请参见 Lua 变量作用域 部分)。推荐的方法是使用适当的 Lua 模块 文件(但不要使用标准 Lua 函数 module() 定义 Lua 模块,因为它也会污染全局命名空间),并在 init_by_lua_block 或其他上下文中调用 require() 加载自己的模块文件。

仅支持一小部分 Nginx API for Lua 在此上下文中:

未来可能会根据用户请求在此上下文中支持更多 Nginx API for Lua。

基本上,您可以安全地在此上下文中使用阻塞 I/O 的 Lua 库,因为在服务器启动期间阻塞主进程是完全可以接受的。即使 Nginx 核心在配置加载阶段也会阻塞 I/O(至少在解析上游主机名时)。

您应非常小心您在此上下文中注册的 Lua 代码中的潜在安全漏洞,因为 Nginx 主进程通常在 root 账户下运行。

此指令首次在 v0.9.17 版本中引入。

有关 OpenResty 和 Nginx 共享内存区域的更多详细信息,请参阅以下博客文章:

返回目录

init_by_lua_file

语法: init_by_lua_file

上下文: http

阶段: 加载配置

init_by_lua_block 等效,但 <path-to-lua-script-file> 指定的文件包含要执行的 Lua 代码或 LuaJIT 字节码

当给定相对路径如 foo/bar.lua 时,它们将被转换为相对于启动 Nginx 服务器时由 -p PATH 命令行选项确定的 server prefix 路径的绝对路径。

此指令首次在 v0.5.5 版本中引入。

返回目录

init_worker_by_lua

语法: init_worker_by_lua

上下文: http

阶段: 启动工作进程

注意v0.9.17 发布以来,不建议使用此指令。请改用 init_worker_by_lua_block 指令。

init_worker_by_lua_block 指令类似,但直接在 Nginx 字符串字面量中接受 Lua 源代码(这需要特殊字符转义)。

例如,

 init_worker_by_lua '
     print("I need no extra escaping here, for example: \r\nblah")
 ';

此指令首次在 v0.9.5 版本中引入。

返回目录

init_worker_by_lua_block

语法: init_worker_by_lua_block { lua-script }

上下文: http

阶段: 启动工作进程

在每个 Nginx 工作进程启动时运行指定的 Lua 代码,当启用主进程时。如果禁用主进程,则此钩子将在 init_by_lua* 之后运行。

此钩子通常用于创建每个工作进程的重复定时器(通过 ngx.timer.at Lua API),无论是用于后端健康检查还是其他定时例行工作。以下是一个示例,

 init_worker_by_lua_block {
     local delay = 3  -- 以秒为单位
     local new_timer = ngx.timer.at
     local log = ngx.log
     local ERR = ngx.ERR
     local check

     check = function(premature)
         if not premature then
             -- 执行健康检查或其他例行工作
             local ok, err = new_timer(delay, check)
             if not ok then
                 log(ERR, "failed to create timer: ", err)
                 return
             end
         end

         -- 在定时器中执行某些操作
     end

     local hdl, err = new_timer(delay, check)
     if not hdl then
         log(ERR, "failed to create timer: ", err)
         return
     end

     --  init_worker_by_lua 中执行其他工作
 }

此指令首次在 v0.9.17 版本中引入。

此钩子自 v0.10.12 版本以来不再在缓存管理器和缓存加载器进程中运行。

返回目录

init_worker_by_lua_file

语法: init_worker_by_lua_file

上下文: http

阶段: 启动工作进程

init_worker_by_lua_block 等效,但 <lua-file-path> 指定的文件包含要执行的 Lua 代码或 Lua 字节码文件。

此指令首次在 v0.9.5 版本中引入。

此钩子自 v0.10.12 版本以来不再在缓存管理器和缓存加载器进程中运行。

返回目录

exit_worker_by_lua_block

语法: exit_worker_by_lua_block { lua-script }

上下文: http

阶段: 退出工作进程

在每个 Nginx 工作进程退出时运行指定的 Lua 代码,当启用主进程时。如果禁用主进程,则此钩子将在 Nginx 进程退出之前运行。

此钩子通常用于释放每个工作进程分配的资源(例如,通过 init_worker_by_lua* 分配的资源),或防止工作进程异常退出。

例如,

 exit_worker_by_lua_block {
     print("log from exit_worker_by_lua_block")
 }

在此处创建定时器(即使是 0 延迟定时器)是不允许的,因为它在所有定时器处理完毕后运行。

此指令首次在 v0.10.18 版本中引入。

[