使用Next.js+Formidable+Express传图时,Safari/Chrome出现连接错误
问题背景
运行在Ubuntu Server的Intel NUC上托管着Next.js应用(对外)和Node Express应用(对内),上传流程为:
Next.js客户端 → Next.js API /pages/api/upload/[room] → 转发请求到Express服务器完成图片上传。
上传图片时偶尔出现连接中断问题:移动端Safari显示Load failed,桌面端Chrome显示ERR_CONNECTION_RESET,大图片触发概率更高,网络面板显示请求未正常发出。
Express核心代码如下:
app.js
var express = require('express') var path = require('path') var cookieParser = require('cookie-parser') var cors = require('cors') var targetsRouter = require('./routes/targets') var app = express() app.use(cors()) app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) app.use('/targets', targetsRouter) /** Error responder. */ app.use((error, req, res, next) => { console.log(new Date(), `(${req.ip} ${req.url})`, '\x1b[31m', error) res.status(error.status ?? 500).json({ message: error.message }) }) module.exports = app
routes/targets.js
var express = require('express') var router = express.Router() const fs = require('fs/promises') const path = require('path') const pendingDirectory = 'public/images/uploads/pending' const approvedDirectory = 'public/images/uploads/approved' router.post('/upload/:room', async function (req, res, next) { try { const filePath = req.body.filePath const fileName = req.body.fileName const roomParam = req.params.room /** Check if the filepath. */ if (typeof filePath !== 'string' || typeof fileName !== 'string') { const error = new Error('Invalid or missing file.') error.status = 400 throw error } /** Move the file to the pending folder. */ const path = `${pendingDirectory}/${roomParam}` await fs.mkdir(`${path}`, { recursive: true }) await fs.rename(filePath, `${path}/${fileName}`) /** Send client response. */ res.json({ message: 'Success, image was uploaded and awaiting approval.', file: `${fileName}`, }) } catch (error) { next(error) } }) module.exports = router
调试与解决方向
1. 检查服务器网络与系统限制
- TCP连接超时配置:查看Ubuntu内核TCP参数,避免长连接被过早截断:
# 查看当前TCP超时设置 sysctl net.ipv4.tcp_fin_timeout net.ipv4.tcp_keepalive_time # 临时调整(重启后失效) sysctl -w net.ipv4.tcp_fin_timeout=60 sysctl -w net.ipv4.tcp_keepalive_time=300 - 文件描述符限制:大文件上传会占用更多文件描述符,检查并调整:
# 查看当前进程限制 ulimit -n # 临时调整当前会话限制 ulimit -n 4096 # 永久调整需修改 /etc/security/limits.conf - 防火墙超时:检查ufw/iptables是否有针对HTTP请求的超时规则,比如
--timeout参数,避免大文件上传未完成就被截断。
2. 调整Node.js/Express请求大小限制
Express默认的express.json()中间件仅允许最大100KB的请求体,大图片的相关数据(如Base64编码或文件路径携带的额外信息)可能超出限制,导致请求被强制断开:
修改app.js中的json中间件配置:
app.use(express.json({ limit: '10mb' })) // 根据实际图片大小调整,比如20mb app.use(express.urlencoded({ extended: false, limit: '10mb' }))
3. 排查Next.js API转发逻辑
- 添加超时处理:Next.js API转发请求到Express时,未设置超时会导致连接因长时间无响应被系统断开,在fetch请求中添加timeout:
// 在Next.js的/pages/api/upload/[room].js中 const response = await fetch(EXPRESS_UPLOAD_URL, { method: 'POST', body: JSON.stringify({ filePath, fileName }), headers: { 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(30000) // 设置30秒超时 }) - 错误捕获与响应:确保Next.js API能捕获转发过程中的所有异常,返回明确的错误响应,而非让连接直接中断:
try { // 转发请求逻辑 } catch (err) { console.error('转发请求失败:', err) return res.status(500).json({ message: '上传失败,请重试' }) } - Next.js请求体限制:在
next.config.js中调整服务器端请求体大小限制:module.exports = { serverRuntimeConfig: { api: { bodyParser: { sizeLimit: '10mb' } } } }
4. 修复Express文件操作潜在问题
- 跨文件系统的文件移动:
fs.rename仅能在同一文件系统内移动文件,如果Next.js的临时文件目录与Express的目标目录不在同一挂载点(比如tmpfs和磁盘),rename会失败,进而导致连接异常。替换为copyFile + unlink:// 替换原fs.rename代码 await fs.copyFile(filePath, `${path}/${fileName}`) await fs.unlink(filePath) - 磁盘空间与权限检查:
- 检查目标磁盘剩余空间:
df -h - 确认Express运行用户对
pendingDirectory有写入权限:ls -ld public/images/uploads/pending
- 检查目标磁盘剩余空间:
- 完善错误日志:在Express的错误处理器中记录完整错误堆栈,便于定位问题:
app.use((error, req, res, next) => { console.log(new Date(), `(${req.ip} ${req.url})`, '\x1b[31m', error.stack) res.status(error.status ?? 500).json({ message: error.message }) })
5. 启用系统与应用日志排查
- 系统日志:查看Ubuntu内核与系统日志,排查是否有OOM(内存不足)、磁盘错误或TCP重置记录:
tail -f /var/log/syslog dmesg | grep -i oom - 应用日志:在Next.js API和Express中添加请求大小、耗时等详细日志,追踪大文件上传时的异常节点:
// Express路由中添加日志 console.log(`上传请求:文件路径${filePath},大小${await fs.stat(filePath).then(s => s.size)}`)
6. 隔离测试定位问题
临时开放Express服务器端口,直接用curl或Postman上传大文件,验证是否是Next.js转发导致的问题:
curl -X POST http://<server-ip>:<express-port>/targets/upload/test-room \ -H "Content-Type: application/json" \ -d '{"filePath": "/path/to/large-image.jpg", "fileName": "test.jpg"}'
如果直接请求Express也出现连接重置,问题集中在Express或服务器配置;否则问题出在Next.js的转发逻辑。
内容的提问来源于stack exchange,提问作者graysonlee123
相关产品推荐
相关产品推荐

