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

Heroku使用Busboy上传视频报H18-Server Request Interrupted如何解决

问题根因
  • Heroku的H18错误本质是服务器端在请求完整接收/处理完成前就主动关闭了连接,你的代码核心问题是busboy的finish事件触发仅代表表单数据解析完成,不代表文件流已经完全写入磁盘。大文件(视频)写入耗时远长于小图片,此时你提前调用res.end(),会导致还在进行的文件写入流程被强制中断,触发连接断开报错,这也是小图片上传正常、视频上传失败的核心原因。
  • 你完全没有处理各类流的错误事件,不管是文件读取流、写入流还是busboy实例的报错,都会触发未捕获异常,直接中断请求。
  • Heroku的临时文件目录(os.tmpDir())有容量限制(默认每个dyno的tmp目录最多512MB),如果你的视频超过这个大小会直接写入失败,也会触发H18报错。
修复方案

核心调整逻辑:等待所有文件写入完成后再返回响应,补全全链路错误监听,避免提前断开连接。修复后代码如下:

const path = require('path');
const os = require('os');
const fs = require('fs');
const http = require('http');
const Busboy = require('busboy');

http.createServer(function(req, res) {
  if (req.method === 'POST') {
    const busboy = new Busboy({ headers: req.headers });
    let fileCount = 0;
    let hasError = false;

    // 全局错误统一处理
    const handleError = (err) => {
      if (hasError) return;
      hasError = true;
      console.error('上传出错:', err);
      res.writeHead(500, { 'Connection': 'close' });
      res.end('上传失败');
      req.resume();
    };

    busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
      fileCount++;
      // 原逻辑用fieldname作为文件名会导致同字段多文件覆盖,这里改为用上传的文件名
      const saveTo = path.join(os.tmpDir(), path.basename(filename));
      const writeStream = fs.createWriteStream(saveTo);
      
      file.pipe(writeStream);

      // 单文件写入完成后计数,所有文件写完再返回成功
      writeStream.on('finish', () => {
        fileCount--;
        if (fileCount === 0 && !hasError) {
          res.writeHead(200, { 'Connection': 'close' });
          res.end("That's all folks!");
        }
      });

      // 补全各类错误监听
      writeStream.on('error', handleError);
      file.on('error', handleError);
    });

    busboy.on('finish', () => {
      // 处理无文件上传的纯表单场景
      if (fileCount === 0 && !hasError) {
        res.writeHead(200, { 'Connection': 'close' });
        res.end("That's all folks!");
      }
    });

    busboy.on('error', handleError);
    req.on('error', handleError);

    return req.pipe(busboy);
  }
  res.writeHead(404);
  res.end();
}).listen(8000, function() {
  console.log('Listening for requests');
});
额外优化建议
  • 如果你的视频普遍大于500MB,不要存储在Heroku的临时目录,直接流式上传到第三方对象存储服务,完全避免本地磁盘容量限制,同时Heroku dyno重启后临时目录文件会被全部清空,本身就不适合持久化存储上传文件。
  • 可以给busboy配置limits参数限制上传文件大小,避免超大文件直接打满磁盘:new Busboy({ headers: req.headers, limits: { fileSize: 500 * 1024 * 1024 } })
  • 分块上传场景下建议校验每个分片的完整性,所有分片上传完成后再合并,避免部分分片上传失败导致完整文件不可用。

内容的提问来源于stack exchange,提问作者ololo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 06:54:04