错误:发送到客户端后无法设置标头

IT技术 javascript node.js express
2020-12-27 16:25:31

我对 Node.js 还很陌生,但遇到了一些问题。

我使用的是 Node.js 4.10 和 Express 2.4.3。

当我尝试访问http://127.0.0.1:8888/auth/facebook 时,我将被重定向到http://127.0.0.1:8888/auth/facebook_callback

然后我收到以下错误:

Error: Can't render headers after they are sent to the client.
    at ServerResponse.<anonymous> (http.js:573:11)
    at ServerResponse._renderHeaders (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:64:25)
    at ServerResponse.writeHead (http.js:813:20)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/auth.strategies/facebook.js:28:15
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:113:13
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/strategyExecutor.js:45:39)
    at [object Object].pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:32:3)
    at [object Object].halt (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:29:8)
    at [object Object].redirect (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/authExecutionScope.js:16:8)
    at [object Object].<anonymous> (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/auth.strategies/facebook.js:77:15)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:195:11)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at param (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:189:13)
    at pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:191:10)
    at Object.router [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:197:6)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at param (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:189:13)
    at pass (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:191:10)
    at Object.router [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/router.js:197:6)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at Object.auth [as handle] (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect-auth/lib/index.js:153:7)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at HTTPServer.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:211:3)
    at Object.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:105:14)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:150:23)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at HTTPServer.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:211:3)
    at Object.handle (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:105:14)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:198:15)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:323:9
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:338:9

node.js:134
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
Error: Can't set headers after they are sent.
    at ServerResponse.<anonymous> (http.js:527:11)
    at ServerResponse.setHeader (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/patch.js:50:20)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:162:13)
    at next (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/http.js:207:9)
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:323:9
    at /home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session.js:338:9
    at Array.<anonymous> (/home/eugene/public_html/all_things_node/projects/fb2/node_modules/connect/lib/middleware/session/memory.js:57:7)
    at EventEmitter._tickCallback (node.js:126:26)

以下是我的代码:

var fbId= "XXX";
var fbSecret= "XXXXXX";
var fbCallbackAddress= "http://127.0.0.1:8888/auth/facebook_callback"

var cookieSecret = "node";     // enter a random hash for security

var express= require('express');
var auth = require('connect-auth')
var app = express.createServer();


app.configure(function(){
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.cookieParser());
    app.use(express.session({secret: cookieSecret}));
    app.use(auth([
        auth.Facebook({
            appId : fbId,
            appSecret: fbSecret,
            callback: fbCallbackAddress,
            scope: 'offline_access,email,user_about_me,user_activities,manage_pages,publish_stream',
            failedUri: '/noauth'
        })
    ]));
    app.use(app.router);
});


app.get('/auth/facebook', function(req, res) {
  req.authenticate("facebook", function(error, authenticated) {
    if (authenticated) {
      res.redirect("/great");
      console.log("ok cool.");
      console.log(res['req']['session']);
    }
  });
});

app.get('/noauth', function(req, res) {
  console.log('Authentication Failed');
  res.send('Authentication Failed');
});

app.get('/great', function( req, res) {
  res.send('Supercoolstuff');
});

app.listen(8888);

我可以知道我的代码有什么问题吗?

6个回答

resExpress 中对象是Node.js 的http.ServerResponse子类阅读 http.js 源代码)。res.setHeader(name, value)可以随心所欲地拨打电话,直到您拨打res.writeHead(statusCode)之后writeHead,标题被烘烤,您只能调用res.write(data),最后res.end(data)

错误“错误:发送后无法设置标头。” 意味着您已经处于 Body 或 Finished 状态,但某些函数试图设置标题或 statusCode。当您看到此错误时,请尝试查找在某些正文已写入后尝试发送标头的任何内容。例如,查找意外调用两次的回调,或发送正文后发生的任何错误。

在您的情况下,您调用了res.redirect(),这导致响应变为 Finished。然后你的代码抛出了一个错误(res.reqis null)。并且由于错误发生在您的实际中function(req, res, next)(而不是在回调中),Connect 能够捕获它,然后尝试发送 500 错误页面。但是由于标头已经发送,Node.jssetHeader抛出了您看到的错误。

Node.js/Express 响应方法的完整列表以及必须调用它们的时间:

响应必须在Head 中并保留在Head 中

  1. res.writeContinue()
  2. res.statusCode = 404
  3. res.setHeader(name, value)
  4. res.getHeader(name)
  5. res.removeHeader(name)
  6. res.header(key[, val]) (仅限快递)
  7. res.charset = 'utf-8' (仅限 Express;仅影响 Express 特定的方法)
  8. res.contentType(type) (仅限快递)

响应必须在Head 中并变成Body

  1. res.writeHead(statusCode, [reasonPhrase], [headers])

响应可以在Head/Body 中并保留在Body 中

  1. res.write(chunk, encoding='utf8')

响应可以在Head/Body 中,并变为Finished

  1. res.end([data], [encoding])

响应可以在Head/Body 中并保持其当前状态:

  1. res.addTrailers(headers)

响应必须在Head 中并变为Finished

  1. return next([err]) (仅限连接/快递)
  2. 中间件中的任何异常function(req, res, next)(仅限 Connect/Express)
  3. res.send(body|status[, headers|status[, status]]) (仅限快递)
  4. res.attachment(filename) (仅限快递)
  5. res.sendfile(path[, options[, callback]]) (仅限快递)
  6. res.json(obj[, headers|status[, status]]) (仅限快递)
  7. res.redirect(url[, status]) (仅限快递)
  8. res.cookie(name, val[, options]) (仅限快递)
  9. res.clearCookie(name[, options]) (仅限快递)
  10. res.render(view[, options[, fn]]) (仅限快递)
  11. res.partial(view[, options]) (仅限快递)
快速链接似乎已死
2021-02-08 16:25:31
在回调结束时使用 return 来避免这种情况通常是个好主意
2021-02-15 16:25:31
是的,检查调用 next() 或其他 cb 两次。
2021-03-01 16:25:31
我在我的中间件中犯了一个非常小的错误,我return以前没有next(),谢谢这指出了我的错误!
2021-03-04 16:25:31
还要注意这个经典错误: res.redirect() 不会停止语句执行......所以在它之后返回。否则可能会执行其他代码,这可能会无意中导致著名的标头错误。谢谢你的解释!
2021-03-07 16:25:31

我也遇到了这个错误一段时间。我想(希望)我已经把头围绕在它上面,想把它写在这里以供参考。

当您使用该方法添加中间件以连接表达(建立在连接上)时app.use,您将项目附加到Server.prototype.stack连接中(至少对于当前的npm install connect,它看起来与本文中的一个 github 完全不同)。当服务器收到请求时,它会遍历堆栈,调用该(request, response, next)方法。

问题是,如果在其中一个中间件项目中写入响应正文或标头(它看起来像是/或出于某种原因),但没有调用response.end()next()然后在核心Server.prototype.handle方法完成时调用,它会注意到那:

  1. 堆栈中没有更多项目,和/或
  2. response.headerSent是真实的。

因此,它会引发错误。但它抛出的错误只是这个基本响应(来自连接http.js源代码:

res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Cannot ' + req.method + ' ' + req.url);

就在那里,它正在调用res.setHeader('Content-Type', 'text/plain');,您可能已经在render方法中设置了它而不调用 response.end(),例如:

response.setHeader("Content-Type", "text/html");
response.write("<p>Hello World</p>");

一切都需要结构化的方式是这样的:

好的中间件

// middleware that does not modify the response body
var doesNotModifyBody = function(request, response, next) {
  request.params = {
    a: "b"
  };
  // calls next because it hasn't modified the header
  next();
};

// middleware that modify the response body
var doesModifyBody = function(request, response, next) {
  response.setHeader("Content-Type", "text/html");
  response.write("<p>Hello World</p>");
  response.end();
  // doesn't call next()
};

app.use(doesNotModifyBody);
app.use(doesModifyBody);

有问题的中间件

var problemMiddleware = function(request, response, next) {
  response.setHeader("Content-Type", "text/html");
  response.write("<p>Hello World</p>");
  next();
};

有问题的中间件在没有调用response.end()和调用的情况下设置响应头next(),这会混淆连接的服务器。

你知道我有这个确切的问题,因为你称之为有问题的中间件,但是我需要一个案例,我返回响应但想在单独的控制器中做进一步处理作为链的一部分,我如何去抑制这个错误?
2021-02-14 16:25:31
+1 这是一个很好的解释,但是当您使用 res.redirect() 时呢?当中间件尝试根据某些条件进行重定向时,我经常遇到这个问题。根据您的“好中间件”示例,中间件不应该重定向吗?
2021-02-18 16:25:31

本问答中的一些答案是错误的。接受的答案也不是很“实用”,所以我想发布一个用更简单的术语解释事物的答案。我的回答将涵盖我一遍又一遍地看到的 99% 的错误。对于错误背后的实际原因,请查看已接受的答案。


HTTP 使用一个循环,每个请求需要一个响应。当客户端发送请求(例如 POST 或 GET)时,服务器应该只向它发送一个响应。

此错误消息:

错误:发送后无法设置标头。

通常发生在您为一个请求发送多个响应时。确保每个请求只调用以下函数一次:

  • res.json()
  • res.send()
  • res.redirect()
  • res.render()

(还有一些很少使用的,请检查已接受的答案)

调用这些 res 函数时,路由回调将不会返回。它将继续运行,直到到达函数末尾或返回语句为止。如果你想发送的响应时返回,你能做到这一点,像这样:return res.send()


以这段代码为例:

app.post('/api/route1', function(req, res) {
  console.log('this ran');
  res.status(200).json({ message: 'ok' });
  console.log('this ran too');
  res.status(200).json({ message: 'ok' });
}

当 POST 请求发送到/api/route1 时,它将运行回调中的每一行。A Can't set headers after they're sent错误信息将被抛出,因为res.json()被调用了两次,意味着发送了两个响应。

每个请求只能发送一个响应!


上面代码示例中的错误很明显。一个更典型的问题是当你有几个分支时:

app.get('/api/company/:companyId', function(req, res) {
  const { companyId } = req.params;
  Company.findById(companyId).exec((err, company) => {
      if (err) {
        res.status(500).json(err);
      } else if (!company) {
        res.status(404).json();      // This runs.
      }
      res.status(200).json(company); // This runs as well.
    });
}

带有附加回调的此路由在数据库中查找公司。当对不存在的公司进行查询时,我们将进入else if分支机构并发送 404 响应。之后,我们将继续下一个语句,该语句也发送响应。现在我们已经发送了两个响应,并且会出现错误消息。我们可以通过确保只发送一个响应来修复此代码:

.exec((err, company) => {
  if (err) {
    res.status(500).json(err);
  } else if (!company) {
    res.status(404).json();         // Only this runs.
  } else {
    res.status(200).json(company);
  }
});

或者在发送响应时返回:

.exec((err, company) => {
  if (err) {
    return res.status(500).json(err);
  } else if (!company) {
    return res.status(404).json();  // Only this runs.
  }
  return res.status(200).json(company);
});

一个大罪人是异步函数。这个问题中获取函数,例如:

article.save(function(err, doc1) {
  if (err) {
    res.send(err);
  } else {
    User.findOneAndUpdate({ _id: req.user._id }, { $push: { article: doc._id } })
    .exec(function(err, doc2) {
      if (err) res.send(err);
      else     res.json(doc2);  // Will be called second.
    })

    res.json(doc1);             // Will be called first.
  }
});

这里我们findOneAndUpdate()在代码示例中有一个异步函数 ( )。如果没有错误 ( err)findOneAndUpdate()将被调用。因为这个函数是异步的,所以res.json(doc1)会被立即调用。假设没有错误findOneAndUpdate()res.json(doc2)else随后将被调用。现在已发送两个响应,并且出现无法设置标头错误消息。

在这种情况下,修复方法是删除res.json(doc1). 要将两个文档都发送回客户端,res.json()可以将 else 中的res.json({ article: doc1, user: doc2 }).

你是一个异步函数里面,且必须returnres.json
2021-02-27 16:25:31
我的问题是res.send在 for 循环中使用
2021-03-02 16:25:31
非常感谢米卡的解释。我有 product().findById().then().then().catch()。两个 .then() 方法都有一个 res.status().json 。删除一个解决了在将标题发送到客户端后无法设置标题的问题。
2021-03-04 16:25:31

我遇到了同样的问题并意识到这是因为我在res.redirect没有return声明的情况下调用,所以该next函数也被立即调用:

auth.annonymousOnly = function(req, res, next) {
    if (req.user) res.redirect('/');
    next();
};

应该是:

auth.annonymousOnly = function(req, res, next) {
    if (req.user) return res.redirect('/');
    next();
};
如果您返回,则无法进入 next()。
2021-02-15 16:25:31
只有你的答案是正确的。使用 return 而不是许多狗屎答案我发现你的答案是正确的。
2021-02-15 16:25:31
@corysimmons 我不明白你的意思。目的是在调用next重定向时不调用不打算在返回后调用 next 。
2021-03-09 16:25:31

很多人都遇到了这个错误。这与异步处理令人困惑。很可能您的某些代码在第一个滴答中设置标头,然后您在未来的滴答中运行异步回调。在这中间,响应标头被发送,但随后的更多标头(如 30X 重定向)尝试添加额外的标头,但为时已晚,因为响应标头已经被传输。

我不确定究竟是什么导致了您的错误,但请将任何回调视为需要调查的潜在领域。

简化代码的一个简单技巧。摆脱app.configure()app.use直接在您的顶级范围内调用

另请参阅Everyauthmodule,该module支持 Facebook 和十几个其他 3rd 方身份验证提供商。

呵呵。我想象着连续 30 次重定向之类的
2021-02-15 16:25:31
30X 重定向是 HTTP 响应代码。w3.org/Protocols/rfc2616/rfc2616-sec10.html 代码300-399是重定向的不同变体,302 和 301 通常用于将客户端发送到备用 URL。当您在 node 中执行 response.redirect(...) 时,将在响应中发送 30X 重定向标头。
2021-02-26 16:25:31