Kemal中文件上传必须用env.params.files而非body或json,因multipart/form-data需特殊解析;文件对象含filename、content_type、tempfile等属性,须校验类型、净化路径、限制大小并安全处理临时文件。

Kemal 框架中接收文件必须用 env.params.files,不是 env.params.body 或 env.params.json —— 这是最常踩的坑。
为什么 env.params.body 拿不到上传的文件?
因为 HTTP 文件上传(multipart/form-data)的数据结构和普通表单完全不同:文件内容是二进制块 + 边界分隔符,不能被当成普通键值对解析。Kemal 会把这类请求自动解析为 HTTP::FormData 对象,并将文件单独挂载到 env.params.files 下,而 env.params.body 只保留纯文本字段(如 name、description 等)。
- 错误写法:
env.params.body["avatar"]→ 返回nil,永远拿不到文件 - 正确入口:
env.params.files["avatar"]→ 返回HTTP::FormData::File实例 - 必须确保 HTML 表单设置了
enctype="multipart/form-data",否则浏览器根本不会发送文件
HTTP::FormData::File 的关键属性和安全处理
拿到文件对象后,别急着 .save 或直接读取内容。它包含原始字节流,但不带路径校验、类型检查或大小限制 —— 直接使用有风险。
-
file.filename:客户端提交的原始文件名(不可信,可能含../路径遍历) -
file.content_type:MIME 类型(如"image/png"),可做基础白名单校验 -
file.tempfile:指向系统临时文件的Tempfile对象,已写入磁盘,可安全读取或移动 -
file.size:文件字节数,建议在接收前通过env.request.headers["Content-Length"]做前置拦截(防超大上传)
post "/upload" do |env| # ✅ 正确获取上传的文件 avatar_file = env.params.files["avatar"] return env.response.status_code = 400 unless avatar_file✅ 安全提取文件名(去路径、加哈希)
orig_name = File.basename(avatar_file.filename) safename = "#{SecureRandom.hex(8)}#{orig_name}"
✅ 白名单校验 MIME 类型
unless ["image/jpeg", "image/png", "image/gif"].includes?(avatar_file.content_type) env.response.status_code = 415 next "Unsupported file type" end
✅ 移动临时文件到 public/uploads/
upload_dir = File.join(Dir.current, "public", "uploads") Dir.mkdir_p(upload_dir) dest_path = File.join(upload_dir, safe_name) File.rename(avatar_file.tempfile.path, dest_path)
"Uploaded: #{safe_name}" end
常见错误现象与修复
上传失败时,通常不是代码报错,而是静默 400/404 或文件为空 —— 因为 Kemal 默认不启用 multipart 解析,或中间件顺序不对。
-
现象:
env.params.files总是空 Hash → 原因:没调用require "kemal"后的默认 multipart 中间件未生效;修复:确认没手动覆盖Kemal.config.middleware,且没禁用Kemal::Multipart -
现象:上传大文件时连接中断 → 原因:Kemal 默认无上传大小限制,但底层
HTTP::Server有缓冲区上限;修复:启动前设置Kemal.config.max_request_size = 20_000_000(单位字节) -
现象:
file.tempfile.path报错 “No such file” → 原因:多次调用file.tempfile会导致临时文件被清理;修复:只调用一次并缓存结果,或直接用file.io流式读取
文件上传看似简单,但绕不开路径净化、类型校验、大小控制和临时文件生命周期管理 —— 这四点漏掉任一,上线后都可能变成安全漏洞或服务雪崩点。










