Fastify对接Dropbox会话上传时append接口返回closed错误如何解决?
问题原因
你遇到的closed错误核心是以下几个问题导致的:
- 前端用FormData格式上传文件,后端直接读取
req.raw原始请求流往Dropbox传输,流中包含了FormData的边界标识、字段描述等非文件内容,和你计算的offset完全不匹配,Dropbox校验块数据失败直接关闭会话。 - 你用
stream.on('data')绑定异步回调处理分片,即使pause了流也无法保证分片按顺序上传,一旦前一个分片的append请求还没完成,后一个分片就发送,offset就会错乱,Dropbox收到乱序的分片会直接关闭会话。 - 所有Dropbox接口请求都没有加异常捕获,只要某一次请求超时或失败,会话状态异常后续所有append请求都会返回closed。
解决步骤
- 安装
@fastify/multipart插件解析FormData,拿到纯文件二进制流,不要直接处理原始请求流 - 用异步迭代器处理文件流,保证分片按顺序串行上传,避免offset错乱
- 给所有Dropbox接口请求添加异常捕获,出错时直接终止流程返回错误
核心代码修改
首先安装依赖:npm install @fastify/multipart
修改Fastify服务的路由逻辑:
require("dotenv").config() const https = require("https") const JSONbig = require("json-bigint") const TOKEN = process.env.DROPBOX_ACCESS_TOKEN const fastify = require("fastify")({ logger: { level: "info" } }) // Promisify https request const httpRequest = (url, options, data) => { return new Promise((resolve, reject) => { const req = https.request(url, options, (res) => { let body = "" res.on("data", (chunk) => { body += chunk.toString() }) res.on("error", reject) res.on("end", () => { if (res.statusCode >= 200 && res.statusCode <= 299) { resolve({ statusCode: res.statusCode, headers: res.headers, body: JSON.parse(body) }) } else { reject("Request failed. status: " + res.statusCode + ", body: " + body) } }) }) req.on("error", reject) if (data) req.write(data) req.end() }) } fastify.register(require("fastify-cors")) // 注册multipart解析插件 fastify.register(require('@fastify/multipart'), { limits: { fileSize: 1024 * 1024 * 1024 // 按需调整最大支持的文件大小 } }) fastify.post("/", async (req, reply) => { try { // 解析获取上传的文件流 const uploadFile = await req.file() const fileStream = uploadFile.file // 初始化上传会话 const initResponse = await httpRequest( "https://content.dropboxapi.com/2/files/upload_session/start", { method: "post", headers: { Authorization: `Bearer ${TOKEN}`, "Dropbox-API-Arg": JSON.stringify({ close: false }), "Content-Type": "application/octet-stream" } } ) const sessionId = initResponse.body.session_id req.log.info(`Initiating session ${sessionId}`) let offset = 0 // 异步迭代器保证分片串行上传 for await (const chunk of fileStream) { req.log.info(`Sending chunk, size: ${chunk.length}, offset: ${offset}`) await httpRequest( `https://content.dropboxapi.com/2/files/upload_session/append_v2`, { method: "post", headers: { Authorization: `Bearer ${TOKEN}`, "Dropbox-API-Arg": JSONbig.stringify({ cursor: { session_id: sessionId, offset }, close: false }), "Content-Type": "application/octet-stream" } }, chunk ) offset += chunk.length } // 完成上传会话 await httpRequest( `https://content.dropboxapi.com/2/files/upload_session/finish`, { method: "post", headers: { Authorization: `Bearer ${TOKEN}`, "Dropbox-API-Arg": JSONbig.stringify({ cursor: { session_id: sessionId, offset: offset }, commit: { path: `/Upload/${uploadFile.filename}`, // 使用实际上传的文件名 mode: "add", autorename: true, mute: false, strict_conflict: false } }), "Content-Type": "application/octet-stream" } } ) req.log.info("upload session finished") return reply.code(204).send() } catch (err) { req.log.error(err) return reply.code(500).send({ error: err }) } }) fastify.listen(3000, (err) => { if (err) { fastify.log.fatal(err) process.exit(1) } console.log(`server listening on ${fastify.server.address().port}`) })
前端代码无需修改,保持原有FormData上传逻辑即可正常运行。
内容的提问来源于stack exchange,提问作者cyberwombat
相关产品推荐
相关产品推荐

