§Coment ソケット
§Comet ソケットを作成するためにチャンクレスポンスを利用する
チャンクレスポンス を応用すると、Comet ソケットを作成することができます。 Comet ソケットは、 <script>
のみを含むチャンク分割された単なる text/html
レスポンスです。それぞれのチャンクに、 web ブラウザによって実行される JavaScript を含んだ <script>
タグを書き込みます。これを利用することで、サーバから web ブラウザへ、イベントをリアルタイムに送信することができます: それぞれのメッセージ毎に、JavaScript のコールバック関数を呼び出す <script>
タグでイベントをラップして、それをチャンクレスポンスに書き込みます。
それでは、これを確かめるデモを作成してみましょう。まず、ブラウザの console.log
関数を呼び出す <script>
タグを生成するような Enumerator を作成します。
def comet = Action {
val events = Enumerator(
"""<script>console.log('kiki')</script>""",
"""<script>console.log('foo')</script>""",
"""<script>console.log('bar')</script>"""
)
Ok.stream(events >>> Enumerator.eof).as(HTML)
}
このアクションを web ブラウザから実行すると、ブラウザのコンソールに3つのイベントログが出力されるでしょう。
Tip:
events >>> Enumerator.eof
はevents.andThen(Enumerator.eof)
の別の書き方です。
Enumerator[A]
を別の Enumerator[B]
へ変換する play.api.libs.iteratee.Enumeratee
を利用すると、この例はもっと簡単に書けます。試しに、標準的なメッセージを <script>
タグでラップするのに使ってみましょう。
import play.api.templates.Html
// Transform a String message into an Html script tag
val toCometMessage = Enumeratee.map[String] { data =>
Html("""<script>console.log('""" + data + """')</script>""")
}
def comet = Action {
val events = Enumerator("kiki", "foo", "bar")
Ok.stream(events >>> Enumerator.eof &> toCometMessage)
}
Tip:
events >>> Enumerator.eof &> toCometMessage
はevents.andThen(Enumerator.eof).through(toCometMessage)
の別の書き方です。
§play.api.libs.Comet
ヘルパーを使う
チャンク分割された comet ストリームを扱うために、上で書いた内容とほぼ同じことを行う Comet ヘルパーを用意しています。
ノート: 実際のところ Comet ヘルパは、ブラウザの互換性のため最初に空のバッファデータを送信したり、メッセージとして String と JSON の両方をサポートするなど、上で書いた内容以上のことを行います。さらに、特定の type class を定義することで、他の型のメッセージをサポートするように拡張することもできます。
これを使って前述の例を書き直してみましょう:
def comet = Action {
val events = Enumerator("kiki", "foo", "bar")
Ok.stream(events &> Comet(callback = "console.log"))
}
Tip:
Enumerator.callbackEnumerator
とEnumerator.pushEnumerator
は反応的 (reactive) かつノンブロッキングな enumerator を手続き的に記述することに使えるヘルパ関数です。
§Forever iframe テクニック
Comet ソケットを書く標準的なテクニックとして、 iframe 内でチャンク分割された Comet レスポンスを無限にロードし、親フレームを呼び出すコールバック関数を特定するというものがあります:
def comet = Action {
val events = Enumerator("kiki", "foo", "bar")
Ok.stream(events &> Comet(callback = "parent.cometMessage"))
}
これを、次のような HTML ページと共に使用します:
<script type="text/javascript">
var cometMessage = function(event) {
console.log('Received event: ' + event)
}
</script>
<iframe src="/comet"></iframe>
Next: WebSocket