12 - Lua 扩展与 OpenResty / Lua & OpenResty
Lua 扩展与 OpenResty / Lua & OpenResty
🟢 基础 / Basics — 什么是 OpenResty?
OpenResty 简介
OpenResty = Nginx + LuaJIT,让 Nginx 具备了编程能力。
传统 Nginx:
配置文件驱动,功能由模块决定,扩展性有限
OpenResty:
Nginx + Lua 脚本 → 可编程的 Web 平台
- 动态路由
- 自定义鉴权
- 实时限流
- 请求/响应改写
- 与 Redis/MySQL 直接交互
安装 OpenResty
# Ubuntu/Debian
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" \
| sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo apt install -y openresty
# CentOS/RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo yum install -y openresty
# 启动
sudo systemctl start openresty
sudo systemctl enable openresty
第一个 Lua 脚本
# /usr/local/openresty/nginx/conf/nginx.conf
http {
server {
listen 80;
server_name localhost;
# content_by_lua_block — 直接在 Nginx 中执行 Lua 代码
location /hello {
default_type text/html;
content_by_lua_block {
ngx.say("<h1>Hello from OpenResty!</h1>")
ngx.say("<p>Time: ", ngx.localtime(), "</p>")
}
}
# access_by_lua_block — 在 access 阶段执行 Lua
location /api/ {
access_by_lua_block {
local token = ngx.var.arg_token
if token ~= "mysecret" then
ngx.exit(403)
end
}
proxy_pass http://127.0.0.1:3000;
}
}
}
Lua 执行阶段 / Execution Phases
server {
location / {
# 1. init_worker — Worker 启动时执行一次
# 用于定时任务、共享字典初始化
# 2. ssl_certificate_by_lua — SSL 握手阶段
# 动态证书选择
# 3. set_by_lua — 设置变量
set_by_lua_block $greeting {
return "Hello, " .. (ngx.var.arg_name or "World")
}
# 4. rewrite_by_lua — rewrite 阶段
rewrite_by_lua_block {
ngx.log(ngx.INFO, "Rewrite phase: ", ngx.var.uri)
}
# 5. access_by_lua — access 阶段(鉴权、限流)
access_by_lua_block {
if ngx.req.get_method() == "DELETE" then
ngx.exit(405)
end
}
# 6. content_by_lua — content 阶段(生成响应)
# 如果使用了 proxy_pass,不需要 content_by_lua
# 7. header_filter_by_lua — 修改响应头
header_filter_by_lua_block {
ngx.header["X-Powered-By"] = "OpenResty"
}
# 8. body_filter_by_lua — 修改响应体
body_filter_by_lua_block {
local chunk = ngx.arg[1]
if chunk then
ngx.arg[1] = chunk:gsub("secret", "****")
end
}
# 9. log_by_lua — 日志阶段
log_by_lua_block {
ngx.log(ngx.INFO, "Request completed: ", ngx.var.status)
}
proxy_pass http://backend;
}
}
🟡 进阶 / Intermediate — 实用 Lua 模块
shared_dict(共享字典 — 进程间共享内存)
http {
# 声明共享内存(所有 Worker 共享)
lua_shared_dict rate_limit 10m; # 10MB
lua_shared_dict api_cache 50m; # 50MB
server {
location /api/ {
access_by_lua_block {
local limit_dict = ngx.shared.rate_limit
local client_ip = ngx.var.remote_addr
-- 简单的滑动窗口限流
local key = "rate:" .. client_ip
local count, err = limit_dict:incr(key, 1, 0, 60) -- 60 秒窗口
if count > 100 then -- 每分钟最多 100 次
ngx.exit(429)
return
end
}
proxy_pass http://backend;
}
# 查看限流状态
location /rate_status {
content_by_lua_block {
local dict = ngx.shared.rate_limit
local keys = dict:get_keys(100)
ngx.say("Active rate limit keys: ", #keys)
for _, k in ipairs(keys) do
local v = dict:get(k)
ngx.say(k, " = ", v)
end
}
}
}
}
HTTP 请求(ngx.http)
location /weather {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
httpc:set_timeout(5000)
local res, err = httpc:request_uri(
"https://api.weather.com/v1/current",
{
method = "GET",
headers = {
["Authorization"] = "Bearer xxx",
},
}
)
if not res then
ngx.log(ngx.ERR, "HTTP request failed: ", err)
ngx.exit(500)
return
end
ngx.header["Content-Type"] = "application/json"
ngx.say(res.body)
}
}
Redis 集成
http {
lua_shared_dict redis_pool 1m;
server {
location /cache/ {
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis connect failed: ", err)
ngx.exit(500)
return
end
local key = ngx.var.arg_key or "default"
local value, err = red:get(key)
if value == ngx.null then
ngx.say("Key not found: ", key)
else
ngx.say("Value: ", value)
end
-- 放回连接池(重要!)
local ok, err = red:set_keepalive(10000, 100)
}
}
}
}
动态路由
http {
lua_shared_dict routing_table 1m;
init_by_lua_block {
-- 初始化路由表
local routes = ngx.shared.routing_table
routes:set("/api/users", "http://127.0.0.1:3001")
routes:set("/api/orders", "http://127.0.0.1:3002")
routes:set("/api/products", "http://127.0.0.1:3003")
}
server {
location /api/ {
access_by_lua_block {
local routes = ngx.shared.routing_table
local uri = ngx.var.uri
-- 匹配路由
for pattern, backend in pairs(routes:get_keys(100)) do
if string.find(uri, "^" .. pattern) then
ngx.var.target_backend = routes:get(pattern)
return
end
end
ngx.exit(404)
}
proxy_pass $target_backend;
}
}
}
🔴 高级 / Advanced — 生产级应用
自定义 WAF
http {
lua_shared_dict waf_rules 10m;
lua_shared_dict waf_stats 1m;
init_worker_by_lua_block {
-- 加载 WAF 规则
local rules = ngx.shared.waf_rules
-- SQL 注入关键词
local sqli_keywords = {
"union%s+select", "insert%s+into", "drop%s+table",
"1%s*=%s*1", "or%s+1%s*=%s*1", "'%s+or%s+'"
}
for i, kw in ipairs(sqli_keywords) do
rules:set("sqli:" .. i, kw)
end
-- XSS 关键词
local xss_keywords = {
"<script", "javascript:", "onerror=", "onload="
}
for i, kw in ipairs(xss_keywords) do
rules:set("xss:" .. i, kw)
end
}
server {
location / {
access_by_lua_block {
local rules = ngx.shared.waf_rules
local stats = ngx.shared.waf_stats
local uri = ngx.var.uri
local args = ngx.var.args or ""
local body = ""
-- 读取请求体
ngx.req.read_body()
body = ngx.req.get_body_data() or ""
local request_data = uri .. "?" .. args .. body
-- 检查 SQL 注入
local keys = rules:get_keys(100)
for _, key in ipairs(keys) do
local pattern = rules:get(key)
if string.find(request_data:lower(), pattern:lower()) then
-- 记录统计
local count = stats:incr("blocked", 1, 0)
ngx.log(ngx.WAF, "Blocked: ", key, " from ", ngx.var.remote_addr)
ngx.exit(403)
return
end
end
}
proxy_pass http://backend;
}
-- WAF 统计接口
location /waf/stats {
content_by_lua_block {
local stats = ngx.shared.waf_stats
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({
blocked_requests = stats:get("blocked") or 0,
total_requests = stats:get("total") or 0
}))
}
}
}
}
API 网关
http {
lua_shared_dict jwt_cache 10m;
lua_shared_dict rate_limit 50m;
server {
listen 443 ssl;
server_name api.example.com;
location /api/ {
access_by_lua_block {
local cjson = require "cjson"
local jwt = require "resty.jwt"
-- 1. JWT 鉴权
local auth_header = ngx.req.get_headers()["Authorization"]
if not auth_header then
ngx.status = 401
ngx.say(cjson.encode({error = "Missing authorization"}))
ngx.exit(401)
return
end
local token = auth_header:match("Bearer%s+(.+)")
if not token then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid format"}))
ngx.exit(401)
return
end
-- 验证 JWT(简化示例)
local jwt_obj = jwt:verify("my_secret_key", token)
if not jwt_obj.verified then
ngx.status = 401
ngx.say(cjson.encode({error = "Invalid token"}))
ngx.exit(401)
return
end
-- 将用户信息传给后端
ngx.req.set_header("X-User-ID", jwt_obj.payload.sub)
ngx.req.set_header("X-User-Role", jwt_obj.payload.role)
-- 2. 限流
local key = "rate:" .. jwt_obj.payload.sub
local limit_dict = ngx.shared.rate_limit
local count = limit_dict:incr(key, 1, 0, 60)
if count > 1000 then
ngx.status = 429
ngx.header["Retry-After"] = "60"
ngx.say(cjson.encode({error = "Rate limit exceeded"}))
ngx.exit(429)
return
end
}
proxy_pass http://backend;
}
-- 路由分发
location /api/v1/users {
proxy_pass http://user-service;
}
location /api/v1/orders {
proxy_pass http://order-service;
}
location /api/v1/products {
proxy_pass http://product-service;
}
}
}
动态上游选择
upstream backend_a { server 10.0.1.10:3000; }
upstream backend_b { server 10.0.2.10:3000; }
server {
location / {
set $target_backend "backend_a";
access_by_lua_block {
-- 根据请求特征选择上游
local headers = ngx.req.get_headers()
local api_version = headers["X-API-Version"]
local client_tier = headers["X-Client-Tier"]
if api_version == "v2" then
ngx.var.target_backend = "backend_b"
elseif client_tier == "premium" then
ngx.var.target_backend = "backend_b"
end
}
proxy_pass http://$target_backend;
}
}
常用 Lua 库
| 库 | 用途 | 安装 |
|---|---|---|
lua-resty-http | HTTP 客户端 | 内置 |
lua-resty-redis | Redis 客户端 | 内置 |
lua-resty-mysql | MySQL 客户端 | 内置 |
lua-resty-jwt | JWT 鉴权 | opm get SkyLothar/lua-resty-jwt |
lua-resty-template | HTML 模板 | opm get bungle/lua-resty-template |
lua-cjson | JSON 编解码 | 内置 |
lua-resty-lrucache | LRU 缓存 | 内置 |
lua-resty-core | Nginx API 增强 | 内置 |
# OpenResty 包管理器(opm)
opm get SkyLothar/lua-resty-jwt
opm get ledgetech/lua-resty-http
# 查看已安装的包
opm list
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | OpenResty = Nginx + LuaJIT,content_by_lua_block,执行阶段 |
| 🟡 进阶 | shared_dict 共享内存,Redis/HTTP 集成,动态路由 |
| 🔴 高级 | 自定义 WAF,API 网关(JWT + 限流 + 路由),动态上游选择 |
全书完 / End of Tutorial
恭喜你完成了 Nginx 从入门到精通的全部课程!
Congratulations on completing the entire Nginx tutorial!