rewrite_by_lua指令

syntax: rewrite_by_lua 
context: http, server, location, location if
phase: rewrite tail

WARNING Since the v0.9.17 release, use of this directive is discouraged; use the new rewrite_by_lua_block directive instead.

扮演着rewrite阶段的handler的角色,每次请求的时候,rewrite_by_lua把字符串<lua-script-str>当做Lua代码执行。在Lua代码中可以执行API调用,并且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 12set $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 ...;
 }

对应的ngx_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也运行在子请求中。
注意:当在一个rewrite_by_lua handler中调用ngx.exit(ngx.OK)的时候,Nginx请求处理控制流仍然会继续,直到Content handler。为了在一个rewrite_by_luahandler内部终止当前请求,需要使用大于等于200(ngx.HTTP_OK)并且小于300(ngx.HTTP_SPECIAL_RESPONSE)的status code调用ngx.exit,来表示成功的退出;需要使用500(ngx.HTTP_INTERNAL_SERVER_ERROR)调用ngx.exit,来表示失败的退出。
如果ngx_http_rewrite_modulerewrite指令,改变了原始的URI,并且重新搜索匹配改变后的URI的location,那么任何当前location中的rewrite_by_luarewrite_by_lua_file代码序列都不会被执行。比如:

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

上面这个例子中,ngx.exit(503)永远也不会被执行,因为rewrite ^ /barlast会发起一个内部的重定向。如果使用break标记(rewrite ^ /bar break),那么就不会发生重定向,因此rewrite_by_lua代码会被执行。
默认情况下,rewrite_by_lua代码总是在rewrite请求处理阶段的末尾执行。可以通过打开rewrite_by_lua_no_postpone指令,来改变这个默认行为。


rewrite_by_lua_block指令

syntax: rewrite_by_lua_block { lua-script }
context: http, server, location, location if
phase: rewrite tail

rewrite_by_lua指令类似,除了:rewrite_by_lua_block指令把Lua代码放在一对花括号的内部,而不是放在一个Nginx字符串中(它需要对特殊字符进行转义)。
下面是一个例子:

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

rewrite_by_lua_file指令

syntax: rewrite_by_lua_file 
context: http, server, location, location if
phase: rewrite tail

rewrite_by_lua等价,除了:Lua代码包含在<path-to-lua-script-file>所指定的文件中(从v0.5.0rc32版本开始,文件的内容也可以是Lua/LuaJIT字节码)。
为了提高伸缩性,<path-to-lua-script-file>字符串中也可以包含Nginx变量,然而这样做可能带来一些风险,因此不推荐。
当指定一个相对地址(比如:foo/bar.lua)的时候,它会被转换成相对server prefix路径的绝对地址,server prefix的值是在启动Nginx的时候,通过-p PATH命令行选项指定的。
当开启Lua代码缓存的时候,用户代码在第一个请求到来时加载一次,然后缓存。每次修改了Lua源代码文件之后,必须重载Nginx配置文件。在开发期间,可以通过将Nginx配置文件中的lua_code_cache指令切换成off,来临时的关闭Lua代码缓存。
默认情况下,rewrite_by_lua_file代码总是在rewrite请求处理阶段的末尾执行。可以通过打开rewrite_by_lua_no_postpone指令,来改变这个默认行为。


ngx.exec API

syntax: ngx.exec(uri, args?)
context: rewrite_by_lua*, access_by_lua*, content_by_lua*

发起一个到uri的内部重定向,与echo-nginx-moduleecho_exec指令类似。

ngx.exec('/some-location');
ngx.exec('/some-location', 'a=3&b=5&c=6');
ngx.exec('/some-location?a=3&b=5', 'c=6');

第二个参数args是可选的,它用来指定额外的URI查询参数,比如:

ngx.exec("/foo", "a=3&b=hello%20world")

同时也可以给args参数传递一个Lua表,ngx_lua负责URI转义和字符串连接。

ngx.exec("/foo", { a = 3, b = "hello world" })

的结果其实与前面的例子相同。
当给args参数传递一个Lua表的时候,其实是调用ngx.encode_args它会按照URI编码规则,把Lua表编码成一个查询字符串。
比如:

ngx.encode_args({foo = 3, ["b r"] = "hello world"})

会生成

foo=3&b%20r=hello%20worldfoo=3&b%20r=hello%20world

表的键必须是Lua字符串。
也支持多值查询参数,只需要使用Lua表作为参数的值,比如:

ngx.encode_args({baz = {32, "hello"}})

会生成:

baz=32&baz=hello

如果参数的值为空表,效果等价于nil值。
如果参数的值为false,效果等价于nil值。 如果参数的值为true,比如:

ngx.encode_args({a = true, b = 1})

会生成:

a&b=1

下面回到ngx.exec
ngx.exec支持命名location,但是在提供第二个参数的时候,它会被忽略。重定向之后的查询字符串会从重定向之前的location继承。
在下面的例子中,GET /foo/file.php?a=hello会返回hello,而不是goodbye

location /foo {
    content_by_lua '
        ngx.exec("@bar", "a=goodbye");
    ';
}

location @bar {
    content_by_lua '
        local args = ngx.req.get_uri_args()
            for key, val in pairs(args) do
                if key == "a" then
                    ngx.say(val)
            end
        end
    ';
}

ngx.execngx.redirect不同,它仅仅只是一个内部重定向,并没有新的外部的HTTP调用。
这个方法会终止当前请求的处理,因此它必须ngx.send_headers之前或通过ngx.printngx.say显式的输出响应体之前,被调用
推荐:在这个函数调用之前加上return语句。当在除了header_filter_by_lua之外的上下文中,调用ngx.exec的时候,可以采用return ngx.exec(...)来强调请求处理正在终止。


ngx.redirect API

syntax: ngx.redirect(uri, status?)
context: rewrite_by_lua*, access_by_lua*, content_by_lua*

发起一个到URI的HTTP 301HTTP 302重定向。
可选的status参数用来指定使用301还是302,默认是302(ngx.HTTP_MOVED_TEMPORARILY)。
下面是一个例子,假设当前的server name是localhost,监听1984端口:

return ngx.redirect("/foo")

等价于:

return ngx.redirect("/foo", ngx.HTTP_MOVED_TEMPORARILY)

ngx.redirect也支持重定向到任意外部的URL,比如:

return ngx.redirect("http://www.google.com")

可以使用数字形式的status code作为status参数:

return ngx.redirect("/foo", 301)

ngx.redirect方法与带有redirect修饰符的rewrite指令类似,比如:

rewrite ^ /foo? redirect;  # nginx config

等价于:

return ngx.redirect('/foo');  -- Lua code

rewrite ^ /foo? permanent;  # nginx config

等价于:

return ngx.redirect('/foo', ngx.HTTP_MOVED_PERMANENTLY)  -- Lua code

也可以指定URI参数,比如:

return ngx.redirect('/foo?a=3&b=4')

这个方法会终止当前请求的处理,因此它必须ngx.send_headers之前或通过ngx.printngx.say显式的输出响应体之前,被调用
推荐:在这个函数调用之前加上return语句。当在除了header_filter_by_lua之外的上下文中,调用ngx.exec的时候,可以采用return ngx.exec(...)来强调请求处理正在终止。


ngx.req.set_uriAPI

syntax: ngx.req.set_uri(uri, jump?)
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*

使用uri参数重写当前请求的URI。uri参数必须是一个Lua字符串,并且长度不能为0,否则会抛出一个Lua异常。
jump参数是可选的,它是一个布尔值,用于触发location的重新匹配。当jumptrue的时候(默认值是false),这个函数不会返回,它会告诉Nginx在之后的post-rewrite阶段,使用新的URI值重新搜索location,然后跳转到新的location。
默认情况下,仅当改变了当前请求的URI,才会触发跳转。当jump参数是false或没传递jump参数的时候,ngx.req.set_uri会返回,并且没有返回值。比如:

rewrite ^ /foo last;

等价于:

ngx.req.set_uri("/foo", true)

同理,

rewrite ^ /foo break;

等价于:

ngx.req.set_uri("/foo", false)

或者是等价于:

ngx.req.set_uri("/foo")

rewrite_by_luarewrite_by_lua_file中,jump参数只能被设置为true。在其它上下文中使用jump是禁止的,会抛出一个Lua异常。
下面是一个使用了正则表达式的例子:

location /test {
    rewrite_by_lua '
        local uri = ngx.re.sub(ngx.var.uri, "^/test/(.*)", "/$1", "o")
        ngx.req.set_uri(uri)
    ';
    proxy_pass http://my_backend;
}

它的功能等价于:

 location /test {
     rewrite ^/test/(.*) /$1 break;
     proxy_pass http://my_backend;
 }

无法使用ngx.req.set_uri重写URI参数。如果想要重写URI参数,需要使用ngx.req.set_uri_argsAPI,比如:
Nginx配置文件:

rewrite ^ /foo?a=3? last;

等价于:

ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo", true)

或:

ngx.req.set_uri_args({a = 3})
ngx.req.set_uri("/foo", true)

ngx.req.set_uri_argsAPI

syntax: ngx.req.set_uri_args(args)
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*

使用args参数重写当前请求的URI查询参数。args参数可以是一个Lua字符串,比如:

ngx.req.set_uri_args("a=3&b=hello%20world")

也可以是一个包含查询参数的key-value对的Lua表,比如:

ngx.req.set_uri_args({ a = 3, b = "hello world" })

args参数是一个Lua表的时候,ngx.req.set_uri_args方法会根据URI转义规则,转义key和value。
ngx.req.set_uri_args也支持多值参数,比如:

ngx.req.set_uri_args({ a = 3, b = {5, 6} })

它会产生一个这样的查询字符串:a=3&b=5&b=6


参考文档