Windows Consumer Preview の WebSocket

Windows 8 Consumer Preview と Server Beta では、IE10 と他のすべての Microsoft WebSocket クライアントおよびサーバー機能で、最終バージョンの IETF WebSocket プロトコル (英語) がサポートされるようになりました。さらに、IE10 には W3C WebSocket API (英語) Candidate Recommendation が実装されています。

WebSocket は安定しているので、開発者はいつでも革新的なアプリケーションとサービスを作り始めることができます。この記事では、W3C WebSocket API (英語) とその基盤になっている WebSocket プロトコルについて簡単に紹介します。更新された Flipbook デモ (英語) では、最新バージョンの API とプロトコルが使われています。

私が書いた以前の記事 (英語) では、次のような WebSocket のシナリオを紹介しました。

WebSocket により、Web アプリケーションはブラウザーにリアルタイムの通知と最新情報を表示することができます。開発者は、ブラウザーに備わっている HTTP 要求/応答モデルの制限に対処するという問題に直面していました。このモデルは、リアルタイム シナリオ向けには設計されていなかったためです。WebSocket により、ブラウザーはサービスとの間で双方向の全二重通信チャネルを開くことができるようになります。そうすれば、どちらの側もこのチャネルを使って直ちにもう一方の側にデータを送信できます。ソーシャル ネットワーキング、ソーシャル ゲーム、金融サイトなど、さまざまなサイトにおいて、異なるブラウザー間で同じマークアップを適切に使って優れたリアルタイム シナリオを実現できるようになりました。

2011 年の 9 月の記事以降、ワーキング グループによりかなりの進歩がもたらされました。現在では、WebSocket プロトコルは IETF 提案の標準プロトコル (英語) になりました。さらに、W3C WebSocket API は W3C Candidate Recommendation になりました。

エコー サンプルを使った WebSocket API の紹介

以下のコード スニペットは、ASP.NET の System.Web.WebSockets 名前空間で作成された簡単なエコー サーバーを使って、アプリケーションから送信されたテキストとバイナリ メッセージをエコー バックしています。アプリケーションで、ユーザーはテキスト メッセージとして送信してエコー バックするテキストを入力したり、バイナリ メッセージとして送信してエコー バックできる絵を描いたりすることができます。

WebSockets エコー サンプル アプリケーションのスクリーンショット。

より複雑なサンプルで WebSocket と HTTP ポーリングにおけるレンテンシとパフォーマンスの違いを試すには、Flipbook デモ (英語) をご覧ください。

WebSocket サーバーへの接続の詳細

ここでは、アプリケーションとサーバーの間の直接接続を利用して簡単に説明します。プロキシが構成されている場合、IE10 は HTTP CONNECT 要求をプロキシに送信することでプロセスを開始します。

WebSocket オブジェクトが作成されると、クライアントとサーバーの間でハンドシェイクが交換されて WebSocket 接続が確立されます。

HTTP クライアントから HTTP サーバーへの HTTP GET アップグレード要求を示す図。

IE10 は、HTTP 要求をサーバーに送信することでプロセスを開始します。

GET /echo HTTP/1.1

Host: example.microsoft.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin: https://microsoft.com

Sec-WebSocket-Version: 13

この要求の各部分を見てみましょう。

接続プロセスは、標準的な HTTP GET 要求で始まります。これにより、要求はファイアウォール、プロキシ、他の中継ポイントを越えることができます。

GET /echo HTTP/1.1

Host: example.microsoft.com

HTTP アップグレード ヘッダー (英語) は、アプリケーション層プロトコルを HTTP から WebSocket プロトコルに切り替えるようサーバーに要求します。

Upgrade: websocket

Connection: Upgrade

サーバーは、その応答で Sec-WebSocket-Key ヘッダーの値を変換し、WebSocket プロトコルを理解していることを表明します。

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Origin ヘッダーは IE10 により設定されており、サーバーは生成元ベースのセキュリティ (英語) を適用できます。

Origin: https://microsoft.com

Sec-WebSocket-Version ヘッダーは、要求されたプロトコル バージョンを特定します。IETF 提案の標準では、バージョン 13 が最終バージョンです。

Sec-WebSocket-Version: 13

サーバーは、アプリケーション層プロトコルのアップグレード要求を承認した場合、HTTP 101 切り替えプロトコル (英語) 応答を返します。

HTTP サーバー クライアントから HTTP クライアントへの HTTP 101 切り替えプロトコル応答を示す図。

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

サーバーは、WebSocket プロトコルを理解していることを表明するため、クライアント要求の Sec-WebSocket-Key で標準化された変換を実行し、Sec-WebSocket-Accept ヘッダーでその結果を返します。

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

次に、IE10 は Sec-WebSocket-Key を Sec-WebSocket-Accept と比較し、サーバーが誇大妄想を抱いている HTTP サーバーではなく WebSocket サーバーであることを検証します。

クライアント ハンドシェイクにより、IE10 とサーバーの間に HTTP-on-TCP 接続が確立されています。サーバーが 101 応答を返すと、アプリケーション層プロトコルは HTTP から、前に確立された TCP 接続を使う WebSocket に切り替わります。

この時点で、HTTP はまったく無関係です。 軽量 WebSocket ワイヤ プロトコルを使って、両方のエンドポイントがメッセージをいつでも送受信できるようになっています。

2 つの Web ソケットの間で送信される WebSocket メッセージを示す図。

WebSocket サーバーへの接続のプログラミング

WebSocket プロトコルでは、HTTP スキームと似た次のような 2 つの新しい URI スキームが定義されています。

  • "ws:""//" host [ ":" port ] path [ "?" query ] は、"http:" スキームの上にモデル化されています。既定のポートは 80 です。セキュリティで保護されていない (暗号化されていない) 接続に使われます。
  • "wss:""//" host [ ":" port ] path [ "?" query ] は、"https:" スキームの上にモデル化されています。既定のポートは 443 です。トランスポート層セキュリティ (英語) によってトンネル化されているセキュリティで保護された接続に使われます。

プロキシやネットワークの中継ポイントが存在する場合、中継ポイントがセキュリティで保護されたトラフィックを変換しようとすることはあまりないため、セキュリティで保護された接続が成功する確率は高くなります。

次のコード スニペットは、WebSocket 接続を確立します。

var host = "ws://example.microsoft.com";

var socket = new WebSocket(host);

ReadyState – 位置に着いて... 用意... ドン...

WebSocket.readyState 属性は、CONNECTING、OPEN、CLOSING、または CLOSED で接続の状態を表します。最初に WebSocket が作成されると、readyState は CONNECTING に設定されます。接続が確立されると、readyState は OPEN に設定されます。接続が確立に失敗した場合、readyState は CLOSED に設定されます。

オープン イベントの登録

接続が作成されたときに通知を受け取るには、アプリケーションにオープン イベントを登録する必要があります。

socket.onopen = function (openEvent) {

document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'OPEN';

};

メッセージの送受信の背後にある詳細

ハンドシェイクに成功した後、アプリケーションと Websocket サーバーは WebSocket メッセージを交換することがあります。メッセージは、1 つ以上のメッセージ フラグメントのシーケンス、つまりデータ "フレーム" として構成されます。

各フレームには、次のような情報が含まれています。

  • フレームの長さ
  • メッセージの最初のフレームに含まれるメッセージの種類 (バイナリまたはテキスト)
  • メッセージの最後のフレームであることを示すフラグ (FIN)

WebSocket フレームの内容を示す図。

IE10 は、フレームをスクリプトに渡す前に完全なメッセージに再構築します。

メッセージの送受信のプログラミング

send API を使用すると、アプリケーションはメッセージを UTF-8 テキスト、ArrayBuffers (英語)、または BLOB として Websocket サーバーに送信できます。

たとえば、次のスニペットはユーザーが入力したテキストを取得し、エコー バックする UTF-8 テキスト メッセージとしてサーバーに送信します。さらに、Websocket が OPEN readyState であることを検証します。

function sendTextMessage() {

if (socket.readyState != WebSocket.OPEN)

return;

 

var e = document.getElementById("textmessage");

socket.send(e.value);

}

次のスニペットはユーザーがキャンバスに描いたイメージを取得し、バイナリ メッセージとしてサーバーに送信します。

function sendBinaryMessage() {

if (socket.readyState != WebSocket.OPEN)

return;

 

var sourceCanvas = document.getElementById('source');

// msToBlob returns a blob object from a canvas image or drawing

socket.send(sourceCanvas.msToBlob());

// ...

}

message イベントの登録

メッセージを受け取るには、アプリケーションに message イベントを登録する必要があります。イベント ハンドラーは、MessageEvent.data にデータが含まれる MessageEvent を受け取ります。データは、テキスト メッセージまたはバイナリ メッセージとして受け取ることができます。

バイナリ メッセージを受け取るとき、メッセージ データを BLOB または ArrayBuffer データ型のどちらとして返すかは WebSocket.binaryType 属性で制御します。この属性は、"blob" または "arraybuffer" のどちらかに設定できます。

次の例では、既定値の "blob" を使っています。

このスニペットは、WebSocket サーバーからエコーされたイメージまたはテキストを受け取ります。データが BLOB の場合、イメージが返され、目的のキャンバスに描画されます。それ以外の場合、UTF-8 テキスト メッセージが返され、テキスト フィールドに表示されます。

socket.onmessage = function (messageEvent) {

if (messageEvent.data instanceof Blob) {

var destinationCanvas = document.getElementById('destination');

var destinationContext = destinationCanvas.getContext('2d');

var image = new Image();

image.onload = function () {

destinationContext.clearRect(0, 0, destinationCanvas.width, destinationCanvas.height);

destinationContext.drawImage(image, 0, 0);

}

image.src = URL.createObjectURL(messageEvent.data);

} else {

document.getElementById("textresponse").value = messageEvent.data;

}

};

WebSocket 接続のクローズの詳細

ハンドシェイクのオープンと同様に、ハンドシェイクのクローズもあります。どちらのエンドポイント (アプリケーションまたはサーバー) でもこのハンドシェイクを開始できます。

特殊なフレーム (クローズ フレーム) がもう一方のエンドポイントに送信されます。クローズ フレームには、オプションのステータス コードとクローズ理由が含まれることがあります。プロトコルでは、ステータス コードに利用できる一連の適切な (英語) が定義されています。クローズ フレームの送信側は、クローズ フレームの後にアプリケーション データをさらに送信することはできません。

もう一方のエンドポイントがクローズ フレームを受け取ると、自身のクローズ フレームで応答します。クローズ フレームで応答する前に、保留メッセージを送信することもあります。

クローズ フレーム メッセージと応答を示す図。

WebSocket のクローズのプログラミングとクローズ イベントの登録

アプリケーションが close API とのオープンな接続でクローズ ハンドシェイクを開始します。

socket.close(1000, "normal close");

接続がクローズしたときに通知を受け取るには、アプリケーションにクローズ イベントを登録する必要があります。

socket.onclose = function (closeEvent) {

document.getElementById("serverStatus").innerHTML = 'Web Socket State::' + 'CLOSED';

};

close API は 2 つのオプション パラメーターを承認します。プロトコルにより定義されたステータス コードと説明です。ステータス コードは、1000 か 3000 ~ 4999 の範囲にする必要があります。クローズが実行されると、readyState 属性が CLOSING に設定されます。IE10 がサーバーからクローズ応答を受け取ると、readyState 属性が CLOSED に設定され、クローズ イベントが呼び出されます。

Fiddler を使った WebSockets トラフィックの参照

Fiddler (英語) は、よく使われる HTTP デバッグ プロキシです。最新バージョンの WebSocket プロトコルもある程度サポートされています。WebSocket ハンドシェイクで交換されるヘッダーを調べることができます。

WebSocket 要求を示す Fiddler のスクリーンショット。

WebSocket メッセージもすべてログに記録されます。次のスクリーンショットでは、"spiral" が UTF-8 テキスト メッセージとしてサーバーに送信され、エコー バックされたのがわかります。

WebSocket 応答を示す Fiddler のスクリーンショット。

まとめ

WebSocket について詳しくお知りになりたい場合は、2011 年 9 月から開催されている Microsoft //Build/ カンファレンスの次のセッションをご覧ください。

Microsoft のテクノロジを使って WebSocket サービスを作ることに興味がおありの場合は、次の記事が役立ちます。

ぜひ、今すぐ WebSocket での開発を始めてフィードバックをお寄せください。

—Windows Networking シニア プログラム マネージャー Brian Raymor