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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:54:58