安裝 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