纯JavaScript无第三方库上传存储图片后文件损坏的原因及解决方案
问题根因
保存的图片无法打开、十六进制内容和原文件不一致,核心原因是服务端未对multipart/form-data格式的请求体做解析,直接将包含表单协议内容的整个请求体写入了图片文件。multipart/form-data是文件上传的标准表单协议格式,请求体除了实际文件二进制内容外,还固定包含表单分隔符、表单项描述头、结束标记等协议内容,这些内容不属于图片本身,混入文件后会直接破坏图片结构。你当前的服务端代码虽然提取了boundary值但完全没有用于内容切分,相当于把HTTP请求的完整原始body直接存为jpeg文件,文件开头不是JPEG标准的FF D8文件头,自然无法被图片查看器识别。
另外客户端的FileReader读ArrayBuffer再转Blob的逻辑属于无意义冗余:File对象本身就是Blob的子类,可以直接append到FormData中,不需要额外转码,这部分逻辑虽然没有直接造成文件损坏,但可以删除简化。
修复方案
简化客户端代码
去掉多余的FileReader转码逻辑,直接将选中的文件对象传入FormData即可:
function myfunction() { const photo = document.getElementById("image-file").files[0]; if (!photo) return; const formData = new FormData(); formData.append("uploadFile", photo, photo.name); fetch('http://127.0.0.1:3000/upload', { method: "POST", body: formData }); }
修正服务端multipart解析逻辑
按照multipart协议规则从请求体中切出纯文件二进制内容再写入磁盘,解析逻辑遵循以下步骤:
- 从Content-Type头中提取boundary值,注意请求体中实际的分隔符格式为
--<boundary值> - 定位第一个分隔符的位置,向后查找头信息和文件内容的分隔标记
\r\n\r\n,标记之后就是文件内容的起始位置 - 从文件起始位置向后查找下一个分隔符的位置,两个位置之间的内容去掉末尾的换行符就是纯文件二进制
- 将切分得到的纯文件Buffer写入磁盘
修复后的服务端代码如下:
const fs = require('fs'); function store_file(req, res) { let body = Buffer.from([]); req.on('data', (data) => { body = Buffer.concat([body, data]); }); req.on('end', () => { const contentType = req.headers['content-type']; if (!contentType || !contentType.includes('boundary=')) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ msg: "missing boundary in content-type" })); return; } // 提取boundary,拼接成实际分隔符Buffer const boundary = contentType.split('boundary=')[1]; const boundaryBuf = Buffer.from(`--${boundary}`); // 定位第一个分隔符位置 const firstBoundaryPos = body.indexOf(boundaryBuf); if (firstBoundaryPos === -1) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ msg: "invalid form data format" })); return; } // 定位表单头和文件内容的分隔标记 const headerEndMarker = Buffer.from('\r\n\r\n'); const headerEndPos = body.indexOf(headerEndMarker, firstBoundaryPos); if (headerEndPos === -1) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ msg: "invalid form header" })); return; } // 计算文件内容起始位置 const fileStartPos = headerEndPos + headerEndMarker.length; // 定位文件内容后的下一个分隔符,确定文件结束位置 const nextBoundaryPos = body.indexOf(boundaryBuf, fileStartPos); if (nextBoundaryPos === -1) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ msg: "invalid file content" })); return; } // 切出纯文件内容,去掉末尾的\r\n换行 const fileBuffer = body.subarray(fileStartPos, nextBoundaryPos - 2); // 写入文件 fs.writeFile('test.jpeg', fileBuffer, (err) => { if (err) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ msg: "file write failed" })); return; } console.log('The file has been saved!'); index_file(req, res); }); }); }
修复后重新对比hexdump可以发现,保存的文件和原始文件二进制完全一致,JPEG文件开头为标准的
FF D8 FF标记,可以正常打开。
内容的提问来源于stack exchange,提问作者Shaheer Ahmed
相关产品推荐
相关产品推荐

