ngx_lua之rewrite

rewrite_by_lua指令

syntax: rewrite_by_lua <lua-script-str>  
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 <path-to-lua-script-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


参考文档

感谢浏览tim chow的作品!

如果您喜欢,可以分享到: 更多

如果您有任何疑问或想要与tim chow进行交流

可点此给tim chow发信

如有问题,也可在下面留言: