跳转至

stream-lua: NGINX 流的 Lua 脚本支持

安装

您可以在任何基于 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-stream-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-stream-lua

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

load_module modules/ngx_stream_lua_module.so;

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


概述

events {
    worker_connections 1024;
}

stream {
    # 定义一个监听端口 1234 的 TCP 服务器:
    server {
        listen 1234;

        content_by_lua_block {
            ngx.say("Hello, Lua!")
        }
    }
}

设置为 SSL TCP 服务器:

stream {
    server {
        listen 4343 ssl;

        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
        ssl_certificate     /path/to/cert.pem;
        ssl_certificate_key /path/to/cert.key;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;

        content_by_lua_block {
            local sock = assert(ngx.req.socket(true))
            local data = sock:receive()  -- 从下游读取一行
            if data == "thunder!" then
                ngx.say("flash!")  -- 输出数据
            else
                ngx.say("boom!")
            end
            ngx.say("the end...")
        }
    }
}

也支持在 UNIX 域套接字上监听:

stream {
    server {
        listen unix:/tmp/nginx.sock;

        content_by_lua_block {
            ngx.say("What's up?")
            ngx.flush(true)  -- 刷新任何待处理的输出并等待
            ngx.sleep(3)  -- 睡眠 3 
            ngx.say("Bye bye...")
        }
    }
}

描述

这是将 ngx_http_lua_module 移植到 Nginx “stream” 子系统,以支持通用的 stream/TCP 客户端。

可用的 Lua API 和 Nginx 指令与 ngx_http_lua 模块的相同。

指令

以下指令直接从 ngx_http_lua 移植。有关它们的使用和行为的更多详细信息,请查看 ngx_http_lua 的文档。

Nginx “http” 子系统中的 send_timeout 指令在 “stream” 子系统中缺失。因此,ngx_stream_lua_module 使用 lua_socket_send_timeout 指令来代替。

注意: 以前版本的 stream_lua_nginx_module 中存在的延迟关闭指令已被移除,如有必要,可以使用新添加的 tcpsock:shutdown API 来模拟。

preread_by_lua_block

语法: preread_by_lua_block { lua-script }

上下文: stream, server

阶段: preread

作为 preread 阶段处理程序,并为每个连接(或数据报模式中的数据包)执行 lua-script 中指定的 Lua 代码字符串。 Lua 代码可以进行 API 调用,并在独立的全局环境(即沙箱)中作为新的协程执行。

可以使用 ngx.req.socket 获取原始请求套接字,并从客户端接收数据或向其发送数据。然而,请记住,调用请求套接字的 receive() 方法将消耗缓冲区中的数据,因此这些消耗的数据将不会被链条下游的处理程序看到。

preread_by_lua_block 代码将始终在 preread 处理阶段结束时运行,除非启用了 preread_by_lua_no_postpone

该指令首次在 v0.0.3 版本中引入。

返回目录

preread_by_lua_file

语法: preread_by_lua_file <path-to-lua-script-file>

上下文: stream, server

阶段: preread

等同于 preread_by_lua_block,不同之处在于 <path-to-lua-script-file> 指定的文件包含要执行的 Lua 代码或 LuaJIT 字节码。

可以在 <path-to-lua-script-file> 字符串中使用 Nginx 变量以提供灵活性。然而,这会带来一些风险,通常不推荐这样做。

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

当 Lua 代码缓存开启时(默认开启),用户代码在第一次连接时加载一次并缓存。每次修改 Lua 源文件时,必须重新加载 Nginx 配置。在开发过程中,可以通过在 nginx.conf 中将 lua_code_cache 设置为 off 来临时禁用 Lua 代码缓存,以避免必须重新加载 Nginx。

该指令首次在 v0.0.3 版本中引入。

返回目录

log_by_lua_block

语法: log_by_lua_block { lua-script }

上下文: stream, server

阶段: log

log 请求处理阶段运行指定为 <lua-script> 的 Lua 源代码。这不会替换当前的访问日志,而是在之前运行。

在此阶段,诸如 ngx.req.socketngx.socket.*ngx.sleepngx.say 等可挂起的 API 不可用

该指令首次在 v0.0.3 版本中引入。

返回目录

log_by_lua_file

语法: log_by_lua_file <path-to-lua-script-file>

上下文: stream, server

阶段: log

等同于 log_by_lua_block,不同之处在于 <path-to-lua-script-file> 指定的文件包含要执行的 Lua 代码或 LuaJIT 字节码。

可以在 <path-to-lua-script-file> 字符串中使用 Nginx 变量以提供灵活性。然而,这会带来一些风险,通常不推荐这样做。

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

当 Lua 代码缓存开启时(默认开启),用户代码在第一次连接时加载一次并缓存。每次修改 Lua 源文件时,必须重新加载 Nginx 配置。在开发过程中,可以通过在 nginx.conf 中将 lua_code_cache 设置为 off 来临时禁用 Lua 代码缓存,以避免必须重新加载 Nginx。

该指令首次在 v0.0.3 版本中引入。

返回目录

lua_add_variable

语法: lua_add_variable $var

上下文: stream

将变量 $var 添加到 “stream” 子系统,并使其可更改。如果 $var 已经存在,则该指令将不执行任何操作。

默认情况下,使用此指令添加的变量被视为“未找到”,使用 ngx.var 读取它们将返回 nil。但是,可以随时通过 ngx.var.VARIABLE API 重新分配它们。

该指令首次在 v0.0.4 版本中引入。

返回目录

preread_by_lua_no_postpone

语法: preread_by_lua_no_postpone on|off

上下文: stream

控制是否禁用将 preread_by_lua* 指令推迟到 preread 处理阶段结束时。默认情况下,该指令关闭,Lua 代码被推迟到 preread 阶段结束时运行。

该指令首次在 v0.0.4 版本中引入。

返回目录

Nginx API for Lua

许多 Lua API 函数是从 ngx_http_lua 移植的。有关这些 Lua API 函数的更多详细信息,请查看 ngx_http_lua 的官方手册。

该模块完全支持 Nginx stream 核心内部的新变量子系统。您可以访问 stream 核心或其他 stream 模块提供的任何 内置变量。 * 核心常量

`ngx.OK`、`ngx.ERROR` 等。

仅支持原始请求套接字,出于明显原因。raw 参数值被忽略,始终返回原始请求套接字。与 ngx_http_lua 不同,您仍然可以在通过此函数获取原始请求套接字后调用输出 API 函数,如 ngx.sayngx.printngx.flush

当 stream 服务器处于 UDP 模式时,从 ngx.req.socket 调用返回的下游套接字读取将仅返回单个数据包的内容。因此,读取调用将永远不会阻塞,并且在数据报中的所有数据被消耗时将返回 nil, "no more data"。但是,您可以选择使用下游套接字向客户端发送多个 UDP 数据包。

该模块返回的原始 TCP 套接字将包含以下额外方法:

返回目录

reqsock:receiveany

语法: data, err = reqsock:receiveany(max)

上下文: content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*

此方法类似于 tcpsock:receiveany 方法。

该方法自 v0.0.8 版本起引入到 stream-lua-nginx-module 中。

返回目录

tcpsock:shutdown

语法: ok, err = tcpsock:shutdown("send")

上下文: content_by_lua*

关闭请求套接字的写入部分,防止进一步写入客户端并发送 TCP FIN,同时保持读取部分打开。

目前仅支持 "send" 方向。使用除 "send" 以外的任何参数将返回错误。

如果您在调用此方法之前调用了任何输出函数(如 ngx.say),请考虑使用 ngx.flush(true) 确保所有忙碌的缓冲区在关闭套接字之前完全刷新。如果检测到任何忙碌的缓冲区,此方法将返回 nil 和错误信息 "socket busy writing"

此功能对于在实际完成所有传入数据的消费之前生成响应的协议特别有用。通常,当调用 tcpsock:close 而未先清空接收缓冲区时,内核将向客户端发送 RST。调用此方法将允许您继续从接收缓冲区读取,并防止发送 RST。

您还可以使用此方法模拟延迟关闭,类似于 ngx_http_core_module 提供的功能,适用于需要此行为的协议。以下是一个示例:

local LINGERING_TIME = 30 -- 30 秒
local LINGERING_TIMEOUT = 5000 -- 5 秒

local ok, err = sock:shutdown("send")
if not ok then
    ngx.log(ngx.ERR, "failed to shutdown: ", err)
    return
end

local deadline = ngx.time() + LINGERING_TIME

sock:settimeouts(nil, nil, LINGERING_TIMEOUT)

repeat
    local data, _, partial = sock:receive(1024)
until (not data and not partial) or ngx.time() >= deadline

返回目录

reqsock:peek

语法: ok, err = reqsock:peek(size)

上下文: preread_by_lua*

preread 缓冲区中查看客户端发送的下游数据,而不消耗它们。 也就是说,通过此 API 返回的数据将在后续阶段仍然向上游转发。

此函数需要一个必需的参数 size,即要查看的字节数。 对该函数的重复调用始终从 preread 缓冲区的开头返回数据。

请注意,preread 阶段发生在 TLS 握手之后。如果 stream 服务器配置为启用 TLS,则返回的数据将为明文。

如果 preread 缓冲区没有请求的数量的数据,则当前 Lua 线程将被挂起,直到有更多数据可用、超过 preread_buffer_sizepreread_timeout 超时。成功的调用始终返回请求的数量的数据,即不会返回部分数据。

当超过 preread_buffer_size 时,当前流会话将立即由流核心模块以会话状态代码 400 终止,并打印错误信息 "preread buffer full" 到错误日志。

当超过 preread_timeout 时,当前流会话将立即由流核心模块以会话状态代码 200 终止。

在这两种情况下,无法对会话进行进一步处理(除了 log_by_lua*)。连接将由流核心模块自动关闭。

请注意,如果已经消费了客户端数据,则无法使用此 API。例如,在调用 reqsock:receive 之后。如果尝试这样做,将抛出 Lua 错误 "attempt to peek on a consumed socket"。在调用此 API 后消费客户端数据是允许且安全的。

以下是使用此 API 的示例:

local sock = assert(ngx.req.socket())

local data = assert(sock:peek(1)) -- 查看包含长度的第一个字节
data = string.byte(data)

data = assert(sock:peek(data + 1)) -- 查看长度 + 大小字节

local payload = data:sub(2) -- 修剪长度字节以获取实际有效负载

ngx.log(ngx.INFO, "payload is: ", payload)

该 API 首次在 v0.0.6 版本中引入。

返回目录

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.13.x(最后测试:1.13.6)

1.13.6 之前的 Nginx 核心(不包括)未经过测试,可能无法正常工作。使用风险自负!

告诉 Nginx 的构建系统在哪里找到 LuaJIT 2.1:

export LUAJIT_LIB=/path/to/luajit/lib export LUAJIT_INC=/path/to/luajit/include/luajit-2.1

在此假设 Nginx 将安装在 /opt/nginx/ 下。

./configure --prefix=/opt/nginx \ --with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib" \ --with-stream \ --with-stream_ssl_module \ --add-module=/path/to/stream-lua-nginx-module

代码库

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

另见

GitHub

您可以在 nginx-module-stream-lua 的 GitHub 仓库中找到该模块的其他配置技巧和文档。