如何在Node.js集群服务器中正确使用Multer?
解决Node.js集群+Socket.io粘性会话下Multer上传报错问题
问题分析
在使用@socket.io/sticky实现粘性会话的集群架构中,Multer处理文件上传时抛出Error: Unexpected end of form错误,原因是主进程转发TCP连接时,multipart请求的数据流未完整传递到工作进程,导致Multer无法正确解析完整的表单数据。
修复方案
1. 调整集群架构逻辑
主进程仅负责监听端口和转发连接,所有业务逻辑(包括Multer上传)都由工作进程处理,同时确保@socket.io/sticky的配置正确关联主进程与工作进程的HTTP服务。
2. 修正后的完整代码
const express = require('express'); const multer = require('multer'); const cluster = require('cluster'); const { createServer } = require('http'); const { setupMaster, setupWorker } = require('@socket.io/sticky'); const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter'); const { Server } = require('socket.io'); const os = require('os'); const fs = require('fs'); // 提前创建上传临时目录,避免工作进程因目录不存在报错 const uploadDir = './temp'; if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir); } const app = express(); const upload = multer({ dest: `${uploadDir}/` }); // 文件上传接口 app.post('/upload', upload.single('image'), (req, res) => { if (!req.file) { res.status(400).send('No file uploaded.'); } else { res.send(`File uploaded successfully! Filename: ${req.file.filename}`); } }); const numCPUs = os.cpus().length; if (cluster.isPrimary) { console.log(`Master ${process.pid} is running`); // 主进程创建空HTTP服务器,仅处理连接转发 const httpServer = createServer(); // 配置sticky会话主进程 setupMaster(httpServer, { loadBalancingMethod: 'least-connection', }); // 配置Socket.io集群适配器主进程 setupPrimary(); cluster.setupPrimary({ exec: __filename, // 工作进程执行当前文件逻辑 }); // 主进程监听端口 httpServer.listen(3000, () => { console.log(`Master server listening on port 3000`); }); // 启动工作进程 for (let i = 0; i < numCPUs; i++) { cluster.fork(); } // 工作进程崩溃自动重启 cluster.on('exit', (worker) => { console.log(`Worker ${worker.process.pid} died, restarting...`); cluster.fork(); }); } else { console.log(`Worker ${process.pid} started`); // 工作进程创建HTTP服务器并挂载Express应用 const httpServer = createServer(app); // 配置Socket.io实例 const io = new Server(httpServer, { path: '/socket', transports: ['websocket'], cors: { origin: '*', methods: ['GET', 'POST'], }, }); // 使用集群适配器 io.adapter(createAdapter()); // 绑定工作进程与sticky会话 setupWorker(io); // 工作进程无需监听端口,由主进程转发连接 }
3. 关键修复点
- 提前创建临时目录:避免工作进程因目录权限或不存在导致隐性错误。
- 主进程职责单一:仅处理端口监听和连接转发,不挂载任何业务逻辑。
- 正确绑定Socket.io与sticky:工作进程通过
setupWorker(io)确保WebSocket连接和HTTP请求路由到同一工作进程,保障会话粘性。 - 修正日志混淆问题:主进程监听回调中打印正确的角色标识,避免日志误导。
验证
启动服务器后,向/upload接口发送multipart/form-data格式的文件上传请求,确认不再抛出Unexpected end of form错误,文件可正常保存到temp/目录。
内容的提问来源于stack exchange,提问作者Mohamad Izzuddin
相关产品推荐
相关产品推荐

