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 变量,实现灵活路由。


九、结语

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

相关推荐
難釋懷4 小时前
OpenResty获取参数的API
junit·openresty
難釋懷19 小时前
OpenResty请求参数处理
junit·openresty
難釋懷19 小时前
OpenResty获取参数并返回
junit·openresty
Boop_wu1 天前
[Java EE 进阶] Spring Boot 日志全面解析 : 配置与实战
junit·java-ee·单元测试
難釋懷4 天前
OpenResty快速入门
junit·openresty
FL4m3Y4n4 天前
Redis协议与异步方式
数据库·redis·junit
難釋懷5 天前
Lua语法入门-条件控制、函数
开发语言·junit·lua
難釋懷6 天前
Lua语法入门-变量和循环
开发语言·junit·lua
Java面试题总结6 天前
Junit到Springboot单元测试
spring boot·junit·单元测试