一、前言:为什么参数处理是 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 名自动转为小写 (如
Authorization→authorization)- 多值 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_size和client_body_buffer_size,或使用流式处理
十、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!