OpenResty请求参数处理

一、前言:为什么参数处理是 OpenResty 的核心能力?

在构建 API 网关、鉴权服务或 WAF 时,你经常需要:

  • ✅ 从 URL 中提取用户 ID
  • ✅ 验证 POST 表单中的手机号
  • ✅ 解析 JSON 请求体做风控
  • ✅ 读取 Header 中的 Token

OpenResty 的强大之处,在于它能让你在 Nginx 层直接获取并处理任意请求参数,无需转发到后端!

但不同参数类型(GET、POST、JSON、Header)的获取方式各不相同,稍有不慎就会出错。

本文将系统性讲解 OpenResty 中所有主流参数的获取与解析方法,附带完整可运行代码!


二、准备工作:启用 body 读取(关键!)

OpenResty 默认不会自动读取请求体(body),必须手动调用:

Lua 复制代码
-- 在 access 或 content 阶段开头添加
ngx.req.read_body()  -- 阻塞式读取 body 到内存

⚠️ 注意

  • 此操作会阻塞当前请求,确保 body 被加载
  • 大文件上传建议用 ngx.req.socket() 流式读取(本文不展开)

三、获取 GET 参数(URL Query)

场景

请求:GET /user?id=1001&name=Alice

代码

Lua 复制代码
-- 获取所有 GET 参数(table)
local args = ngx.req.get_uri_args()

-- 安全访问(防止 nil)
local user_id = args.id or ""
local name = args.name or ""

ngx.say("ID: ", user_id, ", Name: ", name)

特点

  • 自动 URL 解码(如 %E4%B8%AD
  • 多值参数返回 table(如 ?tag=a&tag=b{a, b}

四、获取 POST 表单参数(application/x-www-form-urlencoded)

场景

请求头:Content-Type: application/x-www-form-urlencoded

Body:username=admin&password=123456

代码

Lua 复制代码
ngx.req.read_body()  -- 必须先读取 body!

-- 获取 POST 参数
local post_args = ngx.req.get_post_args()

local username = post_args.username
local password = post_args.password

if not username or not password then
    ngx.exit(400)
end

ngx.say("Login user: ", username)

⚠️ 限制

  • 仅支持标准表单格式
  • 不支持嵌套结构(如 user[name]=Alice

五、解析 JSON 请求体(application/json)

这是现代 API 最常见的格式!

场景

请求头:Content-Type: application/json

Body:

Lua 复制代码
{
  "userId": 1001,
  "action": "login",
  "device": {
    "os": "iOS",
    "version": "17.0"
  }
}

代码(需使用 cjson 模块)

Lua 复制代码
ngx.req.read_body()

-- 获取原始 body 字符串
local body_data = ngx.req.get_body_data()
if not body_data then
    ngx.exit(400)
end

-- 解析 JSON
local cjson = require "cjson.safe"  -- 推荐 safe 版本
local data, err = cjson.decode(body_data)

if not data then
    ngx.log(ngx.ERR, "JSON decode failed: ", err)
    ngx.exit(400)
end

-- 访问嵌套字段
local user_id = data.userId
local os = data.device and data.device.os or ""

ngx.say("User ", user_id, " from ", os)

🔑 关键点

  • 使用 cjson.safe 避免解析错误导致进程崩溃
  • 检查字段是否存在(避免 attempt to index a nil value

六、获取请求头(Headers)

场景

从 Header 中读取 Token 或设备信息

代码

Lua 复制代码
-- 获取单个 Header(自动转小写)
local token = ngx.req.get_headers()["authorization"]
-- 或
local user_agent = ngx.var.http_user_agent  -- 特殊变量方式

-- 获取所有 Headers
local headers = ngx.req.get_headers()
for key, val in pairs(headers) do
    ngx.log(ngx.INFO, "Header: ", key, " = ", tostring(val))
end

💡 注意

  • Header 名自动转为小写 (如 Authorizationauthorization
  • 多值 Header 返回 table(如 Set-Cookie

七、获取原始请求体(Raw Body)

适用于 XML、Protobuf、自定义二进制协议等场景。

代码

Lua 复制代码
ngx.req.read_body()
local raw_body = ngx.req.get_body_data()  -- string 类型

-- 或从临时文件读取(大 body)
if not raw_body then
    local file_name = ngx.req.get_body_file()
    if file_name then
        local file = io.open(file_name, "r")
        raw_body = file:read("*a")
        file:close()
    end
end

ngx.say("Raw body length: ", #raw_body)

📌 触发文件存储的条件

body 大小 > client_body_buffer_size(默认 8k/16k)


八、实战:统一参数校验中间件

将上述能力整合,实现一个通用参数检查器:

Lua 复制代码
location /api/ {
    access_by_lua_block {
        -- 1. 读取 body(兼容 GET/POST/JSON)
        if ngx.req.get_method() ~= "GET" then
            ngx.req.read_body()
        end

        -- 2. 获取参数(根据 Content-Type 分发)
        local params = {}
        local headers = ngx.req.get_headers()
        local content_type = headers["content-type"] or ""

        if ngx.req.get_method() == "GET" then
            params = ngx.req.get_uri_args()
        elseif content_type:find("application/json", 1, true) then
            local body = ngx.req.get_body_data()
            if body then
                local cjson = require "cjson.safe"
                params = cjson.decode(body) or {}
            end
        else
            params = ngx.req.get_post_args()
        end

        -- 3. 校验必填字段
        if not params.userId then
            ngx.log(ngx.WARN, "Missing userId")
            ngx.exit(400)
        end

        -- 4. 存入上下文供后续使用
        ngx.ctx.request_params = params
    }

    proxy_pass http://backend;
}

优势 :在 Nginx 层完成参数标准化与校验,减轻后端负担


九、常见问题与避坑指南

❌ 问题 1:get_post_args() 返回 nil

  • 原因 :未调用 ngx.req.read_body()
  • 解决:确保在读取 body 前调用该函数

❌ 问题 2:JSON 解析中文乱码

  • 原因:客户端未发送 UTF-8 编码
  • 解决 :要求客户端设置 Content-Type: application/json; charset=utf-8

❌ 问题 3:大 body 导致内存溢出

  • 解决 :调整 client_max_body_sizeclient_body_buffer_size,或使用流式处理

十、结语

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

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