设置Content-Length后fs.createReadStream.pipe(res)失效问题排查
问题:设置Content-Length后文件传输突然中断,移除后恢复正常
相关代码
router.get('*', authz.validateAccount, (request, response) => { const accountId = request.user.accountId; const path = shared.resolveDataPath(decodeURI(request.url), accountId); const stat = fs.statSync(path, { throwIfNoEntry: false, bigint: true }); // Make sure it's a valid file if (stat == undefined || stat.isDirectory()) { shared.errorResponse(response, 404, 'The requested URL was not found on this server.'); return; } // Upload the file to the client response.setHeader('Content-Type', mime.getType(path)); response.setHeader('Content-Length', stat.size); fs.createReadStream(path).pipe(response); });
问题背景
这段代码正常运行多年,近期突然失效:文件传输立即停止且连接关闭。排查发现移除Content-Length头后传输恢复正常。想确认:
- 是否操作有误?
- 不是应该设置Content-Length吗?当前无压缩操作,该头值应为正确值。
- 这是Node.js的bug吗?
问题分析与解答
1. 操作确实存在潜在问题
你代码里的stat.size是BigInt类型——因为调用fs.statSync时指定了bigint: true选项。而HTTP协议要求Content-Length头的值必须是字符串或普通数字,直接将BigInt传给response.setHeader,在新版本Node.js中会触发响应头格式错误,导致客户端直接关闭连接。
2. 为什么之前正常现在失效?
旧版Node.js可能会自动将BigInt隐式转换为数字或字符串,兼容了这个问题。但后续Node.js版本(比如v16及之后的部分更新)对HTTP头的类型校验变得更严格,BigInt无法被正确序列化为合法的HTTP头值,因此触发了传输中断。
3. 不是Node.js的bug
这是类型不匹配导致的问题,而非Node.js本身的bug。正确的做法是将BigInt类型的stat.size转换为字符串或普通数字后再设置头:
// 推荐转为字符串(避免大文件超出Number范围) response.setHeader('Content-Length', stat.size.toString()); // 若文件大小在Number安全范围内(小于2^53),也可以用 // response.setHeader('Content-Length', Number(stat.size));
4. 移除Content-Length后为什么正常?
当不手动设置Content-Length时,Node.js会自动切换为分块传输编码(Chunked Transfer Encoding),这种模式不需要提前指定文件总长度,而是分块发送数据,因此不会触发头格式错误的问题,传输自然恢复正常。
内容的提问来源于stack exchange,提问作者DaedalusAlpha
相关产品推荐
相关产品推荐

