Server-Sent Event Streamを作りました(Stream2ハッカソンに行ってきた!)

Server-Sent Event Stream というのを作りました。

yosuke-furukawa/sse_stream · GitHub

Server-Sent Event とは

long pollingを利用してサーバー - クライアント間の通信を行う仕組みです。
WebSocketとは違ってサーバーからクライアントへのブロードキャストにしか使えませんが、これまでのHTTPの枠組みの中で動くので、
WebSocketが使えない環境でも動作できるというメリットがあります。

Server-Sent Event Streamとは

その特性を生かして、作ったStream拡張です。
ファイル監視をして変更をリアルタイムにブラウザに通知することやコマンドの実行結果をリアルタイムにブラウザに通知することができます。

インストール
$ npm install -g sse_stream
demo

TwitterのStream APIをそのままブラウザにpushするサンプル

$ sse_server --cmd "wget --output-document=- --user=XXXXXXX --password=XXXXXX https://stream.twitter.com/1/statuses/sample.json"

これをブラウザで http://localhost:3001/ とかで見れば流れているのが見れます。

他にもファイル監視して読み込む場合はこんな感じ。

$ sse_server --file "xxxx.log"

tail してるみたいな感じで結果がブラウザに流せます。
もちろん tail も使えますが。

$ sse_server --cmd "tail -f xxxx.log"


ポート番号を変えたければこう。

$ sse_server --file "xxxx.log" --port 8080

Server-Sent Event Streamを組み込んで利用する場合

Server Side

var http = require("http");
var SSEStream = require("sse_stream");
var fs = require("fs");

http.createServer(function(req, res){
  if (req.headers["accept"] === "text/event-stream") {
    var sse_stream = new SSEStream();
    var exec = require('child_process').exec;
    var child = exec("tail -f test.log");
    res.writeHead(200, {
    'Content-type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
    });
    child.stdout.pipe(sse_stream).pipe(res);
    sse_stream.on("end", function() {
      res.end();
    });
    readStream.on("end", function() {
      res.removeAllListeners();
    });
    sse_stream.on("error", function(error) {
      console.error("error " + error);
    });
    readStream.on("error", function(error) {
      console.error("error " + error);
    });
  } else {
    fs.createReadStream("index.html").pipe(res);
  }
}).listen(8080);

Client Side

<!DOCTYPE html>
<html>
  <head>
    <title>Server Sent Events</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
    <script>
      $(function() {
        var source = new EventSource("/");
        source.onmessage =  function(event) {
          $("p").append(event.data);
          $('html, body').animate({
            scrollTop: $(document).height()
          }, 100);
        };
      });
    </script>
  </head>
  <body>
    <p></p>
  </body>
</html>

こんな感じでサーバーに組み込んで利用することも可能です。

実はこれ、Stream2ハッカソンで作った奴なんです。

先週の土曜日に開催されたStream2ハッカソンで作りました。

Stream2ハッカソンでは、最初の2時間ほど、Stream2の講義があり、概要を理解した所でハッカソンという形式でした。
Stream2の詳しい話は Stream2の基本 に記載されています。

これ、Streamを理解する上で抑えておいたほうがいい話が多いです。課題もあり、非常に勉強になることが多い会でした。

今回のsse_streamもTransformStreamを基に作りました。StreamがわかるとNode.jsの使用できるレベルが一段階上に上がる気がします。

実装内容

sse_streamそのものは至極単純で数行しかありません。

var Transform = require('stream').Transform;
var util = require('util');
util.inherits(SSEStream, Transform);

function SSEStream(option) {
  Transform.call(this, option);
  this.id = 0;
  this.retry = (option && option.retry) || 0;
}

SSEStream.prototype._transform = function(chunk, encoding, cb) {
  var data = chunk.toString();
  if (data) {
    // ここが肝。 受け取ったデータをServer Sent Eventに加工してる。
    this.push("id:" + this.id + "\n" +
              "data:"+ data + "\n\n" +
              "retry: " + this.retry);
  }
  this.id++;
  cb();
};

module.exports = SSEStream;

このStreamの形式に乗っかって書くだけで後はpipeでつなぐだけの簡単なお仕事。

これだけで標準入力から標準出力にSSE加工したものを流せる。

  var SSEStream = require("sse_stream");
  var sse_stream = new SSEStream();
  process.stdin.pipe(sse_stream).pipe(process.stdout);

まとめ

  • Server-Sent Event Stream というモジュールを作り、リリースしました。
  • コレ、使えば手軽にコマンドライン結果やファイルの監視結果を共有出来ます。
  • Stream2ハッカソンは楽しかった、ちなみに大津さんの資料はmust read。