Nginx Lua 模块指令

Nginx Lua 模块指令

Nginx Lua 模块指令

Nginx共11个处理阶段,而相应的处理阶段是可以做插入式处理,即可插拔式架构;另外指令可以在http、server、server if、location、location if几个范围进行配置:

指令 所处处理阶段 使用范围 解释
init_by_lua init_by_lua_file loading-config http nginx Master进程加载配置时执行; 通常用于初始化全局配置/预加载Lua模块
init_worker_by_lua init_worker_by_lua_file starting-worker http 每个Nginx Worker进程启动时调用的计时器,如果Master进程不允许则只会在init_by_lua之后调用; 通常用于定时拉取配置/数据,或者后端服务的健康检查
set_by_lua set_by_lua_file rewrite server,server if,location,location if 设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快;
rewrite_by_lua rewrite_by_lua_file rewrite tail http,server,location,location if rrewrite阶段处理,可以实现复杂的转发/重定向逻辑;
access_by_lua access_by_lua_file access tail http,server,location,location if 请求访问阶段处理,用于访问控制
content_by_lua content_by_lua_file content location,location if 内容处理器,接收请求处理并输出响应
header_filter_by_lua header_filter_by_lua_file output-header-filter http,server,location,location if 设置header和cookie
body_filter_by_lua body_filter_by_lua_file output-body-filter http,server,location,location if 对响应数据进行过滤,比如截断、替换。
log_by_lua log_by_lua_file log http,server,location,location if log阶段处理,比如记录访问量/统计平均响应时间

更详细的解释请参考http://wiki.nginx.org/HttpLuaModule#Directives

init_by_lua

每次Nginx重新加载配置时执行,可以用它来完成一些耗时模块的加载,或者初始化一些全局配置;在Master进程创建Worker进程时,此指令中加载的全局变量会进行Copy-OnWrite,即会复制到所有全局变量到Worker进程。

1. nginx.conf配置文件中的http部分添加如下代码

1
2
3
4
#共享全局变量,在所有worker间共享
lua_shared_dict shared_data 1m;

init_by_lua_file /usr/example/lua/init.lua;

2. init.lua

1
2
3
4
5
6
7
8
9
10
-- 初始化耗时的模块
local redis = require("resty.redis")
local cjson = require("cjson")

-- 全局变量,不推荐使用
count = 1

-- 共享全局内存
local shared_data = ngx.shared.shared_data
shared_data:set("count", 1)

3. test.lua

1
2
3
4
5
6
count = count + 1
ngx.say("global variable:", count)
local shared_data = ngx.shared.shared_data
ngx.say(", shared memory : ", shared_data:get("count"))
shared_data:incr("count", 1)
ngx.say("hello world")

4. 检验结果

访问如http://127.0.0.1/lua 会发现全局变量在增加,共享内存也一直递增。
global variable : 9 , shared memory : 8 hello world

但是对于global 变量,官方文档强烈不建议使用。在并发请求中会导致竞争

Note that the use of global Lua variables is strongly discouraged, as it may lead to unexpected race conditions between concurrent requests.

建议用 Use the ngx.shared.DICT 代替,或者Use data storage mechanisms such as memcached, redis, MySQL or PostgreSQL

另外注意一定在生产环境开启lua_code_cache,否则每个请求都会创建Lua VM实例。

init_worker_by_lua

用于启动一些定时任务,比如心跳检查,定时拉取服务器配置等等;此处的任务是跟Worker进程数量有关系的,比如有2个Worker进程那么就会启动两个完全一样的定时任务。

1. nginx.conf配置文件中的http部分添加如下代码

1
init_worker_by_lua /usr/openResty/lua/init_worker.lua;

2. init_worker.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local count = 0
local delayInSeconds = 3
local heartbeatCheck = nil
heartbeatCheck = function ( args )
count = count + 1
ngx.log(ngx.ERR, "do check ", count)

local ok, err = ngx.timer.at(delayInSeconds, heartbeatCheck)

if not ok then
ngx.log(ngx.ERR, "failed to startup heartbeart worker...", err)
end
end

heartbeatCheck()

ngx.timer.at:延时调用相应的回调方法;ngx.timer.at(秒单位延时,回调函数,回调函数的参数列表);可以将延时设置为0即得到一个立即执行的任务,任务不会在当前请求中执行不会阻塞当前请求,而是在一个轻量级线程中执行。
另外根据实际情况设置如下指令
lua_max_pending_timers 1024; #最大等待任务数
lua_max_running_timers 256; #最大同时运行任务数

set_by_lua

设置nginx变量,我们用的set指令即使配合if指令也很难实现负责的赋值逻辑;

1.1 openResty.conf配置文件

1
2
3
4
5
location /lua_set_1 {
default_type "text/html";
set_by_lua_file $num /usr/openResty/lua/test_set_1.lua;
echo $num;
}

set_by_lua_file:语法set_by_lua_file $var lua_file arg1 arg2…; 在lua代码中可以实现所有复杂的逻辑,但是要执行速度很快,不要阻塞;

1.2 test_set_1.lua

1
2
3
4
5
local uri_args = ngx.req.get_uri_args()
local i = uri_args["i"] or 0
local j = uri_args["j"] or 0

return i + j

得到请求参数进行相加然后返回。
访问如http://127.0.0.1/lua_set_1?i=1&j=10进行测试。 如果我们用纯set指令是无法实现的。
再举个实际例子,我们实际工作时经常涉及到网站改版,有时候需要新老并存,或者切一部分流量到新版
2.1 在openResty.conf中使用map指令来映射host到指定nginx变量,方便我们测试

1
2
3
4
5
############ 测试时使用的动态请求  
map $host $item_dynamic {
default "0";
item2014.jd.com "1";
}

如绑定hosts
192.168.1.2 item.jd.com;
192.168.1.2 item2014.jd.com;
此时我们想访问item2014.jd.com时访问新版,那么我们可以简单的使用如

1
2
3
4
if ($item_dynamic = "1") {  
proxy_pass http://new;
}
proxy_pass http://old;

但是我们想把商品编号为为8位(比如品类为图书的)没有改版完成,需要按照相应规则跳转到老版,但是其他的到新版;虽然使用if指令能实现,但是比较麻烦,基本需要这样

1
2
3
4
5
6
7
8
9
10
11
12
set jump "0";
if($item_dynamic = "1") {
set $jump "1";
}
if(uri ~ "^/6[0-9]{7}.html") {
set $jump "${jump}2";
}
#非强制访问新版,且访问指定范围的商品
if (jump == "02") {
proxy_pass http://old;
}
proxy_pass http://new;

以上规则还是比较简单的,如果涉及到更复杂的多重if/else或嵌套if/else实现起来就更痛苦了,可能需要到后端去做了;此时我们就可以借助lua了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
set_by_lua $to_book '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= "1" and ngx.re.match(skuId, "^[0-9]{8}$")
if r then return "1" else return "0" end;
';
set_by_lua $to_mvd '
local ngx_match = ngx.re.match
local var = ngx.var
local skuId = var.skuId
local r = var.item_dynamic ~= "1" and ngx.re.match(skuId, "^[0-9]{9}$")
if r then return "1" else return "0" end;
';
#自营图书
if ($to_book) {
proxy_pass http://127.0.0.1/old_book/$skuId.html;
}
#自营音像
if ($to_mvd) {
proxy_pass http://127.0.0.1/old_mvd/$skuId.html;
}
#默认
proxy_pass http://127.0.0.1/proxy/$skuId.html;

rewrite_by_lua

执行内部URL重写或者外部重定向,典型的如伪静态化的URL重写。其默认执行在rewrite处理阶段的最后。

1.1 openResty.conf配置文件

1
2
3
4
5
location /lua_rewrite_1 {  
default_type "text/html";
rewrite_by_lua_file /usr/openResty/lua/test_rewrite_1.lua;
echo "no rewrite";
}

1.2 test_rewrite_1.lua

1
2
3
if ngx.req.get_uri_args()["jump"] == "1" then
return ngx.redirect("http://www.baidu.com?jump=1", 302)
end

当我们请求http://127.0.0.1/lua_rewrite_1时发现没有跳转,而请求http://127.0.0.1/lua_rewrite_1?jump=1时发现跳转到百度首页了。 此处需要301/302跳转根据自己需求定义。

2.1 openResty.conf配置文件

1
2
3
4
5
location /lua_rewrite_2 {
default_type "text/html";
rewrite_by_lua_file /usr/openResty/lua/test_rewrite_2.lua;
echo "no rewrite";
}

2.2 test_rewrite_2.lua

1
2
3
4
5
if ngx.req.get_uri_args()["jump"] == "1" then
ngx.req.set_uri("/lua_rewrite_3", false);
ngx.req.set_uri("/lua_rewrite_4", false);
ngx.req.set_uri_args({a = 1, b = 2});
end

ngx.req.set_uri(uri, false):可以内部重写uri(可以带参数),等价于 rewrite ^ /lua_rewrite_3;通过配合if/else可以实现 rewrite ^ /lua_rewrite_3 break;这种功能;此处两者都是location内部url重写,不会重新发起新的location匹配;
ngx.req.set_uri_args:重写请求参数,可以是字符串(a=1&b=2)也可以是table;

访问如http://127.0.0.1/lua_rewrite_2?jump=0时得到响应
rewrite2 uri : /lua_rewrite_2, a :

访问如http://127.0.0.1/lua_rewrite_2?jump=1时得到响应
rewrite2 uri : /lua_rewrite_4, a : 1

3.1 openResty.conf配置文件

1
2
3
4
5
location /lua_rewrite_3 {
default_type "text/html";
rewrite_by_lua_file /usr/openResty/lua/test_rewrite_3.lua;
echo "no rewrite";
}

3.3 test_rewrite_3.lua

1
2
3
4
5
if ngx.req.get_uri_args()["jump"] == "1" then
ngx.req.set_uri("/lua_rewrite_4", true);
ngx.log(ngx.ERR, "=========")
ngx.req.set_uri_args({a = 1, b = 2});
end

ngx.req.set_uri(uri, true):可以内部重写uri,即会发起新的匹配location请求,等价于 rewrite ^ /lua_rewrite_4 last;此处看error log是看不到我们记录的log。
所以请求如http://127.0.0.1/lua_rewrite_3?jump=1会到新的location中得到响应,此处没有/lua_rewrite_4,所以匹配到/lua请求,得到类似如下的响应
global variable : 2 , shared memory : 1 hello world

rewrite ^ /lua_rewrite_3; 等价于 ngx.req.set_uri(“/lua_rewrite_3”, false);
rewrite ^ /lua_rewrite_3 break; 等价于 ngx.req.set_uri(“/lua_rewrite_3”, false); 加 if/else判断/break/return
rewrite ^ /lua_rewrite_4 last; 等价于 ngx.req.set_uri(“/lua_rewrite_4”, true);
注意,在使用rewrite_by_lua时,开启rewrite_log on;后也看不到相应的rewrite log。

access_by_lua

用于访问控制,比如我们只允许内网ip访问,可以使用如下形式

1
2
3
4
5
allow     127.0.0.1;  
allow 10.0.0.0/8;
allow 192.168.0.0/16;
allow 172.16.0.0/12;
deny all;

1. openResty.conf配置文件

1
2
3
4
5
location /lua_access {
default_type "text/html";
rewrite_by_lua_file /usr/openResty/lua/test_access.lua;
echo "access";
}

2. test_access.lua

1
2
3
if ngx.req.get_uri_args()["token"] ~= "123" then
return ngx.exit(403)
end

即如果访问如http://17.0.0.12/lua_access?token=234将得到403 Forbidden的响应。这样我们可以根据如cookie/用户token来决定是否有访问权限。

转载:https://www.kancloud.cn/inwsy/project/1128130


 

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×