ExpressJS如何解析请求体二进制上传数据获取Buffer对象
Express处理纯二进制流文件上传实现方案
问题背景
当前前端上传逻辑不使用multipart/form-data格式,直接将文件二进制内容作为POST请求体发送,文件扩展名通过自定义请求头File-Extension传输,需要在Express服务端获取完整请求体的Buffer实例,最终保存为文件名随机、后缀匹配上传扩展名的本地文件。
前端参考实现
document.querySelector("form").onsubmit = e => { e.preventDefault(); const input = document.querySelector("input"), files = input.files; if(files.length > 0) { const ext = input.value.split("/").pop().split(".").pop(); fetch("https://example.com/fileUpload", { method: "POST", headers: { "File-Extension": "." + ext }, body: files[0] }) } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> </head> <body> <form> 上传文件<input type="file"> <input type="submit" value="upload"> </form> </body> </html>
具体实现方案
平时用来解析JSON、文本请求的body-parser本身就内置了原始二进制解析能力,不需要额外引入multer等专门处理multipart/form-data的上传中间件,解析完成后可以直接拿到完整的Buffer实例。Express 4.16.0及以上版本甚至不需要单独安装body-parser,框架本身内置了同款express.raw()中间件可以直接使用。
实现步骤
- 注册二进制解析中间件,配置匹配的请求类型和上传大小上限
const express = require('express'); const fs = require('fs/promises'); const crypto = require('crypto'); const path = require('path'); const app = express(); // 注册raw解析中间件,所有请求的二进制内容会被自动拼接为Buffer挂载到req.body app.use(express.raw({ type: '*/*', // 匹配所有Content-Type的请求 limit: '100mb' // 根据业务需求调整最大允许上传的文件大小 })); - 编写上传接口逻辑,读取扩展名、生成随机文件名、写入本地存储
app.post('/fileUpload', async (req, res) => { try { // req.body 就是完整的文件二进制Buffer实例 const fileBuffer = req.body; // 读取请求头中的扩展名,做安全校验过滤非法字符,防止路径遍历攻击 let ext = req.get('File-Extension') || ''; ext = ext.replace(/[^.a-zA-Z0-9]/g, ''); if (!ext.startsWith('.')) ext = '.' + ext; // 生成随机文件名,避免重名 const saveFileName = crypto.randomUUID() + ext; const saveDir = path.join(__dirname, 'upload'); const savePath = path.join(saveDir, saveFileName); // 存储目录不存在时自动创建 await fs.mkdir(saveDir, { recursive: true }); // 将Buffer写入本地文件 await fs.writeFile(savePath, fileBuffer); res.json({ code: 0, msg: '上传成功', data: { fileName: saveFileName } }) } catch (err) { res.status(500).json({ code: 500, msg: '上传失败', error: err.message }) } }) app.listen(3000, () => { console.log('服务启动在3000端口'); })
注意事项
- 必须手动配置
limit参数,Express/body-parser默认的请求体大小上限仅为100kb,无法满足常规文件上传需求。- 从请求头获取的扩展名必须做非法字符过滤,避免攻击者传入
../等特殊字符触发路径遍历漏洞,将文件写入非预期目录。- 如果不想使用内置中间件,也可以手动监听请求对象的
data事件接收数据块、end事件拼接完整Buffer,但生产环境直接使用官方维护的内置中间件更稳妥,已经处理好了断流、超限等边界情况。
内容的提问来源于stack exchange,提问作者c136
相关产品推荐
相关产品推荐

