# nginx-lua-module-zh-wiki **Repository Path**: chingliu/nginx-lua-module-zh-wiki ## Basic Information - **Project Name**: nginx-lua-module-zh-wiki - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-27 - **Last Updated**: 2024-10-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Name ==== ngx_http_lua_module - 嵌入强有力的 Lua 到 Nginx HTTP 服务中。 *该模块不是随着 Nginx 源码发行。* 更多请看 [安装说明](#installation)。 Table of Contents ================= * [Name](#name) * [Status](#status) * [Version](#version) * [Synopsis](#synopsis) * [Description](#description) * [Typical Uses](#typical-uses) * [Nginx Compatibility](#nginx-compatibility) * [Installation](#installation) * [Building as a dynamic module](#building-as-a-dynamic-module) * [C Macro Configurations](#c-macro-configurations) * [Installation on Ubuntu 11.10](#installation-on-ubuntu-1110) * [Community](#community) * [English Mailing List](#english-mailing-list) * [Chinese Mailing List](#chinese-mailing-list) * [Code Repository](#code-repository) * [Bugs and Patches](#bugs-and-patches) * [Lua/LuaJIT 字节码 support](#lualuajit-bytecode-support) * [System Environment Variable Support](#system-environment-variable-support) * [HTTP 1.0 support](#http-10-support) * [Statically Linking Pure Lua Modules](#statically-linking-pure-lua-modules) * [Data Sharing within an Nginx Worker](#data-sharing-within-an-nginx-worker) * [Known Issues](#known-issues) * [TCP socket connect operation issues](#tcp-socket-connect-operation-issues) * [Lua Coroutine Yielding/Resuming](#lua-coroutine-yieldingresuming) * [Lua Variable Scope](#lua-variable-scope) * [Locations Configured by Subrequest Directives of Other Modules](#locations-configured-by-subrequest-directives-of-other-modules) * [Cosockets Not Available Everywhere](#cosockets-not-available-everywhere) * [Special Escaping Sequences](#special-escaping-sequences) * [Mixing with SSI Not Supported](#mixing-with-ssi-not-supported) * [SPDY Mode Not Fully Supported](#spdy-mode-not-fully-supported) * [Missing data on short circuited requests](#missing-data-on-short-circuited-requests) * [TODO](#todo) * [Changes](#changes) * [Test Suite](#test-suite) * [Copyright and License](#copyright-and-license) * [See Also](#see-also) * [Directives](#directives) * [Nginx API for Lua](#nginx-api-for-lua) * [Obsolete Sections](#obsolete-sections) * [Special PCRE Sequences](#special-pcre-sequences) Status ====== 生产版本可用 Version ======= 该文档描述的 ngx_lua [v0.10.7](https://github.com/openresty/lua-nginx-module/tags) 是2016年11月4号发布。 Synopsis ======== ```nginx # 设置纯 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_block { ngx.req.read_body() -- explicitly read the req body local data = ngx.req.get_body_data() if data then ngx.say("body data:") ngx.print(data) return end -- body may get buffered in a temp file: 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 { # 通过 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 / { lua_need_request_body on; 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 -- tests passed } # proxy_pass/fastcgi_pass/etc settings } } ``` [返回目录](#table-of-contents) Description =========== 该模块通过标准 Lua5.1 解释器或 [LuaJIT 2.0/2.1](http://luajit.org/luajit.html),把 Lua 嵌入到 Nginx 里面, 并利用 Nginx 子请求,把强大的 Lua 线程(Lua协程)混合到 Nginx 的事件模型中。 与 [Apache's mod_lua](https://httpd.apache.org/docs/trunk/mod/mod_lua.html)、[Lighttpd's mod_magnet](http://redmine.lighttpd.net/wiki/1/Docs:ModMagnet) 不同的是, 只要使用这个模块提供的[Nginx API for Lua](#nginx-api-for-lua)来处理请求上游服务,该模块的 Lua 代码被执行在网络上是 100% 非阻塞的。其中上游请求服务有:MySQL、PostgreSQL、Memcached、Redis或upstream HTTP web 服务等。 至少下面这些 Lua 库、Nginx 模块是可以与 ngx_lua 模块配合使用的: * [lua-resty-memcached](https://github.com/openresty/lua-resty-memcached) * [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql) * [lua-resty-redis](https://github.com/openresty/lua-resty-redis) * [lua-resty-dns](https://github.com/openresty/lua-resty-dns) * [lua-resty-upload](https://github.com/openresty/lua-resty-upload) * [lua-resty-websocket](https://github.com/openresty/lua-resty-websocket) * [lua-resty-lock](https://github.com/openresty/lua-resty-lock) * [lua-resty-logger-socket](https://github.com/cloudflare/lua-resty-logger-socket) * [lua-resty-lrucache](https://github.com/openresty/lua-resty-lrucache) * [lua-resty-string](https://github.com/openresty/lua-resty-string) * [ngx_memc](http://github.com/openresty/memc-nginx-module) * [ngx_postgres](https://github.com/FRiCKLE/ngx_postgres) * [ngx_redis2](http://github.com/openresty/redis2-nginx-module) * [ngx_redis](http://wiki.nginx.org/HttpRedisModule) * [ngx_proxy](http://nginx.org/en/docs/http/ngx_http_proxy_module.html) * [ngx_fastcgi](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html) 几乎所有的 Nginx 模块都可以通过 [ngx.location.capture](#ngxlocationcapture) 或 [ngx.location.capture_multi](#ngxlocationcapture_multi) 与 ngx_lua 模块完成调用,但推荐使用类似 `lua-resty-*` 库,而不是子请求访问 Nginx 上游模块,因为前者更加灵活并且内存效率更高。 在单个 Nginx worker 中,标准 Lua 或 LuaJIT 的实例在所有请求中是共享使用的,但每个请求上下文是通过轻量的 Lua 协程做到隔离的。 在 Nginx worker 进程中加载 Lua 模块,坚持小内存的使用,即使在重负载下依然如此。 该模块是 Nginx 的 HTTP 子系统插件,所以它只能对 HTTP 环境的下游进行对话(例如:HTTP 0.9/1.0/1.1/2.0, WebSockets等)。 如果你想获得通用的 TCP 下游客户端对话能力,这时应使用 [ngx_stream_lua](https://github.com/openresty/stream-lua-nginx-module#readme) 模块,同样它也是兼容 Lua API 的。 [返回目录](#table-of-contents) Typical Uses ============ 列举部分: * 在 Lua 中揉和处理各种不同的 Nginx 上游输出(proxy, drizzle, postgres, redis, memcached等) * 在请求真正到达上游服务之前,Lua 可以随心所欲的做复杂访问控制和安全检测 * 随心所欲的操控响应头里面的信息(通过 Lua) * 从外部存储服务(比如 redis, memcached, mysql, postgresql)中获取后端信息,并用这些信息来实时选择哪一个后端来完成业务访问 * 在内容 handler 中随意编写复杂的 web 应用,使用同步非阻塞的方式,访问后端数据库和其他存储 * 在 rewrite 阶段,通过 Lua 完成非常复杂的 URL dispatch * 用 Lua 可以为 Nginx 子请求和任意 location,实现高级缓存机制 本模块会把你带入一个拥有无限可能的服务端开发新世界,你可以把 Nginx 的各种功能进行自由拼接, 更重要的是,开发门槛并不高,这一切都是用强大轻巧的 Lua 语言来操控。 本模块的脚本有充分的灵活性,并且性能和原生 C 语言编程相比毫不逊色,无论是 CPU 时间还是内存占用方面。当然这里需要启用 LuaJIT 2.x。 其他脚本语言的实现通常很难达到类似性能。 Lua state(Lua VM instance)会被共享给单个 nginx worker 内所有的请求,从而达到最小化内存消耗。 [返回目录](#table-of-contents) Nginx Compatibility =================== 最新模块版本和 Nginx 的兼容列表: * 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 更老的版本 *不* 再提供支持。 [返回目录](#table-of-contents) Installation ============ 强烈推荐使用 [OpenResty](http://openresty.org) 安装包,它包含了 Nginx, ngx_lua, LuaJIT 2.0/2.1 (或者可选的标准 Lua 5.1解释器),还包含很多强劲、好用的 Nginx 模块。使用一个简单的命令就可以完成基础安装:`./configure --with-luajit && make && make install`。 当然,ngx_lua 也可以手动的编译到 Nginx 中: 1. 安装LuaJIT 2.0 或 2.1 (推荐) 或 Lua 5.1 (Lua 5.2 暂时还 *不支持* )。 LuaJIT可从 [The LuaJIT project 站点](http://luajit.org/download.html) 获取, Lua 5.1可从 [Lua project 站点](http://www.lua.org/) 获取。 2. 下载最新版本的 ngx_devel_kit (NDK)开发模块 [这里](https://github.com/simpl/ngx_devel_kit/tags) 。 3. 下载最新版本的 ngx_lua [这里](https://github.com/openresty/lua-nginx-module/tags) 。 4. 下载最新版本的 Nginx [这里](http://nginx.org/) (查看 [Nginx 兼容列表](#nginx-compatibility))。 源码编译本模块: ```bash wget 'http://nginx.org/download/nginx-1.11.2.tar.gz' tar -xzvf nginx-1.11.2.tar.gz cd nginx-1.11.2/ # tell nginx's build system where to find LuaJIT 2.0: export LUAJIT_LIB=/path/to/luajit/lib export LUAJIT_INC=/path/to/luajit/include/luajit-2.0 # tell nginx's build system where to find LuaJIT 2.1: export LUAJIT_LIB=/path/to/luajit/lib export LUAJIT_INC=/path/to/luajit/include/luajit-2.1 # or tell where to find Lua if using Lua instead: #export LUA_LIB=/path/to/lua/lib #export LUA_INC=/path/to/lua/include # Here we assume Nginx is to be installed under /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 make -j2 make install ``` [返回目录](#table-of-contents) Building as a dynamic module ---------------------------- 从 NGINX 1.9.11 开始,你也能编译动态模块了,使用 `--add-dynamic-module=PATH` 选项替代 `./configure` 命令行的 `--add-module=PATH` 。然后就能在 `nginx.conf` 配置中通过 [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) 指令完成模块加载,例如: ```nginx load_module /path/to/modules/ndk_http_module.so; # assuming NDK is built as a dynamic module too load_module /path/to/modules/ngx_http_lua_module.so; ``` [返回目录](#table-of-contents) C Macro Configurations ---------------------- 通过 OpenResty 或者 Nginx 内核方式构建该模块,你可以定义下面的 C 宏定义作为可选项提供给 C 编译器: * `NGX_LUA_USE_ASSERT` 声明后,将在ngx_lua C代码中开启断言。推荐用在调试或者测试版本中。启用后,它会引入额外一些(小的)运行时开销。在 `v0.9.10` 版本中首次引入此选项。 * `NGX_LUA_ABORT_AT_PANIC` 当 Lua/LuaJIT 虚拟机出现 panic 错误时,ngx_lua 默认会让当前的工作进程优雅退出。通过指定这个宏定义,ngx_lua 将立即终止当前的 Nginx 工作进程(通常会生成一个core dump文件)。这个选项主要用来调试虚拟机的 panic 错误。在 `v0.9.8` 版本中首次引入此选项。 * `NGX_LUA_NO_FFI_API` 去除 Nginx 中 FFI-based Lua API 需要的的纯 C 函数(例如 [lua-resty-core](https://github.com/openresty/lua-resty-core#readme) 所需要的)。开启这个宏可以让 Nginx 二进制代码更小。 在 Nginx 或者 OpenResty 启用一个或多个宏定义,只用传给`./configure`脚本几个额外的C编译选项。例如: ``` # ./configure --with-cc-opt="-DNGX_LUA_USE_ASSERT -DNGX_LUA_ABORT_AT_PANIC" ``` [返回目录](#table-of-contents) Installation on Ubuntu 11.10 ---------------------------- 注意:这里推荐使用 LuaJIT 2.1 或 LuaJIT 2.0 替换掉标准 Lua 5.1 解释器。 如果不得不使用标准的 Lua 5.1 解释器,在 Ubuntu 上使用这个命令完成安装: ```bash apt-get install -y lua5.1 liblua5.1-0 liblua5.1-0-dev ``` 应该可以正确被安装,除了一个小 "麻烦": `liblua.so`库在 liblua5.1 包中已经发生改变,只能使用`liblua5.1.so`,并且需要被链接到`/usr/lib`,这样才可以在 configure 执行阶段被找到。 ```bash ln -s /usr/lib/x86_64-linux-gnu/liblua5.1.so /usr/lib/liblua.so ``` [返回目录](#table-of-contents) Community ========= 英文邮件列表 -------------------- 英文邮件列表: [openresty-en](https://groups.google.com/group/openresty-en) 。 [返回目录](#table-of-contents) 中文邮件列表 -------------------- 中文邮件列表: [openresty](https://groups.google.com/group/openresty) 。 [返回目录](#table-of-contents) Code Repository =============== 本项目代码放在github上 [openresty/lua-nginx-module](https://github.com/openresty/lua-nginx-module)。 [返回目录](#table-of-contents) Bugs and Patches ================ 提交bug报告、想法或补丁,可通过下面方式: 1. 创建一个ticket [GitHub Issue Tracker](https://github.com/openresty/lua-nginx-module/issues) 1. 或者发到这里 [OpenResty 社区](#community). [返回目录](#table-of-contents) Lua/LuaJIT 字节码 support =========================== 从 `v0.5.0rc32` release 开始,所有 `*_by_lua_file` 的配置指令(比如 [content_by_lua_file](#content_by_lua_file)) 都支持直接加载 Lua 5.1 和 LuaJIT 2.0/2.1 的二进制字节码文件。 请注意,LuaJIT 2.0/2.1 生成的二进制格式与标准 Lua 5.1 解析器是不兼容的。 所以如果在 ngx_lua 下使用 LuaJIT 2.0/2.1,那么 LuaJIT 兼容的二进制文件必须是下面这样生成的: ```bash /path/to/luajit/bin/luajit -b /path/to/input_file.lua /path/to/output_file.luac ``` `-bg` 选项是在 LuaJIT 字节码文件中包含调试信息。 ```bash /path/to/luajit/bin/luajit -bg /path/to/input_file.lua /path/to/output_file.luac ``` 对于`-b` 选项,请参考官方 LuaJIT 文档获取更多细节: 同样的,由 LuaJIT 2.1 生成的字节码文件对于 LuaJIT 2.0 也是 *不* 兼容的,反之亦然。 第一次对于 LuaJIT 2.1 版本的字节支持,是在 v0.9.3 上完成的。 近似的,如果使用标准 Lua 5.1 解释器,Lua 的兼容字节码文件必须用 **luac** 命令行来生成: ```bash luac -o /path/to/output_file.luac /path/to/input_file.lua ``` 与 LuaJIT 不太一样, Lua 5.1 的字节码文件是默认包含调试信息的。这里可以使用 `-s` 选项去掉调试信息: ```bash luac -s -o /path/to/output_file.luac /path/to/input_file.lua ``` 在使用 LuaJIT 2.0/2.1 的 ngx_lua 实例中试图加载标准 Lua 5.1 字节码文件(反之亦然),将会在 error.log 中记录一条类似的错误信息: [error] 13909#0: *1 failed to load Lua inlined code: bad byte-code header in /path/to/test_file.luac 使用 Lua `require` 和 `dofile` 这类原语加载字节码文件,应该总能按照预期工作。 [返回目录](#table-of-contents) System Environment Variable Support =================================== 如果你想在 Lua 中通过标准 Lua API [os.getenv](http://www.lua.org/manual/5.1/manual.html#pdf-os.getenv) 来访问系统环境变量,例如`foo`, 那么你需要在你的 nginx.conf 中,通过[ env 指令](http://nginx.org/en/docs/ngx_core_module.html#env),把这个环境变量列出来。 例如: ```nginx env foo; ``` [返回目录](#table-of-contents) HTTP 1.0 support ================ HTTP 1.0 协议不支持分块输出,当响应体不为空时,需要在响应头中明确指定 `Content-Length`,以支持 HTTP 1.0 长连接。所以当一个 HTTP 1.0 请求发生,同时把 [lua_http10_buffering](#lua_http10_buffering)指令设置为 `on` 时,ngx_lua 将缓存 [ngx.say](#ngxsay) 和 [ngx.print](#ngxprint) 的所有输出,同时延迟发送响应头直到接收到所有输出内容。这时 ngx_lua 可以计算响应体的总长度且构建一个正确的 `Content-Length` 响应头返回给 HTTP 1.0 客户端。即使已经将 [lua_http10_buffering](#lua_http10_buffering) 指令设置为 `on`,但如果正在执行的 Lua 代码中设置了 `Content-Length` 响应头,这种缓冲模式也将被禁用。 对于大型流式响应输出,禁用 [lua_http10_buffering](#lua_http10_buffering) 以最小化内存占用非常重要。 请注意,一些常见的 HTTP 性能测试工具,例如 `ab` 和 `http_load` 默认发送 HTTP 1.0 请求。要强制 `curl` 发送 HTTP 1.0 请求,使用 `-0` 选项。 [返回目录](#table-of-contents) Statically Linking Pure Lua Modules =================================== 当使用 LuaJIT 2.x 时,可以把一个纯 Lua 字节码模块,静态链接到可运行的 Nginx 中。 首先你要使用 `LuaJIT` 的可执行程序, 把 `.lua` 的 Lua 模块编译成 `.o` 的目标文件(包含导出的字节码数据), 然后链接这些 `.o` 文件到你的 Nginx 构造环境。 用下面这个小例子来印证一下。这里我们的 `.lua` 文件使用 `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时, 传给 `./configure` 脚本 `--with-ld-opt="foo.o"` 选项: ```bash ./configure --with-ld-opt="/path/to/foo.o" ... ``` 最后,你可以在运行在 ngx_lua 中的任意 Lua 代码中调用: ```lua local foo = require "foo" foo.go() ``` 并且,这段代码再也不会依赖外部的 `foo.lua` 文件, 因为它已经被编译到了 `nginx` 程序中。 在调用`require`时, 如果你想 Lua 模块名中使用点号,如下所示: ```lua local foo = require "resty.foo" ``` 那么在你使用 `LuaJIT` 命令行工具把他编译成 `.o` 文件之前, 你需要把 `foo.lua` 文件重命名为 `resty_foo.lua`。 把 `.lua` 文件编译成 `.o` 文件,和构建 Nginx + ngx_lua, 这两个过程中,使用完全相同版本的 LuaJIT 是至关重要的。 这是因为 LuaJIT 字节码格式在不同版本之间可能是不兼容的。 当字节码文件出现了不兼容情况,你将看到一行 Lua 运行时错误信息:没找到 Lua 模块。 当你拥有多个 `.lua` 文件需要链接, 你可以一次指明所有的 `.o` 文件,并赋给 `--with-ld-opt` 选项,参考: ```bash ./configure --with-ld-opt="/path/to/foo.o /path/to/bar.o" ... ``` 如果你有非常多的 `.o` 文件,把这些文件的名字都写到命令行中不太可行, 这种情况下,对你的 `.o` 文件可以构建一个静态库(或者归档),参考: ```bash ar rcus libmyluafiles.a *.o ``` 然后你就可以把 `myluafiles` 链接到你的 nginx 可执行程序中: ```bash ./configure \ --with-ld-opt="-L/path/to/lib -Wl,--whole-archive -lmyluafiles -Wl,--no-whole-archive" ``` `/path/to/lib` 目录中应包含 `libmyluafiles.a` 文件。 应当指出的是,这里要添加链接选项 `--whole-archive`, 否则我们的归档将被跳过,因为在我们的归档没有导出 nginx 执行需要的函数符号。 [返回目录](#table-of-contents) Data Sharing within an Nginx Worker =================================== 在同一个 nginx worker 进程处理的所有请求中共享数据, 需要将共享数据封装进一个 Lua 模块中,并使用 Lua 语言内置的 `require` 方法加载该模块, 之后就可以在 Lua 中操作共享数据了。 这种方法之所以可行,是因为(在同一个nginx worker中)加载模块的操作仅被执行一次, 所有的协程都会共享同一份拷贝(包括代码和数据)。 但是请注意,Lua的全局变量(注意,不是模块级变量)将因为“每个请求一个协程”的隔离要求而不被保持。 下面是一个完整的例子: ```lua -- 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 访问: ```nginx location /lua { content_by_lua_block { local mydata = require "mydata" ngx.say(mydata.get_age("dog")) } } ``` 例子中的 `mydata` 模块将只在第一个请求到达 `/lua` 的时候被加载运行, 之后同一个nginx worker进程处理的所有请求,都将使用此模块已经加载的实例,并共享实例中的数据, 直到 nginx 主进程接到 `HUP` 信号强制重新进行加载。 这种数据共享技术是基于本模块(ngx_lua)的高性能 Lua 应用的基础。 注意,这种数据共享方式是基于worker而不是基于服务器的。 也就是说,当 Nginx 主进程下面有多个 worker 进程时,数据共享不能跨越这些 worker 之间的进程边界。 一般来说,仅推荐使用这种方式共享只读数据。 当计算过程中没有非阻塞性 I/O 操作时(包括 [ngx.sleep](#ngxsleep)), 你也可以在 nginx worker 进程内所有并发请求中共享可改变的数据。 只要你不把控制权交还给 nginx 事件循环以及 ngx_lua 的轻量级线程调度器(包括隐含的),它们之间就不会有任何竞争。 因此,当你决定在 worker 中共享可变数据时,一定要非常小心。 错误的优化经常会导致在高负载时产生竞争,这种 bug 非常难以发现。 如果需要在服务器级别共享数据,请使用以下方法: 1. 使用本模块提供的 [ngx.shared.DICT](#ngxshareddict) API 2. 使用单服务器单 nginx worker 进程(当使用多CPU或者多核CPU的服务器时不推荐) 3. 使用类似 `memcached`, `redis`, `MySQL` 或 `PostgreSQL` 等数据共享机制。 与本模块相关的 [OpenResty 软件包](http://openresty.org)包含了一系列相关的 Nginx 模块以及 Lua 库, 提供与这些数据存储机制的交互界面。 [返回目录](#table-of-contents) Known Issues ============ [返回目录](#table-of-contents) TCP socket 连接操作遗留问题 ----------------------------------- [tcpsock:connect](#tcpsockconnect)方法,返回 `success` 但实际上连接故障,例如出现 `Connection Refused` 错误。 然而,后面尝试对 cosocket 对象的任何操作都将失败,并返回由失效连接操作所产生实际的错误状态消息。 这个问题是由于在 Nginx 的事件模型的局限性,似乎只影响 Mac OS X 系统。 [返回目录](#table-of-contents) Lua 协程 Yielding/Resuming ------------------------------- 无论Lua 5.1 and LuaJIT 2.0/2.1,内建的 `dofile` 和 `require` 当前都是通过绑定 C 函数的方式,如果Lua文件的加载是`dofile` 或 `require` 并调用 [ngx.location.capture*](#ngxlocationcapture), [ngx.exec](#ngxexec), [ngx.exit](#ngxexit), 或者 Lua 中其他 API 函数的 *top-level* 范围调用 yielding ,均会得到 "attempt to yield across C-call boundary" 的错误信息。为了避免这个情况,把需要调用 yielding 部分放到你自己的 Lua 函数中,这样在当前文件就不再是 *top-level* 范围。 对于标准 Lua 5.1 解析器的虚拟机唤醒支持是不完善的,[ngx.location.capture](#ngxlocationcapture), [ngx.location.capture_multi](#ngxlocationcapture_multi), [ngx.redirect](#ngxredirect), [ngx.exec](#ngxexec) 和 [ngx.exit](#ngxexit) 方法,在 Lua [pcall()](http://www.lua.org/manual/5.1/manual.html#pdf-pcall) 或 [xpcall()](http://www.lua.org/manual/5.1/manual.html#pdf-xpcall) 中是不能使用的。甚至 `for ... in ...` 小节的第一行在标准 Lua 5.1解析器中都会报错 `attempt to yield across metamethod/C-call boundary`。 请使用 LuaJIT 2.x,它可以完美支持虚拟机唤醒,避免这些问题。 [返回目录](#table-of-contents) Lua Variable Scope ------------------ 在代码中导入模块时应注意一些细节,推介使用如下格式: ``` local xxx = require('xxx') ``` 而非: ``` require('xxx') ``` 理由如下:从设计上讲,全局环境的生命周期和一个 Nginx 的请求的生命周期是相同的。为了做到请求隔离,每个请求都有自己的Lua全局变量环境。Lua 模块在第一次请求打到服务器上的时候被加载起来,通过`package.loaded`表内建的`require()`完成缓存,为后续代码复用。并且一些 Lua 模块内的`module()`存在边际问题,对加载完成的模块设置成全局表变量,但是这个全局变量在请求处理最后将被清空,并且每个后续请求都拥有自己(干净)的全局空间。所以它将因为访问`nil`值收到Lua异常。 一般来说,在 ngx_lua 的上下文中使用 Lua 全局变量真的不是什么好主意: 1. 滥用全局变量的副作用会对并发场景产生副作用,比如当使用者把这些变量看作是本地变量的时候; 1. Lua的全局变量需要向上查找一个全局环境(只是一个Lua表),代价比较高; 1. 一些Lua的全局变量引用只是拼写错误,这会导致出错很难排查。 所以,我们极力推介在使用变量的时候总是使用 local 来定义以限定起生效范围是有理由的。 为了在你的 Lua 代码中找出所有使用 Lua 全局变量的地方,你可以运行 [lua-releng tool](https://github.com/openresty/nginx-devel-utils/blob/master/lua-releng) 把所有 .lua 源文件检测一遍: $ 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 关键字定义过的,否则将会抛出一个运行时异常。这样能阻止类似变量这样的资源的竞争。理由请参考 [Data Sharing within an Nginx Worker](http://wiki.nginx.org/HttpLuaModule#Data_Sharing_within_an_Nginx_Worker) [返回目录](#table-of-contents) Locations Configured by Subrequest Directives of Other Modules -------------------------------------------------------------- [ngx.location.capture](#ngxlocationcapture) 和 [ngx.location.capture_multi](#ngxlocationcapture_multi) 指令无法抓取包含以下指令的 location: [add_before_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_before_body), [add_after_body](http://nginx.org/en/docs/http/ngx_http_addition_module.html#add_after_body), [auth_request](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request), [echo_location](http://github.com/openresty/echo-nginx-module#echo_location), [echo_location_async](http://github.com/openresty/echo-nginx-module#echo_location_async), [echo_subrequest](http://github.com/openresty/echo-nginx-module#echo_subrequest) 或 [echo_subrequest_async](http://github.com/openresty/echo-nginx-module#echo_subrequest_async) 。 ```nginx location /foo { content_by_lua_block { res = ngx.location.capture("/bar") } } location /bar { echo_location /blah; } location /blah { echo "Success!"; } ``` ```nginx $ curl -i http://example.com/foo ``` 将不会按照预期工作。 [返回目录](#nginx-api-for-lua) Cosockets Not Available Everywhere ---------------------------------- 归咎于 `nginx` 内核的各种限制规则,cosocket API 在这些环境中是被禁的: [set_by_lua*](#set_by_lua), [log_by_lua*](#log_by_lua), [header_filter_by_lua*](#header_filter_by_lua) 和 [body_filter_by_lua*](#body_filter_by_lua)。 cosocket 在[init_by_lua*](#init_by_lua) 和 [init_worker_by_lua*](#init_worker_by_lua) 小节中也是被禁的,但我们后面将会添加这些环境的支持,因为在 nginx 内核上是没有这个限制的(或者这个限制是可以被绕过的)。 这里有个绕路方法,前提是原始场景 *不* 需要等待cosocket结果。就是说,通过 [ngx.timer.at](#ngxtimerat) API 创建一个零延迟的`timer`,在`timer`中完成 cosocket 的处理结果,用这种同步的方式进行协作。 [返回目录](#table-of-contents) Special Escaping Sequences -------------------------- **注意** 自引入 `*_by_lua_block {}` 配置指令,我们将不再被该问题折磨。 PCRE 的转义符号例如 `\d`,`\s` 以及 `\w` 等需要特别注意,因为在字符串语义中,反斜线字符 `\` 会被 Lua 语言解析器和 Nginx 配置文件解析器在执行前同时处理掉,所以以下代码片段将无法按预期运行: ```nginx # 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 ? } ? } # 结果为 "not matched!" ``` 为避免这个问题,需要双重转义反斜线符号: ```nginx # 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" ``` 这里的 `\\\\d+`,先被 Nginx 配置文件解析器处理成 `\\d+` ,再被 Lua 语言解析器处理成 `\d+`,之后才被执行。 或者,正则表达式模板可以使用 Lua 字符串"长括号"语义写出,其语法形式为 `[[...]]`,在这种情况下,反斜线仅需为 Nginx 配置文件解析器转义一次。 ```nginx # 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 } } # 结果为 to "1234" ``` 这里,`[[\\d+]]` 被 Nginx 配置文件解析器处理成 `[[\d+]]`,符合预期。 注意,当正则表达式模板中包括 `[...]` 序列时,Lua 语言中“更长的长括号”形式 `[=[...]=]` 是必要的。如果需要,可以将`[=[...]=]` 作为默认形式。 ```nginx # nginx.conf location /test { content_by_lua_block { local regex = [=[[0-9]+]=] local m = ngx.re.match("hello, 1234", regex) if m then ngx.say(m[0]) else ngx.say("not matched!") end } } # 结果为 "1234" ``` 还有一种转义 PCRE 序列的方法是把 Lua 代码放到外部脚本文件中,通过各种 `*_by_lua_file` 指令执行。在这种方法中,反斜线仅被 Lua 语言解析器处理,因此只需要转义一次。 ```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 字符串,则无需修改。 ```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" ``` [返回目录](#table-of-contents) Mixing with SSI Not Supported ----------------------------- 在同样的 Nginx 请求混合 SSI 与 ngx_lua 是完全不被支持的,只使用 ngx_lua 即可。使用 SSI 你可以做的任何事情,使用 ngx_lua 可以更好的完成,并且效能更棒。 [返回目录](#table-of-contents) SPDY Mode Not Fully Supported ----------------------------- 一些 ngx_lua 提供的 Lua APIs 在 Nginx 的`SPDY`模式下确定不能工作:[ngx.location.capture](#ngxlocationcapture), [ngx.location.capture_multi](#ngxlocationcapture_multi) 和 [ngx.req.socket](#ngxreqsocket)。 [返回目录](#table-of-contents) Missing data on short circuited requests ---------------------------------------- Nginx提前销毁一个请求的可能(至少): * 400 (Bad Request) -- 错误请求 * 405 (Not Allowed) -- 不允许 * 408 (Request Timeout) -- 请求超时 * 414 (Request URI Too Large) -- 请求URI过大 * 494 (Request Headers Too Large) -- 请求Headers过大 * 499 (Client Closed Request) -- 客户端关闭请求 * 500 (Internal Server Error) -- 内部服务错误 * 501 (Not Implemented) -- 未实现 这意味着正常执行阶段被跳过,如重写或访问阶段。这也意味着,后面的所有阶段,例如 [log_by_lua](#log_by_lua),将无法获得在这个阶段存放的普通信息。 [返回目录](#table-of-contents) TODO ==== * cosocket:实现 LuaSocket 非连接的 UDP API。 * 实现普通的 UDP 服务替代 HTTP 服务,并支持 Lua 代码。例如: ```lua datagram { server { listen 1953; handler_by_lua_block { -- custom Lua code implementing the special UDP server... } } } ``` * shm:实现一个 "shared queue API",对 [shared dict](#lua_shared_dict) 补充 API。 * cosocket:在 [init_by_lua*](#init_by_lua) 添加支持。 * cosocket:对于流式 cosocket,实现 bind() 方法。 * cosocket:基于池子的后端并发连接控制,当后端并发超过它的连接池限制,实现自动排队的 `connect`。 * cosocket:查看合并 aviramc's 的 [patch](https://github.com/openresty/lua-nginx-module/pull/290),添加 `bsdrecv` 方法。 * 添加新的API函数完成标准 `add_header` 配置功能。 * 查看、合并 vadim-pavlov 的 补丁,给 [ngx.location.capture](#ngxlocationcapture)添加 `extra_headers` 选项。 * 使用 `ngx_hash_t` 去优化内建的 header 查找,涉及[ngx.req.set_header](#ngxreqset_header), [ngx.header.HEADER](#ngxheaderheader) 等。 * cosocket 连接池溢出,支持配置选项定义不同策略。 * 添加新的小节,当 nginx 关闭时执行一段代码。 * 添加 `ignore_resp_headers`, `ignore_resp_body` 和 `ignore_resp`选项给 [ngx.location.capture](#ngxlocationcapture)、[ngx.location.capture_multi](#ngxlocationcapture_multi),对于用户提升微小性能。 * 增加抢占式的协程调度,用来自动 yielding 或 resuming Lua 虚拟机。 * 添加 `stat` 类似 [mod_lua](https://httpd.apache.org/docs/trunk/mod/mod_lua.html)。 * cosocket: 添加 SSL certificiate 客户端支持。 [返回目录](#table-of-contents) Changes ======= 该模块每个发行版本的变更,都记录在OpenResty绑定的变更日志中: [返回目录](#table-of-contents) Test Suite ========== 为了运行测试套件,依赖下面这些条件: * Nginx version >= 1.4.2 * Perl modules: * Test::Nginx: * Nginx 模块: * [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) * [ngx_set_misc](https://github.com/openresty/set-misc-nginx-module) * [ngx_auth_request](http://mdounin.ru/files/ngx_http_auth_request_module-0.2.tar.gz) (如果你使用 1.5.4.+,这不是必须的) * [ngx_echo](https://github.com/openresty/echo-nginx-module) * [ngx_memc](https://github.com/openresty/memc-nginx-module) * [ngx_srcache](https://github.com/openresty/srcache-nginx-module) * ngx_lua (该模块) * [ngx_lua_upstream](https://github.com/openresty/lua-upstream-nginx-module) * [ngx_headers_more](https://github.com/openresty/headers-more-nginx-module) * [ngx_drizzle](https://github.com/openresty/drizzle-nginx-module) * [ngx_rds_json](https://github.com/openresty/rds-json-nginx-module) * [ngx_coolkit](https://github.com/FRiCKLE/ngx_coolkit) * [ngx_redis2](https://github.com/openresty/redis2-nginx-module) `configure`时添加的这些模块顺序是非常重要的。因为在 filter 链中不同的过滤模块位置决定最终输出。正确的添加顺序如上所示。 * 第三方 Lua 库: * [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) * 应用: * mysql: 创建数据库 'ngx_test', 对用户'ngx_test'赋予所有权限,密码也是'ngx_test'。 * memcached: 监听默认端口,11211. * redis: 监听默认端口, 6379. 查看 [developer build script](https://github.com/openresty/lua-nginx-module/blob/master/util/build.sh) 内容,在搭建测试环境时确定更多细节。 在默认的测试模式下启动测试套件: 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 documentation](http://search.cpan.org/perldoc?Test::Nginx),有更多不同高级测试方式的资料。也可以看看在 Amazon EC2 的 Nginx 集群测试报告 。 [返回目录](#table-of-contents) Copyright and License ===================== 该模块是根据BSD许可证授权。 Copyright (C) 2009-2016, by Xiaozhe Wang (chaoslawful) . Copyright (C) 2009-2016, by Yichun "agentzh" Zhang (章亦春) , CloudFlare Inc. 版权所有。 在源代码和二进制形式的二次发行和使用,无论修改与否,允许的前提是满足以下条件: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. ``` THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` [返回目录](#table-of-contents) See Also ======== * [ngx_stream_lua_module](https://github.com/openresty/stream-lua-nginx-module#readme) Nginx "stream" 子系统的官方模块版本(通用的下游 TCP 对话)。 * [lua-resty-memcached](https://github.com/openresty/lua-resty-memcached) 基于 ngx_lua cosocket 的库。 * [lua-resty-redis](https://github.com/openresty/lua-resty-redis) 基于 ngx_lua cosocket 的库。 * [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql) 基于 ngx_lua cosocket 的库。 * [lua-resty-upload](https://github.com/openresty/lua-resty-upload) 基于 ngx_lua cosocket 的库。 * [lua-resty-dns](https://github.com/openresty/lua-resty-dns) 基于 ngx_lua cosocket的库。 * [lua-resty-websocket](https://github.com/openresty/lua-resty-websocket) 提供 WebSocket 的客户端、服务端,基于 ngx_lua cosocket 的库。 * [lua-resty-string](https://github.com/openresty/lua-resty-string) 基于 [LuaJIT FFI](http://luajit.org/ext_ffi.html) 的库。 * [lua-resty-lock](https://github.com/openresty/lua-resty-lock) 一个简单非阻塞 lock API 库。 * [lua-resty-cookie](https://github.com/cloudflare/lua-resty-cookie) HTTP cookie 的库。 * [基于 URI 参数,路由到发起不同 MySQL 查询](http://openresty.org/#RoutingMySQLQueriesBasedOnURIArgs) * [基于 Redis 和 Lua 的动态路由](http://openresty.org/#DynamicRoutingBasedOnRedis) * [使用 LuaRocks 与 ngx_lua](http://openresty.org/#UsingLuaRocks) * [ngx_lua 的介绍信息](https://github.com/openresty/lua-nginx-module/wiki/Introduction) * [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) * [echo-nginx-module](http://github.com/openresty/echo-nginx-module) * [drizzle-nginx-module](http://github.com/openresty/drizzle-nginx-module) * [postgres-nginx-module](https://github.com/FRiCKLE/ngx_postgres) * [memc-nginx-module](http://github.com/openresty/memc-nginx-module) * [OpenResty 捆绑包](http://openresty.org) * [Nginx Systemtap 工具箱](https://github.com/openresty/nginx-systemtap-toolkit) [返回目录](#table-of-contents) Directives ========== * [lua_use_default_type](#lua_use_default_type) * [lua_malloc_trim](#lua_malloc_trim) * [lua_code_cache](#lua_code_cache) * [lua_regex_cache_max_entries](#lua_regex_cache_max_entries) * [lua_regex_match_limit](#lua_regex_match_limit) * [lua_package_path](#lua_package_path) * [lua_package_cpath](#lua_package_cpath) * [init_by_lua](#init_by_lua) * [init_by_lua_block](#init_by_lua_block) * [init_by_lua_file](#init_by_lua_file) * [init_worker_by_lua](#init_worker_by_lua) * [init_worker_by_lua_block](#init_worker_by_lua_block) * [init_worker_by_lua_file](#init_worker_by_lua_file) * [set_by_lua](#set_by_lua) * [set_by_lua_block](#set_by_lua_block) * [set_by_lua_file](#set_by_lua_file) * [content_by_lua](#content_by_lua) * [content_by_lua_block](#content_by_lua_block) * [content_by_lua_file](#content_by_lua_file) * [rewrite_by_lua](#rewrite_by_lua) * [rewrite_by_lua_block](#rewrite_by_lua_block) * [rewrite_by_lua_file](#rewrite_by_lua_file) * [access_by_lua](#access_by_lua) * [access_by_lua_block](#access_by_lua_block) * [access_by_lua_file](#access_by_lua_file) * [header_filter_by_lua](#header_filter_by_lua) * [header_filter_by_lua_block](#header_filter_by_lua_block) * [header_filter_by_lua_file](#header_filter_by_lua_file) * [body_filter_by_lua](#body_filter_by_lua) * [body_filter_by_lua_block](#body_filter_by_lua_block) * [body_filter_by_lua_file](#body_filter_by_lua_file) * [log_by_lua](#log_by_lua) * [log_by_lua_block](#log_by_lua_block) * [log_by_lua_file](#log_by_lua_file) * [balancer_by_lua_block](#balancer_by_lua_block) * [balancer_by_lua_file](#balancer_by_lua_file) * [lua_need_request_body](#lua_need_request_body) * [ssl_certificate_by_lua_block](#ssl_certificate_by_lua_block) * [ssl_certificate_by_lua_file](#ssl_certificate_by_lua_file) * [ssl_session_fetch_by_lua_block](#ssl_session_fetch_by_lua_block) * [ssl_session_fetch_by_lua_file](#ssl_session_fetch_by_lua_file) * [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block) * [ssl_session_store_by_lua_file](#ssl_session_store_by_lua_file) * [lua_shared_dict](#lua_shared_dict) * [lua_socket_connect_timeout](#lua_socket_connect_timeout) * [lua_socket_send_timeout](#lua_socket_send_timeout) * [lua_socket_send_lowat](#lua_socket_send_lowat) * [lua_socket_read_timeout](#lua_socket_read_timeout) * [lua_socket_buffer_size](#lua_socket_buffer_size) * [lua_socket_pool_size](#lua_socket_pool_size) * [lua_socket_keepalive_timeout](#lua_socket_keepalive_timeout) * [lua_socket_log_errors](#lua_socket_log_errors) * [lua_ssl_ciphers](#lua_ssl_ciphers) * [lua_ssl_crl](#lua_ssl_crl) * [lua_ssl_protocols](#lua_ssl_protocols) * [lua_ssl_trusted_certificate](#lua_ssl_trusted_certificate) * [lua_ssl_verify_depth](#lua_ssl_verify_depth) * [lua_http10_buffering](#lua_http10_buffering) * [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) * [access_by_lua_no_postpone](#access_by_lua_no_postpone) * [lua_transform_underscores_in_response_headers](#lua_transform_underscores_in_response_headers) * [lua_check_client_abort](#lua_check_client_abort) * [lua_max_pending_timers](#lua_max_pending_timers) * [lua_max_running_timers](#lua_max_running_timers) 构建基本的 Nginx Lua 脚本均是通过指令完成。当用户 Lua 代码执行时,这些指令用来指名 如何使用脚本的返回结果。下面的流程图给我们演示这些指令的执行顺序。 ![Lua Nginx 指令](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png) [返回目录](#table-of-contents) lua_use_default_type -------------------- **语法:** *lua_use_default_type on | off* **默认:** *lua_use_default_type on* **环境:** *http, server, location, location if* 指定响应头中 Content-Type 的默认值,是否使用 default_type 指令中指明的 MIME 类型。 如果你的 Lua 请求处理程序不想要默认的 Content-Type 响应头,可以关闭这个指令。 默认情况下该指令被打开。 这个指令在 0.9.1 版本中首次引入。 [返回目录](#table-of-contents) lua_malloc_trim --------------- **语法:** *lua_malloc_trim <request-count>* **默认:** *lua_malloc_trim 1000* **环境:** *http* 当 NGINX 核心每执行 `N` 次请求,告诉底层 `libc` 运行库,释放已缓存空闲内存还给操作系统。 `N` 的默认值是 1000。可以配置新的数值来控制 "请求数",小的数字意味着更频繁的释放,这可能会引入比较高的 CPU 时间消耗和较少内存占用。 而大的数字通常则占用较少的 CPU 时间消耗和较大的内存占用。 所以这里需要根据自己的使用场景来调整。 配置参数为 `0` ,代表关闭周期性的内存整理。 ```nginx lua_malloc_trim 0; # 完全关闭 trim ``` 为了完成请求计数,目前是在 NGINX 的 log 阶段实现的。 当有子请求存在并且 `nginx.conf` 中出现 [log_subrequest on](http://nginx.org/en/docs/http/ngx_http_core_module.html#log_subrequest) 指令,可以能更快获得计数增长。 默认情况只统计“主请求”计数。 注意:该指令 *不* 影响 LuaJIT 基于 `mmap` 系统调用的内存分配。 该指令在 `v0.10.7` 版本首次引入。 [返回目录](#directives) lua_code_cache -------------- **语法:** *lua_code_cache on | off* **默认:** *lua_code_cache on* **环境:** *http, server, location, location if* 打开或者关闭 *_by_lua_file 指令(类似 set_by_lua_file 和 content_by_lua_file) 中指定的 Lua 代码,以及 Lua 模块的 Lua 代码缓存。 当缓存关闭时,每个 ngx_lua 处理的请求都将会运行在一个单独的 Lua 虚拟机实例中,从 0.9.3 版本开始。 所以在 set_by_lua_file, content_by_lua_file, access_by_lua_file 中引用的 Lua 文件不会被缓存, 并且所有使用的 Lua 模块都会从头开始加载。有了这个选项,开发者很容易通过编辑文件并重新请求的方法进行测试。 然而需要注意的是,当你编辑写在 nginx.conf 中内嵌的 Lua 代码时, 比如通过 set_by_lua, content_by_lua, access_by_lua, rewrite_by_lua 这些指令 写在 nginx.conf 里面的 Lua 代码,缓存不会被更新。 因为只有 Nginx 的配置文件解释器才能正确解析 nginx.conf, 所以重新加载配置文件的唯一办法是发送 HUP 信号或者重启 Nginx。 ```shell kill -HUP pid nginx -s reload ``` 即使代码缓存打开了,在 *_by_lua_file 中使用 dofile 或 loadfile 函数时内容不会被缓存(除非你自己缓存结果)。 通常你可以在 init_by_lua 或 init_by_lua_file 指令中加载所有这些文件, 或者让这些 Lua 文件变成真正的 Lua 模块,通过 require 来加载。 现在 ngx_lua 模块还不支持 Apache mod_lua 模块中可用的 stat 模式(已经列入待做任务)。 不推荐在生产环境中关闭 lua 代码缓存,请确保它只在开发环境中使用,他对整体性能有非常明显的影响。 举个例子,输出“你好世界”在没有开启 lua 代码缓存时可以降低一个量级。 [返回目录](#directives) lua_regex_cache_max_entries --------------------------- **语法:** *lua_regex_cache_max_entries <num>* **默认:** *lua_regex_cache_max_entries 1024* **环境:** *http* 在工作进程级别,指定正则表达式编译缓存允许的最大数目。 正则表达式被用于 [ngx.re.match](#ngxrematch), [ngx.re.gmatch](#ngxregmatch), [ngx.re.sub](#ngxresub), 和 [ngx.re.gsub](#ngxregsub),如果使用`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](https://github.com/openresty/lua-resty-core) 实现的 `ngx.re.*`, 一个基于 LRU 的缓存将被用到这里的正则表达式缓存。 对于部分正则表达式(字符串的各种替换,如 [ngx.re.sub](#ngxresub) 和 [ngx.re.gsub](#ngxregsub)),不要使用 `o`选项,这类正则每次都不一样,缓存无法被利用。这样我们可以避免撞上最大数的限制。 [返回目录](#directives) lua_regex_match_limit --------------------- **语法:** *lua_regex_match_limit <num>* **默认:** *lua_regex_match_limit 0* **环境:** *http* 指定执行 [ngx.re API](#ngxrematch) 时使用 PCRE 库的"匹配限制"。引述 PCRE 手册,“the limit ... has the effect of limiting the amount of backtracking that can take place”。 当触发了这个限制,在 Lua 代码的 [ngx.re API](#ngxrematch) 函数,将返回错误信息 "pcre_exec() failed: -8"。 当设置了限制为 0,将使用编译 PCRE 库的默认 "match limit"。这也是这个配置的默认值。 这个指令是在`v0.8.5`发行版被首次引入的。 [返回目录](#directives) lua_package_path ---------------- **语法:** *lua_package_path <lua-style-path-str>* **默认:** *当前环境 LUA_PATH 的环境变量或编译指定的默认值* **环境:** *http* 设置 [set_by_lua*](#set_by_lua),[content_by_lua*](#content_by_lua) 和 其他脚本对 Lua 模块的查找路径。路径字符串是标准 Lua 路径格式,特殊标识 `;;` 可被用来代表原始搜索路径。 从`v0.5.0rc29`发行版开始,特殊符号`$prefix` 或 `${prefix}`可用于搜索路径字符串中。`server prefix`的值,通常是由 Nginx 服务启动时的`-p PATH`命令行决定的。 [返回目录](#directives) lua_package_cpath ----------------- **语法:** *lua_package_cpath <lua-style-cpath-str>* **默认:** *当前环境 LUA_CPATH 的环境变量或编译指定的默认值* **环境:** *http* 设置 [set_by_lua*](#set_by_lua),[content_by_lua*](#content_by_lua) 和其他脚本对 Lua C 模块的查找路径。 cpath 路径字符串是标准 Luacpath 路径格式,特殊标识`;;` 可被用来代表原始 cpath 路径。 从`v0.5.0rc29`发行版开始,特殊符号`$prefix` 或 `${prefix}`可用于搜索路径字符串中。`server prefix`的值,通常是由 Nginx 服务启动时的`-p PATH`命令行决定的。 [返回目录](#directives) init_by_lua ----------- **语法:** *init_by_lua <lua-script-str>* **环境:** *http* **阶段:** *loading-config* **注意** 自从 `v0.9.17` 版本, 不鼓励使用该指令,应使用新的 [init_by_lua_block](#init_by_lua_block) 指令进行替代。 当 Nginx master 进程(如果有)加载 Nginx 配置文件时,在全局的 Lua 虚拟机上运行 `` 指定的 Lua 代码。 当 Nginx 收到`HUP`信号并开始重新加载配置文件,Lua 虚拟机将重新创建并且`init_by_lua`在新的 Lua 虚拟机中再次执行。为防止[lua_code_cache](#lua_code_cache)指令是关闭的(默认打开),对于这个场景`init_by_lua`将在每个请求之上运行,因为在这个场景中,每个请求都会创建新的 Lua 虚拟机,他们都是独立存在。 通常,你可以在服务启动时注册 Lua 全局变量或预加载 Lua 模块。这是个预加载Lua模块的示例代码: ```nginx init_by_lua 'cjson = require "cjson"'; server { location = /api { content_by_lua_block { ngx.say(cjson.encode({dog = 5, cat = 6})) } } } ``` 你也可以在这个阶段初始化[lua_shared_dict](#lua_shared_dict)共享内存内容。这里是示例代码: ```nginx lua_shared_dict dogs 1m; init_by_lua ' 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](#lua_shared_dict)的共享数据是不会被清空的。这种情况下,如果你不想在`init_by_lua`再次初始化你的共享数据,你需要设置一个个性标识并且在你的`init_by_lua`代码中每次都做检查。 因为在这个上下文中的Lua代码是在 Nginx fork 工作进程之前(如果有)执行,加载的数据和代码将被友好 [Copy-on-write (COW)](http://en.wikipedia.org/wiki/Copy-on-write) 特性提供给其他所有工作进程,从而节省了大量内存。 不要在这个上下文中初始化你自己的 Lua 全局变量,因为全局变量的使用有性能损失并会带来全局命名污染(可以查看 [Lua 变量范围](#lua-variable-scope)获取更多细节)。推荐的方式是正确使用 [Lua模块](http://www.lua.org/manual/5.1/manual.html#5.3) 文件(不要使用标准 Lua 函数 [module()](http://www.lua.org/manual/5.1/manual.html#pdf-module)来定义 Lua 模块,因为它同样对全局命名空间有污染),在`init_by_lua` 或 其他上下文中调用 [require()](http://www.lua.org/manual/5.1/manual.html#pdf-require) 来加载你自己的模块文件。[require()](http://www.lua.org/manual/5.1/manual.html#pdf-require) 会在全局 Lua 注册的`package.loaded`表中缓存 Lua 模块,所以在整个 Lua 虚拟机实例中你的模块将只会加载一次。 在这个上下文中,只有一小部分的 [Nginx Lua API](#nginx-api-for-lua) 是被支持的: * 记录日志的 APIs:[ngx.log](#ngxlog) 和 [print](#print) * 共享内存字典 APIs:[ngx.shared.DICT](#ngxshareddict) 在这个上下文中,根据用户的后续需要,将会支持更多的 Nginx Lua APIs。 基本上,在这个上下文中,你可以保守使用 Lua 库完成阻塞 I/O 调用,因为在 master 进程的阻塞调用在服务的启动过程中是完全没问题的。进一步说在配置加载阶段,Nginx 核心就是阻塞 I/O 方式处理的(至少在解析上游主机名称时)。 你应该非常小心,在这种情况下注册的 Lua 代码潜在的安全漏洞,因为 Nginx 的主进程经常是'root`帐户下运行。 这个指令是 `v0.5.5` 版本中第一次引入的。 [返回目录](#directives) init_by_lua_block ----------------- **语法:** *init_by_lua_block { lua-script }* **环境:** *http* **阶段:** *loading-config* 与 [init_by_lua](#init_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx init_by_lua_block { print("I need no extra escaping here, for example: \r\nblah") } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) init_by_lua_file ---------------- **语法:** *init_by_lua_file <path-to-lua-script-file>* **环境:** *http* **阶段:** *loading-config* 与 [init_by_lua](#init_by_lua) 等价,通过``指定文件的 Lua 代码 或 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support)来执行。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 该指令在`v0.5.5`发行版第一次被引入。 [返回目录](#directives) init_worker_by_lua ------------------ **语法:** *init_worker_by_lua <lua-script-str>* **环境:** *http* **阶段:** *starting-worker* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [init_worker_by_lua_block](#init_worker_by_lua_block) 指令进行替代。 开启 master 进程模式,Nginx 工作进程启动时执行指定的 Lua 代码。关闭 master 模式,将在 [init_by_lua*](#init_by_lua) 后直接运行。 这个指令经常被用来创建单进程的反复执行定时器(通过 [ngx.timer.at](#ngxtimerat) Lua API 创建),可以是后端服务健康检查,也可以是其他定时的日常工作。下面是个例子: ```nginx 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 ok, err = new_timer(delay, check) if not ok then log(ERR, "failed to create timer: ", err) return end '; ``` 这个指令是在`v0.9.5`发行版第一次引入。 [返回目录](#directives) init_worker_by_lua_block ------------------------ **语法:** *init_worker_by_lua_block { lua-script }* **环境:** *http* **阶段:** *starting-worker* 与 [init_worker_by_lua](#init_worker_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx init_worker_by_lua_block { print("I need no extra escaping here, for example: \r\nblah") } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) init_worker_by_lua_file ----------------------- **语法:** *init_worker_by_lua_file <lua-file-path>* **环境:** *http* **阶段:** *starting-worker* 与 [init_worker_by_lua](#init_worker_by_lua)等价,通过``指定的 Lua 或 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 文件来执行。 该指令是在`v0.9.5`发行版第一次引入。 [返回目录](#directives) set_by_lua ---------- **语法:** *set_by_lua $res <lua-script-str> [$arg1 $arg2 ...]* **环境:** *server, server if, location, location if* **阶段:** *rewrite* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [set_by_lua_block](#set_by_lua_block) 指令进行替代。 使用可选的输入参数`$arg1 $arg2 ...`,执行指定的代码``,并返回字符串结果到`$res`。 ``的代码可以做 [API 调用](#nginx-api-for-lua),并能从`ngx.arg`表中获取输入参数(下标起始值是`1`并顺序增长)。 该指令被设计为执行短小、快速的代码块,因为代码执行时Nginx的事件循环是被阻塞的。因此应避免耗时的代码处理。 这个指令是通过挂载自定义命令到标准 [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html) 模块列表来实现。因为模块 [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html) 中是不支持非阻塞 I/O,所以在本指令中,是无法 yield 当前 Lua 的“轻线程”。 在`set_by_lua`的上下文中,至少下列 API 函数目前是被禁止的: * 输出 API 函数 (例如 [ngx.say](#ngxsay) 和 [ngx.send_headers](#ngxsend_headers)) * 控制 API 函数 (例如 [ngx.exit](#ngxexit)) * 子请求 API 函数 (例如 [ngx.location.capture](#ngxlocationcapture) 和 [ngx.location.capture_multi](#ngxlocationcapture_multi)) * Cosocket API 函数 (例如 [ngx.socket.tcp](#ngxsockettcp) 和 [ngx.req.socket](#ngxreqsocket)) * 休眠 API 函数 [ngx.sleep](#ngxsleep) 额外注意的,本指令一次只能写回一个值到一个 Nginx 变量。尽管如此,可以使用 [ngx.var.VARIABLE](#ngxvarvariable) 接口绕过这个限制。 ```nginx location /foo { set $diff ''; # we have to predefine the $diff variable here set_by_lua $sum ' local a = 32 local b = 56 ngx.var.diff = a - b; -- write to $diff directly return a + b; -- return the $sum value normally '; echo "sum = $sum, diff = $diff"; } ``` 这个指令可以自由的与其他指令模块混合使用,如 [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html), [set-misc-nginx-module](http://github.com/openresty/set-misc-nginx-module) 和 [array-var-nginx-module](http://github.com/openresty/array-var-nginx-module)。所有这些指令的执行顺序,将和他们配置文件中出现的顺序一致。 ```nginx set $foo 32; set_by_lua $bar 'return tonumber(ngx.var.foo) + 1'; set $baz "bar: $bar"; # $baz == "bar: 33" ``` 自 `v0.5.0rc29` 版本开始,本指令的 `` 参数中不再支持内联 Nginx 变量,所以可以直接使用 $ 字符作为其字面值。 这个指令需要 [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) 模块。 [返回目录](#directives) set_by_lua_block ---------------- **语法:** *set_by_lua_block $res { lua-script }* **环境:** *server, server if, location, location if* **阶段:** *rewrite* 与 [set_by_lua*](#set_by_lua) 指令相似,以下情况除外: 1. 该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义) 1. 该指令和 [set_by_lua*](#set_by_lua) 一样,在 Lua 脚本的后面不支持额外参数 例如: ```nginx set_by_lua_block $res { return 32 + math.cos(32) } # $res now has the value "32.834223360507" or alike. ``` 在 Lua 代码块中无需任何的特殊转义。 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) set_by_lua_file --------------- **语法:** *set_by_lua_file $res <path-to-lua-script-file> [$arg1 $arg2 ...]* **环境:** *server, server if, location, location if* **阶段:** *rewrite* 除了通过文件``的内容指定 Lua 代码外,该指令与 [set_by_lua*](#set_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 对于该指令,对``的字符串参数支持内联 Nginx 变量。但必须要额外注意注入攻击。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 当 Lua 代码缓存开启(默认),用户代码在第一次请求时完成加载(只有一次)并缓存,当 Lua 文件被修改时,每次都要对 Nginx 配置进行重新加载。Lua 代码缓存是可以暂时被禁用,通过开关 [lua_code_cache](#lua_code_cache) 在`nginx.conf`中设置为`off`,这样就可以避免反复重新加载 Nginx。 该指令需要 [ngx_devel_kit](https://github.com/simpl/ngx_devel_kit) 模块。 [返回目录](#directives) content_by_lua -------------- **语法:** *content_by_lua <lua-script-str>* **环境:** *location, location if* **阶段:** *content* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [content_by_lua_block](#content_by_lua_block) 指令进行替代。 作为"内容处理程序",为每一个请求执行``中指定的Lua代码。 这些 Lua 代码可以调用 [全部 API](#nginx-api-for-lua),并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。 不要将本指令和其他内容处理程序指令放到同一个 location 中。 比如,本指令和 [proxy_pass](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) 指令就不能在同一个 location 中使用。 [返回目录](#directives) content_by_lua_block -------------------- **语法:** *content_by_lua_block { lua-script }* **环境:** *location, location if* **阶段:** *content* 与 [content_by_lua](#content_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx content_by_lua_block { ngx.say("I need no extra escaping here, for example: \r\nblah") } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) content_by_lua_file ------------------- **语法:** *content_by_lua_file <path-to-lua-script-file>* **环境:** *location, location if* **阶段:** *content* 除了通过文件``的内容指定 Lua 代码外,该指令与 [content_by_lua](#content_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 在``中可以使用 Nginx 的内置变量来提高灵活性,然而这带有一定的风险,通常并不推荐使用。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 当 Lua 代码缓存开启(默认),用户代码在第一次请求时完成加载(只有一次)并缓存,当 Lua 文件被修改时,每次都要对 Nginx 配置进行重新加载。Lua 代码缓存是可以暂时被禁用,通过开关 [lua_code_cache](#lua_code_cache) 在`nginx.conf`中设置为`off`,这样就可以避免反复重新加载 Nginx。 支持通过 Nginx 变量完成动态调度文件路径,例如: ```nginx # 注意: nginx 变量必须要小心过滤,否则它将带来严重的安全风险! location ~ ^/app/([-_a-zA-Z0-9/]+) { set $path $1; content_by_lua_file /path/to/lua/app/root/$path.lua; } ``` 一定要非常小心恶意用户的输入,并始终仔细验证或过滤掉用户提供的路径项。 [返回目录](#directives) rewrite_by_lua -------------- **语法:** *rewrite_by_lua <lua-script-str>* **环境:** *http, server, location, location if* **阶段:** *rewrite tail* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [rewrite_by_lua_block](#rewrite_by_lua_block) 指令进行替代。 作为一个重写阶段的处理程序,为每个请求执行由``指定的 Lua 代码。 这些 Lua 代码可以调用 [全部 API](#nginx-api-for-lua),并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。 注意这个处理过程总是在标准 [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html) 的 *后* 面。所以下面的示例可以按照预期执行: ```nginx location /foo { set $a 12; # create and initialize $a set $b ""; # create and initialize $b rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; echo "res = $b"; } ``` 因为 `set $a 12` 和 `set $b ""` 的执行都在 [rewrite_by_lua](#rewrite_by_lua) 的前面。 另一方面,下面的示例是不能按照预期执行: ```nginx ? location /foo { ? set $a 12; # create and initialize $a ? set $b ''; # create and initialize $b ? rewrite_by_lua 'ngx.var.b = tonumber(ngx.var.a) + 1'; ? if ($b = '13') { ? rewrite ^ /bar redirect; ? break; ? } ? ? echo "res = $b"; ? } ``` 因为 `if` 是 [rewrite_by_lua](#rewrite_by_lua) 之 *前* 执行的,尽管在配置中它被放到 [rewrite_by_lua](#rewrite_by_lua) 后面。 正确的处理方式应该是这样: ```nginx location /foo { set $a 12; # create and initialize $a set $b ''; # create and initialize $b rewrite_by_lua ' ngx.var.b = tonumber(ngx.var.a) + 1 if tonumber(ngx.var.b) == 13 then return ngx.redirect("/bar"); end '; echo "res = $b"; } ``` 注意,[ngx_eval](http://www.grid.net.ru/nginx/eval.en.html) 模块可以近似的使用 [rewrite_by_lua](#rewrite_by_lua)。例如: ```nginx location / { eval $res { proxy_pass http://foo.com/check-spam; } if ($res = 'spam') { rewrite ^ /terms-of-use.html redirect; } fastcgi_pass ...; } ``` 在 ngx_lua 中可以这样实施: ```nginx location = /check-spam { internal; proxy_pass http://foo.com/check-spam; } location / { rewrite_by_lua ' local res = ngx.location.capture("/check-spam") if res.body == "spam" then return ngx.redirect("/terms-of-use.html") end '; fastcgi_pass ...; } ``` 如同其他重写阶段处理,[rewrite_by_lua](#rewrite_by_lua) 在子请求中也执行的。 注意,在 [rewrite_by_lua](#rewrite_by_lua) 处理内部,当调用`ngx.exit(ngx.OK)`时,nginx 请求将继续下一阶段的内容处理。要在 [rewrite_by_lua](#rewrite_by_lua)处理中终结当前请求,调用 [ngx.exit](#ngxexit),成功的请求设定 status >= 200 (`ngx.HTTP_OK`) 并 status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`),失败的请求设定`ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (或其他相关的)。 如果使用了 [ngx_http_rewrite_module](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html) 的 [rewrite](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite) 指令来改变 URI 并发起内部重定向,那么在当前 location 内任何有序的 [rewrite_by_lua](#rewrite_by_lua) 或 [rewrite_by_lua_file](#rewrite_by_lua_file) 代码都将不被执行。例如: ```nginx location /foo { rewrite ^ /bar; rewrite_by_lua 'ngx.exit(503)'; } location /bar { ... } ``` 如果 `rewrite ^ /bar last` 被用做一个类似内部重定向使用,它将被忽略,这里的 Lua 代码 `ngx.exit(503)` 是永远不能执行的。如果使用了 `break` 标识,这里将没有内部重定向,并且执行 `rewrite_by_lua` 中的 Lua 代码。 `rewrite_by_lua`代码将永远在 `rewrite` 请求处理阶段后面,除非 [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) 配置开启。 [返回目录](#directives) rewrite_by_lua_block -------------------- **语法:** *rewrite_by_lua_block { lua-script }* **环境:** *http, server, location, location if* **阶段:** *rewrite tail* 与 [rewrite_by_lua](#rewrite_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx rewrite_by_lua_block { do_something("hello, world!\nhiya\n") } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) rewrite_by_lua_file ------------------- **语法:** *rewrite_by_lua_file <path-to-lua-script-file>* **环境:** *http, server, location, location if* **阶段:** *rewrite tail* 除了通过文件``的内容指定 Lua 代码外,该指令与 [rewrite_by_lua](#rewrite_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 在``中可以使用 Nginx 的内置变量用来提高灵活性,然而这带有一定的风险,通常并不推荐使用。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 当 Lua 代码缓存开启(默认),用户代码在第一次请求时完成加载(只有一次)并缓存,当 Lua 文件被修改时,每次都要对 Nginx 配置进行重新加载。Lua 代码缓存是可以暂时被禁用,通过开关 [lua_code_cache](#lua_code_cache) 在`nginx.conf`中设置为`off`,这样就可以避免反复重新加载 Nginx。 `rewrite_by_lua_file`代码将永远在 `rewrite` 请求处理阶段后面,除非 [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone) 配置开启。 支持通过 Nginx 变量完成动态调度文件路径,就像 [content_by_lua_file](#content_by_lua_file) 一样。 [返回目录](#directives) access_by_lua ------------- **语法:** *access_by_lua <lua-script-str>* **环境:** *http, server, location, location if* **阶段:** *access tail* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [access_by_lua_block](#access_by_lua_block) 指令进行替代。 扮演 access 阶段处理,对每次请求执行在``中指名的 Lua 代码。 这些 Lua 代码可以调用 [全部 API](#nginx-api-for-lua),并作为一个新的协程,在一个独立的全局环境中执行(就像一个沙盒)。 注意:本指令的处理总是在标准 [ngx_http_access_module](http://nginx.org/en/docs/http/ngx_http_access_module.html) 的 *后* 面。所以下面的示例可以按照预期工作: ```nginx location / { deny 192.168.1.1; allow 192.168.1.0/24; allow 10.1.1.0/16; deny all; access_by_lua ' local res = ngx.location.capture("/mysql", { ... }) ... '; # proxy_pass/fastcgi_pass/... } ``` 换句话说,如果一个客户端 IP 地址在黑名单中,它将在 [access_by_lua](#access_by_lua) 中的 Mysql 复杂认证请求之前被拒绝。 注意,[ngx_auth_request](http://mdounin.ru/hg/ngx_http_auth_request_module/) 模块可以近似的被 [access_by_lua](#access_by_lua) 实现: ```nginx location / { auth_request /auth; # proxy_pass/fastcgi_pass/postgres_pass/... } ``` 使用 ngx_lua 是这样: ```nginx location / { access_by_lua ' local res = ngx.location.capture("/auth") if res.status == ngx.HTTP_OK then return end if res.status == ngx.HTTP_FORBIDDEN then ngx.exit(res.status) end ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) '; # proxy_pass/fastcgi_pass/postgres_pass/... } ``` 和其他 access 阶段处理实现一样,[access_by_lua](#access_by_lua) 将 *不* 能运行在子请求中。 注意,在 [access_by_lua](#access_by_lua) 处理内部,当调用`ngx.exit(ngx.OK)`时,nginx 请求将继续下一阶段的内容处理。要在 [access_by_lua](#access_by_lua) 处理中终结当前请求,调用 [ngx.exit](#ngxexit),成功的请求设定 status >= 200 (`ngx.HTTP_OK`) 并 status < 300 (`ngx.HTTP_SPECIAL_RESPONSE`),失败的请求设定`ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)` (或其他相关的)。 从 `v0.9.20` 版本开始,你可以使用 [access_by_lua_no_postpone](#access_by_lua_no_postpone) 指令来控制该 handler 在 Nginx 的 "access" 请求处理中的执行时机。 [返回目录](#directives) access_by_lua_block ------------------- **语法:** *access_by_lua_block { lua-script }* **环境:** *http, server, location, location if* **阶段:** *access tail* 与 [access_by_lua](#access_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 For instance, ```nginx access_by_lua_block { do_something("hello, world!\nhiya\n") } ``` 该指令在 `v0.9.17` 版本首次引入。 access_by_lua_file ------------------ **语法:** *access_by_lua_file <path-to-lua-script-file>* **环境:** *http, server, location, location if* **阶段:** *access tail* 除了通过文件``的内容指定 Lua 代码外,该指令与 [access_by_lua](#access_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 在``中可以使用 Nginx 的内置变量用来提高灵活性,然而这带有一定的风险,通常并不推荐使用。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 当 Lua 代码缓存开启(默认),用户代码在第一次请求时完成加载(只有一次)并缓存,当 Lua 文件被修改时,每次都要对 Nginx 配置进行重新加载。Lua 代码缓存是可以暂时被禁用,通过开关 [lua_code_cache](#lua_code_cache) 在`nginx.conf`中设置为`off`,这样就可以避免反复重新加载 Nginx。 支持通过 Nginx 变量完成动态调度文件路径,就像 [content_by_lua_file](#content_by_lua_file) 一样。 [返回目录](#directives) header_filter_by_lua -------------------- **语法:** *header_filter_by_lua <lua-script-str>* **环境:** *http, server, location, location if* **阶段:** *output-header-filter* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [header_filter_by_lua_block](#header_filter_by_lua_block) 指令进行替代。 用``中指名的lua代码,来完成应答消息头部的过滤。 注意,下列的接口函数在这个执行阶段是被禁用的: - 输出类函数(例:ngx.say 和 ngx.send_headers) - 控制类函数(例:ngx.redirect 和 ngx.exec) - 子请求相关函数(例:ngx.location.capture和ngx.location.capture_multi) - cosocket 类函数(例:ngx.socket.tcp 和 ngx.req.socket) 这里有个使用 Lua 过滤完成覆盖应答头的例子(如果没有则添加): ```nginx location / { proxy_pass http://mybackend; header_filter_by_lua 'ngx.header.Foo = "blah"'; } ``` 该指令在版本 `v0.2.1rc20` 中第一次引入。 [返回目录](#directives) header_filter_by_lua_block -------------------------- **语法:** *header_filter_by_lua_block { lua-script }* **环境:** *http, server, location, location if* **阶段:** *output-header-filter* 与 [header_filter_by_lua](#header_filter_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx header_filter_by_lua_block { ngx.header["content-length"] = nil } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) header_filter_by_lua_file ------------------------- **语法:** *header_filter_by_lua_file <path-to-lua-script-file>* **环境:** *http, server, location, location if* **阶段:** *output-header-filter* 除了通过文件``的内容指定 Lua 代码外,该指令与 [header_filter_by_lua](#header_filter_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 该指令是在`v0.2.1rc20`版本第一次引入。 [返回目录](#directives) body_filter_by_lua ------------------ **语法:** *body_filter_by_lua <lua-script-str>* **环境:** *http, server, location, location if* **阶段:** *output-body-filter* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [body_filter_by_lua_block](#body_filter_by_lua_block) 指令进行替代。 使用``指定的 Lua 代码定义一个输出应答体过滤器。 输入数据块是 [ngx.arg](#ngxarg)\[1\](Lua的字符串形式),结束标识"eof"是应答体数据最后一位[ngx.arg](#ngxarg)\[2\](Lua的布尔值形式)。 在这个场景下,结束标识"eof"仅仅是 Nginx chain 缓冲区的`last_buf`(主请求)或`last_in_chain`(子请求)。(在`v0.7.14`版本之前,结束标识"eof"在子请求中是完全不能使用的。) 使用下面 Lua 代码,可以对输出数据流立即终止: ```lua return ngx.ERROR ``` 这样截断响应体,通常导致结果不完整的,也是无效的响应。 本指令的 Lua 代码可以使用 Lua 字符串或字符串的表重写 [ngx.arg](#ngxarg)\[1\] 输入数据块内容,从而完成 Nginx 输出体下游过滤数据修改。例如,在输出体转换所有的小写字母,我们可以这样用: ```nginx location / { proxy_pass http://mybackend; body_filter_by_lua 'ngx.arg[1] = string.upper(ngx.arg[1])'; } ``` 当设置`nil`或一个空的 Lua 字符串值给`ngx.arg[1]`,将没有任何数据块下发到 Nginx 下游。 同样,新的结束标识"eof"也可以通过对 [ngx.arg](#ngxarg)\[2\] 设定一个布尔值。例如: ```nginx location /t { echo hello world; echo hiya globe; body_filter_by_lua ' local chunk = ngx.arg[1] if string.match(chunk, "hello") then ngx.arg[2] = true -- new eof return end -- just throw away any remaining chunk data ngx.arg[1] = nil '; } ``` 然后 `GET /t` 将返回下面的结果: hello world 就是说,当应答体过滤发现一个块包含关键字 "hello",它将立即设置结束标识"eof"为 true ,应答内容被截断尽管后面还有有效数据。 当Lua代码可能改变应答体的长度时,我们必须总是清空响应头中的`Content-Length`(如果有),强制使用流式输出,如: ```nginx location /foo { # fastcgi_pass/proxy_pass/... header_filter_by_lua_block { ngx.header.content_length = nil } body_filter_by_lua_block { ngx.arg[1] = string.len(ngx.arg[1]) .. "\\n" }; } ``` 注意:下面这些 API 函数在这个环境中是禁用的,这受制于当前 Nginx 输出过滤器的实现: * 输出 API 函数类(例如:[ngx.say](#ngxsay) 和 [ngx.send_headers](#ngxsend_headers)) * 控制 API 函数类(例如:[ngx.redirect](#ngxredirect) 和 [ngx.exec](#ngxexec)) * 子请求函数类(例如:[ngx.location.capture](#ngxlocationcapture) 和 [ngx.location.capture_multi](#ngxlocationcapture_multi)) * cosocket 函数类(例如:[ngx.socket.tcp](#ngxsockettcp) 和 [ngx.req.socket](#ngxreqsocket)) Nginx 输出过滤器在一个单独请求中可能被调用多次,因为应答体可能使用块的方式进行投递。所以,本指令中的 Lua 代码在这个单独的 HTTP 请求生命周期内,同样会执行多次。 该指令在`v0.5.0rc32`版本中首次引入。 [返回目录](#directives) body_filter_by_lua_block ------------------------ **语法:** *body_filter_by_lua_block { lua-script-str }* **环境:** *http, server, location, location if* **阶段:** *output-body-filter* 与 [body_filter_by_lua*](#body_filter_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx body_filter_by_lua_block { local data, eof = ngx.arg[1], ngx.arg[2] } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) body_filter_by_lua_file ----------------------- **语法:** *body_filter_by_lua_file <path-to-lua-script-file>* **环境:** *http, server, location, location if* **阶段:** *output-body-filter* 除了通过文件``的内容指定 Lua 代码外,该指令与 [body_filter_by_lua*](#body_filter_by_lua) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 该指令是在`v0.5.0rc32`版本第一次引入。 [返回目录](#directives) log_by_lua ---------- **语法:** *log_by_lua <lua-script-str>* **环境:** *http, server, location, location if* **阶段:** *log* **注意** 自从 `v0.9.17` 版本, 使用该指令是 *不优雅* 的,应使用新的 [log_by_lua_block](#log_by_lua_block) 指令进行替代。 在 `log` 请求处理阶段执行内嵌在``的 Lua 代码。它不替代当前access的日志,而是在其前面执行。 注意,当前环境中以下 API 函数当前是被禁用的: * 输出API函数类(例如:[ngx.say](#ngxsay) 和 [ngx.send_headers](#ngxsend_headers)) * 控制API函数类(例如:[ngx.exit](#ngxexit) 和 [ngx.exec](#ngxexec)) * 子请求函数类(例如:[ngx.location.capture](#ngxlocationcapture) 和 [ngx.location.capture_multi](#ngxlocationcapture_multi)) * cosocket 函数类(例如:[ngx.socket.tcp](#ngxsockettcp) 和 [ngx.req.socket](#ngxreqsocket)) 这是一个收集 [$upstream_response_time](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_response_time) 平均处理的例子: ```nginx lua_shared_dict log_dict 5M; server { location / { proxy_pass http://mybackend; log_by_lua ' local log_dict = ngx.shared.log_dict local upstream_time = tonumber(ngx.var.upstream_response_time) local sum = log_dict:get("upstream_time-sum") or 0 sum = sum + upstream_time log_dict:set("upstream_time-sum", sum) local newval, err = log_dict:incr("upstream_time-nb", 1) if not newval and err == "not found" then log_dict:add("upstream_time-nb", 0) log_dict:incr("upstream_time-nb", 1) end '; } location = /status { content_by_lua_block { local log_dict = ngx.shared.log_dict local sum = log_dict:get("upstream_time-sum") local nb = log_dict:get("upstream_time-nb") if nb and sum then ngx.say("average upstream response time: ", sum / nb, " (", nb, " reqs)") else ngx.say("no data yet") end } } } ``` 该指令在`v0.5.0rc31`版本被首次引入。 [返回目录](#directives) log_by_lua_block ---------------- **语法:** *log_by_lua_block { lua-script }* **内容:** *http, server, location, location if* **阶段:** *log* 与 [log_by_lua](#log_by_lua) 指令相似,只不过该指令在一对括号(`{}`)中直接内嵌 Lua 代码,替代之前 Nginx 的字符串(需要特殊字符转义)。 例如: ```nginx log_by_lua_block { print("I need no extra escaping here, for example: \r\nblah") } ``` 该指令在 `v0.9.17` 版本首次引入。 [返回目录](#directives) log_by_lua_file --------------- **语法:** *log_by_lua_file <path-to-lua-script-file>* **环境:** *http, server, location, location if* **阶段:** *log* 除了通过文件``的内容指定 Lua 代码外,该指令与[log_by_lua](#log_by_lua)是等价的,该指令从`v0.5.0rc32`开始支持[Lua/LuaJIT 字节码](#lualuajit-bytecode-support)的执行。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 该指令是在`v0.5.0rc31`版本第一次引入。 [返回目录](#directives) balancer_by_lua_block --------------------- **语法:** *balancer_by_lua_block { lua-script }* **环境:** *upstream* **阶段:** *content* 该指令执行上游的负载均衡 Lua 代码(任何上游实体),代码配置在 `upstream {}` 小节中。 举例: ```nginx upstream foo { server 127.0.0.1; balancer_by_lua_block { -- 使用 Lua 作为一个动态均衡器完成一些有趣的事情 } } server { location / { proxy_pass http://foo; } } ``` Lua 的负载均衡可以和任何已经存在的 nginx 上游模块一起工作,例如:[ngx_proxy](http://nginx.org/en/docs/http/ngx_http_proxy_module.html) 和 [ngx_fastcgi](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)。 同样, Lua 负载均衡可以和标准的上游连接池机制一起工作,例如标准的 [keepalive](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) 指令。只要确保在单个`upstream {}` 配置小节中 [keepalive](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) 指令要放在 `balancer_by_lua_block` 小节的 *后* 面。 Lua 负载均衡能完全忽略配置在 `upstream {}` 小节中定义的服务列表,并且从一个完全动态的服务列表中挑选一个节点(甚至每次请求都在变),所有这些均是通过 [lua-resty-core](https://github.com/openresty/lua-resty-core) 库的 [ngx.balancer](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md) 完成。 该指令配置的 Lua 代码在单个下游请求中可能被调用多次,例如使用 [proxy_next_upstream](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream) 配置小节,这是 nginx 自身上游尝试请求机制。 这里 Lua 代码的执行环境不支持 yield 操作,所以可能 yield 的 Lua API (例如 cosockets 和 “轻线程”),在这个环境中是被禁用的。一个可以使用并绕过这个限制的玩法,是可以在更早的阶段处理,比如 [access_by_lua*](#access_by_lua)),并传递结果到环境的 [ngx.ctx](#ngxctx) 表。 该指令在 `v0.10.0` 版本首次引入。 [返回目录](#directives) balancer_by_lua_file -------------------- **语法:** *balancer_by_lua_file <path-to-lua-script-file>* **环境:** *upstream* **阶段:** *content* 除了通过文件 `` 的内容指定 Lua 代码外,该指令与 [balancer_by_lua_block](#balancer_by_lua_block) 是等价的,该指令从 `v0.5.0rc32` 开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如 `foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项 `-p PATH` 决定的 `server prefix`。 该指令在 `v0.10.0` 版本首次引入。 [返回目录](#directives) lua_need_request_body --------------------- **语法:** *lua_need_request_body <on|off>* **默认:** *off* **环境:** *http, server, location, location if* **阶段:** *depends on usage* 在运行 rewrite/access/access_by_lua* 之前决定是否强制获取请求体数据。 Nginx 内部默认不读取客户端请求体,如果需要读取请求体数据,需要使用该指令设置为 `on` 或者在 Lua 代码中调用 [ngx.req.read_body](#ngxreqread_body) 函数。 为了读取请求体数据到 [$request_body](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body) 变量,[client_body_buffer_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size) 必须要与 [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) 有同样的大小。因为内容大小超过 [client_body_buffer_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size) 但是小于 [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) 时, Nginx 将把缓冲内存数据存到一个磁盘的临时文件上,这将导致 [$request_body](http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body) 变量是一个空值。 如果当前 location 包含 [rewrite_by_lua*](#rewrite_by_lua) 指令,请求体将在 [rewrite_by_lua*](#rewrite_by_lua) 代码运行之前(还是在`rewrite`阶段)被读取。如果只有 [content_by_lua](#content_by_lua) 指令,请求体直到内容生成的 Lua 代码执行时才会读取(既,请求体在处理生成返回数据阶段才回被读取)。 无论如何都非常推荐,使用 [ngx.req.read_body](#ngxreqread_body) 和 [ngx.req.discard_body](#ngxreqdiscard_body) 函数,可以更好的控制请求体的读取过程。 这个规则也适用于 [access_by_lua*](#access_by_lua) 。 [返回目录](#directives) ssl_certificate_by_lua_block ---------------------------- **语法:** *ssl_certificate_by_lua_block { lua-script }* **环境:** *server* **阶段:** *right-before-SSL-handshake* 当 Nginx 开始对下游进行 SSL(https) 握手连接时,该指令执行用户 Lua 代码。 特别是基于每个请求,设置 SSL 证书链与相应的私有密钥,这种情况特别有用。通过非阻塞 IO 操作,从远程(例如,使用 [cosocket](#ngxsockettcp) API)加载 SSL 握手配置也是很有用的。并且在每请求中使用纯 Lua 完成 OCSP stapling 处理也是可以的。 另一个典型应用场景是在当前环境中非阻塞的方式完成 SSL 握手信号控制,例如在 [lua-resty-limit-traffic](https://github.com/openresty/lua-resty-limit-traffic) 库的辅助下。 我们也可以针对来自客户端的 SSL 握手请求做一些有趣的处理,比如可以有选择地拒绝使用了 SSL v3 甚至更低版本协议的老客户端。 [ngx.ssl](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md) 和 [ngx.ocsp](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ocsp.md) Lua 模块是由 [lua-resty-core](https://github.com/openresty/lua-resty-core/#readme) 库提供,并且在该环境中特别有用。你可以使用这两个模块提供的 Lua API,处理当前 SSL 连接初始化的 SSL 证书链和私有密钥。 不管怎样,对于当前 SSL 连接,在 Nginx/OpenSSL 通过 SSL session IDs 或 TLS session tickets 成功唤醒之前,该 Lua 是不会运行的。换句话说,这个 Lua 只有当 Nginx 已经发起了完整的 SSL 握手才执行。 下面是个简陋的与 [ngx.ssl](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md) 模块一起使用的例子: ```nginx server { listen 443 ssl; server_name test.com; ssl_certificate_by_lua_block { print("About to initiate a new SSL handshake!") } location / { root html; } } ``` 更多信息,可以参考 [ngx.ssl](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl.md) 的更多复杂例子 和 [ngx.ocsp](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ocsp.md) Lua 模块的官方文档。 在用户 Lua 代码中未捕获的 Lua 异常将立即终止当前 SSL 请求,就如同使用 `ngx.ERROR` 错误码调用 [ngx.exit](#ngxexit) 。 该环境下的 Lua 代码执行 *支持* yielding,所以可能 yield 的 Lua API 在这个环境中是启用的(例如 cosockets,sleeping,和 “轻线程”)。 注意,无论如何,你仍然需要配置 [ssl_certificate](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) 和 [ssl_certificate_key](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) 指令,尽管你将完全不再使用这个静态的证书和私有密钥。这是因为 Nginx 内核要求它们出现否则启动 Nginx 时你将看到下面错误: nginx: [emerg] no ssl configured for the server 该指令需要能工作的 Nginx 补丁版本地址: 对于 OpenResty 1.9.7.2 (或更高)绑定的 Nginx 版本,已经默认打上了补丁。 此外,该指令需要至少 OpenSSL `1.0.2e` 版本才能工作。 该指令是在 `v0.10.0` 版本首次引入。 [返回目录](#directives) ssl_certificate_by_lua_file --------------------------- **语法:** *ssl_certificate_by_lua_file <path-to-lua-script-file>* **环境:** *server* **阶段:** *right-before-SSL-handshake* 除了通过文件``的内容指定 Lua 代码外,该指令与 [ssl_certificate_by_lua_block](#ssl_certificate_by_lua_block) 是等价的,该指令从`v0.5.0rc32`开始支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如`foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项`-p PATH`决定的`server prefix`。 该指令是在 `v0.10.0` 版本首次引入。 [返回目录](#directives) ssl_session_fetch_by_lua_block ------------------------------ **语法:** *ssl_session_fetch_by_lua_block { lua-script }* **环境:** *http* **阶段:** *right-before-SSL-handshake* 该指令执行的代码,根据当前下游的 SSL 握手请求中的会话 ID,查找并加载 SSL 会话(如果有)。 由 [lua-resty-core](https://github.com/openresty/lua-resty-core#readme) Lua 模块库内置的 [ngx.ssl.session](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/session.md) API,可以获取当前会话 ID 并加载一个已缓存的 SSL 缓存数据。 Lua API 可能会挂起,比如 [ngx.sleep](#ngxsleep) 和 [cosockets](#ngxsockettcp), 在这个环境中是启用的。 该钩子可以与 [ssl_session_store_by_lua*](#ssl_session_store_by_lua_block) 一起使用,实现纯 Lua 的分布式缓存模型(例如基于 [cosocket](#ngxsockettcp) API)。 如果找到一个已缓存 SSL 会话,将会加载到当前 SSL 会话环境中,SSL 会话将立即启动恢复,绕过昂贵的完整 SSL 握手过程(这里有非常昂贵 CPU 计算代价)。 请注意,TLS 会话票证是非常不同的,当使用会话票证时它是客户端完成 SSL 会话状态缓存。 SSL 会话恢复是基于 TLS 会话票证自动完成,不需要该钩子参与(也不需要 [ssl_session_store_by_lua_block](#ssl_session_store_by_lua) 钩子)。 该钩子主要是给老版本或缺少 SSL 客户端能力(只能通过会话 ID 方式完成 SSL 会话)。 当同时指定了 [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block),该钩子通常在 [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block) 之前运行。 找到 SSL 会话并成功对当前 SSL 连接加载后, SSL 会话将会恢复,从而绕过 [ssl_certificate_by_lua*](#ssl_certificate_by_lua_block) 钩子。这种情况下,NGINX 也将直接绕过 [ssl_session_store_by_lua_block](#ssl_session_store_by_lua) 钩子,不需要了嘛。 借助现代网络浏览器,在本地是比较容易测试这个钩子的。你可以暂时把下面这行配置放到 https server 小节,禁用 TLS 回话票证。 ssl_session_tickets off; 但是在你把网站放到外网之前,不要忘记注释掉这行配置。 如果你使用 [OpenResty](https://openresty.org/) 1.11.2.1 或后续版本绑定的 [官方的预编译包](http://openresty.org/en/linux-packages.html) ,那么一切都应只欠东风。 如果你正在使用的不是 [OpenResty](https://openresty.org) 提供的 OpenSSL 库, 你需要对 OpenSSL 1.0.2h 或后续版本打个补丁: 如果你没有使用 [OpenResty](https://openresty.org) 1.11.2.1 或后续版本绑定的 Nginx , 那么你需要对标准 Nginx 1.11.2 或后续版本打个补丁: 该小节在 `v0.10.6` 首次引入。 请注意: 从 `v0.10.7` 版本开始,该指令只允许在 **http context** 环境中使用(因为 SSL 会话唤醒发生在服务名生效之前)。 [返回目录](#directives) ssl_session_fetch_by_lua_file ----------------------------- **语法:** *ssl_session_fetch_by_lua_file <path-to-lua-script-file>* **环境:** *http* **阶段:** *right-before-SSL-handshake* 除了通过文件``的内容指定 Lua 代码外,该指令与 [ssl_session_fetch_by_lua_block](#ssl_session_fetch_by_lua_block) 是等价的,该指令支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如 `foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项 `-p PATH` 决定的 `server prefix` 。 该指令在 `v0.10.6` 版本首次引入。 请注意: 从 `v0.10.7` 版本开始,该指令只允许在 **http context** 环境中使用(因为 SSL 会话唤醒发生在服务名生效之前)。 [返回目录](#directives) ssl_session_store_by_lua_block ------------------------------ **语法:** *ssl_session_store_by_lua_block { lua-script }* **环境:** *http* **阶段:** *right-after-SSL-handshake* 该指令执行的代码,根据当前下游的 SSL 握手请求中的会话 ID,获取并保存 SSL 会话(如果有)。 This directive runs Lua code to fetch and save the SSL session (if any) according to the session ID provided by the current SSL handshake request for the downstream. 被保存或缓存的 SSL 会话数据能被用到将来的 SSL 连接,恢复 SSL 会话却不需要历经完整 SSL 握手过程(这里有非常昂贵 CPU 计算代价)。 The saved or cached SSL session data can be used for future SSL connections to resume SSL sessions without going through the full SSL handshake process (which is very expensive in terms of CPU time). Lua API 可能会挂起,比如 [ngx.sleep](#ngxsleep) 和 [cosockets](#ngxsockettcp), 在这个环境中被 *禁用* 了。尽管如此,你仍然可以通过 [ngx.timer.at](#ngxtimerat) API 来创建一个零延迟的 timer 用来异步方式保存 SSL 会话数据到外部服务中(比如 `redis` 或 `memcached`)。 由 [lua-resty-core](https://github.com/openresty/lua-resty-core#readme) Lua 模块库提供的 [ngx.ssl.session](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/session.md) API,可以获取当前会话 ID 并关联到会话状态数据。 借助现代网络浏览器,在本地是比较容易测试这个钩子的。你可以暂时把下面这行配置放到 https server 小节,禁用 TLS 回话票证。 ssl_session_tickets off; 但是在你把网站放到外网之前,不要忘记注释掉这行配置。 该指令在 `v0.10.6` 版本首次引入。 请注意: 从 `v0.10.7` 版本开始,该指令只允许在 **http context** 环境中使用(因为 SSL 会话唤醒发生在服务名生效之前)。 [返回目录](#directives) ssl_session_store_by_lua_file ----------------------------- **语法:** *ssl_session_store_by_lua_file <path-to-lua-script-file>* **环境:** *http* **阶段:** *right-before-SSL-handshake* 除了通过文件``的内容指定 Lua 代码外,该指令与 [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block) 是等价的,该指令支持 [Lua/LuaJIT 字节码](#lualuajit-bytecode-support) 的执行。 当给定了一个相对路径如 `foo/bar.lua`,它将会被转换成绝对路径,前面增加的部分路径是 Nginx 服务启动时通过命令行选项 `-p PATH` 决定的 `server prefix` 。 该指令在 `v0.10.6` 版本首次引入。 请注意: 从 `v0.10.7` 版本开始,该指令只允许在 **http context** 环境中使用(因为 SSL 会话唤醒发生在服务名生效之前)。 [返回目录](#directives) lua_shared_dict --------------- **语法:** *lua_shared_dict <name> <size>* **默认:** *no* **环境:** *http* **阶段:** *depends on usage* 声明一个共享内存区块 ``,用来存储基于共享内存的 Lua 字典 `ngx.shared.`。 在当前 Nginx 服务器实例中,共享内存区块被所有 nginx worker 进程共享。 `` 参数可以通过类似 `k` 和 `m` 的大小单位来设置。 ```nginx http { lua_shared_dict dogs 10m; ... } ``` 硬编码限制最小大小是 8KB,而实际的最小大小取决于实际中用户数据集(有些人是从 12KB 开始)。 更多细节请参考 [ngx.shared.DICT](#ngxshareddict)。 这个指令最早出现在版本 `v0.3.1rc22` 中。 [返回目录](#directives) lua_socket_connect_timeout -------------------------- **语法:** *lua_socket_connect_timeout <time>* **默认:** *lua_socket_connect_timeout 60s* **环境:** *http, server, location* 该指令控制 TCP/unix-domain socket 对象的 [connect](#tcpsockconnect) 方法默认超时时间,这个值可以被 [settimeout](#tcpsocksettimeout) 或 [settimeouts](#tcpsocksettimeouts) 方法覆盖。 `