Node.js流操作出现Error: write ECONNABORTED问题求助
问题分析与解决:文件上传的ECONNABORTED错误及成功上传失败问题
问题背景
我正在编写一段服务器端代码,要求仅当文件不存在且大小不超过1Mb时才将其写入服务器,但目前遇到两个问题:
- 上传过大文件时,自动化测试抛出
Error: write ECONNABORTED错误,查资料得知该错误与过早调用res.end()有关,但找不到代码中的问题点; - 正常大小文件的成功上传测试也失败了。
服务器端相关代码
fs.stat(filepath, (err, file) => { if (err) { if (err.code === 'ENOENT') { const stream = new LimitSizeStream({limit: 1024*1024}); const outStream = fs.createWriteStream(pathname); req.pipe(stream).pipe(outStream); stream.on('error', (e) => { stream.destroy(); outStream.destroy(); if (e instanceof LimitExceededError) { res.statusCode = 413; res.end('File is too big'); } else { res.statusCode = 500; res.end('Internal error'); } }); outStream.on('error', (e)=> { stream.destroy(); outStream.destroy(); console.log('outStream error', e); }); req.on('aborted', () => { stream.destroy(); outStream.destroy(); }); outStream.on('end', () => { res.statusCode = 201; res.end('Successfully loaded'); }); } } else if (file.isFile()) { res.statusCode = 409; res.end('File already exists'); return; }; });
测试用例
抛出ECONNABORTED错误的测试用例
it('上传过大文件时返回413错误', (done) => { const request = http.request( 'http://localhost:3001/big.png', {method: 'POST'}, (response) => { expect( response.statusCode, '服务器状态码应为413' ).to.equal(413); request.on('error', (err) => { // 响应关闭后继续管道传输数据会触发EPIPE/ECONNRESET错误 if (!['ECONNRESET', 'EPIPE'].includes(err.code)) done(err); }); fse.createReadStream(path.join(fixturesFolder, 'big.png')).pipe(request); });
失败的成功上传测试用例
it('成功创建文件', (done) => { const request = http.request( 'http://localhost:3001/small.png', {method: 'POST'}, (response) => { expect( response.statusCode, '状态码应为201' ).to.equal(201); expect( fse.existsSync(path.join(filesFolder, 'small.png')), 'small.png文件应已写入磁盘' ).to.be.true; done(); }); request.on('error', done); fse.createReadStream(path.join(fixturesFolder, 'small.png')).pipe(request); });
问题分析与修复方案
1. 成功上传测试失败的核心原因
你监听了outStream的end事件,但Node.js的文件写入流(fs.createWriteStream)不会触发end事件,它在所有数据写入磁盘完成后触发的是finish事件。把outStream.on('end', ...)改为outStream.on('finish', ...)就能解决成功上传测试失败的问题。
2. 大文件上传时ECONNABORTED错误的原因
当文件大小超过限制时,LimitSizeStream触发error事件,你立刻调用res.end()关闭了响应,但此时客户端仍在持续发送数据。Node.js的HTTP服务器在响应结束后,会自动销毁未完成的请求流,这会导致客户端侧抛出ECONNABORTED错误,但你的测试用例只允许ECONNRESET和EPIPE,所以该错误被判定为测试失败。
另外,错误处理的顺序存在问题:你先销毁了流再发送响应,应该先发送响应,再销毁流(确保响应能正常发送完成)。
修复后的服务器端代码
fs.stat(filepath, (err, file) => { if (err) { if (err.code === 'ENOENT') { const stream = new LimitSizeStream({limit: 1024*1024}); const outStream = fs.createWriteStream(pathname); req.pipe(stream).pipe(outStream); stream.on('error', (e) => { // 先发送响应,再销毁流 if (e instanceof LimitExceededError) { res.statusCode = 413; res.end('File is too big'); } else { res.statusCode = 500; res.end('Internal error'); } stream.destroy(); outStream.destroy(); }); outStream.on('error', (e)=> { console.log('outStream error', e); res.statusCode = 500; res.end('Internal error'); stream.destroy(); outStream.destroy(); }); req.on('aborted', () => { stream.destroy(); outStream.destroy(); }); // 替换为finish事件 outStream.on('finish', () => { res.statusCode = 201; res.end('Successfully loaded'); }); } else { // 处理fs.stat的其他错误(如权限问题) res.statusCode = 500; res.end('Internal error'); } } else if (file.isFile()) { res.statusCode = 409; res.end('File already exists'); return; }; });
测试用例调整
针对大文件上传的测试,需要将ECONNABORTED加入允许的错误列表:
request.on('error', (err) => { // 新增ECONNABORTED到允许列表 if (!['ECONNRESET', 'EPIPE', 'ECONNABORTED'].includes(err.code)) done(err); else done(); // 正常结束测试 });
内容的提问来源于stack exchange,提问作者Mrmld Sky
相关产品推荐
相关产品推荐

