草案
このページは完成していません。
Overview
WebSocketとはサーバーのポートをListenするTCPアプリケーションです。独自のサーバーを作ることは難しいように聞こえますが、シンプルなWebSocketサーバーを作るのはそれほど難しくありません。
WebSocketサーバーはC(++)、Python、PHPやサーバーサイドJavascriptといった、Berkeley socketsをサポートするプログラミング言語で記述することができます。このドキュメントは特定のプログラミング言語のチュートリアルではなく、自分でWebSocketサーバーを作る一般的なガイドとなります。
このドキュメントを理解するには、HTTPの仕組みと中級程度のプログラミング経験が必要となります。また、言語によって、TCP socketsの知識も必要になるかもしれません。このドキュメントはWebSocketサーバーを書くための必要最低限の情報を解説します。
WebSocketsの最新の規格(RFC 6455)にも目を通してください。特にSection 1, 4~7はサーバー開発に役に立ちます。また、WebSocketsのセキュリティについて解説しているセクション10は、アプリケーションを公開する前に必ず精読するようにしてください。
Step 1: The WebSocket Handshake
まず、サーバーは標準的なTCP socketを使って接続をListenする必要があります。選択するプラットフォームによっては、自動で行われているかもしれません。本ドキュメントでは、example.comの8000ポートをListenし、/chat
のHTTP/GETのリクエストを受け付けるWebSocketサーバーを例として解説します。
Warning: サーバーはどんなポートもListenすることができますが、80か443以外のポートを選択すると、ファイアーウォールとプロキシで問題が発生するかも知れません。443ポートは問題無い可能性が高いですが、TLS/SSL によるセキュアな接続が必要になります。また、大抵のブラウザ(Firefox 8+)は、セキュアなページからの非セキュアなWebSocketサーバーへの接続を許可していないことも留意してください。
ハンドシェイクはWebSocketsにおける"Web"で、HTTPとWebSocketsの橋渡しを行います。ハンドシェイクでは、接続のパラメータ等の詳細が取り交わされ、条件が満たされない場合はどちらかの側が引き下がります。サーバー側がクライアントが要求することを間違って解釈した場合は、セキュリティ問題に発展する可能性があります。
Client Handshake Request
WebSocketサーバー開発においても、まずはクライアントからのハンドシェイクから始める必要があります。そのため、クライアントのリクエストを理解する必要があります。clientは以下のような標準的なHTTPリクエストを送ります:
GET /chat HTTP/1.1 Host: example.com:8000 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
クライアントはヘッダに拡張やサブプロトコルを指定することができます。詳細はMiscellaneousを参照してください。また、User-Agent
やCookie等の一般的なヘッダも指定することができます。それらのヘッダはWebSocketに直接関係しないため、適宜必要なものを指定してください。
無視してもらっても構いません。
間違いがあったり、解釈されないヘッダがある場合は、サーバーは"400 Bad Request"を返してソケットを閉じてください。サーバーはHTTPレスポンスボディに、ハンドシェイクが失敗した理由を設定することができますが、セキュリティの観点からブラウザには表示されません。 サーバーが特定のWebSocketバージョンを扱うことができない場合は、サーバーがサポートするバージョンをSec-WebSocket-Version
ヘッダに指定しえてクライアントに返してください。(本ドキュメントではv13を使用します)。では次に、一番興味深いSec-WebSocket-Keyを説明します。
Tip: すべてのブラウザはOriginヘッダを送ります。このヘッダを送信元のチェック、ホワイト/ブラックリストに使用してください。
サーバーが送信元を許可しない場合は、403 Forbiddenをクライアントに返してください。ただし、ブラウザ以外のユーザーエージェントは 偽のOriginを使うこともできるので注意してください。Originヘッダの無いリクエストは拒否することをお薦めします。
Tip: リクエストURI(ここでは/chat
) は規格上は特別な意味はありませんので、WebSocketに複数のアプリケーションを割り当てることができます。 例えば、 example.com/chat
をチャットアプリ、/game
をゲームに割り当てることができます。
Note: 一般的な HTTP status codesはハンドシェイクの前だけ使用されます。ハンドシェイク成功後は、別のコード(規格7.4に定義されています)を使用してください。
Server Handshake Response
サーバーがハンドシェイクのリクエストを受け取ると、以下のような、ちょっと変わった(でもHTTPです)レスポンスをクライアントへ送ります。(各ヘッダの末尾には \r\n
、、最後のヘッダにはもう一つ\r\n
が付与されます):
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
加えて、サーバーは拡張とサブプロトコルをここで選ぶことができます。詳細はMiscellaneousを参照してください。Sec-WebSocket-Acceptヘッダは興味深いです。
サーバーはクライアントから送られてきたSec-WebSocket-Key
とマジックナンバー"258EAFA5-E914-47DA-95CA-C5AB0DC85B11
"を文字連結してSHA-1 hashを取り、base64>エンコーディングして Sec-WebSocket-Accept
を生成します。
FYI: この部分はとても複雑に思えますが、サーバー側がWebSocketをサポートするかを判断するのに必要なプロセスなのです。これはとても重要なことで、もしサーバーがWebSocketをHTTPリクエストとして解釈してしまうとセキュリティ問題に発展しかねないからです。
例えば、キーが"dGhlIHNhbXBsZSBub25jZQ==
"ならば、 Sec-WebSocket-Acceptヘッダは
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
"になります。サーバーがここで説明したヘッダーを送ったらハンドシェイクの完了です。データの送受信ができます!
Step 2: Exchanging Data Frames
サーバー、クライアントはいつでもメッセージを送れます。 まるで魔法のようです。しかし、残念ながらデータフレームと呼ばれるデータから情報を抽出するのは魔法のような経験とは言えないかもしれません。データフレームにはフォーマットがあり、32-bit keyでXOR encryptionが行われます。この部分の詳細については規格のSection 5を参照してください。
Format
クライアント、サーバーから送信された、すべてのデータフレームは以下のフォーマットに従います:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
RSV1-3は今後の拡張のための領域です。無視してください。MASKはメッセージがエンコードされているかのビットです。クライアントからサーバーへのメッセージは常にエンコードされますので、常に1が立っていると考えてください。
次の2つは一緒に使います。WebSocketはメッセージを複数のデータフレームに分割送信できることを思い出しましょう。FINはメッセージに続きがあるのかを表すビットです。このビットが0の場合はサーバーは次のメッセージを待ち、1の場合は 送信完了と判断します。opcode はこのデータフレームが何のためであるかを表します。opcodeが0x0の場合
、サーバーはこのデータフレームのpayloadを直前のpayloadに連結して、次を待ちます。0x1の場合は
payloadはテキスト、0x2の場合は
payloadはバイナリデータになります。 (opcodeについては後に詳しく説明します) 例えば:
Client: FIN=1, opcode=0x1, msg="hello" Server: OK. Hi. Client: FIN=0, opcode=0x0, msg="and a" Server: (listening silently) Client: FIN=0, opcode=0x0, msg="happy new" Server: (listening silently) Client: FIN=1, opcode=0x0, msg="year!" Server: OK. Happy new year to you too.
Decoding Payload Length
payloadの長さを表すpayload lengthというフィールドがあります。payload lengthを読み込むには、残念ながら少し複雑な手順が必要となります。手順は以下の通りです:
- 9-15番目のビットをunsigned integerとして読み込み、 値が125以下の場合は、それがpayload lengthとなります。値が126の場合は手順2に、127の場合は手順3に進みます。
- 次の16ビットををunsigned integerとして読み込みんだ値がpayload lenghtになります。
- 次の64ビットををunsigned integerとして読み込みんだ値がpayload lenghtになります。
Reading and Unmasking the Data
TODO. このセクションは追記が必要です。
Miscellaneous
WebSocketのコード、拡張、サブプロトコルはIANA WebSocket Protocol Registryに登録されています。このページでは上記以外のIANAへのリンクはありません。
WebSocketの拡張とサブプロトコルの使用はハンドシェイク時に取り決められます。時々、拡張とサブプロトコルはほとんど違いがないと思われがちですが、 明確な違いがひとつあります。拡張はデータフレームを管理し、payloadに変更を加えます。一方、サブプロトコルはpayloadに変更は加えません。Extensions are usually optional and generalized; subprotocols are usually mandatory and localized. 拡張、サブプロトコルのユースケースと例を見て行きましょう。
Extensions
このセクションは追記が必要です。どなたかお願いします。
拡張はファイルをEメールで送信する前に圧縮するようなものと考えてください。Dropboxやファイル共有サービスでも構いません。 もしくは、天気予報でデータを補完するようなものでもいいです... 何にしろあなたは同じインターネットを使って、ことなるフォーマットのファイルを送ることができます。受信者はあなたの手元のコピーと全く同じファイルを受け取れるはずです。これが拡張です。WebSocketsはデータ送信のプロトコルと基本的な方法を定義します。拡張は同じプロトコルで、データを圧縮したり統合(multiplex)して送信することができます。
拡張は規格の5.8, 9, 11.3.2および、11.4に記載されています。
TODO
Subprotocols
サブプロトコルはカスタムXML やdoctype宣言のようなものと考えてください。 XMLのシンタックスを叱っていますが、特定の構造に書き方に従う必要があります。WebSocket のプロトコルはそういったものと考えてください。サブプロトコルは派手なものではありませんが、構造を定義することができます。doctypeやschemaのように使用者はサブプロトコルに従う必要があります。doctypeやschemaと異なるのは、サブプロトコルはサーバーサイドで実装されており、クライアントからは明示的に見ることはできません。
サブプロトコルは規格の1.9, 4.2, 11.3.4及び、11.5に記載されています。.
クライアントから特定のサブプロトコルを要求するには、シャンドシェイク時以下のような情報を送ります:
GET /chat HTTP/1.1 ... Sec-WebSocket-Protocol: soap, wamp
もしくは以下のようにも書けます:
... Sec-WebSocket-Protocol: soap Sec-WebSocket-Protocol: wamp
サーバーはサポートするサブプロトコルの中から一つを選びます。もし一つ以上候補がある場合は、最初の一つを選びます。この例では、サーバーがsoap
とwampのサポート
するので、ハンドシェイクのレスポンスとして以下を返します。
Sec-WebSocket-Protocol: soap
サーバーがサブプロトコルの使用を許可しない場合、Sec-Websocket-Protocol
ヘッダを送ってはいけません。空のヘッダを送るのは間違いです。クライアントは要求したサブプロトコルのヘッダが受け取れない場合は、接続を閉じることができます。
サーバーを特定のサブプロトコルをサポートさせるためには、それ用のコードが必要になってきます。例えば送受信データをJSON形式にするjsonサブプロトコルを使いたい場合、っサーバー側に
JSONパーサーが必要となります。実際にはこの部分はライブラリで行われますが、それでもサーバーはライブラリにデータを受け渡し等をする必要があります。
Tip: サブプロトコルの名前の衝突を避けるために、サブプロトコルの名前をドメインの一部にすることをお薦めします。Example Inc.という会社がプロプライエタリなチャットアプリを開発した場合は、このようなヘッダにすると良いかもしれませんSec-WebSocket-Protocol: chat.example.com。
また、プロトコルのバージョンを表すのによく使われるのは、 chat.example.com/2.0
とすることです。この命名ルールは必須ではなく単なる慣習です。あなたの好きな文字をサブプロトコル名としても構いません。