HTML5のServer-Sent Eventsの仕様は、HTTP通信を使ってサーバサイドからのデータプッシュを行うCometを、JavaScriptのAPIを定義するなどして使いやすくまとめたものらしい。クライアント側のAPIはSafari5やChrome8では既に実装されているそうなので試してみた。
サーバ側は最近流行のnode.jsで実装してみた。Server-Sent Eventsのサーバ側はCGIやPHPでも実装可能だが、Apacheのようなマルチスレッドでリクエストを処理するタイプのサーバでは、ロングポーリングで停止しているだけの、ほとんど仕事しないスレッドが沢山作られてリソースが無駄になり非効率的だ。ノンブロッキングIOでシングルスレッドでイベント駆動型のnode.jsならば効率的なはず。
サンプル。gitでソースを落として node server.js して http://localhost:8124/ に接続
$ git clone git://github.com/paulownia/sse-sample.git $ cd sse $ node server.js
クライアントサイドのコードは非常に簡単。
var source = new EventSource(url); source.onmessage = function(event) { // データ受信時の処理を書く // event.data でサーバから送られてきたデータを取得できる }
次はサーバサイド。イベント処理を行うgeneratorというモジュールを作成。独自のイベント処理を行うにはEventEmitterというnode.jsの組み込みオブジェクトを拡張すればいい。exportsとrequireを使ったモジュールの定義と呼び出し方法はCommonJSという仕様で決まっているらしい。
// EventEmitterを取得 var events = require("events"); var emitter = new events.EventEmitter(); // EventEmitterを拡張 emitter.id = 0; emitter.data = ""; emitter.start = function() { var chars = "node.js"; var self = this; setInterval(function() { var pos = Math.floor(Math.random() * chars.length * 2); if (pos < chars.length) { self.id++; self.data = chars.charAt(pos); // generatedイベントとして登録されたリスナを呼び出す self.emit("generated", self.id, self.data); } }, 1000); }; // module.exportsに設定されたオブジェクトがrequireの戻り値となる。 module.exports = emitter;
サーバはリクエストを受け付けてもレスポンスをすぐに返さずに、generatorにイベントリスナを登録して終了。イベントリスナでレスポンス処理を行う。これでgeneratorのイベントが発生するまでロングポーリングされるようになる。
var http = require("http") http.createServer(handler).listen(8124); var generator = require("./generator") generator.start(); function handler(req, res) { function response(id, data) { generator.removeListener("generated", arguments.callee); res.writeHead(200, { "Content-type": "text/event-stream" }); res.write("id:" + id + "\n"); res.write("data:" + data + "\n"); res.write("retry: 1000"); res.end(); } generator.on("generated", response); }
というわけでnode.jsならばServer Sent Eventsのサーバサイドの実装も簡単だ。