一、前言:OpenResty 如何"监听"每一个 HTTP 请求?
你可能已经知道 OpenResty 能在 Nginx 中运行 Lua 脚本,但你是否清楚:
- ✅ 请求到达时,Lua 代码在哪个时机执行?
- ✅ 如何在不同阶段拦截、修改或终止请求?
- ✅ 为什么有些逻辑必须放在
access阶段,而不能放在content?
OpenResty 并非"主动监听",而是通过 Nginx 的事件驱动模型,在预设的 11 个阶段中插入 Lua 逻辑 。
掌握这些阶段,你就能精准控制请求生命周期!
本文将带你彻底搞懂 OpenResty 的请求处理流程,并演示如何在关键阶段实现鉴权、限流、日志等核心功能。
二、OpenResty 请求处理的 11 个阶段(重点掌握 5 个)
OpenResty 基于 Nginx 的处理流程,扩展了多个 Lua 执行点。最常用的是以下 5 个:
| 阶段 | 指令 | 典型用途 |
|---|---|---|
rewrite |
rewrite_by_lua* |
URI 重写、参数校验 |
access |
access_by_lua* |
鉴权、IP 黑名单、限流(最常用!) |
content |
content_by_lua* |
生成响应内容(如调用 Redis/DB) |
header_filter |
header_filter_by_lua* |
修改响应头 |
body_filter |
body_filter_by_lua* |
流式修改响应体 |
log |
log_by_lua* |
记录访问日志、埋点统计 |
🔑 核心原则:
- 越早拦截,性能越好 (如在
access阶段拒绝非法请求,避免后续开销)- 不要在
content阶段做鉴权(太晚了!)
三、实战 1:在 access 阶段实现 API 鉴权
场景
所有 /api/ 开头的请求必须携带有效 Token。
配置
XML
location /api/ {
# 在 access 阶段拦截
access_by_lua_block {
local token = ngx.req.get_headers()["Authorization"]
if not token or token ~= "Bearer my-secret-token" then
ngx.log(ngx.WARN, "Invalid token from ", ngx.var.remote_addr)
ngx.exit(401) -- 直接返回 401,不进入后端
end
-- 鉴权通过,继续流程
}
# 转发到后端服务
proxy_pass http://backend;
}
✅ 优势 :非法请求在 Nginx 层就被拒绝,不消耗后端资源!
四、实战 2:在 rewrite 阶段重写 URI
场景
将 /v1/user/123 重写为 /user?id=123
配置
XML
location /v1/ {
rewrite_by_lua_block {
local uri = ngx.var.uri -- 如 "/v1/user/123"
if uri:match("^/v1/user/(%d+)$") then
local id = uri:match("/v1/user/(%d+)")
-- 重写内部 URI(不影响客户端)
ngx.req.set_uri("/user")
ngx.req.set_uri_args({id = id})
else
ngx.exit(404)
end
}
proxy_pass http://backend;
}
💡 注意 :
ngx.req.set_uri()只改变 Nginx 内部处理路径,客户端 URL 不变。
五、实战 3:在 log 阶段记录请求耗时
配置
XML
server {
# 初始化:记录请求开始时间
set $start_time "";
init_by_lua_block {
-- 全局初始化(通常用于加载模块)
}
location / {
access_by_lua_block {
-- 记录开始时间(纳秒)
ngx.ctx.start_time = ngx.now()
}
proxy_pass http://backend;
# 日志阶段:计算耗时
log_by_lua_block {
local latency = ngx.now() - ngx.ctx.start_time
ngx.log(ngx.INFO, "Request to ", ngx.var.uri,
" took ", latency * 1000, " ms")
}
}
}
📊 输出示例 :
2025/11/03 10:00:00 [info] ... Request to /api/data took 12.5 ms
六、关键变量与 API 速查
| 变量/方法 | 说明 |
|---|---|
ngx.var.remote_addr |
客户端 IP |
ngx.req.get_headers() |
获取所有请求头 |
ngx.req.get_uri_args() |
获取 GET 参数 |
ngx.req.read_body() + ngx.req.get_post_args() |
获取 POST 参数 |
ngx.ctx |
当前请求上下文(跨阶段共享数据) |
ngx.exit(code) |
终止请求并返回状态码 |
ngx.redirect(url) |
302 重定向 |
⚠️ 重要 :
ngx.ctx是单个请求内共享数据的唯一安全方式!
七、避坑指南:常见错误与最佳实践
❌ 错误 1:在 content 阶段做鉴权
- 后果:即使鉴权失败,也已消耗后端资源
- 正确做法 :鉴权逻辑放在
access_by_lua
❌ 错误 2:在 rewrite 阶段读取 POST Body
- 原因 :
rewrite阶段 body 尚未读取 - 解决 :需先调用
ngx.req.read_body(),但会阻塞,不推荐 → 改用access阶段
✅ 最佳实践
-
轻量逻辑:每个 Lua 块执行时间 < 5ms
-
错误处理 :始终检查
ok, err = ... -
配置分离 :复杂逻辑抽离为
.lua文件access_by_lua_file /etc/openresty/lua/auth.lua;
八、高级技巧:动态路由与 A/B 测试
location / {
access_by_lua_block {
local version = ngx.req.get_headers()["X-App-Version"] or "1.0"
if version >= "2.0" then
-- 路由到新服务
ngx.var.backend = "new_backend"
else
ngx.var.backend = "old_backend"
end
}
proxy_pass http://$backend;
}
🔧 原理 :通过
ngx.var.xxx动态设置 Nginx 变量,实现灵活路由。
九、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!