Nginx 使用Lua脚本
安装 Nginx Lua支持
- 安装系统依赖库
yum install readline-devel pcre-devel openssl-devel gcc
- 安装LuaJIT
$ wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
$ tar zxvf LuaJIT-2.0.5.tar.gz
$ cd LuaJIT-2.0.5
$ make install
# 安装成功
==== Successfully installed LuaJIT 2.0.5 to /usr/local ====
- 设置LuaJIT环境变量
$ export LUAJIT_LIB=/usr/local/lib
$ export LUAJIT_INC=/usr/local/include/luajit-2.0
$ echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
$ ldconfig
- 下载相关模块
$ wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
$ tar zxvf v0.3.0.tar.gz
# 解压缩后目录名
ngx_devel_kit-0.3.0
$ wget https://github.com/openresty/lua-nginx-module/archive/v0.10.10.tar.gz
$ tar zxvf v0.10.10.tar.gz
# 解压缩后目录名
lua-nginx-module-0.10.10
- 重新编译Nginx模块
# 最好先查看下之前编译的
./sbin/nginx -V
./configure --prefix=/home/ifan/software/nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_stub_status_module \
--with-http_realip_module \
--with-threads \
--with-http_realip_module \
--add-module=/home/ifan/lua-nginx-module-0.10.10 \
--add-module=/home/ifan/ngx_devel_kit-0.3.0
make && make install
- 配置Nginx环境
6.1 添加 Http lua 支持
http {
# 第三方库(cjson)地址luajit-2.0/lib
lua_package_path './lua/?.lua;;';
lua_package_cpath '/usr/local/include/luajit-2.0/lib/?.so;;';
}
6.2 添加一个Lua服务
server {
location /ip {
# 正式环境需要去掉,现在加上方便调试lua脚本
lua_code_cache off;
default_type 'text/plain';
# nginx下的相对路径
content_by_lua_file lua/get_ip.lua;
# content_by_lua 'ngx.say("hello, lua")';
# content_by_lua_block {
# ngx.say("hello lua")
# }
}
}
6.3 添加nginx/lua/get_ip.lua
local headers = ngx.req.get_headers()
for k, v in pairs(headers) do
ngx.say(k,":", v)
end
ngx.say("X-REAL-IP", ":", headers["X-REAL-IP"])
ngx.say("X_FORWARDED_FOR", ":", headers["X_FORWARDED_FOR"])
ngx.say("remote_addr", ":", ngx.var.remote_addr)
一些配置信息
lua常用系统变量
方法 | 类型 | 说明 |
---|---|---|
ngx.var | 请求 | 如果要赋值如 ngx.var.b = 2,此变量必须提前声明;另外对于 nginx location 中使用 正则捕获的捕获组可以使用 ngx.var [捕获组数字]获取; |
ngx.req.get_headers | 请求 | 获取请求头,默认只获取前100,如果想要获取所以可以调用ngx.req.get_header s(0);获取带中划线的请求头时请使用如 headers.user_agent 这种方式;如果一个请求头有多个值,则返回的 是 table; |
ngx.req.get_uri_args | 请求 | 获取 url 请求参数,其用法和 get_headers 类似; |
ngx.req.get_post_args | 请求 | 获取 post 请求内容体,其用法和 get_headers 类似,但是必须提前调用 ngx.req.r ead_body() 来读取 body 体(也可以选择在 nginx 配置文件使用lua_need_request_body on;开启读取 bod y 体,但是官方不推荐); |
ngx.req.raw_header | 请求 | 未解析的请求头字符串; |
ngx.req.get_body_data | 请求 | 为解析的请求 body 体内容字符串。 |
ngx.req.get_method | 请求 | 获取请求的大写字母形式的请求方式 |
ngx.header | 响应 | 通过ngx.header.header_name的形式获取或设置响应头信息。 |
ngx.exit | 响应 | 以某个状态码返回响应内容 |
ngx.redirect | 响应 | 重定向当前请求到新的 url |
ngx.log | 其他 | 输出到log日志 |
ngx.re.match | 其他 | 正则匹配 |
ngx.md5 | 其他 | md5编码 |
ngx.encode_base64 | 其他 | base64解码 |
ngx.decode_base64 | 其他 | base64编码 |
模块指令
每个模块都有*_lua
(指令)、*_lua_block
(代码块)、*_lua_file
(脚本文件)
指令 | 所在阶段 | 使用范围 | 说明 |
---|---|---|---|
init_by_lua | 加载配置文件 | http | 可以用于初始化全局配置 |
set_by_lua | rewrite | server location location if | 复杂逻辑的变量赋值,注意是阻塞的 |
rewrite_by_lua | rewrite | http server location location if | 实现复杂逻辑的转发或重定向 |
content_by_lua | content | location location if | 处理请求并输出响应 |
header_filter_by_lua | 响应头信息过滤 | http server location location if | 设置响应头信息 |
body_filter_by_lua | 输出过滤 | http server location location if | 对输出进行过滤或修改 |
案例
访问权限控制
location / {
set $allowed '115.171.226.212';
access_by_lua_block {
if ngx.re.match(ngx.req.get_method(), "PUT|POST|DELETE") and not ngx.re.match(ngx.var.request_uri, "_search") then
start, _ = string.find(ngx.var.allowed, ngx.var.remote_addr)
if not start then
ngx.exit(403)
end
end
}
proxy_pass http://127.0.0.1:8000$request_uri;
}
访问频率控制
在 Nginx 配置文件的 location 部分配置 Lua 脚本基本参数,并配置 Lua 模块指令:
default_type "text/html";
set rate_per 300
access_by_lua_file lua/access.lua;
Lua 脚本实现频率控制逻辑,使用 Redis 对单位时间内的访问次数做缓存,key 为访问 uri 拼接 token 后的 md5 值。具体内容如下:
local redis = require "resty.redis"
local red = redis:new()
local limit = tonumber(ngx.var.rate_per) or 200
local expire_time = tonumber(ngx.var.rate_expire) or 1000
local key = "rate.limit:string:"
red:set_timeout(500)
local ok, err = red:connect("www.fanhaobai.com", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect redis: " .. err)
return
end
key = key .. ngx.md5(ngx.var.request_uri .. (ngx.req.get_uri_args()['token'] or ngx.req.get_post_args()['token']))
local times, err = red:incr(key)
if not times then
ngx.log(ngx.ERR, "failed to exec incr: " .. err)
return
elseif times == 1 then
ok, err = red:expire(key, expire_time)
if not ok then
ngx.log(ngx.ERR, "failed to exec expire: " .. err)
return
end
end
if times > limit then
return ngx.exit(403)
end
return