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
相关产品推荐
相关产品推荐

