WebSocket の能力について
現在の World Wide Web は、リアルタイムのメディアになるようにはデザインされていません。Web アプリケーションが絶えず実行されているような印象を与えるには、AJAX や時間がかかるポーリング要求を、SignalR や Comet などのアドホック ライブラリを使って効果的に実装した、昔ながらポーリング ソリューションが必要です。大半のアプリケーションのニーズにとっては、クライアントからサーバーおよびサーバーからクライアントへの通信に待ち時間が生じるとしても、ポーリングが優れたソリューションです。今回は、WebSocket という新しい手法を取り上げます。
ソーシャル メディアによって Web アプリケーションとモバイル アプリケーションとの統合が広がるにつれて、クライアントとサーバー間の通信に求められる速度がますます速くなっています。Facebook のステータスを更新したら、更新した情報がすぐに友人に公開されることを望みます。投稿に関心を寄せている人がいれば、すぐに知らせてほしいと思います。今日では、このようにあらゆる機能にリアルタイム性が求められており、Facebook が世界的に受け入れられ、ソーシャル ネットワーク現象が爆発的に広まった理由の 1 つでもあります。したがって、ソリューションやツールの開発者からは、Web 上でリアルタイム通信を実装するための多くの要求が寄せられています。
Web クライアントとサーバー間で遅れのない接続を実現するには、HTTP プロトコル以上のものが必要です。そこで、WebSocket プロトコルの出番です。現在、インターネット技術標準化委員会では、WebSocket プロトコル向けの標準を定めています (bit.ly/va6qSS、英語)。このプロトコルを実装するための標準 API は、ブラウザーでサポートできるように、World Wide Web コンソーシアム (W3C) によって形式が定められています (bit.ly/h1IsjB、英語)。この仕様は、現在、「勧告候補」の状態です。
WebSocket プロトコル
新しい WebSocket プロトコルは、HTTP プロトコルの構造上の制限を克服することが目的です。この構造上の制限により、HTTP プロトコルは、ブラウザーでホストされる Web アプリケーションが固定接続を使用してサーバーとの接続を保持するという、非効率なプロトコルになっています。WebSocket プロトコルは、1 つの TCP ソケットを使って、Web アプリケーションと Web サーバーとの双方向通信を可能にします。つまり、このプロトコルは、サーバーの負荷、メモリやリソースの消費量などのコストを最小限に抑えて、ブラウザーでホストされている Web アプリケーションと Web エンドポイントとの間で常時接続の状態を保持できるようにします。実質的な効果は、遅延することなく、また追加の要求を調整する必要なく、ブラウザーと Web サーバーとの間でデータや通知をやり取りできることです。強調しすぎかもしれませんが、WebSocket プロトコルは、開発者にまったく新しい世界の可能性を開き、ポーリング ベースの技法やフレームワークを過去のものにしました。すみません、これは少し言いすぎですね。
現時点での WebSocket の使用
ブラウザーでの WebSocket プロトコルのサポートは迅速に強化される予定ですが、現時点で WebSocket がサポートされるのは最新バージョンのブラウザーのみです。ブラウザーを定期的にアップグレードしないユーザー (または、厳しい企業ポリシーによってアップグレードが許可されていないユーザー) には、サポートが提供されません。
つまり、開発者は、AJAX ポーリングまたは時間がかかるポーリングのソリューションに基づくコードの使用をすぐに取りやめることはできません。この点については、SignalR があります。SignalR は、ブラウザーと Web サーバーとの間で遅延のないメッセージングを可能にする新しいマイクロソフト フレームワークです。SignalR は、固定接続を抽象化し、WebSocket がサポートされている場合には WebSocket への自動切り替えを行い、サポートされていない場合は実行時間の長いポーリングを使用します。SignalR については最近のコラムで取り上げましたが、まだご覧になっていない場合は、できるだけ早くお読みになることを重ねてお勧めします。SignalR には、すべての開発者およびすべての Web アプリケーションにとって優れたライブラリやツールになる要素が含まれています。
現時点での WebSocket サポート
図 1 に、最もよく利用されているブラウザーによって現在提供されている、WebSocket のサポートの概要を示します。
図 1 WebSocket のブラウザー サポート
ブラウザー | WebSocket のサポート |
Internet Explorer | Internet Explorer 10 でサポート予定です。JavaScript と HTML5 を使用して記述される Metro スタイル アプリでもサポート予定です。 |
Firefox | 2011 年半ばにリリースされたバージョン 6 のブラウザーからサポートが開始されています。ごく初期のサポートはバージョン 4 で提供され、バージョン 5 に引き継がれました。 |
Chrome | 2011 年 9 月にリリースされたバージョン 14 からサポートが開始されています。 |
Opera | バージョン 11 でサポートが削除されています。 |
Safari | 以前のバージョンの WebSocket プロトコルをサポートします。 |
Firefox 以外のブラウザーは、window.WebSocket オブジェクトを確認することで、WebSocket がサポートされているかどうかをプログラムでチェックできます。Firefox の場合、現状では MozWebSocket オブジェクトをチェックします。ほとんどの HTML5 関連の機能は、ブラウザー内で Modernizr (modernizr.com、英語) などの特殊なライブラリを使用して、サポートされているかどうかをチェックできます。具体的には、Modernizr ライブラリをページにリンクしている場合、以下のような JavaScript コードを用意します。
if (Modernizr.websockets)
{
...
}
WebSocket 実装の使用を開始する場合、おそらく Modernizr が優れた選択肢です。これは、Modernizr には特定の機能が現在のブラウザーでサポートされていない場合に自動的に実行されるポリフィルが用意されているためです。
現状では、さまざまなベンダーが同じようにはサポートしていませんが、WebSocket は非常に魅力的な機能です。マイクロソフトは、新しい Internet Explorer 10、IIS、ASP.NET、Windows Communication Foundation (WCF)、および Windows ランタイム (WinRT) を通じて、WebSocket を幅広くサポートします。ただし、WebSocket には公式の標準 API がまだ存在しないため、初期のサポートは関心の表れを示していることに注意が必要です。現状では、抽象層を通じて WebSocket を使用することをお勧めします。あまり変更を加えたくない場合や、WebSocket を開閉するコードを独自に作成する場合は、Modernizr の使用をお勧めします。余分な機能を省き、基盤となる多くの詳細について把握する必要なく、ブラウザーや Web エンドポイントに透過的に固定接続するフレームワークが必要な場合は、SignalR の使用をお勧めします。
WebSocket プロトコルについて
双方向通信用の WebSocket プロトコルでは、クライアント アプリケーションとサーバー アプリケーションの両方がプロトコルの詳細に対応する必要があります。つまり、WebSocket 準拠のエンドポイントを呼び出す、WebSocket 準拠の Web ページが必要です。
WebSocket の通信は、両者 (ブラウザーとサーバー) が固定接続経由で通信する意思を相互に確認するハンドシェイクから始めます。次に、大量のメッセージ パケットを TCP 経由で両方向に送信します。図 2 は、WebSocket プロトコルのしくみを示しています。
図 2 WebSocket プロトコル スキーマ
図 2 の補足として、接続の終了時には、2 つのエンドポイントが確実に接続を閉じるように、接続終了のフレームを交換します。初期ハンドシェイクは、クライアントから Web サーバーに送信するプレーンな HTTP 要求で構成されます。この要求は、アップグレード要求として構成される HTTP GET です。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
HTTP では、Upgrade ヘッダーを含むクライアント要求は、別のプロトコルへの切り替えをサーバーに要求します。WebSocket プロトコルを使用する場合、サーバーへのアップグレード要求には、サーバーがアップグレード要求を受け入れたことを示す証拠として返す一意のキーを含みます。これは、サーバーが WebSocket プロトコルを認識したことを示す実質的な証拠になります。ハンドシェイク要求への応答のサンプルを次に示します。
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
アップグレードが成功すると状態コードは必ず 101 になります。その他の状態コードは、WebSocket プロトコルへのアップグレードが拒否されたことを示します。サーバーは受け取ったキーを固定 GUID 文字列と連結し、連結した文字列からハッシュを計算します。ハッシュ値は Base64 にエンコードされ、Sec-WebSocket-Accept ヘッダーを通じてクライアントに返されます。
また、クライアントが使用を希望するサブプロトコルを指定するため、Sec-WebSocket-Protocol など、その他のヘッダーを送信することもできます。サブプロトコルとは、基本的な WebSocket プロトコルの上位に構築されるアプリケーション レベルのプロトコルです。サーバーが、いくつか指定されたサブプロトコルを認識すると、その 1 つを選択し、同じヘッダーを通じてクライアントにその名前を送信します。
ハンドシェイクが終了すれば、クライアントとサーバーは、WebSocket プロトコル経由でメッセージを自由に送信できます。ペイロードは、実行する操作を示すオペコードから始まります。このようなオペコードの例として、たとえば、0x8 はセッション終了要求を示します。WebSocket メッセージは非同期に送信されるため、HTTP のように、送信された要求に対して必ずしもすぐに応答が返されるとは限りません。WebSocket プロトコルを使用するときは、クライアントからサーバーまたはサーバーからクライアントに送信されるメッセージの汎用性を考え、従来の HTTP の要求/応答パターンを忘れることをお勧めします。
WebSocket エンドポイントの一般的な URL は次のような形式になります。
var myWebSocket =
new WebSocket("ws://www.websocket.org");
セキュリティ保護されたソケット接続を行う場合は、プロトコルのプレフィックスに ws を使用します (セキュリティ保護された接続は、通常、仲介ノードが存在するとより正常に機能します)。最後に、WebSocket プロトコルは、送信元が異なる通信の問題を認識し対処します。WebSocket クライアントは、一般に (必ずではありません)、任意のドメインにあるエンドポイントに要求を送信できます。ただし、ハンドシェイク要求の許可または拒否を決定するのは WebSocket サーバーです。
WebSocket API について
前述のとおり、現在、W3C によって WebSocket プロトコル API の標準化が行われています。そのため、さまざまなドラフト版が利用可能になると、ブラウザーではすぐに対応されます。そのため、現在のコードが機能しなくなるブラウザーもあります。さらに重要なことに、ブラウザーの新しいリリースが提供されると、同じブラウザーでもコードが機能しなくなる可能性もあるため注意が必要です。いずれにしても、ある程度 WebSocket コードを操作してみればそれで十分です。今後必要になる可能性がある変更は、小規模なものだと考えられます。
WebSocket プロトコルを試す場合は、このプロトコルをサポートするブラウザーで websocket.org にアクセスしてます。サポートするブラウザーには、Internet Explorer 10 のプレビュー版や、Google Chrome の最新版などがあります。図 3 は、ハンドシェイクを Fiddler によって追跡した結果を示します。
図 3 ブラウザーとサーバー間の実際のハンドシェイク
当然のことながら、現バージョンの Fiddler (バージョン 2.3.x) は、HTTP トラフィックしかキャプチャしません。WebSocket トラフィックに対応する新しいバージョンの Fiddler は、現在ベータ版です。
WebSocket API は非常にシンプルです。ブラウザー側では、WebSocket ブラウザー クラスのインスタンスを作成する必要があります。このクラスは、興味深いイベントを公開するので、適切なハンドラーを用意します。
var wsUri = " ws://echo.websocket.org/";
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onerror = function(evt) { onError(evt) };
onopen イベントは、接続が確立すると発生します。onmessage イベントは、クライアントがサーバーからメッセージを受信したら発生します。onclose イベントは、接続が閉じられたら発生します。最後に、onerror イベントは、エラーが発生したら必ず発生します。
メッセージをサーバーに送信するには、次に示すように、send メソッドを呼び出すだけです。
var message = "Cutting Edge test: " +
new Date().toString();
websocket.send(message);
図 4 は、websocket.org Web サイトに用意されているエコーの例を応用したサンプル ページを示しています。この例では、サーバーが受信したメッセージをクライアントにそのままエコーしています。
図 4 動作中の WebSocket プロトコル
Internet Explorer 10 での WebSocket プログラミングに興味がある方は、bit.ly/GNYWFh (英語) を参照してください。
WebSocket のサーバー側
今回は、WebSocket プロトコルのクライアント側に重点を置いて説明しました。WebSocket クライアントを使用するには、要求を認識し、適宜応答できる、WebSocket 準拠の適切なサーバーが必要です。WebSocket サーバーを構築するためのフレームワークは既に公開されています。たとえば、Java や Node.js の Socket.IO (socket.io) を試してみてください。Microsoft .NET Framework 関連のコンテンツをお探しの場合は、The Code Project (bit.ly/lc0rjt、英語) の「WebSocket サーバー」を参照してください。また、WebSocket のマイクロソフト サーバー サポートが IIS、ASP.NET、および WCF で提供されています。詳細については、Channel 9 のビデオ「IIS、ASP.NET、および WCF で WebSocket を使用してリアルタイムの Web アプリケーションを作成する」(bit.ly/rnYaw5、英語) を参照してください。
最高に優れた WebSocket
よく言われるように、WebSocket は最も実用的な画期的発明です。WebSocket を理解すると、WebSocket なしで、ソフトウェアの世界がどのようにして進歩してきたのか不思議に思われるほどです。WebSocket は数多くのアプリケーションで役に立ちますが、すべてのアプリケーションに役立つわけではありません。インスタント メッセージングが重要になるアプリケーションでは、1 つの WebSocket サーバーと多数のクライアント (Web、モバイル、またはデスクトップ) から構築するシナリオを真剣に考えることになります。それとは別に、ゲーム アプリケーションやリアルタイムで動作するアプリケーションは、WebSocket プロトコルを使用することで多くのメリットが得られる分野になります。WebSocket は間違いなく最高の発明です。
Dino Esposito は、『プログラミング Microsoft ASP.NET 4』(日経BP社、2012 年) と『Programming ASP.NET MVC 3』(Microsoft Press、2010 年) の著者であり、『Microsoft .NET: Architecting Applications for the Enterprise』(Microsoft Press、2008 年) の共著者です。Esposito はイタリアに在住し、世界各国で開催される業界のイベントで頻繁に講演しています。Twitter (twitter.com/despos、英語) で彼をフォローしてください。
この記事のレビューに協力してくれた技術スタッフの Levi Broderick および Brian Raymor に心より感謝いたします。