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

Node.js CRUD API出现ERR_STREAM_WRITE_AFTER_END错误求助

解决Node.js API中POST/PUT/DELETE触发write after end错误的问题

先直接点出核心:这个错误说明你的response对象在调用resp.end()之后,又被尝试写入内容了。结合你的代码和日志,我帮你拆解问题根源和具体解决办法:

问题根源分析

从你的日志输出能看到关键时序:

Before send
Before Write
Write Head
Write body
end
After send
events.js:174
      throw er; // Unhandled 'error' event

明明已经执行了resp.end(),但后续又触发了一次resp.write()操作。具体有几个触发点:

  1. 错误处理时重复发送响应:你的try/catch块和异步数据库回调里都有错误处理(msg.show500),如果数据库操作出错,可能已经发送过一次错误响应,后续又执行了正常路径的sendJSON,导致重复写入。
  2. 请求体过大时未终止后续逻辑:在req.on("data")里触发show413后,没有终止请求的后续处理,req.on("end")依然会执行,继续调用staff.add/update,再次尝试发送响应。
  3. 异步回调未及时终止:数据库操作的回调里,出错后没有用return终止后续代码,可能导致正常响应逻辑也被执行。

具体解决方案

1. 给所有响应方法加「防重复发送」校验

Node.js的ServerResponse对象自带headersSent属性,可以用来检查是否已经发送过响应头。修改你的httpMsgs.js里所有发送响应的方法:

exports.sendJSON = function(req, resp, data){
    if(resp.headersSent) return; // 已发送过响应则直接返回
    if(data){
        console.log('Before Write')
        resp.writeHead(200, {"Content-type" : "application/json"});
        console.log('Write Head')
        resp.write(JSON.stringify(data));
        console.log('Write body')
        resp.end();
        console.log('end');
    }
}

// 同样修改show500、show413、show404
exports.show500 = function(req, resp, err){
    if(resp.headersSent) return;
    resp.writeHead(500, {"Content-type" : "application/json"});
    resp.write(JSON.stringify({error: err.message}));
    resp.end();
}

exports.show413 = function(req, resp){
    if(resp.headersSent) return;
    resp.writeHead(413, {"Content-type" : "application/json"});
    resp.write(JSON.stringify({error: "Request entity too large"}));
    resp.end();
}

exports.show404 = function(req, resp){
    if(resp.headersSent) return;
    resp.writeHead(404, {"Content-type" : "application/json"});
    resp.write(JSON.stringify({error: "Resource not found"}));
    resp.end();
}

这一步能从根源上避免重复写入响应。

2. 请求体过大时终止后续请求处理

当触发show413后,不仅要返回响应,还要停止接收请求数据、阻止end事件触发。修改controller.js里的POST/PUT逻辑:

case "POST":
    if (req.url === "/staff/add"){
        var reqBody = '';
        // 保存data事件回调的引用,方便后续移除
        const onData = function (data){
            reqBody += data;
            if(reqBody.length > 1e7){
                msg.show413(req, resp);
                req.removeListener("data", onData); // 停止接收数据
                req.destroy(); // 终止请求,阻止end事件触发
            }
        };
        req.on("data", onData);
        req.on("end", function() {
            if(resp.headersSent) return; // 已发送响应则跳过后续逻辑
            staff.add(req, resp, reqBody)
        });
    }
    break;

PUT部分做同样的修改,这样就不会在返回413后还执行后续的数据库操作。

3. 异步回调里加终止逻辑

在staff.js的数据库回调中,出错后要及时用return终止后续代码,避免正常响应逻辑被误执行:

db.executeSql("SELECT MAX(ID) AS ID FROM jarvisdb.staff",function (maxID, err){
    if(err){
        msg.show500(req, resp, err);
        return; // 终止后续代码,避免执行正常逻辑
    } 
    // 后续正常插入逻辑
});

// 插入数据的回调也要加
db.executeSql(sql, (data, err)=>{
    if(err){
        msg.show500(req, resp, err);
        return;
    } 
    msg.sendJSON(req,resp,data)
})

4. 小细节修正

你在POST的end事件里打印了console.log(req.data),但req.data并没有被赋值,建议去掉或者修正,避免混淆日志。

为什么GET接口正常?

因为GET接口没有处理请求体的异步data/end事件,逻辑是同步触发的,响应只会发送一次,不会出现重复写入的情况。而POST/PUT需要异步接收请求体+异步数据库操作,很容易出现多次调用响应方法的情况。

按上面的步骤修改后,应该就能彻底解决write after end的问题了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:08:48