WebSocket備忘録

社内でWebSocketに関する勉強会をやってきました。今回WebSocket自身を調べ直したことでだいぶ自分の勉強になりました。
普段アプリケーションを書く上であまり気にならないですがプロトコルの中身を知っておくことは重要であまりその辺りを書いている人もいなかったので書いておこうと思います。
詳しくは RFC6455日本語訳に書いてます。

WebSocketとは

ステートフルなプロトコルで、一度接続を行うとその後はサーバからでもクライアントからでも好きなタイミングでデータのやり取りを行うことができる。Googleさん提唱でだいたいどのブラウザからでも利用可能。

WebSocket接続手順

接続確立については最初はHTTPを利用する。
これはファイアウォールやプロキシなど既存のHTTP環境との親和性を持つためにやっている。
またHTTPと同様にTLSを利用した仕組みでセキュアな接続も可能である。

RFC6455の例を引用すると最初のクライアントからのリクエストヘッダは以下のようなものが送られる。(注釈は自分で勝手に追加したもの)

GET /chat HTTP/1.1
# HTTPのバージョン、パス、メソッド
Host: server.example.com
# ホスト名
Upgrade: websocket
# 下記のConnectionヘッダにあるUpgrade要求でwebsocketにUpgradeしてねといっている
Connection: Upgrade
# 接続のWebSocket切り替えを依頼
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
# クライアントはこのキーをランダムに生成する。サーバからのSec-WebSocket-Acceptから複合化して正しい自分へのレスポンスであることを確認する
Origin: http://example.com
# Originヘッダはブラウザから自動で追加される。サーバ側はWebSocket接続していいものかどうか判断するときに利用する
Sec-WebSocket-Protocol: chat, superchat
# WebSocket上でカスタマイズされたプロトコルを利用する場合クライアントは要求を送る。ただしサーバ上でこのカスタマイズされたプロトコルが利用できなければいけない
Sec-WebSocket-Version: 13
# WebSocketのバージョン。RFC6455の場合は13となる

このようなリクエストがくるとサーバ側で解釈して正しかった場合は以下のようなレスポンスを返す。ただし認証が必要な場合401を返すこともできる。

HTTP/1.1 101 Switching Protocols
# プロトコル変更ができましたというレスポンスコード
Upgrade: websocket
Connection: Upgrade
# WebSocketにUpgradeできたことが返る
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# サーバ側でSec-WebSocket-Keyから暗号化した文字列を返す。クライアント側でこれを複合化して検証
Sec-WebSocket-Protocol: chat
# クライアントから送られてきたカスタムプロトコルに対応していたものを返す。これで接続後は'chat'というプロトコルでやり取りが行われる。

Sec-WebSocket-Key及びSec-WebSocket-Acceptに用いられるアルゴリズムは以下のとおり。

  • Sec-WebSocket-Keyに特定文字列を追加
  • SHA-1を利用してハッシュ値を取得
  • それをBase64符号化するとSec-WebSocket-Acceptになる。

これで接続完了となる。

接続が切れてしまった時の対応

もしクライアントもサーバも望んでいないタイミングで接続が切れてしまった場合、RFCを見る限り、クライアントからサーバに再度接続をすることが望ましいとされている。また何度も接続が走るとサーバ側への負荷がかかるかもしれないので少しずつ間隔をのばしつつ接続するとのこと。
Chroniumのソースコードをちょっと見たところその部分の実装がみつからなかったので後日試してみよう。

データのやり取り

一度接続確立されるとフレームという単位でデータのやり取りを行う。
データのやり取りにはHTTP Headerがつかないためネットワークの転送量が削減される。またWebSocketはデータの中身をテキストかバイナリどちらかで送ることができる。バイナリを利用した場合テキストに比べておおよそ30%転送量が削減される。
フレームの種類は4つのタイプがある。

  • Closeフレーム
  • Pingフレーム
  • Pongフレーム
  • Dataフレーム
Closeフレーム

接続終了を行うフレーム。
クライアント及びサーバどちらからでも送ることができる。ただしサーバから送信することが望ましいとされている。理由はサーバからCloseフレームを送った場合TCP接続がTIME_WAIT状態の時にその接続が再利用されないからである。

Pingフレーム

クライアントとサーバの間で接続維持できているか確認する。
一般的Pingは一度送るとすぐに結果がかえってくるがWebSocketでは早急に返すのは必須ではないとされている。(望ましいとはかいてある)HeartBeatの代わりになり得るものだと理解している。

Pongフレーム

PongフレームはPingフレームの応答用フレーム。
ただしPongフレームはPingフレームがこないと送れないものではない。
すなわち一方方向のリクエスト送信に利用することができる。

Dataフレーム

Dataフレームはアプリケーション間でのデータのやり取りに利用する。
テキストないしバイナリでの送信が可能。

ベースフレーミング

RFCを見ると以下のようなフレームになっている。

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 ...                |
+---------------------------------------------------------------+
  • FIN: このフレームがデータの終わりなのかどうかを示す
  • MASK: データがマスク化されているかどうかを示す(クライアントからサーバに送られる場合必ずマスク化される)
  • Payload len: アプリケーションデータの長さを示す
  • Masking-key: マスク化している場合キーデータを置く
  • Payload Data: アプリケーションデータ

クライアントからサーバへのデータのマスキングについて

クライアントからサーバへデータを転送する際必ずデータのマスキングが行われる。手法はシンプルでデータとマスキングキーのXORで表される。フレームにマスキングキーが入っていることからわかるようにこのフレームを盗まれるとデータは簡単に復元されてしまう。
これは元々このマスク化はデータの中身をセキュアにするわけではなくプロキシ汚染による攻撃を防ぐためのマスク化だからである。なのでデータをセキュアにする場合違う方法をとるべきである。

TCPパケットとの違い

http://msdn.microsoft.com/ja-jp/magazine/jj863133.aspx
に書いてます。とてもわかりやすくていい記事だと思います。