openresty通过upload模块流式校验文件上传,即在form:read()循环中分阶段校验:header阶段验文件名与content-type,body首块验magic bytes,累计长度超限则413,part_end验基础完整性;全程轻量同步、禁io、用ngx.re.match;nginx需配client_max_body_size 0等参数配合。

OpenResty 利用 Lua 脚本实现文件上传的实时流式校验,核心在于不缓存整个文件、边接收边检查——即在 upload 模块逐块读取(form:read())过程中,对每个数据块或关键元信息(如文件头、大小、类型、名称)即时判断,不符合规则就中断上传并返回错误。
关键:在 upload 循环中嵌入校验逻辑
标准的 upload 处理是 while true 调用 form:read(),返回类型包括 "header"、"body"、"part_end"、"eof"。流式校验就插在这几个阶段里:
-
收到 header 时校验文件名和 Content-Type:提取
filename,用ngx.re.match判断是否含非法字符或后缀;检查Content-Type是否在白名单内(如"image/jpeg"、"application/pdf") -
首次收到 body 块时校验文件头(Magic Bytes):用
string.sub(res, 1, 4)取前几个字节,比对 JPEG("\216\217")、PNG("\137PNG")等签名,不匹配立即ngx.exit(400) -
累计 body 长度时做大小限制:声明局部变量
total_len = 0,每次typ == "body"就加#res;一旦超过预设阈值(如 5MB),调用form:discard()清空剩余,并ngx.exit(413) - part_end 阶段可做轻量完整性检查:比如要求文件名非空、长度 > 0,否则拒绝该 part
避免阻塞,保持流式特性
校验必须轻量、同步、无 IO 等待。不能在循环里做 DNS 查询、远程 HTTP 请求或写磁盘。所有判断都基于内存中的 res 字符串或已知变量:
- 禁用
io.open、os.execute或http:request等耗时操作 - 正则匹配用
ngx.re.match(C 实现,快),不用 Lua 原生string.find处理复杂模式 - 文件类型判断优先用 Magic Bytes,而非依赖扩展名(易伪造)
- 若需更严格校验(如 PDF 结构),可在
eof后用lua-resty-string做快速解析,但不推荐在大文件上传路径中执行
配合 Nginx 配置启用流式支持
仅 Lua 层校验不够,Nginx 侧需确保不提前缓冲或截断:
- 设置
client_max_body_size 0(禁用 Nginx 自身体长限制,交由 Lua 控制) - 关闭代理缓冲:
proxy_buffering off(若走反向代理) - 调大超时:
client_header_timeout 60s、client_body_timeout 300s,防止大文件上传中途断连 - 确保
lua_code_cache off仅用于开发;生产环境开启以提升性能
错误响应要即时、明确
校验失败时,不能只 ngx.exit,应主动输出清晰提示并终止连接:
- 用
ngx.status = 400或413,再ngx.header.content_type = "text/plain" - 调用
ngx.say("Invalid file type")后立即ngx.flush(true),确保客户端立刻收到 - 必要时调用
form:discard()主动丢弃后续数据,避免客户端继续发送











