如何用Lua通过multipart/form-data向Paperless-ngx API上传文件?
解决Paperless-ngx API Lua上传400错误(document: No file was submitted)
问题根源
你遇到的400错误是因为Lua代码构建的multipart/form-data请求格式不符合Paperless-ngx API要求,核心问题在于文件字段的Content-Disposition格式不正确,或是请求体未正确包含文件内容、边界符设置错误。
先参考你能正常运行的curl命令格式:
curl -X POST -H "Authorization: Token YOUR_API_TOKEN" \ -F "document=@/home/user/invoice.pdf;type=application/pdf" \ -F "title=2024年5月发票" \ http://paperless.example.com/api/documents/post_document/
curl会自动处理multipart边界符、Content-Type头及文件字段的正确格式,而Lua需要手动构建这些内容(或依赖HTTP库的正确API)。
解决方案(分两种常用Lua HTTP库)
1. 使用lua-resty-http(OpenResty/Nginx环境)
这是最推荐的方式,库本身支持multipart请求构建,无需手动拼接请求体:
local http = require "resty.http" local httpc = http.new() -- 设置超时(解决之前的超时问题) httpc:set_timeout(10000) -- 10秒超时 local file_path = "/path/to/your/document.pdf" local file_handle = io.open(file_path, "rb") if not file_handle then ngx.log(ngx.ERR, "无法打开文件: ", file_path) return end local file_content = file_handle:read("*a") file_handle:close() local res, err = httpc:request_uri("http://paperless.example.com/api/documents/post_document/", { method = "POST", headers = { ["Authorization"] = "Token YOUR_API_TOKEN", -- 无需手动设置Content-Type,库会自动处理 }, body = { -- 核心:文件字段需指定filename和content_type document = { filename = "invoice.pdf", content_type = "application/pdf", content = file_content }, title = "2024年5月发票", -- 可选字段示例:correspondent = "1"(对应联系人ID) }, multipart = true -- 启用multipart模式 }) if not res then ngx.log(ngx.ERR, "请求失败: ", err) return end ngx.say("状态码: ", res.status) ngx.say("响应内容: ", res.body) httpc:close()
2. 使用luasocket + luasocket.http(通用Lua环境)
普通Lua环境下需手动构建multipart请求体:
local http = require("socket.http") local ltn12 = require("ltn12") local boundary = "----LuaPaperlessBoundary" .. os.time() local file_path = "/path/to/your/document.pdf" local file_handle = io.open(file_path, "rb") if not file_handle then print("无法打开文件: ", file_path) return end local file_content = file_handle:read("*a") file_handle:close() -- 构建multipart请求体各部分 local parts = { -- title字段 "--" .. boundary .. "\r\n", 'Content-Disposition: form-data; name="title"\r\n\r\n', "2024年5月发票\r\n", -- document文件字段(必须包含filename和Content-Type) "--" .. boundary .. "\r\n", 'Content-Disposition: form-data; name="document"; filename="invoice.pdf"\r\n', "Content-Type: application/pdf\r\n\r\n", file_content .. "\r\n", -- 结束边界 "--" .. boundary .. "--\r\n" } local request_body = table.concat(parts) local response_body = {} local res, code, headers, status = http.request{ url = "http://paperless.example.com/api/documents/post_document/", method = "POST", headers = { ["Authorization"] = "Token YOUR_API_TOKEN", ["Content-Type"] = "multipart/form-data; boundary=" .. boundary, ["Content-Length"] = tostring(#request_body) }, source = ltn12.source.string(request_body), sink = ltn12.sink.table(response_body) } print("状态码: ", code) print("响应内容: ", table.concat(response_body))
关键注意事项
- 文件字段必须指定filename:Paperless-ngx API依赖
filename参数识别文件字段,缺少会返回document: No file was submitted错误。 - 正确设置Content-Type:文件字段要对应正确的MIME类型(如pdf用
application/pdf,图片用image/jpeg)。 - 超时问题解决:显式设置超时时间(如10秒),避免因网络或Paperless处理缓慢导致超时。
- API Token权限:确保你的Token拥有
document.add权限,否则会返回403错误(虽当前是400,但也需排查)。
内容的提问来源于stack exchange,提问作者nodecentral
相关产品推荐
相关产品推荐

