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

Node.js报错ERR_HTTP_HEADERS_SENT问题求助

Node.js 端频繁崩溃:ERR_HTTP_HEADERS_SENT 错误

错误日志

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:406:5)        
    at ServerResponse.setHeader (node:_http_outgoing:652:11)
    at ServerResponse.header (C:\Users\devgarg\Downloads\Projects\React Projects\CENACLE\server\node_modules\express\lib\response.js:776:10)
    at ServerResponse.json (C:\Users\devgarg\Downloads\Projects\React Projects\CENACLE\server\node_modules\express\lib\response.js:264:10)
    at PassThrough.<anonymous> (file:///C:/Users/devgarg/Downloads/Projects/React%20Projects/CENACLE/server/controller/post.js:32:17)
    at PassThrough.emit (node:events:526:35)
    at PassThrough.emit (node:domain:488:12)
    at Request.<anonymous> (C:\Users\devgarg\Downloads\Projects\React Projects\CENACLE\server\node_modules\aws-sdk\lib\request.js:590:14)
    at Request.callListeners (C:\Users\devgarg\Downloads\Projects\React Projects\CENACLE\server\node_modules\aws-sdk\lib\sequential_executor.js:106:20)
    at Request.emit (C:\Users\devgarg\Downloads\Projects\React Projects\CENACLE\server\node_modules\aws-sdk\lib\sequential_executor.js:78:10) {
  code: 'ERR_HTTP_HEADERS_SENT',
  time: 2024-04-24T17:46:46.189Z
}

Node.js v20.9.0

问题背景

错误指向 server/controller/post.js 第32行,该函数负责从AWS S3存储桶获取图片并通过流返回给客户端。已尝试终止重复请求、添加错误处理逻辑,但问题仍未解决。

相关代码

let readStreams = {};

export const getPost = (req, res) => {
    const Key = req.path.split('/')[1];

    // 终止同Key的现有流
    if (readStreams[Key]) {
        readStreams[Key].destroy();
    }

    readStreams[Key] = getFile(Key);

    // 标记响应是否已发送
    let responseSent = false;

    // 错误处理
    readStreams[Key].on("error", (err) => {
        if (!responseSent) {
            res.json({ error: "读取流时发生错误" });
            responseSent = true;
        }
    });

    // 将流pipe到响应
    readStreams[Key].pipe(res);

    // 流结束后关闭并清理
    readStreams[Key].on("end", () => {
        if (!responseSent) {
            readStreams[Key].destroy();
            responseSent = true;
        }
        delete readStreams[Key];
    });
}

问题分析

这个错误的核心是在已经向客户端发送响应后,尝试再次设置HTTP响应头,具体原因:

  1. 执行 stream.pipe(res) 后,响应头已发送给客户端,此时若流触发 error 事件,调用 res.json() 会再次尝试发送头信息,导致冲突。
  2. 全局对象 readStreams 管理不严谨,多个请求同时操作同一个Key时,易出现流状态混乱(比如旧流未完全销毁就被覆盖)。
  3. end 事件中调用 destroy() 冗余,流结束后会自动销毁,此时响应已完成,设置 responseSent 无实际意义。

解决方案

修改代码优化流管理和错误处理逻辑,避免重复发送响应头:

const readStreams = new Map(); // 用Map替代普通对象,避免属性冲突和类型问题

export const getPost = (req, res) => {
    const Key = req.path.split('/')[1];

    // 终止同Key的现有流并清理
    if (readStreams.has(Key)) {
        const existingStream = readStreams.get(Key);
        if (!existingStream.destroyed) {
            existingStream.destroy();
        }
        readStreams.delete(Key);
    }

    const stream = getFile(Key);
    readStreams.set(Key, stream);

    // 错误处理:仅在响应头未发送时设置状态码并结束响应
    stream.on("error", () => {
        if (!res.headersSent) {
            res.status(500).end("读取图片时发生错误");
        }
        // 仅清理当前请求对应的流记录
        if (readStreams.get(Key) === stream) {
            readStreams.delete(Key);
        }
    });

    // 流结束时清理记录
    stream.on("end", () => {
        if (readStreams.get(Key) === stream) {
            readStreams.delete(Key);
        }
    });

    // 客户端断开连接时销毁流,避免内存泄漏
    res.on("finish", () => {
        if (!stream.destroyed) {
            stream.destroy();
        }
        readStreams.delete(Key);
    });

    // 将流pipe到响应
    stream.pipe(res);
};

修改说明

  • 改用 Map 存储流,比普通对象更安全,避免Key的隐式类型转换和意外属性覆盖
  • 错误处理时先检查 res.headersSent,确保仅在响应未发送时修改状态,避免触发头信息重复发送错误
  • 移除冗余的 responseSent 标志,直接用Node.js原生的 res.headersSent 判断响应状态
  • 添加 res.finish 事件监听,客户端断开连接时主动销毁流,避免内存泄漏
  • 流结束或销毁时,严格匹配当前流再清理记录,避免误删其他请求的流

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:45:12