本章我们介绍一下OpenResty Lua模块的指令,并对其中一些指令的用法进行说明。

1. lua指令

使用Lua来构建nginx脚本就是通过一条条指令来完成的,指令常用于指定 Lua 代码是什么时候执行的以及如何使用运行的结果。下图展示了指令执行的顺序:

ngx-lua-order

下面我们再给出Nginx http的11个处理阶段的图,以做比较:

ngx-proc-phase

如下我们列出nginx lua相关的一些指令:

lua_load_resty_core          lua_capture_error_log              lua_use_default_type            lua_malloc_trim                  lua_code_cache
lua_regex_cache_max_entries  lua_regex_match_limit              lua_package_path                lua_package_cpath                init_by_lua   
init_by_lua_block            init_by_lua_file                   init_worker_by_lua              init_worker_by_lua_block         init_worker_by_lua_file
set_by_lua                   set_by_lua_block                   set_by_lua_file                 content_by_lua                   content_by_lua_block
content_by_lua_file          rewrite_by_lua                     rewrite_by_lua_block            rewrite_by_lua_file              access_by_lua                
access_by_lua_block          access_by_lua_file                 header_filter_by_lua            header_filter_by_lua_block       header_filter_by_lua_file
body_filter_by_lua           body_filter_by_lua_block           body_filter_by_lua_file         log_by_lua                       log_by_lua_block
log_by_lua_file              balancer_by_lua_block              balancer_by_lua_file            lua_need_request_body            ssl_certificate_by_lua_block
ssl_certificate_by_lua_file  ssl_session_fetch_by_lua_block     ssl_session_fetch_by_lua_file   ssl_session_store_by_lua_block   ssl_session_store_by_lua_file
lua_shared_dict              lua_socket_connect_timeout         lua_socket_send_timeout         lua_socket_send_lowat            lua_socket_read_timeout
lua_socket_buffer_size       lua_socket_pool_size               lua_socket_keepalive_timeout    lua_socket_log_errors            lua_ssl_ciphers
lua_ssl_crl                  lua_ssl_protocols                  lua_ssl_trusted_certificate     lua_ssl_verify_depth             lua_http10_buffering
rewrite_by_lua_no_postpone   access_by_lua_no_postpone          lua_transform_underscores_in_response_headers
lua_check_client_abort       lua_max_pending_timers             lua_max_running_timers          lua_sa_restart

下面我们简单的介绍一下其中一些lua指令:

1) lua_load_resty_core

syntax: lua_load_resty_core on|off

default: lua_load_resty_core on

context: http

v0.10.16版本起该指令就在本模块失效了。当前resty.core模块会在Lua VM初始化的时候被强制加载。在当前的版本,使用此指令不会用任何的作用。

2) lua_capture_error_log

syntax: lua_capture_error_log size

default: none

context: http

使用一个指定大小的缓存来捕获所有的nginx错误日志(注: 不仅包括http、http subsystem所产生的错误日志,而是所有),而不是保存到文件或磁盘中。

在设置缓冲区大小时可以使用km等单位,例如:

lua_capture_error_log 100k;

根据我们的实践经验,一个4k的缓存大概可以容纳20条典型的错误日志信息。因此,假如我们需要容纳10000条错误日志,那大概就需要2000K大小的缓存空间。

注意事项:

  • 当我们设置好缓存之后,该空间就不会增大了。当缓存满时,新的错误日志就会覆盖缓存中最老的日志条目。

  • 缓存的大小必须大于单条错误日志消息的最大长度(openresty中单条错误日志消息的长度最大可以是4K, nginx生产版本单条错误日志消息的最大长度可以是2k)

  • 可以通过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,则无法捕获调试日志。由于高昂的开销,在生产版本中强烈建议禁用调试日志。

3) lua_code_cache

syntax: lua_code_cache on | off

default: lua_code_cache on

context: http, server, location, location if

启用禁用对Lua代码的缓存。当启用对Lua代码的缓存时,其可以缓存由*_by_lua_file指令(如set_by_lua_file、content_by_lua_file)Lua代码,也可以缓存Lua模块中的脚本代码。

  • 0.9.3版本开始,当关闭lua_code_cache时,通过ngx_lua处理的每个请求都将在一个单独的Lua VM实例中运行。因此,由set_by_lua_file、content_by_lua_file、access_by_lua_file等指令所引用的Lua files都不会被缓存,所有使用到的Lua模块都将从头开始加载。通过该指令,开发人员就可以采用edit-and-refresh模式来调试。

  • 注意,当我们编辑nginx.conf配置文件中的内联Lua代码(比如set_by_lua、content_by_lua、access_by_lua、rewrite_by_lua指令中的Lua代码)时,运行中的nginx将不能够动态的感知到这些更新,这是因为只有nginx配置文件解析器才能够解析nginx.conf配置文件。因此当我们更改这些内联lua代码时,我们可能必须得重启nginx。

  • 即使是启用了代码缓存,由*_by_lua_file指令中的dofileloadfile所加载的Lua脚本也不能被缓存(除非你自己缓存了结果)。通常我们可以使用init_by_lua或者init_by_lua_file指令来加载所有这些文件,又或者使这些Lua文件成为真正的Lua模块,然后通过require来进行加载。

  • ngx_lua模块不支持Apache mod_lua模块可用的stat模式;

  • 强烈建议在生产环境中启用Lua代码缓存,在调试环境中如果不会太影响性能的话我们可以不启用。例如,在禁用lua代码缓存后,hello, world Lua示例的性能可能会下降一个数量级。

4) lua_package_path

syntax: lua_package_path <lua-style-path-str>

default: The content of LUA_PATH environment variable or Lua's compiled-in defaults.

context: http

设置set_by_luacontent_by_lua等指令中搜索Lua模块的路径。这个路径字符串是标准的 lua 路径格式,;;常用于表示原始的搜索路径。

从发行版 v0.5.0rc29 开始,可以在搜索路径中使用特殊符号 $prefix${prefix}来指示服务器前缀的路径,通常在 Nginx 服务器启动时通过-p PATH命令行选项来指定server prefix。

5) lua_package_cpath

syntax: lua_package_cpath <lua-style-cpath-str>

default: The content of LUA_CPATH environment variable or Lua's compiled-in defaults.

context: http

设置set_by_luacontent_by_lua等指令中Lua C模块的搜索路径。这个cpath字符串是标准的lua C路径格式,;;常用于表示原始的cpath搜索路径。

从发行版 v0.5.0rc29 开始,可以在搜索路径中使用特殊符号 $prefix${prefix}来指示服务器前缀的路径,通常在 Nginx 服务器启动时通过-p PATH命令行选项来指定server prefix。

对于openresy默认路径下的基础lua库,openresy可以自动找到,我们不必在nginx.conf的http配置段中通过如下命令来显式指定(这里假设默认安装路径为/usr/local/openresty):

lua_package_path "/usr/local/openresty/lualib/?.lua;;";
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";

6) init_by_lua

syntax: init_by_lua <lua-script-str>

context: http

phase: loading-config

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用init_by_lua_block指令代替

  • 当Nginx Master进程(如果有)加载Nginx配置文件的时候,在全局Lua VM级别上运行由参数<lua-script-str>指定的Lua代码;

  • 当Nginx接收到HUP信号并开始重新加载配置文件时,Lua VM将会被重新创建,且init_by_lua也将在新的VM上再次运行。如果lua_code_cache是关闭的(默认打开), init_by_lua处理程序将在每个请求上都运行一次,因此在这种特殊模式下,总是每个请求创建一个独立的Lua VM。

  • 通常,你可以通过该hook在服务启动时预加载一些所用到的Lua模块,并充分利用现代操作系统的写时复制(COW)优化。如下是一个预加载Lua modules的例子:

 # this runs before forking out nginx worker processes:
 init_by_lua_block { require "cjson" }

 server {
     location = /api {
         content_by_lua_block {
             -- the following require() will just  return
             -- the already loaded module from 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"))
         }
     }
 }

但需要注意的是,lua_shared_dict shm共享内存并不能够通过重新加载配置文件(如发送HUP信号)来来进行清除。因此,假如你并不想在init_by_lua代码中重新初始化shm共享内存的话,那么你可以在共享内存中自定义一个flag标志,然后在init_by_lua代码中检查该标志即可。

因为该Lua代码在Nginx fork出 worker子进程之前运行,所以在这里加载的数据或代码将享受到操作系统的COW特性,在worker进程创建时并不会真正的复制数据,从而节省大量的内存。

  • 不要在此上下文中初始化你自己的Lua全局变量,因为使用Lua全局变量将会造成一定的性能损失,并可能造成全局名称空间(namespace)污染(请参看Lua Variable Scope章节)。我们推荐的做法是使用适当的Lua module文件(但不要使用标准Lua函数模块去定义Lua模块,因为它也会污染全局命名空间),同时在 init_by_lua 或其他上下文中调用 require 去加载你自己的模块文件。(require() 会将加载的模块缓存在全局的 Lua 注册表 package.loaded 中,因此你的模块仅在整个 Lua VM 实例中加载一次)

  • 在该上下文中,仅支持一小部分Nginx Lua API:

    • 日志API: ngx.log()和print()

    • 共享字典API: ngx.shared.DICT

在未来用户请求中,该上下文将支持更多的 Lua 的 Nginx API。

基本上,你可以在此上下文中安全地使用阻塞IO的Lua库,因为在服务器启动期间阻塞master进程完全是可以的。甚至Nginx内核也会在配置加载阶段阻塞(至少在解析 upstream 域名时)。

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

7) init_by_lua_block

syntax: init_by_lua_block { lua-script }

context: http

phase: loading-config

init_by_lua指令类似,不同之处在于此指令直接在一对花括号({})内部而不是在 Nginx 字符串(需要特殊字符串转义)中内联 lua 代码。例如:

init_by_lua_block {
     print("I need no extra escaping here, for example: \r\nblah")
 }

8) init_by_lua_file

syntax: init_by_lua_file <path-to-lua-script-file>

context: http

phase: loading-config

init_by_lua等价,除了这里是通过path-to-lua-script-file来指定所要执行的Lua脚本外。

注:当我们使用类似于foo/bar.lua这样的相对路径时,会转换成相对于server prefix的绝对路径。我们可以在Nginx启动的时候通过-p Path来指定server prefix path。

9) init_worker_by_lua

syntax: init_worker_by_lua <lua-script-str>

context: http

phase: starting-worker

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用init_worker_by_lua_block指令代替

当启用了Nginx master进程时,则通过init_worker_by_lua所指定的Lua代码会在每个Nginx worker进程启动时执行; 而如果禁用了Nginx master,那么此hook将会在init_by_lua*指令之后执行。

本hook常用于创建每个worker重复发生的计时器(通过ngx.timer.at Lua API),用于后端运行状况检查或其他定时工作。参看如下示例:

 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

         -- do something in timer
     end

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

     -- other job in init_worker_by_lua
 ';

v0.10.12版本以来,该钩子不再在 cache manager 和 cache loader 进程中运行。

10) init_worker_by_lua_block

syntax: init_worker_by_lua_block { lua-script }

context: http

phase: starting-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 进程中运行

11) init_worker_by_lua_file

syntax: init_worker_by_lua_file <lua-file-path>

context: http

phase: starting-worker

init_worker_by_lua等价,除了这里是通过lua-file-path来指定所要执行的Lua脚本外。

v0.10.12版本以来,该钩子不再在 cache manager 和 cache loader 进程中运行。

12) set_by_lua

syntax: set_by_lua $res <lua-script-str> [$arg1 $arg2 ...]

context: server, server if, location, location if

phase: rewrite

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用set_by_lua_block指令代替

  • 执行lua-script-str所指定的Lua代码,参数通过$arg1 $arg2 ....传入,并将输出字符串保存到res中。lua-script-str可以执行Nginx Lua API调用,并且可以从ngx.arg表中获取输入参数(参数索引从1开始)。

  • 本指令被设计用于执行简短快速的Lua脚本,因为当在执行这一Lua脚本时,其会阻塞Nginx event loop。因此,我们应该尽力避免在lua-script-str中执行耗时任务。

  • 本指令的实现原理是通过向ngx_http_rewrite_module的命令列表注入自定义命令。由于本身ngx_http_rewrite_module中的命令并不支持非阻塞IO,因此在本指令中并不能运行Lua API中涉及的light thread

  • 不能在set_by_lua上下文中执行如下的API函数:

    • 输出API函数(例如: ngx.say、ngx.send_headers)

    • 控制API函数(例如: ngx.exit)

    • subrequest API函数(例如: ngx.location.capture、ngx.location.capture_multi)

    • Cosocket API函数(例如: ngx.socket.tcp、ngx.req.socket)

    • Sleeping API函数(例如: ngx.sleep)

另外,值得注意的是本指令一次只能返回一个值。然而,我们可以使用ngx.var.VARIABLE来实现返回多个值。例如:

 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、set-misc-nginx-module、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"

13) set_by_lua_block

syntax: set_by_lua_block $res { lua-script }

context: server, server if, location, location if

phase: rewrite

set-by-lua指令类似,不同之处在于:

  • 此指令直接在一对花括号({})内部而不是在NGINX字符串文字(需要特殊字符转义)中内联Lua代码

  • 本指令不支持向其传递参数

参看如下使用示例:

set_by_lua_block $res { return 32 + math.cos(32) } # $res now has the value "32.834223360507" or alike.

14) set_by_lua_file

syntax: set_by_lua_file $res <path-to-lua-script-file> [$arg1 $arg2 ...]

context: server, server if, location, location if

phase: rewrite

set-by-lua等价,除了这里是通过path-to-lua-script-file来指定所要执行的Lua脚本外。

  • set-by-lua-file指令支持传递参数字符串,但请注意可能导致的注入攻击

  • 当path-to-lua-script-file是一个类似于foo/bar.lua这样的相对路径,最终会转化成一个相对于server prefix的绝对路径。我们可以在Nginx启动的时候通过-p Path来指定server prefix path

  • 如果启用了Lua code cache(默认会被启用),则Lua代码会在第一个请求时被加载并缓存。因此,假如我们要修改相应的Lua脚本的话,则我们必须重新加载配置文件。在测试调试过程中,我们可以通过lua_code_cache指令临时关闭cache,从而避免我们需要每次手动重新加载配置文件。

15) content_by_lua

syntax: content_by_lua <lua-script-str>

context: location, location if

phase: content

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用content_by_lua_block指令代替

作为一个content handler,其会为每一个请求执行<lua-script-str>所指定的Lua 代码。该lua代码可能会进行API调用,并且在独立的全局环境(例如 sandbox)中作为一个新派生的协程执行。

不要在同一个location中同时使用该指令和其他的content handler指令。例如,该指令和proxy_pass指令不应该在同一个location中出现。

16) content_by_lua_block

syntax: content_by_lua_block { lua-script }

context: location, location if

phase: content

content_by_lua指令类似,不同之处在于此指令直接在一对花括号({})内部而不是在NGINX字符串文字(需要特殊字符转义)中内联Lua代码。例如:

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

17) content_by_lua_file

syntax: content_by_lua_file <path-to-lua-script-file>

context: location, location if

phase: content

content_by_lua指令类似,不同之处在于此指令在于通过path-to-lua-script-file来指定所要执行的Lua脚本的路径。

  • 可以在path-to-lua-script-file字串中使用nginx variables以提供更高的灵活性。然而这可能会带来某些风险,不建议使用此方式。

  • 当我们指定的是一个类似于foo/bar.lua这样的相对路径,最终会转化成一个相对于server prefix的绝对路径。我们可以在Nginx启动的时候通过-p Path来指定server prefix path

  • 如果启用了Lua code cache(默认会被启用),则Lua代码会在第一个请求时被加载并缓存。因此,假如我们要修改相应的Lua脚本的话,则我们必须重新加载配置文件。在测试调试过程中,我们可以通过lua_code_cache指令临时关闭cache,从而避免我们需要每次手动重新加载配置文件。

  • 在文件路径中可以支持nginx variables,这样就可以实现动态的分发。例如:

# CAUTION: contents 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;
}

注意: 使用此种方式可能存在一定的风险,请对相应的输入参数进行仔细的校验。

18) rewrite_by_lua

syntax: rewrite_by_lua <lua-script-str>

context: http, server, location, location if

phase: rewrite tail

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用rewrite_by_lua_block指令代替

  • rewrite_by_lua作为一个rewrite阶段的handler,其会在每一个请求时执行lua-script-str所指定的Lua脚本。在Lua脚本中可以执行nginx lua api调用,并且该调用是通过一个全局环境独立的coroutine来执行。

  • 值得注意的是,rewrite_by_lua总是运行于ngx_http_rewrite_module之后,因此如下配置是可以正常工作的:

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指令之前的。

相反对于下面的配置,将不能够进行正常工作:

 ?  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指令之后)。

针对这种情况,正确的做法是:

 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";
 }
  • 通过使用rewrite_by_lua,我们可以实现类似于ngx_eval模块的功能。例如:
 location / {
     eval $res {
         proxy_pass http://foo.com/check-spam;
     }

     if ($res = 'spam') {
         rewrite ^ /terms-of-use.html redirect;
     }

     fastcgi_pass ...;
 }

如下我们使用rewrite_by_lua来实现:

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阶段的handler一样,rewrite_by_lua也是运行于subrequest中。

  • 值得注意的是,当在一个rewrite_by_lua指令中调用ngx.exit(ngx.OK)时,nginx请求处理控制流程仍会继续执行后续的content handler。如果要在rewrite_by_lua handler中止当前的请求处理,那么在执行ngx.exit时传入如下status参数:

    • 要返回成功,status的值应该>=200(ngx.HTTP_OK)且<300(ngx.HTTP_SPECIAL_RESPONSE);

    • 要返回失败,status的值应该为ngx.HTTP_INTERNAL_SERVER_ERROR;

  • 假如使用ngx_http_rewrite_module的rewrite指令来更改URI,并且重新进行location的查找(internal redirections),那么rewrite_by_lua或者rewrite_by_lua_file中的任何Lua脚本序列都将不会被执行。例如:

location /foo {
     rewrite ^ /bar;
     rewrite_by_lua 'ngx.exit(503)';
 }
 location /bar {
     ...
 }

这里的Lua代码ngx.exit(503)将不会被执行。这里rewrite ^ /barrewrite ^ /bar last类似,都会初始化一个内部重定向。但是假如我们使用break修正符来代替的话,那么将不会执行内部重定向,这样rewrite_by_lua指令就会得到执行。

除非我们启用了rewrite_by_lua_no_postpone,否则rewrite_by_lua指令会在rewrite请求处理的最后阶段才开始执行。

19) rewrite_by_lua_block

syntax: rewrite_by_lua_block { lua-script }

context: http, server, location, location if

phase: rewrite tail

rewrite_by_lua指令类似,不同之处在于此指令直接在一对花括号({})内部而不是在NGINX字符串文字(需要特殊字符转义)中内联Lua代码。例如:

 rewrite_by_lua_block {
     do_something("hello, world!\nhiya\n")
 }

本指令是从v0.9.17版本开始引入的。

20) rewrite_by_lua_file

syntax: rewrite_by_lua_file <path-to-lua-script-file>

context: http, server, location, location if

phase: rewrite tail

rewrite_by_lua指令类似,不同之处在于此指令是通过path-to-lua-script-file来指定要执行的lua脚本的路径。

  • 可以在path-to-lua-script-file字串中使用nginx variables,从而可以获得更高的灵活性。但是请注意,这可能会带来某种类型的安全风险,不建议在路径字串中使用nginx variables

  • 当我们指定的是一个类似于foo/bar.lua这样的相对路径,最终会转化成一个相对于server prefix的绝对路径。我们可以在Nginx启动的时候通过-p Path来指定server prefix path

  • 如果启用了Lua code cache(默认会被启用),则Lua代码会在第一个请求时被加载并缓存。因此,假如我们要修改相应的Lua脚本的话,则我们必须重新加载配置文件。在测试调试过程中,我们可以通过lua_code_cache指令临时关闭cache,从而避免我们需要每次手动重新加载配置文件。

  • 除非我们启用了rewrite_by_lua_no_postpone,否则rewrite_by_lua指令会在rewrite请求处理的最后阶段才开始执行。

20) access_by_lua

syntax: access_by_lua <lua-script-str>

context: http, server, location, location if

phase: access tail

注:注意在 v0.9.17 发行版以后不鼓励使用该指令,应使用access_by_lua_block指令代替

作为一个access阶段的handler,为每一个请求执行lua-script-str所指定的Lua代码。该lua代码可能会进行API调用,并且在独立的全局环境(例如 sandbox)中作为一个新派生的协程执行。

  • 本handler会在ngx_http_access_module模块之后运行,因此如下代码可以工作正常:
 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/...
 }

上述代码中,假如client IP在blacklist中,那么在执行access_by_lua指令查询MySQL进行更复杂的身份认证之前,该请求就会被拒绝掉。

  • 注意, ngx_auth_request模块可以近似于采用如下代码来实现:
 location / {
     auth_request /auth;

     # proxy_pass/fastcgi_pass/postgres_pass/...
 }

采用ngx_lua的实现类似如下:

 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 phase handlers一样,access_by_lua也不能够运行在subrequests中

  • 值得注意的是,当在access_by_lua中执行ngx.exit(ngx.OK)时,nginx请求处理控制流程仍会继续执行后续的content handler。如果想要在access_by_lua脚本中终止当前的请求处理,那么在执行ngx.exit()时传入如下status参数:

    • 要返回成功,status的值应该>=200(ngx.HTTP_OK)且<300(ngx.HTTP_SPECIAL_RESPONSE);

    • 要返回失败,status的值应该为ngx.HTTP_INTERNAL_SERVER_ERROR;

注: 上面请注意ngx.ok与ngx.HTTP_OK的不同,调用ngx.exit(ngx.ok)时才会执行后续的content handler

  • 从版本v0.9.20开始,可以使用access_by_lua_no_postpone指令来控制在access请求处理阶段的哪一个时间点来执行access_by_lua指令;

21) access_by_lua_block

syntax: access_by_lua_block { lua-script }

context: http, server, location, location if

phase: access tail

access_by_lua指令相似,不同之处在于此指令直接在一对花括号({})内部而不是在NGINX字符串文字(需要特殊字符转义)中内联Lua代码。例如:

 access_by_lua_block {
     do_something("hello, world!\nhiya\n")
 }

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

22) access_by_lua_file

syntax: access_by_lua_file <path-to-lua-script-file>

context: http, server, location, location if

phase: access tail

access_by_lua指令类似,不同之处在于此指令是通过path-to-lua-script-file来指定要执行的lua脚本的路径。

  • 可以在path-to-lua-script-file字串中使用nginx variables,从而可以获得更高的灵活性。但是请注意,这可能会带来某种类型的安全风险,不建议在路径字串中使用nginx variables

  • 当我们指定的是一个类似于foo/bar.lua这样的相对路径,最终会转化成一个相对于server prefix的绝对路径。我们可以在Nginx启动的时候通过-p Path来指定server prefix path

  • 如果启用了Lua code cache(默认会被启用),则Lua代码会在第一个请求时被加载并缓存。因此,假如我们要修改相应的Lua脚本的话,则我们必须重新加载配置文件。在测试调试过程中,我们可以通过lua_code_cache指令临时关闭cache,从而避免我们需要每次手动重新加载配置文件。

23) lua_shared_dict

syntax: lua_shared_dict <name> <size>

default: no

context: http

phase: depends on usage

声明一个共享内存区域,用于存储Lua共享字典。共享内存由当前Nginx实例的所有worker进程所共享。

在指定size时可以接受km两种单位:

 http {
     lua_shared_dict dogs 10m;
     ...
 }

通常我们要求size最小为8k12k

可以查看ngx.shared.DICT以获得更详细的信息介绍。

2. 为Lua提供的Nginx API

nginx.conf配置文件中大量的*_by_lua*_by_lua_block*_by_lua_file指令类似于Lua API网关。如下所描述的Nginx Lua API只能够在这些配置指令的Lua代码中调用。

这些所暴露出来的Lua API通常在ngxndk这两个标准的package中。这些包位于ngx_lua默认的全局作用域中,在ngx_lua指令中可以直接使用。

同时,这些package也可以被引入到外部的Lua模块中。例如:

local say = ngx.say

local _M = {}

function _M.foo(a)
 say(a)
end

return _M

此外,也可以在外部Lua模块直接require这些包:

local ngx = require "ngx"
local ndk = require "ndk"

注: 从版本v0.2.1rc19开始就可以采用此种方式引入这些package

如果想在用户自己的Lua代码中执行网络IO操作的话,那么就只能通过Nginx Lua API调用来实现,否则可能会造成Nginx event loop阻塞并严重的降低性能。而对于磁盘操作读写少量数据的话,我们可以使用标准的Lua IO库即可,但无论如何我们还是应该避免大数据量的文件IO读写操作,因为其可能会严重阻塞Nginx进程。我们强烈推荐将所有的Network IO以及disk IO操作委托给nginx subrequest来处理,以达到最高的性能(通过ngx.location.capture或其他类似的指令)。

3. 示例

1) nginx.conf

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
   
    include conf.d/*.conf;
    lua_package_path 'conf.d/lua/?.lua;;';
}

2) conf.d/alibaba_s3.conf

server {
  listen 80 default_server;
  server_name alibaba_s3;
  chunked_transfer_encoding on;
  
  location /{
      deny all;
  }
  
  location ~* /proxy {
    # note: can not use lua in this block
    internal;
    set_by_lua $target_backend 'return ngx.ctx.target_backend';
    proxy_pass http://$target_backend$request_uri;
  }
  
  location ~* ^/([a-z]\w+)/(\S+) {
    # this is the object operation
    set $bucket $1;
    set $object $2;
    
    access_by_lua_block{
      if ngx.var.bucket == "aaa" then
        ngx.say(ngx.var.bucket)
        ngx.say(ngx.var.object)
        ngx.exit(ngx.HTTP_OK)
      end
    }
    proxy_pass http://rgw;
    #echo $bucket;
    #echo $2;
       
       
  }
   
  location ~* ^/([a-z]\w+) {
    # this is the bucket operations(Note: must be placed after object operations)
    set $bucket $1;
    
    echo "bucket operations";
    echo $1;
    
  }
   
}



[参考]

  1. OpenResty® Linux 包

  2. OpenResty官方网站

  3. OpenResty installation

  4. openresty之指令与常用API

  5. OpenResty中LUA指令的执行顺序

  6. nginx api for lua

  7. Nginx Lua 模块指令

  8. Nginx 请求的11个阶段

  9. nginx的十一个阶段处理

  10. lua-resty-string官网

  11. Lua-HMAC-SHA256

  12. lua-resty-hmac