【问题标题】:Best way to debug 'Can't set headers after they are sent' error in Express / Node.js?在 Express / Node.js 中调试“发送后无法设置标头”错误的最佳方法?
【发布时间】:2023-03-12 10:25:01
【问题描述】:

我正在尝试调试 node.js Express 应用程序中可怕的 Can't set headers after they are sent 错误。具体来说,我使用knox 库与s3 对话。

大致来说,我有这个 Express 处理程序,使用 knox s3client 的全局实例:

function foo(req, res) {
    //Region A
    var s3req = global.s3client.get('foo').on('response', function(s3res){
        //Region B
        res.set('content-length', s3res.headers['content-length']); //This will fail
        s3res.on('data', function(chunk){
            res.write(chunk);
        });
    });
    //Region C
    s3req.end();
}

如果我在区域 A 的 res 上设置任何标题或状态代码,一切正常。如果我在区域 B 中尝试任何这些,我会收到“发送后无法设置标头”错误。请注意,我想在res 上设置标题,而不是s3res

大概response.writeHead 在调用 knox s3client 的响应回调之前被调用或触发。是否有一些调试标志或其他方法可以让 Node 在调用 writeHead 时/在何处吐出?节点 0.10 添加了一个response.headersSent,但是通过检查这个标志来确定它被调用的位置将非常难以填充我的代码和所有 3rd 方库。还是有其他方法可以解决这个问题?

【问题讨论】:

  • 我前段时间遇到过这个。我可以建议先尝试不同版本的 express,这意味着升级或降级你的 express.js。
  • 我已经尝试过 Express 3.1 和 3.2.1。我也试过节点 0.10.3 和节点 0.10.5

标签: node.js express


【解决方案1】:

您可以尝试包含一个简单的中间件,在调用writeHead 时将堆栈跟踪转储到标准输出:

app.use(function(req, res, next) {
  res.on('header', function() {
    console.trace('HEADERS GOING TO BE WRITTEN');
  });
  next();
});

您必须在可能触发您的问题的路由/中间件之前插入它。

FWIW,我猜这个问题是由 C 区发生的事情触发的(res.sendres.endres.jsonres.render 在那里被调用):

function foo(req, res) {
    //Region A
    var s3req = global.s3client.get('foo').on('response', function(s3res){
        //Region B
        res.set('content-length', s3res.headers['content-length']);
    }); 
    //Region C
}

编辑如果s3res 是正确的流,你可以试试这个:

function foo(req, res) {
  global.s3client.get('foo').on('response', function(s3res) {
    s3res.pipe(res);
  }).end();
}

但请注意,S3 请求返回的 所有 标头都将传递给 Express 响应。

【讨论】:

  • 感谢您的回复。我用更多细节编辑了问题。确实在 C 区调用 s3req.end() (请参阅更新的问题)。但是删除它会得到Error: socket hang up。如果我只想将 s3res 流式传输回 foo 的 res,并设置 content-type 以及 content-length 标头,这里最好的解决方案是什么?
  • s3req.end() 应该不是问题,这不是 Express 的一部分。我想这取决于您调用foo() 的上下文,尽管(正如@wayne 已经建议的那样)我似乎还记得Node 的一些问题,这些问题会无缘无故地产生这个问题。
  • foo 只是我的 api 方法之一,注册为快速获取处理程序。该方法的高级目的是返回私有 s3 存储桶的内容(顺便说一句,我意识到我可以为此使用过期的已签名 S3 url。但现在我需要保持与使用 s3 之前的向后兼容性)。是否有更简单的方法将 s3res 代理/附加回 res(API 方法的调用者看到的响应)?如您所见,像我一样“手动”执行此操作很容易出错。
  • 查看我的编辑。不确定它是否会起作用,但值得一试:)
  • 标记为已接受。你所有的建议都是正确的。您建议记录写入头的“中间件”工作,并指向我的代码中的一个点,该点在假定的错误情况下在流上调用 end()。正如您所怀疑的那样,它在区域 C 中“有效”(尽管实际上由于我简化了代码而有点复杂)。您的管道建议也很有价值,我什至可以在调用管道到管道 s3res 到 res 之前设置自己的标头,这会显示在实际响应中。所以一切又好了 - 非常感谢!
【解决方案2】:

我不确定它是否适用于此,但是当我看到错误时我立即想到的是“模型层”某处的“回调逻辑”中的一个简单非常简单的错误。在两个不同的情况下,我一直对此感到头疼,在这两种情况下,结果都是我两次调用了回调(即最终会调用响应写入回调的回调)。这只会在我的模型层出现错误时发生,因为它的代码如下:

if (err) cb(err) // note the missing "return" here
cb(null,result) // alternatively, also no "else"

如果我现在遇到这样的错误,我要做的第一件事就是检查是否为任何操作调用了两次响应写入回调。只需两个 console.log 语句,一个在区域 A(启动 Knox 请求的范围)和一个在区域 C(响应写入回调)就足以至少消除这种可能性。

原则上,您的res 对象应该受到很好的保护。只有 connect / express 中间件可以访问它,当然还有这个特定的请求处理程序。所以只有有限数量的地方可以写标题。

【讨论】:

  • 我也遇到了同样的问题,显然比这更复杂,但这就是发生的事情!
  • 没错,后来的 express 版本似乎不太能容忍这个错误。
猜你喜欢
  • 1970-01-01
  • 2012-07-25
  • 2018-05-05
  • 2016-12-19
  • 1970-01-01
  • 1970-01-01
  • 2017-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多