ブラウザベースのアプリケーションで通信を行うためのテクノロジは,現在のところ2つある。Bayeux (別名 CometD) と,それより新しい WebSocket だ。どちらが優れているだろうか,あるいは,共存できるのに十分なだけの違いがあるのだろうか?
CometD フレームワークは Bayeux プロトコルの実装であり,信頼性の低いネットワーク上で,クライアントとサーバ間の複数チャネルの非同期通信ストリームを実現する。数多くの言語 (JavaScript,Java,Perl ...) から使用できるが,ブラウザベースの AJAX アプリケーションでの利用が圧倒的に多い。Bayeux のアドバンテージは,新たな情報を受信した時に行う非同期/バックグラウンド処理を,ブラウザが提供する HTTP 通信サービスのみで実現可能なことにあり,Google Mail の新規メール通知のような処理を,AJAX に対応するすべてのブラウザ上で動作するように実装することができる。実際に,異なる言語で記述されたデバイスや,モバイルデバイスのように不安定な通信環境でも,同じプロトコルが使用されている。
もうひとつの WebSocket は ドラフト標準 であり,Google や Apple など HTML 5 の標準化を行う WhatWG ワーキンググループのメンバがスポンサーとなっている。そのため HTML 5 をサポートするブラウザ (Chrome,Safari) がまず,WebSocket プロトコルのサポートを始めている。
どちらも既存アプリケーションに対して通信層の役割を果たすというより,Webベースの AJAX アプリケーションと他のサービスの非同期メッセージ通信,あるいはソケットベース接続を通じた通信の実現を目的としたプロトコルだ。メッセージの配信を通信層に任せることで,アプリケーションはコンポーネント部品の設計に集中することができる。さらに2つとも,コネクションを長時間維持しておくことが可能で,アプリケーションへの非同期イベント配信が実現されている。ただしこれは,特に新しい機能という訳ではない。HTTP 1.1 でもコネクションのパイプライン化 (個々のリクエスト完了後コネクションを維持する,リクエストの応答を受ける前に複数のリクエストを送信する) がサポートされていたし,IMAP などのプロトコルでは,送信データが存在しない場合にコネクションを休眠 (hibernate) 状態にしておいて,サーバが任意のタイミングで新しいメッセージをプッシュすることができる IDLE コマンドがサポートされている。実際のところ Bayeux や WebSocket が登場する前には,“HTTP Push” というのは,HTTP 上の持続的な通信チャネルに関連する一般的な機構を指す用語だったのだ。
ただし持続型のコネクションにも問題がない訳ではない。通信データが一定時間ないコネクションは死んでいるものと判断され,その後いずれかのタイミングで切断される。そのために IMAP IDLE ではクライアントに,29分ごとに IMAP IDLE コマンドを再送信してコネクション切断を回避するように推奨している。それでも HTTP 上に配置された他のプロキシを使用する場合には,クライアントとサーバは進行中のコネクション維持に合意していても,プロキシが利用されていないと判断して,コネクションを切断するかも知れない。
他にリソース関連の問題がある。サーバ (あるいはネットワークリンク) の占有を回避するために,ブラウザには単一サーバに対する HTTP 同時接続数に制限を設けられているのが一般的である。大部分のブラウザでは,一度に接続できる数は2ないし4に制限されている。
Bayeux と WebSocket はともに,フォールバック機構を利用した long polling (Bayeux の場合)や HTTP ベース以外の第2のプロトコルに切り替えることによって,このリソース制限の回避を図っている。従ってこれらのライブラリを利用するユーザは,通常はブラウザやインフラの設定するリソース制限について心配する必要はない。
Erlang の父である Joe Armstrong 氏は,WebSocket によって Comet が不要となる という考えを持っている。
実験を重ねた結果,純粋な非同期メッセージパッシングを使用して,Erlang と Web ページが通信可能になりました。
この結果として以下のテクノロジが不要になるものと考えています。
- comet
- long-poll
- AJAX
- keep-alive ソケット
これらはすべて単なるハックであって,Web ブラウザでは通常のアプリケーションのように単純にソケットをオープンして非同期I/Oを行うことができない,という根本的な問題に対しては不十分なプログラム方法なのです。
Jetty と Bayeux プロトコル の開発者のひとりである Greg Wilins 氏は,仕様定義の観点とコネクション切断時の動作の2点において,WebSocket には改良の余地がある と考えている。仕様定義に関する主要な問題のひとつは,その取り組みのレベルについてである。
この仕様スタイルについての,より実務的な問題は,仕様が不可解な文章で埋め尽くされていることです。例えばこんな具合です。
/b_v/ に /b/ の下位7ビットに対応する値 ( /b/ と 0x7F の論理積(and)で得られる値) を設定する。
/length/ を 128 倍して /b_v/ を加え,その結果を /length/ にセットする。
/b/の最上位ビットがセットされていれば(つまり /b/ と 0x80 の論理積(and) が 0x80 であれば),上の _length_ ラベルに戻る。
読者のみなさん,これを読んでクライアント側のフレーミングとサーバ側のフレーミングに対称性があり,同じデータフレーミングを実装する,ということが理解できますか!
IETF 仕様では通常,このような冗長な記述は使用しません。混乱や実装上の誤りを避けるために,拡張 Backus-Naur 形式(ABNF RFC5234) という厳密性のある言語を用いて,プロトコルの形式的記述を行います。定義が明確であることを示すために,4.2 章の内容を BNF に置き換えてみましょう ...
Bayeux に加えて,Jetty が現在 WebSocket もサポートしている ことには注目するべきだろう。公開調査の精神に則って,氏は iWebSocket を使って Chat を実装 した様子をブログに記録している。しかし,すべてがスムーズに運んだ訳ではない。
チャットルームの標準的なユースケースは,ルームに入場したら明示的に退場するまでそこに留まる,というものです。これを webchat のコンテキストに当てはめれば,ブラウザを閉じるか,あるいは別のページに移動するまでチャットメッセージを送受信することができる,ということになります。残念ながら simple chat の実装では,このセマンティックを実現できていません。websocket プロトコルによる接続にアイドルタイムアウトが存在するからです。
チャットルームに留まるために,アプリケーションは keep-alive メッセージを送信して,アイドルタイムアウトによって websocket がクローズされるのを回避することができます。しかしアプリケーションにはアイドルタイムアウト時間を知る方法がないので,適当な頻度(例えば 30秒)で keep-alive を送信して,それがパスのアイドルタイムより小さい値であることを願うしかないのです (long-polling がやっていることと大差ありません)。
onClose 処理,keep-alive 処理,メッセージキュー,タイムアウトとリトライなどを実装すれば,ユーザが Web ページに留まっている間,滞在できるチャットルームができあがります。しかし残念なことにまだ完成ではありません。異常処理や永続的なエラーに関する処理がさらに必要です。
将来的にすべての人々に対して事態が改善されるように,氏は WebSocket に対していくつかの機能追加を提案している。
- websocket の将来バージョンでタイムアウト検出がサポートされて,アプリケーションに keep-alive メッセージを送信する時期を伝えたり,アプリケーションに代わって keep-alive を送信可能になることが理想です。
- 正しい順序のクローズメッセージがサポートされて,ネットワークエラー (この場合には,チャットルーム内でのユーザの所在を保持しなければなりません) とユーザのページ移動による正しいクローズ (ユーザの所在を削除します) をアプリケーションが区別できるようになることが望まれます。
- websocket で正常なクローズ手続き(orderly close) がサポートされ,正常な通信完了を確認可能になることが理想です。最高の通信品質(Quality of Service)を必要としない場合であれば,これによって複雑な確認応答(ACK)処理を省略できます。
- 現在より詳細な接続エラー情報にアクセスできることが望まれます。ホスト到達不可(no-route-to-host)の処理と,サーバの 401 未認証応答を完全に区別して処理するために必要なものです。
- winsocket の将来バージョンでは,ネットワークエラーやアイドルタイムアウトなどと区別された形でエラーステータスを送信できることが理想です。そうすれば,リトライ処理が不要であることを,アプリケーションが認識できるようになります。
最後に要約と将来的な希望を書きとめて,氏はこの記事を締めくくっている。
このブログ で言いたいのは,WebSocket を使ったとしても堅牢な comet web アプリケーションを開発するときに直面する複雑性はほとんど変わらない,銀の弾丸は存在しない,ということなのです。希望としては keep alive やタイムアウトネゴシエーション,適正なクローズ(orderly close),エラー通知などの機能が将来 websocket に組み込まれて欲しいと思っています。しかし,高度なキューやタイムアウト,再接続,リトライやバックオフなどを提供するのは websocket の役割ではありません。より高品質なサービスが必要ならば,それを利用するアプリケーションあるいはフレームワークがこれらの機能を扱うべきでしょう。
まもなくリリースされる CometD バージョン2では,現在サポートされている JSON long polling と JSONP callback polling に加えて,トランスポートとしてwebsocket が選択可能になります。このブログ で議論された機能はすべてサポートされて,websocket サポートの有無に関わらず,すべてのブラウザから透過的に利用できます。
その後リリースされた Jetty 8.0.0 M0 においても,Servlet 3.0 API と WebSocket が サポートされている。