HerokuでSSE使ったら途中で切れることがあるなーと思ったら単なるリクエストタイムアウトだった。
ロングポーリング系は30秒最初のレスポンスがないか、途中で55秒何も送らなかっらコネクション切られるらしい。
SSEではコロンで始まる行はコメントとみなされるので、これをタイムアウト対策で定期的におくるのがよい。
例えばPOSTされたデータを定期的に通知するようなNode.jsのサーバーをこんな感じで書いてみた。
app.js
var express = require('express');
var http = require('http');
var app = express();
var EventEmitter = require('events').EventEmitter;
var ev = new EventEmitter();
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.get('/stream', function(req, res) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'no-cache');
// 55秒のタイムアウト対策
var timer = setInterval(function() {
res.write(':\n\n');
}, 50000);
// 最初の30秒のタイムアウト対策
res.write(':\n\n');
ev.on('data', function(data) {
res.write('data: ' + data + '\n\n');
});
req.on('close', function() {
clearTimeout(timer);
});
});
app.post('/stream', function(req, res) {
ev.emit('data', JSON.stringify(req.body));
res.end();
});
var port = process.env.PORT || 3000;
http.createServer(app).listen(port, function() {
console.log('Express server listening on port ' + port);
});
このサーバー起動してクライアント側をこんな感じで書いたら
public/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>SSE Sample</title>
</head>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script>
var es = new EventSource('/stream');
es.onmessage = function(event) {
$('<div>').text(event.data).appendTo('body');
};
</script>
</body>
</html>
HTTPでPOSTしたらブラウザに表示されるはず。
$ curl -d 'foo=bar' http://localhost:3000/stream
ちなみにSSEの仕様に、Proxyのタイムアウトを防ぐためにコメント使うといいよって書いてあるので間違った使い方ではない。