目录
一、init_by_lua
二、init_worker_by_lua
三、set_by_lua
四、rewrite_by_lua
五、access_by_lua
六、content_by_lua
七、header_filter_by_lua
八、body_filter_by_lua
九、log_by_lua
十、balancer_by_lua_block
一、init_by_lua
init_by_lua 在每次 Nginx 重新加载配置时执行,可用来完成一些耗时模块的加载,或者初始化一些全局配置。在管理进程创建工作进程时,指令中的全局变量会复制到工作进程中。
1)在 nginx.conf 的 http 模块中写入
# 设置全局共享变量(字典),名字为 shared_data 共享内存的大小是1M
lua_shared_dict shared_data 1m;
init_by_lua_file /usr/example/lua/init.lua # 初始化的lua代码位置
2)编写 init.lua
# 加载一些耗时模块
local redis = require "resty.redis"
local cjson = require "cjson"
-- 设置全局变量,不推荐
count = 1
-- 设置共享内存
local shared_data = ngx.shared.shared_data # 获取到 http 模块配置的 shared_data 共享内存
shared_data:set("count", 1) # 在共享内存中设置一个变量 count 值为 1
3)编写 test.lua
-- 对全局变量进行+1操作
count = count + 1
-- 输出全局变量的值
ngx.say("global variable : ", count)
-- 获取共享内存的值
local shared_data = ngx.shared.shared_data
ngx.say(", shared memory : ", shared.data.get("count"))
-- 对共享内存的值 + 1 操作
shared_data:incr("count", 1)
4)增加 location 测试
location /lua {
default_type "application/json";
content_by_lua_file "test.lua"
}
// 启动 Nginx ,访问 /lua 可见全局变量一直保持不变,共享内存中的变量每次访问依次 + 1,因为共享内存的变量只在 Nginx 退出后才被收回,而每个 worker 接受 /lua 请求后都能获取共享变量并执行 test.lua 文件
二、init_worker_by_lua
用于启动一些定时任务,比如心跳检查,定时拉取服务器配置等。有多少个工作进程,该定时任务就会启动多少个,一个工作进程拥有一个定时器
1)在 nginx.conf 的 http 模块中增加
init_worker_by_lua_file /usr/example/lua/init_worker.lua;
2)编写 init_worker.lua
#!/usr/local/bin/lua
local count = 0
local delayInSeconds = 3
local heartBeatCheck = nil
heartBeatCheck = function()
count = count + 1
# 写入到error日志中
ngx.log(ngx.ERR, "do check", count)
local ok, err = ngx.timer.at(delayInSeconds, heartBeatCheck)
# 每隔delayInSeconds时间触发heartBeatCheck回调函数,每次回调函数的执行不会在当前请求中执行,是在一个轻量级线程中执行。
if not ok then
ngx.log(ngx.ERR, "failed to startup heartbeart worker", err)
end
end
heartBeatCheck()
-- 根据实际情况可设置
lua_max_pending_timers 1024; -- 指定最大的等待任务数(定时器)
lua_max_running_timers 256; -- 最大同时运行任务数(定时器)
3)保存重启 nginx ,可见每隔3秒在 error.log 会出现 do check 1, 2, 3... 的信息
三、set_by_lua
用于设置 Nginx 变量,用于处理一些特定的情况
1、例子:参数相加
1)在 nginx.conf 中添加
location /lua_set_1 {
default_type "text/html";
set_by_lua_file $num /usr/example/lua/test_set_1.lua
# 语法 : set_by_lua_file $var lua_file arg1 arg2 ...
# 该指令可以在 Lua 代码中实现所有复杂的逻辑,但执行速度要快,不要阻塞
echo $num; # 在页面中输出该变量
}
2)编写 test_set_1.lua
#!/usr/local/bin/lua
local uri_args = ngx.req.get_uri_args() -- 获取 http request 的参数
local i = uri_args["i"] or 0
local j = uri_args["j"] or 0
return i + j
3)访问 /lua_set_1?i=100&j=200 输出 300
2、例子:不同域名响应不同的内容
1)在 nginx 的 conf 文件中添加
#map主机
map $host $host_var { -- 根据域名确定 $host_var 的值是1或是0
default 0;
2.test.com 1;
}
2)修改 /etc/hosts
127.0.0.1 test.com 2.test.com
3)在 nginx.conf 的 server 模块中添加
set_by_lua $host_jump '
local var = ngx.var.host_var
if var == "1" then
return 1
else
return 0
end
';
location /test {
echo $host_jump;
if ($host_jump) {
proxy_pass http://localhost/$host_jump.html;
break;
}
if ($host_jump = 0) {
proxy_pass http://localhost/$host_jump.html;
break;
}
}
4)发起 curl 请求测试
curl http://test.com:9999/test -- 响应 localhost/0.html
curl http://2.test.com:9999/test -- 响应 localhost/1.html
四、rewrite_by_lua
用于执行内部 URL 重写或外部重定向,典型的如伪静态化 url 重写,本阶段在 rewrite 处理阶段的最后默认执行。
1、rewrite 阶段实现新老页面跳转
1)修改 nginx.conf 添加配置
location /lua_rewrite_1 {
default_type "text/html";
rewrite_by_lua_file /usr/example/lua/test.lua;
echo "no rewrite";
}
2)编写 test.lua
if ngx.req.get_uri_args["jump"] == "1" then
return ngx.redirect("http://jd.com", 302)
end
-- 301 或者 302 可根据自己需求
3)当请求 /lua_rewrite_1?jump=1 发现页面已被跳转到京东首页,而如果没有 jump=1 参数会输出 no rewrite
2、使用 set_uri_args 重写请求内部实现重新发起定位
1)修改 nginx.conf 增加配置
location /lua_rewrite_2 {
default_type "text/html";
rewrite_by_lua_file /usr/example/lua/test.lua;
echo "rewrite2 uri : $uri, a : $arg_a";
# $uri 获取当前请求的uri地址 $arg_a 能直接获取GET参数的a的值
}
2)编写 test.lua
if ngx.req.get_uri_args()["jump"] == "1" then
ngx.req.set_uri("/lua_rewrite_3", false) -- 设置uri不发生重定向,当第二个参数为true时,会发生重定向,而不会执行后面的代码
ngx.req.set_uri("/lua_rewrite_4", false)
ngx.req.set_uri_args({a = 1, b = 2}) -- 设置uri参数
end
4)发送 curl 请求查看
curl http://localhost:9999/lua_rewrite_2
# 输出 uri /lua_rewrite_2,a =
curl http://localhost:9999/lua_rewrite_2?jump=1
# 输出 uri /lua_rewrite_4,不会跳转与重定向,地址不发生改变,只设置uri。输出 a = 1
五、access_by_lua
用于访问控制,如限定某个指定token的用户才能访问页面
1)修改 nginx.conf
location /lua_access {
default_type "text/html";
access_by_lua_file /root/access.lua;
echo "access";
}
2)修改 access.lua
if ngx.req.get_uri_args()["token"] ~= "123" then
ngx.exit(403)
end
3)发起 curl 测试
curl http://localhost:9999/lua_access # 403 Forbidden
curl http://localhost:9999/lua_access?token=123 # access
六、content_by_lua
lua 应用最多的阶段,正式处理内容基本都在这阶段
1)修改 nginx.conf 增加
# 定义一个返回get参数的api
location /lua_content_testapi {
default_type "text/html";
echo $args;
}
# 使用 content_by_lua
location /lua_content_test {
default_type "application/json"; -- 响应格式为json
# lua_need_request_body on; -- 确定在运行rewrite / access / access_by_lua *之前是否强制读取请求正文数据。
# client_max_body_size 50k;
# client_body_buffer_size 50k;
## 要读取$ request_body变量中的请求正文数据,client_body_buffer_size必须与client_max_body_size具有相同的值
content_by_lua '
local vdata = {}
local cjson = require "cjson"
vdata["name"] = "pan"
vdata["age"] = 12
local jsonRequest = cjson.encode(vdata) -- 将table转为json对象,并发给上面的api接口
local resp = ngx.location.capture("/lua_content_testapi", {method = ngx.HTTP_GET, args = jsonRequest})
-- 返回res.status, res.header, res.body, res.truncated.
-- 使用uri发出同步但仍无阻塞的Nginx子请求
-- 子请求只是模仿HTTP接口,但没有额外的HTTP / TCP流量,也没有涉及IPC。
if resp.status == ngx.HTTP_OK and resp.body then
ngx.print(resp.body) -- 打印子请求访问的内容主体
end
';
}
2)发起 curl 测试
curl http://localhost:9999/lua_content_test
# 响应:{"age":12,"name":"pan"}
七、header_filter_by_lua
设置应答消息的头部信息,即 response header
修改 nginx.conf
location / {
default_type "text/html";
header_filter_by_lua 'ngx.header.Foo = "panguangyu"';
echo $uri;
}
-- 刷新请求,可见Response Header中出现 Foo = panguangyu
八、body_filter_by_lua
用于修改应答body的内容
在 nginx.conf 中添加
# body_filter_by_lua 测试
location /lua_body {
default_type "text/html";
echo a;
echo b;
echo c;
echo d;
body_filter_by_lua '
local chunk = ngx.arg[1];
if (string.match(chunk, "c")) then -- 如果遇到 c ,将下一个输出流该为f
ngx.arg[1] = "f" -- flag
end
';
}
// 输出 a b cf ,ngx.arg[1] 代表每次处理的数据流块,如 a b c d nginx会将其划分为 chunk 数据流作为输出。当设置 ngx.arg[2] = true 时,会截断后面所有的数据流chunk,不会被输出。如在flag行指定 ngx.arg[2] = true 将只输出 a b c
九、log_by_lua
用于log请求处理阶段,用lua处理日志,但并不替换原有log处理。下面以简单统计页面PV为例
1)在 nginx.conf 设置
# 在 server 外层定义
lua_shared_dict log_dict 5M;
# log_by_lua 测试
location /lua_log_count {
default_type "text/html";
log_by_lua '
local log_dict = ngx.shared.log_dict
local newvalue, err = log_dict:incr("pv", 1)
if not newvalue and err == "not found" then
log_dict:add("pv", 0)
log_dict:incr("pv", 1)
end
';
content_by_lua '
ngx.say("success")
';
}
location /lua_log_see {
default_type "text/html";
content_by_lua '
local log_dict = ngx.shared.log_dict
local pv = log_dict:get("pv")
if pv then
ngx.say("total pv : ", pv)
end
';
}
2)依次请求 /lua_log_count,然后请求 /lua_log_see 查看 PV
十、balancer_by_lua_block
可作为上游服务器的负载均衡器
### 在两个端口创建服务,根据请求参数除2作为hash值,取出对应的端口作为服务
upstream backend {
server 0.0.0.0;
balancer_by_lua_block {
local balancer = require "ngx.balancer"
local port = {8088, 8089}
local param = ngx.req.get_uri_args()["server"] or 0
local hash = (param % 2) + 1
local ok, err = balancer.set_current_peer("127.0.0.1", port[hash])
if not ok then
return ngx.exit(500)
end
}
}