You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:59:55