关于nginx+lua的Tips
主要参考:https://github.com/openresty/lua-nginx-module,不完全翻译
一、关于nginx lua
⭐️require和dofile这种调用ngx.location.capture*,ngx.exec,ngx.exit等类似
requiring yielding in the
top-level
scope的function,会抛
attempt to yield across C-call boundary异常,所以把这些放function里
⭐️lua5.1的解释器的VM不是完全可恢复的,用luajit2.0避免。
⭐️变量作用域
用local xxx = require(‘xxx')
要有local声明,否则会变成全局变量,尽量不要用全局变量
全局变量和与他关联的nginx request handler有相同的声明周期。
用lua-releng可以检测全局变量。
⭐️Locations Configured by Subrequest Directives of Other Modules
⭐️Special Escaping Sequences
反斜杠作为逃逸字符,用lua的正则时反斜杠要转义两次。
# nginx.conf
?
location
/test {
?
content_by_lua
'
? local regex = "\d+" -- THIS IS WRONG!!
? local m = ngx.re.match("hello, 1234", regex)
? if m then ngx.say(m[0]) else ngx.say("not matched!") end
? ';
? }
# evaluates to "not matched!"
比如local regex = "\d+" -- THIS IS WRONG!,不能匹配数字,要用local regex = "\\\\d+"
这里第一次被nginx conf解析成的\\d+,然后再被解析成lua的\d+
或者用[[….]]
# nginx.conf
location
/test {
content_by_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
';
}
# evaluates to "1234"
这里被nginx解析成\d+,在[[..]]里可以直接被lua用。
如果正则本身有[..],就用[=[…]=]
# nginx.conf
location
/test {
content_by_lua
'
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
';
}
# evaluates to "1234"
但是用*_by_lua_file的方式载入,只会被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
-- evaluates to "1234"
这样就可以
或者
-- 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
-- evaluates to "1234"
二、nginx lua module相关指令
⭐️lua_use_default_type
⭐️lua_code_cache:缓存lua代码,为off不缓存,性能影响大
⭐️lua_regex_cache_max_entries 缓存正则
⭐️lua_regex_match_limit 缓存正则命中
⭐️la_package_path:lua module 路径,两个分号“;;”代表默认搜索路径
⭐️la_package_cpath:lua c module路径
⭐️init_by_lua:(loading-config阶段)
配置地点:http
从v0.9.17开始,不推荐用这个了,推荐用init_by_lua_block
当nginx接收到HUP重新加载config file的时候,lua VM也会重新创建,init_by_lua也会在新lua VM上重新运行,这个时候,lua_code_cache会被关闭,所以在这种特殊的情况,每个request都会创建一个单独的lua VM,从而每个单独的lua VM都会运行init_by_lua。
通常可以在这里注册lua全局变量和预加载lua module
init_by_lua
'cjson = require "cjson"';
server {
location
= /api {
content_by_lua
'
ngx.say(cjson.encode({dog = 5, cat = 6}))
';
}
}
也可以初始化字典:
lua_shared_dict dogs
1m;
init_by_lua
'
local dogs = ngx.shared.dogs;
dogs:set("Tom", 56)
';
server {
location
= /api {
content_by_lua
'
local dogs = ngx.shared.dogs;
ngx.say(dogs:get("Tom"))
';
}
}
nginx master process 加载nginx的conf文件时,在lua VM全局级别运行的lua代码,重启重新加载,可以require,lua_shared_dic等,lua_shared_dict的内容不会因配置文件重新加载而清空,因为nginx先启动新的worker processes,共享这块内存。不要再这里初始化自己的lua全局变量,会使命名空间污染。
推荐用合适的lua module files(不要用lua的module() function,因为他会污染全局变量空间)和调用require()加载module files在init_by_lua或者其他的context里。(require()会将加载的lua module缓存在全局的package.loaded table里,所以在整个lua VM里,module只加载注册一次)
官方说这里可以运行一些阻塞I/O的命令,因为阻塞server start还好,甚至nginx本身在configure-loading的时候就是阻塞的(至少在解析upstream的host name的时候是)。
⭐️init_by_lua_block
基本同上,带block的就是把lua用{}括起来了。since v0.9.17
init_by_lua_block {
print("
I need no extra escaping here, for example: \r\nblah
")
}
⭐️init_by_lua_file:大致同上
⭐️init_worker_by_lua:(starting-worker阶段) nginx worker process启动的时候运行的lua代码,常用来nginx.timeer.at这种定时健康检查
⭐️init_worker_by_lua_file:大致同上
⭐️set_by_lua: (rewrite阶段)
语法:set_by_lua $res <lua-script-str> [$arg1 $arg2...]
操作输入参数$arg1 $arg2运行lua代码,返回到$res,此时通过lua脚本可以取得nginx.arg table里面的参数(index从1开始计数)
阻塞,lua脚本要short,fast,因其是植入到ngx_http_rewrite_module里的,这里不支持非阻塞I/O
不能运行下面这些:
Output API functions (e.g., ngx.say and ngx.send_headers)
Control API functions (e.g., ngx.exit)
Subrequest API functions (e.g., ngx.location.capture and ngx.location.capture_multi)
Cosocket API functions (e.g., ngx.socket.tcp and ngx.req.socket).
Sleeping API function ngx.sleep
只能传出一个变量,解决办法是用ngx.var
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";
}
和nginx的变量会按顺序运行:
set
$foo
32;
set_by_lua
$bar
'return tonumber(ngx.var.foo) + 1';
set
$baz
"bar: $bar";
# $baz == "bar: 33"
需要ngx_devel_kit module
⭐️set_by_lua_file:大致同上
lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️content_by_lua
使用环境:location,location if
阶段:content
是一个content handler,每个request都运行指定的lua代码。
不能把这个命令和其他的content handler命令用在同一个location里,比如不能喝proxy_pass同时出现在一个location里。
⭐️content_by_lua_file
基本同上,nginx变量可以用于动态path
# WARNING: 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;
}
lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️rewrite_by_lua
使用环境:http,server,location,location if
阶段:rewrite tail
就是个rewrite阶段的handler,每个request都运行指定的lua代码。
这个handler总是运行在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之前运行了,之后才设置的b=a+1,所以好改成这样:
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模块可以约等于rewrite_by_lua:
location / {
eval $res {
proxy_pass http://foo.com/check-spam;
}
if ($res = 'spam') {
rewrite ^ /terms-of-use.html redirect;
}
fastcgi_pass ...;
}
can be implemented in ngx_lua as:
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阶段的处理一样,rewrite_by_lua也可以在子请求中运行。
注:在rewrite_by_lua中调用ngx.exit(ngx.OK)后,nginx的请求处理会继续执行content handler,要想在rewrite_by_lua中终止当前请求,用大于200(ngx.HTTP_OK)小于300(ngx.HTTP_SPECIAL_RESPONSE)的状态表示正常,用ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)或类似的表示失败。
如果调用了nginx_http_rewrite_module的rewrite指令改变了URI,并且内部跳转,那当前location中的rewrite_by_lua和rewrite_by_lua_file不会执行。
location
/foo {
rewrite
^ /bar;
rewrite_by_lua
'ngx.exit(503)';
}
location
/bar {
...
}
上面的ngx.exit(503)永远不会运行,如果用rewrite ^ /bar last,效果一样,但是换成break,这里就不会跳转,rewrite_by_lua就会运行了。
rewrite_by_lua在rewrite的request-processing阶段总会运行,除非打开了rewrite_by_lua_no_postpone。
⭐️rewrite_by_lua_file
基本同上,文件也可以用nginx参数,但是有风险,不推荐。
lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️access_by_lua
使用环境:http, server, location, location if
阶段:access tail
跟access handler一样,每个request都执行lua的代码。
在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/...
}
这里是判断查mysql的请求的ip是否在黑名单里,是就阻止访问。
ngx_auth_request模块和access_by_lua类似:
location / {
auth_request /auth;
# proxy_pass/fastcgi_pass/postgres_pass/...
}
can be implemented in ngx_lua as:
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_by_lua里运行ngx.exit(ngx.OK)会继续运行nginx request processing control后面的content handler,要在rewrite_by_lua中终止当前请求,用大于200(ngx.HTTP_OK)小于300(ngx.HTTP_SPECIAL_RESPONSE)的状态表示正常,用ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)或类似的表示失败。
⭐️access_by_lua_file
基本同上,文件也可以用nginx参数,但是有风险,不推荐。
lua code cache打开时(默认打开),user code只有在第一个请求到来时加载并被缓存,nginx配置文件被修改时,必须reload,lua code cache在开发时可以被临时停用避免重复reload nginx。
⭐️header_filter_by_lua
使用环境:http, server, location, location if
阶段:output-header-filter
就是用lua做个output header filter
不能用的几个指令:
Output API functions (e.g., ngx.say and ngx.send_headers)
Control API functions (e.g., ngx.redirect and ngx.exec)
Subrequest API functions (e.g., ngx.location.capture and ngx.location.capture_multi)
Cosocket API functions (e.g., ngx.socket.tcp and ngx.req.socket).
一个覆盖或添加header的例子:
location
/ {
proxy_pass
http://mybackend;
header_filter_by_lua
'ngx.header.Foo = "blah"';
}
⭐️header_filter_by_lua_file
跟header_filter_by_lua一样。
⭐️body_filter_by_lua
使用环境:http, server, location, location if
阶段:output-body-filter
就是用lua做个output body filter。
输入的字符用ngx.arg[1]取(lua的string),表明body流结尾的eof标记用ngx.arg[2](lua的boolean值)表示。