OpenResty监听请求

一、前言: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 变量,实现灵活路由。


九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
lhldsg20 小时前
家政派单系统开发实战:从需求分析到智能调度全流程指南
java·junit·小程序·uni-app·需求分析
肠畔码农3 天前
深度解密 Redis 核心引擎:从 MULTI/EXEC 事务机制到 EVAL 脚本的原子性演进
数据库·redis·junit
会编程的吕洞宾5 天前
Spring Cloud Gateway限流:Redis令牌桶防刷实战
数据库·redis·junit
難釋懷7 天前
Nginx-Lua
nginx·junit·lua
萧瑟余晖8 天前
Java深入解析篇三十七之JUnit 5 详解
java·junit
骑士雄师11 天前
superpowers使用流程
junit·skill
重庆小透明12 天前
深入探寻微服务【第五篇微服务限流实战】
微服务·junit·架构
難釋懷12 天前
Nginx-Openresty
nginx·junit·openresty
小努蛋13 天前
linux编译openresty异常问题,lua_cjson.c:706:5: 错误: ‘for‘ 循环初始化声明只在 C99 或 C++ 模式下允许
开发语言·lua·openresty
難釋懷23 天前
Nginx外置缓存-redis2-nginx-module
nginx·缓存·junit