EventSourceについて発表してきた

昨日、partake.inで、10分ぐらいでEventSourceの話をしてきました。

発表資料は一応あるのですが、それだけだと全く意味が分からないので、当日話したことをここにまとめておきます。

EventSource とは

HTTP の仕組みを使って、クライアント側でリアルタイムにデータを受信するための仕組みです。
html5 の一部になっていて、 Server-sent Events に仕様が載っています。


EventSource は、技術的には単なるロングポーリングです。
つまりクライアントは HTTP を繋ぎっぱなしにすることで、レスポンスデータをリアルタイムにずっと受信し続けるという仕組みです。


普通の HTTP 通信だと、クライアントがリクエストを送信すると、サーバがレスポンスを返して終わりなのですが、EventSource はレスポンスのデータを途中までしか送信しないので、接続が終わらず、ずっと繋ぎっぱなしになります。


ずっと HTTP を繋ぎっぱなしにしていると、当然いつかタイムアウトで接続が切られてしまうわけですが、EventSource は『切られたら即繋ぎ直す』という仕様があるので、何も考えなくても勝手に再接続してくれます。


どのブラウザでサポートしているのかちゃんと試してないので知りませんが、とりあえず Chrome はちゃんと動きます。
まあでも単なる HTTP 通信なので、やろうと思えば多分 jQuery.ajax あたりを使って実装できますね。

なぜ EventSource を使ったのか

こんなサイトを作るためです。Yesod 製です。

ideone みたいな Web 上でコンパイル/実行する仕組みですが、その結果がリアルタイムで出力されるという点が違います。

WebSocket を使うという手もあったのですが、wai-websockets の使い方が本気で意味分からなかったので、より簡単そうな wai-eventsource を使うことにしました。

Mighty の EventSource 問題

自分は↑のサイトとは別に、もうひとつ、別のサイトを作っています。こっちも当然 Yesod 製です。

こちらは普通のサイトで、EventSource を使ったりといったことはしてません。


で、2つのサイトを別々に作ったのですが、これを両方公開する場合、お互いがHTTPの80番ポートを使ってしまって起動できないことに気が付きました。


これはどうすればいいんだろうと悩んでいたのですが、どうやらこれはリバースプロキシなるものを使うのが一般的らしく、Haskell のリバースプロキシ可能なアプリとして Mighty があるので、これを使えばいいようです。

2つのサイトをそれぞれ 3000 番、4000 番ポートで起動して、 /wandbox に対するアクセスは全部 3000 番に流して、/yesodbookjp に対するアクセスは全部 4000 番ポートに流すようにしました。


これで無事2つのサイトが1つのドメインに入ってくれました。
が、こうすると、なぜか EventSource のデータがちゃんと受信できなくなったのです。


そもそも EventSource のデータは、リアルタイムで細切れに送信するために、ソケットを flush するタイミングというのが明確に決まっています。
つまり EventSource は、空行がある(つまり改行コードが2連続で続く)場合に、ソケットを flush します。


しかし Mighty は当然 EventSource のそんな特殊な仕様なんて知らないので、普通にソケットのバッファが一杯になるか、ソケットが閉じられるまで待ちます。


で、そうなるとリアルタイムで受信することができなくなり、EventSource が動かなくなってしまいます。


ということで、これを解決するため、Mighty のリバースプロキシのコード*2を自分のところに持ってきて、改行コードが2連続で続いた場合は即 flush するというコードを入れました。*3

これでちゃんと EventSource が動きました。めでたしめでたし。


という話を、昨日のYesod勉強会でしました。
この修正、EventSource 専用の修正だから全く別コードにしたのですが、Mighty 作者の kazu-yamamoto さんに Pull Request していいよと言われたので、近々そうしようと思います。*4

*1:まだ開発中なのでイジメコードを送信しないで下さい。イジメカッコワルイ

*2: https://github.com/kazu-yamamoto/wai-app-file-cgi/blob/f3bf3033f679189e26ef497027dda92a059f1e49/Network/Wai/Application/Classic/RevProxy.hs

*3: https://github.com/melpon/melpon.org/blob/95fc0d68f17d8a77af88a0fa1bdca45b9cc235e8/RevProxy.hs#L96

*4:実は今の実装だと問題があって、EventSource は CR, LF, CRLF の改行コードを認めているのだけど、今の実装は LF しか見ていないので、そこら辺をちゃんと直さないといけなかったり