在Deno的Oak路由中处理文件上传时的用户中断问题
Deno Oak 文件上传中断时删除不完整文件的解决方案
问题背景
在Node.js中,我们可以通过监听请求对象的相关事件处理上传中断:
req.on("data", (data) => console.log(`on data: ${data.length}`)); req.on("close", () => console.log("req closed")); req.on("aborted", () => console.log("req aborted")); req.on("error", (err) => console.log("req error", err));
但在Deno + Oak框架实现文件上传时,若用body.value.read()直接将文件存储到磁盘,用户中途中断上传或出现网络问题时,磁盘会残留不完整文件,需要实现自动清理逻辑。
解决方案
核心思路是手动处理流式上传,监听请求中断信号,用临时文件存储上传中的数据,上传完成后再确认保留,中断则删除临时文件,具体实现如下:
修改后的上传路由代码
//imports import { serve } from "https://deno.land/std@0.166.0/http/server.ts"; import { Application, Router } from 'https://deno.land/x/oak/mod.ts'; // ...其他代码 router.post("/upload", async (ctx) => { let tempFilePath: string | undefined; try { const body = ctx.request.body({ type: "form-data" }); const formReader = body.value; console.log("File upload started"); // 监听请求中断信号 const abortController = new AbortController(); ctx.request.signal.addEventListener("abort", () => { abortController.abort(); // 中断时删除已创建的临时文件 if (tempFilePath) { Deno.remove(tempFilePath).catch(console.error); } }); let hasValidFile = false; while (true) { const part = await formReader.read({ signal: abortController.signal }); if (part.done) break; if (part.value.type === "file") { const filePart = part.value; // 生成唯一临时文件名 tempFilePath = `./uploads/temp_${crypto.randomUUID()}_${filePart.filename}`; const fileWriter = await Deno.open(tempFilePath, { write: true, create: true }); // 流式写入文件,绑定中断信号 await filePart.value.pipeTo(fileWriter.writable, { signal: abortController.signal }); await fileWriter.close(); // 上传完成,重命名为正式文件 const finalFilePath = `./uploads/${filePart.filename}`; await Deno.rename(tempFilePath, finalFilePath); tempFilePath = undefined; // 重置临时路径,避免误删正式文件 ctx.response.status = 200; ctx.response.body = { path: filePart.filename }; hasValidFile = true; } // 若需要处理表单字段,可在此添加逻辑 } if (!hasValidFile) { ctx.response.status = 400; ctx.response.body = { msg: "No file provided" }; } } catch (error) { console.log(error); // 处理中断错误 if (error.name === "AbortError" && tempFilePath) { await Deno.remove(tempFilePath).catch(console.error); ctx.response.status = 499; // 自定义客户端中断状态码 ctx.response.body = { msg: "Upload aborted by client" }; } else { // 其他错误:清理临时文件并返回500 if (tempFilePath) { Deno.remove(tempFilePath).catch(console.error); } ctx.response.status = 500; ctx.response.body = { msg: error.message }; } } }); // ...启动服务器代码
关键逻辑说明
- 监听中断信号:通过
ctx.request.signal监听请求的中止事件,触发时调用abortController.abort()终止文件流写入,并删除临时文件。 - 临时文件机制:上传过程中先将文件写入临时文件,上传完成后再重命名为正式文件,避免中断时留下不完整的正式文件。
- 错误处理分支:在catch块中区分中断错误(
AbortError)和其他服务器错误,统一清理临时文件并返回对应状态码。
内容的提问来源于stack exchange,提问作者Fuad Hasan
相关产品推荐
相关产品推荐

