Posted at

Node.js のsocket.ioでCan't set headers after they are sent

More than 1 year has passed since last update.

Node.jsでsocket.ioで「Can't set headers after they are sent」のエラーで詰まった。根本的なところが曖昧だけれども、とりあえず記載。

nodeのバージョンはv6.11.0

socket.ioのバージョンは2.0.3


事象

エントリーポイントとなる、sever.jsにsocket.ioを使う処理を追加した。

この状態で、「http:localhost:6677/controller」にブラウザでアクセスすると、「Can't set headers after they are sent」でエラーになる。

■server.js

var http = require("http");

var server = http.createServer();

//urlディスパッチャに使う
var url = require("url");

//htmlファイルの読み込みに使う
var fs = require("fs");

//socket.ioで使う
var io = require("socket.io")(server);

server.on("request",function(req, res){
console.log("リクエストがあったよ");

var incomingUrl = url.parse(req.url);

console.log(incomingUrl.pathname);

if(incomingUrl.pathname === "/controller"){

fs.readFile("./client/controller.html","utf-8",(err,data)=> {

if (err) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.write('<head><meta charset="UTF-8"></head>');
res.end("<h1>Not Found</h1>");
}else{
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
}
});

}else{
res.writeHead(404,{'Content-Type':'text/html'});
res.write('<head><meta charset="UTF-8"></head>');
res.end("<h1>Not Found</h1>");
}

});

//socket確認用のコード
io.sockets.on("connection",function (socket) {
console.log("socket connection");

socket.on("sendMessage",function (data) {
socket.emit('test',data);
});

});

server.listen(6677);


原因

下記処理をserver.on("request",function(){})より前に行っているから。

//socket.ioで使う

var io = require("socket.io")(server);


根本的原因

「Can't set headers after they are sent」

上記エラーは、リクエスト処理に対して応答して結果を返しているのに、もっかい結果を返しちゃだめだよというもの。


「http:localhost:6677/controller」にアクセスした場合、レスポンスはfs.readFile以下の処理しか動かないはずで、res.end()後にもっかいres.write()するようなことはないのにどうしてだろうと詰まった。

コンソールに吐き出している、console.log(incomingUrl.pathname)の結果をみると、以下の内容が出力されていることが確認できた。

リクエストがあったよ

/controller
リクエストがあったよ
/socket.io/socket.io.js
リクエストがあったよ
/socket.io/socket.io.js.map

「http:localhost:6677/controller」にアクセスした際に返却するcontroller.htmlでは、以下のようにsocket.io.jsを読み込むようにしている。

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="UTF-8">
<title>レゴカーコントローラー</title>
</head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<!--socket.ioを使うため-->
<script src="/socket.io/socket.io.js"></script>

問題のコードは以下の挙動をする。第4項の動きはまずそうな気がする。


  1. ブラウザから「http:localhost:6677/controller」にアクセス

  2. server.jsの「server.on("request",xxxx)の処理が呼ばれて、controller.htmlをクライアント側に返却

  3. クライアント側でcontroller.htmlを解析して、src="/socket.io/~"のjsファイルをサーバーに要求

  4. server.jsの「server.on("request",xxxx)の処理が呼ばれるが、urlがsocket.ioなので該当するものがなく、Not Foundで返す。

ただ、var io = require("socket.io")(server) の処理順を変更することで、第4項の処理が呼ばれなくなる理由がいまいちわからない。