Ошибка: невозможно установить заголовки после их отправки клиенту - PullRequest
575 голосов
/ 12 августа 2011

Я довольно новичок в 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);

Могу ли я узнать, что не так с моим кодом?

Ответы [ 23 ]

914 голосов
/ 17 августа 2011

Объект res в Express является подклассом Node.js's http.ServerResponse ( читайте источник http.js ).Вам разрешается звонить res.setHeader(name, value) так часто, как вы хотите, пока вы не позвоните res.writeHead(statusCode).После writeHead заголовки запекаются, и вы можете вызвать только res.write(data), и, наконец, res.end(data).

Ошибка «Ошибка: невозможно установить заголовки после их отправки».означает, что вы уже в состоянии Body или Finished, но некоторые функции пытались установить заголовок или statusCode.Когда вы видите эту ошибку, попробуйте найти все, что пытается отправить заголовок после того, как часть тела уже написана.Например, поищите обратные вызовы, которые были случайно вызваны дважды, или любую ошибку, которая произошла после отправки тела.

В вашем случае вы позвонили res.redirect(), что привело к завершению ответа.Затем ваш код выдал ошибку (res.req - это null).и так как ошибка произошла в вашем фактическом function(req, res, next) (не в обратном вызове), Connect смог ее перехватить и попытался отправить страницу с ошибкой 500.Но поскольку заголовки уже были отправлены, Node.js setHeader выдал ошибку, которую вы видели.

Полный список методов ответа Node.js / Express и когда они должны быть вызваны:

Ответ должен быть в Голова и остается в Голова :

  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]) (только Express)
  7. res.charset = 'utf-8' (только Express; влияет только на методы, специфичные для Express)
  8. res.contentType(type) (только экспресс)

Ответ должен быть в Голова и становится Кузов :

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

Ответ может быть либо Голова / Тело и остается в Тело :

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

Ответ может быть либо Голова / Тело и становится Завершено :

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

Ответ может быть либо Голова / Тело и остается в его текущем состоянии:

  1. res.addTrailers(headers)

Ответ должен быть в Голова и становится Завершено :

  1. return next([err]) (только подключение / экспресс)
  2. Любойисключения в промежуточном программном обеспечении function(req, res, next) (только подключение / экспресс)
  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]) (только экспресс)
87 голосов
/ 17 октября 2011

Я тоже столкнулся с этой ошибкой некоторое время. Я думаю (надеюсь), что я обернул голову вокруг этого, хотел написать это здесь для справки.

Когда вы добавляете промежуточное ПО в connect или express (который построен на соединении) с использованием метода app.use, вы добавляете элементы к Server.prototype.stack в connect (At по крайней мере, с текущим npm install connect, который выглядит совсем не так, как один github на этот пост). Когда сервер получает запрос, он выполняет итерацию по стеку, вызывая метод (request, response, next).

Проблема в том, что если в одном из элементов промежуточного программного обеспечения выполняется запись в тело или заголовки ответа (похоже, это либо / или по какой-то причине), , но не вызывается response.end() и вы вызываете next() затем, когда основной метод Server.prototype.handle завершится, он заметит, что:

  1. в стеке больше нет предметов и / или
  2. что response.headerSent верно.

Итак, выдает ошибку. Но ошибка, которую он выдает - это просто базовый ответ (из исходного кода connect 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(), что приводит к путанице на сервере подключения.

42 голосов
/ 14 февраля 2015

У меня была такая же проблема, и я понял, что это потому, что я звонил 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();
};
35 голосов
/ 12 августа 2011

Многие люди попали в эту ошибку. Это путает с асинхронной обработкой. Скорее всего, часть вашего кода устанавливает заголовки в первом тике, а затем вы запускаете асинхронный обратный вызов в будущем. Между ними отправляется заголовок ответа, но затем дополнительные заголовки (например, перенаправление 30X) пытаются добавить дополнительные заголовки, но уже слишком поздно, поскольку заголовок ответа уже был передан.

Я точно не знаю, что является причиной вашей ошибки, но посмотрим на любые обратные вызовы как на возможные области для расследования.

Один простой совет, чтобы упростить ваш код. Избавьтесь от app.configure() и просто позвоните app.use прямо в область вашего верхнего уровня.

См. Также модуль everyauth , который используется в Facebook и дюжине других сторонних поставщиков аутентификации.

31 голосов
/ 06 января 2018

Некоторые ответы в этом вопросе и ответах неверны.Принятый ответ также не очень «практичен», поэтому я хочу опубликовать ответ, который объясняет вещи в более простых терминах.Мой ответ покроет 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 Невозможно установить заголовки после их отправки. Будет выдано сообщение об ошибке, поскольку 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() в другом можно записать как res.json({ article: doc1, user: doc2 }).

14 голосов
/ 04 июля 2015

Я вскинул голову над этой проблемой, и это произошло из-за неосторожной ошибки при обработке обратных вызовов.невозвратные обратные вызовы приводят к тому, что ответ устанавливается дважды.!

В моей программе был код, который проверяет запрос и запрашивает базу данных.после проверки, если есть ошибка, я перезванивал index.js с ошибками проверки.И если проверка проходит успешно, она идет вперед и поражает БД с успехом / неудачей.

    var error = validateRequestDetails("create",queryReq);
    if (error)
        callback(error, null);
   else
    some code 
    callback(null, success);

Что происходило: валидация Incase завершается неудачно, вызывается обратный вызов и устанавливается ответ.Но не вернулся.Таким образом, он все еще продолжается, метод переходит к БД и поражает успех / неудачу.Он снова вызывает тот же обратный вызов, в результате чего ответ будет установлен дважды.

Таким образом, решение простое, вам нужно «вернуть» обратный вызов, чтобы метод не продолжал выполняться после возникновения ошибки, и, следовательно, установить объект ответа один раз

  var error = validateRequestDetails("create",queryReq);
    if (error)
        callback(error, null);
        return;
    else
       some code 
       callback(null, success);
13 голосов
/ 28 сентября 2016

Этот тип ошибки вы получите, когда будете передавать заявления после отправки ответа.

Например:

res.send("something response");
console.log("jhgfjhgsdhgfsdf");
console.log("sdgsdfhdgfdhgsdf");
res.send("sopmething response");

В результате вы увидите ошибку, потому что после отправки ответа следующее res.send не будет выполнено.

Если вы хотите что-то сделать, вы должны сделать это до отправки ответа.

5 голосов
/ 08 марта 2017

Иногда вы можете получить эту ошибку при попытке вызвать функцию next () после res.end или res.send , попробуйте удалить, если у вас есть next () после res .send или res.end в вашей функции. Примечание: здесь next () означает, что после ответа клиенту своим ответом ( т.е. res.send или res.end ) вы все еще пытаетесь выполнить некоторый код для повторного ответа, поэтому это недопустимо.

Пример:

router.get('/',function (req,res,next){
     res.send("request received");
     next(); // this will give you the above exception 
});

уберите next() из вышеуказанной функции, и она будет работать.

3 голосов
/ 02 июня 2015

В моем случае это произошло с React и postal.js, когда я не отписался от канала в обратном вызове componentWillUnmount моего компонента React.

3 голосов
/ 17 мая 2014

В моем случае проблема была вызвана ответом 304 (кэшированием).

Самое простое решение:

app.disable('etag');

Альтернативное решение здесь, если вы хотите больше контроля:

http://vlasenko.org/2011/10/12/expressconnect-static-set-last-modified-to-now-to-avoid-304-not-modified/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...