OpenResty之指令与常用API


1. 指令

通过 Lua 编写 Nginx 脚本的基本构建块是指令。指令常用于指定 Lua 代码是几时执行的以及如何使用运行的结果。下图展示了指令执行的顺序。
image

lua_capture_error_log

语法:lua_capture_error_log size
默认:none
上下文:http

启用一个指定大小的缓冲区来捕获所有的 Nginx 错误日志,而不是保存到文件或磁盘中。

如下,可以使用 k 或 m 表示大小:

lua_capture_error_log 100k;

一个经验:4KB 缓存可以容纳大约 20 个典型的错误日志消息。

注意事项:

  • 这个缓存不会增长,如果满了,则新的错误日志消息将会覆盖缓存中旧的消息。
  • 这个缓存的大小一定要比单个的错误日志消息的大小更大。
  • 可以通过lua-resty-core 库的 ngx.errlog 模块的 get_logs 函数来读取 Lua land 缓冲区的消息。这个 Lua API 将返回捕获的错误日志消息,并且将读取到的消息从全局捕获缓存区中移除,为任何新的错误日志消息腾出空间。基于这个原因,如果用户读取缓冲去中的错误日志消息足够快的话不宜将该缓冲区配置得太大。
  • error_log 指令指定的日志级别将会影响该缓冲区的捕获,该缓冲区仅捕获级别不低于 error_log 指令指定的日志级别的消息。用户可以通过 Lua API 函数 errlog.set_filter_level 来动态设置更高的过滤日志级别,这比静态的 error_log 指令更灵活。
  • 如果没有使用 ./configure 选项 --with-debug 来编译 OpenResty 或 Nginx,则无法捕获调试日志。由于高昂的开销,在生产版本中强烈建议禁用调试体质。

lua_code_cache

语法:lua_code_cache on | off
默认:lua_lua_cache on
上下文:http, server, location, location if
  • 在 *_by_lua_file 指令(类似 set_by_lua_file 和 content_by_lua_file)或 lua 模块中使能或禁止 Lua 代码的 lua 代码缓存功能。
  • 当关闭时,通过 ngx_lua 提供的每个请求都将在一个单独的 Lua VM 实例中运行。因此,由 set_by_lua_file,content_by_lua_file,access_by_lua_file 等中引用的 lua 文件都将不会被缓存,所有的 lua 模块都将从头开始加载。通过该指令,开发人员可以采用编辑并刷新的模式。
  • 注意,当在 nginx.conf 配置文件中编辑内联 Lua 代码时,由于仅有 Nginx 配置文件解析器,所以在 nginx.conf 中内联写入的 lua 代码(如由 set_by_lua,content_by_lua,access_by_lua 和 rewrite_by_lua 指定的 lua 代码)将不会被更新。仅有的方法是通过发送 HUP 信号来重新加载配置文件或重启 Nginx。
  • 即使启用了代码缓存,也无法缓存由 *_by_lua_file 中的 dofile 或 loadfile 加载的 lua 文件(除非你自己缓存了结果)。通常可以使用 init_by_lua 或 init_by_lua_file 指令来加载所有这些文件,或者通过使这些 Lua 文件成功真正的 lua 模块并通过 require 来加载它们。
  • ngx_lua 模块不支持 Apache mod_lua 模块可用的 stat 模式。
  • 强烈禁止在生产环境中禁用 Lua 代码缓存,仅可以在开发期间使用。因为它会对整体性能产生负面的影响。例如,在禁用 lua 代码缓存后,"hello world" Lua 示例的性能可能会下降一个数量级。

lua_package_path

语法:lua_package_path 
默认:LUA_PATH 环境变量内容或者 lua 编译的默认值
上下文:http
  • 设置由 set_by_lua,content_by_lua 等指定的脚本使用的 lua 模块搜索路径。这个路径字符串是标准的 lua 路径格式,";;" 常用于表示原始的搜索路径。
  • 从发行版 v0.5.0rc29 开始,可以在搜索路径中使用特殊符号 $prefix 或 ${prefix}来指示服务器前缀的路径,通常在 Nginx 服务器启动时通过 -p PATH 命令行选项来指定 server prefix。

lua_package_cpath

语法:lua_apckage_cpath 
默认:LUA_CPATH 环境变量的内容或 lua 的默认编译
上下文:http
  • 设置通过由 set_by_lua,content_by_lua 等指定的脚本的 Lua C-module 的搜索路径。这个 cpath 字符串是标准的 lua cpath 格式,";;" 常用于表示原始的 cpath。
  • 从 v0.5.0rc29 发行版开始,可以在搜索路径字符串中使用特殊字符 $prefix 或 ${prefix} 来指示 server prefix(该前缀通常在 Nginx 服务器启动时通过 -p PATH 命令行选项来指定)。

init_by_lua

语法:init_by_lua 
上下文:http
阶段:加载配置
  • 注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用 init_by_lua_block 指令代替。
  • 当 Nginx master 进程(如果有)在加载 Nginx 配置文件的时候,在全局 Lua VM 级别上运行由参数指定的 lua 代码。
  • 当 Nginx 接收到 HUP 信号并开始重新加载配置文件时,Lua VM 将会被重新创建,且 init_by_lua 也将在新的 VM 上再次运行。如果 lua_code_cache 指令是关闭的(默认打开),init_by_lua 处理程序将在每个请求上都运行一次,因此这这种特殊模式下,总是每个请求创建一个独立的 Lua VM。
  • 通常,你可以通过该挂钩在服务器启动时预加载 Lua 模块,并利用现代操作系统的写时复制(COW)优化。这里是一个预加载 Lua 模块的示例:
# this runs before forking out nginx worker processes:
init_by_lua_block { require "cjson" }

server {
    location = /api {
        content_by_lua_block {
            -- the following requrei() will just return
            -- the already loaded module from package.loaded:
            ngx.say(require "cjson".encode{dog = 5, cat = 6} -- {"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"))
        }
    }
}
  • 但注意,lua_shared_dict shm 共享内存将不会通过重新加载配置(例如通过 HUP 信号)清除。在这种情况下,如果你不想在 init_by_lua 代码中重新初始化 shm 内存,则需要在 shm 内存中设置一个自定义 flag,并在 init_by_lua 代码中总是检测该 flag。
  • 因为该 Lua 代码在 Nginx fork 出 worker 子进程前运行,所以在这里加载的数据或代码将享受许多操作系统在所有 worker 进程中提供的写时复制功能,这将节省大量的内存。
  • 不要在此上下文中初始化你自己的 Lua 全局变量,因为使用的 Lua 全局变量有性能损失,并且可能导致全局命名空间污染(更多细节参阅Lua Variable Scope)。建议的方法是使用正确的 Lua 模块文件(但不要使用标准 Lua 函数模块去定义 Lua 模块,因为它也会污染全局命名空间),同时在 init_by_lua 或其他上下文中调用 require 去加载你自己的模块文件(require() 会将加载的模块缓存在全局的 Lua 注册表 package.loaded 中,因此你的模块仅在整个 Lua VM 实例中加载一次)。
  • 在该上下文中,仅支持一小部分 Lua 的 Nginx API:
    • 日志 API:ngx.log 和 print
    • 共享字典 API:ngx.shared.DICT
  • 在未来用户请求中,该上下文将支持更多的 Lua 的 Nginx API。
  • 基本上,你可以在此上下文中安全地使用阻塞 I/O 的 Lua 库,因为在服务器启动期间阻塞 master 进程完全是可以的。甚至 Nginx 内核也会在配置加载阶段阻塞(至少在解析 upstream 域名时)。
  • 应该非常小心在此上下文中注册的 Lua 代码中潜在的安全漏洞,因为 Nginx master 进程通常在 root 用户下运行。

init_by_lua_block

语法:Init_by_lua_block { lua-script }
上下文:http
阶段:加载配置
  • 与 init_by_lua 指令类似,不同之处在于此指令直接在一对花括号({})内部而不是在 Nginx 字符串(需要特殊字符串转义)中内联 lua 代码。如:
init_by_lua_block {
    print("I need no extra escaping here, for example: \r\nblah")
}

init_by_lua_file

语法:init_by_lua_file 
上下文:http
阶段:加载配置
  • 等同于 init_by_lua。

init_worker_by_lua

语法:init_worker_by_lua 
上下文:http
阶段:启动 worker 时
  • 在 v0.9.17 版本后不鼓励使用此指令,改用 init_worker_by_lua_block 指令代替。
  • 当 master 进程使能时,在每个 Nginx worker 进程启动时运行指定的 Lua 代码。当 master 进程被禁用时,这个钩子将在 init_by_lua* 之后运行。
  • 这个钩子常用于创建每个 worker 重复发生的计时器(通过 ngx.timer.at ),用于后端运行状况检查或其他定时工作。如下示例:
init_worker_by_lua '
    local delay = 3 -- in seconds
    local new_timer = ngx.timer.at
    local log = ngx.log
    local ERR = ngx.ERR
    local check
    
    check = function(premature)
        if not premature then
            -- do the health check or other routine work
            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
';
  • 自 v0.10.12 版本以来,该钩子不再在 cache manager 和 cache loader 进程中运行。

init_worker_by_lua_block

语法:init_worker_by_lua_block { lua-script }
上下文:http
阶段:启动 worker 时
  • 与 init_worker_by_lua 指令类似,不同之处在于此指令直接在一对花括号({})内部而不是在 NGINX 字符串文字(需要特殊字符转义)中内联 Lua 代码。如下:
init_worker_by_lua_block {
    print("I need no extra escaping here, for example: \r\nblah")
}
  • 自 v0.10.12 版本以来,该钩子不再在 cache manager 和 cache loader 进程中运行。

init_worker_by_lua_file

语法:init_worker_by_lua_file 
上下文:http
阶段:启动 worker 时

与 init_worker_by_lua 类似。

content_by_lua

语法:content_by_lua 
上下文:location,location if
阶段:content
  • 注意在 v0.9.17 发行版之后不鼓励使用此指令,改为使用 content_by_lua_block 指令代替。
  • 作为一个 "content handler" 为每个请求执行在 中指定的 lua 代码字符串。该 lua 代码可能会进行 API 调用,并且在独立的全局环境(例如 sandbox)中作为一个新派生的协程执行。
  • 不要在同一个 location 中同时使用该指令和其他的 content handler 指令。如,该指令和 proxy_pass 指令不应该在通过一个 location 中出现。

content_by_lua_block

语法:content_by_lua_block { lua-script }
上下文:location,location if
阶段:content
  • 与 content_by_lua 指令类似,不同之处在于该指令直接在一对花括号({})内联 lua 源码,而不是在 Nginx 字符串文件中(它需要特殊字符转义)。

示例:

content_by_lua_block {
    ngx.say("I need no extra escaping here, for example: \r\nblah")
}

该指令最初是在 v0.9.17 发行版中引入的。

content_by_lua_file

语法:content_by_lua_file 
上下文:location,location if
阶段:content
  • 等同于 content_by_lua,不同之处在于 指定的文件包含 lua 代码,从 v0.5.0rc32 发行版开始,将执行 Lua/LuaJIT 字节码。
  • Nginx 变量可以用在 字符串中以提供灵活性,但是这会带来一些风险,因此通常不建议这么做。
  • 当给定一个类似 foo/bar.lua 这样的相对路径时,会转变为相对于 server prefix 的绝对路径(server prefix 是在 Nginx 服务器启动时,通过 -p PATH 命令行选项执行的)。
  • 当 lua 代码缓存打开时(默认打开),用户代码在第一个请求时被加载一次并缓存,并且每次修改 lua 源码文件时都必须重新加载 Nginx 配置。可以通过 在 nginx.conf 中使用 lua_code_cache off 在开发期间临时禁用 lua 代码缓存,来避免重新加载 Nginx。
  • 在动态分派的文件路径中支持 Nginx 变量,如下:
# CAUTION: conntents in nginx var must be carefully filtered,
# otherwise there'll be great security risk!
location ~ ^/app/([-_a-zA-Z0-9/]+) {
    set $path $1;
    content_by_lua_file /path/to/lua/app/root/$path.lua;
}

ssl_certificate_by_lua_block

语法:ssl_certificate_by_lua_block { lua-script }
上下文:server
阶段:在 SSL 握手之前
  • 当 Nginx 将要与下游 SSL(https)连接开始 SSL 握手时,运行该指令指定的用户 Lua 代码。
  • 该指令对于基于每个请求而设置的 SSL 证书链和相应的私钥特别有用。从远端(如 cosocket API)加载此类非阻塞的握手配置也很有用。此外,还可以在纯 Lua 中按要求执行 OCSP stapling handling。
  • 另一个典型的使用情况是在 lua-resty-limit-traffic#readme 库的帮助下执行非阻塞的 SSL 握手流量控制。
  • 也可以对来自客户端的 SSL 握手请求做一些有趣的事情,比如拒绝使用 SSLv3 协议的旧的 SSL 客户端,甚至是选择性地拒绝。
  • 由 lua-resty-core 库提供的 ngx.ssl 和 ngx.ocsp Lua 模块在这种上下文中特别有用。可以使用这两个 Lua 模块提供的 Lua API 来操作 SSL 证书链和私钥,以启动当前 SSL 连接。
  • 但是,当 NGINX/OpenSSL 通过 SSL 会话 ID 或当前 SSL 连接的 TLS 会话 ticket 成功地恢复 SSL 会话时,这个 Lua 处理程序根据没有运行。也就是说,这个 Lua 处理程序仅在 Nginx 开始完整 SSL 握手时运行。
  • 下面是一个使用 ngx.ssl 模块同时进行的简单的示例:
server {
    listen 443 ssl;
    server_name test.com;
    
    ssl_certificate_by_lua_block {
        print("About to initiate a new SSL handshake!")
    }
    
    location / {
        root html;
    }
}
  • 注意,即使你根本不使用此静态证书和私钥,你也需要配置 ssl_certificate 和 ssl_certificate_key 指令。这用于满足 Nginx 配置的占位符,否则在启动 Nginx 时会看到以下错误:
nginx: [emerg] no ssl configured for the server

ssl_certificate_by_lua_file

语法:ssl_certificate_by_lua_file 
上下文:server
阶段:在 SSL 握手之前
  • 等价于 ssl_certificate_by_lua_block,若给定一个类似 foo/bar.lua 的相对路径,则该路径将会被转变为相对 server prefix(由 Nginx 启动时通过 -p PATH 选项指定) 的绝对路径。

2. Nginx API for Lua

ngx.arg

语法:val = ngx.arg[index]
上下文:set_by_lua*, body_filter_by_lua*
  • 当在 set_by_lua* 指令的上下文中使用它时,该表是只读的,并且保存配置指令的输入参数:
value = ngx.arg[n]

示例:

location /foo {
    set $a 32;
    set $b 56;
    
    set_by_lua $sum
        'return tonumber(ngx.arg[1]) + tonumber(ngx.arg[2])'
        $a $b;
    
    echo $sum;
}

输出为 88,即 32 和 56 的总和。

  • 当在 body_filter_by_lua* 的上下文中使用该表时,第一个元素将输入数据块保存到 output filter code 中,第二个参数保存指示整个输出数据流结束的 "eof" 标志的布尔标志。
  • 传递给下游 Nginx 输出过滤器的数据块和 "eof" 标志也可以通过将值直接分配给相应的表元素来覆盖。当将 ngx.arg[1] 设置为 nil 或空的 lua 字符串值时,数据块将不会被传递到下游 Nginx 的输出过滤中。