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


九、结语

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

相关推荐
努力努力再努力wz3 小时前
【Lua 入门系列】从顺序执行到分支与循环:一文理解 Lua 控制结构、作用域与跳转规则
junit·单元测试·lua
努力努力再努力wz1 天前
【Lua入门系列】从动态类型到变量作用域:彻底理解 local、全局变量与跨文件访问
开发语言·junit·lua
aloha_7893 天前
Java 基础、集合、异常与 JUnit 5 速查
java·junit·sqlserver
极小狐14 天前
MR 里测试摘要空白、覆盖率不显示?JUnit 与覆盖率配置 9 步排查
数据库·ci/cd·junit·单元测试·mr·devops·代码覆盖率
Best-Wishes16 天前
科来网络分析系统(技术交流版)使用说明书
开发语言·junit·php
贾宝玉的玉宝贾16 天前
FreeSWITCH 简单图形化界面 70 - 监听/密语的lua脚本
开发语言·junit·lua·voip·freeswitch·sip·ippbx
企业数字化笔记17 天前
多租户系统从哪里开始设计?租户身份、数据隔离、权限与套餐的整体方案
java·spring boot·后端·junit
众链网络21 天前
大客流票务系统怎么防超卖:库存并发、候补队列与幂等设计的工程取舍
junit·架构
深念Y24 天前
rime-雾凇拼音-配置记录
linux·junit·软件·拼音·kde
白山编程大哥1 个月前
Lua 中使用 C 语言的用户自定义类型——userdata
c语言·junit·lua