Hatena::ブログ(Diary)

gtk2kの日記

2012-02-03

1.WebSocket仕様解説 実装WebSocketクライアント対応プロトコルバージョン確認編 (2012/09/28 Update)

WebSocket API(日本語訳)は、全二重の双方向通信を可能にするAPIで、これによりリアルタイムなWebアプリケーションを実現することができるようになります。WebSocketのプロトコル側の仕様が半年ほど前にRFCとして策定され、ようやく落ちついたところです。(まだ仕様変更がある可能性がないとは言い切れないけど)
※なお、ブラウザーは現時点(2012/07/26)での各最新のブラウザーを対象とします。(Chrome20,Firefox14,Opera12,Safari5+Safari6)
また、ブラウザーに実装されているWebSocket(API)のことを"WebSocketクライアント"と呼ぶことにします。

WebSocketクライアントが実装されているブラウザー

主要なブラウザーのうち現在においてWebSocketクライアントを実装しているのはChrome,Firefox,Opera,Safariの4つです。
また、モバイル(スマートフォン、タブレット)ではiOSのSafari/Chrome、AndroidのChrome/Firefox/Opera MobileがWebsocketクライアントを実装しています。
しかし、現在において対応しているプロトコルバージョンが一部のブラウザーにおいてまだ新しいバージョンに対応していません。
次の項目で対応しているプロトコルバージョンを確認してみます。

実装されているWebSocketクライアントが対応しているプロトコルバージョンを実際に確認してみる

さて、実際にブラウザーがどのプロトコルバージョンに対応したものを実装しているのか確認してみましょう。ただし、ブラウザー上で確認する方法がないので、WebSocketで接続を開始するときにクライアントがサーバーに送るハンドシェイクリクエストを見ることで対応している大体のプロトコルバージョンを確認してみます。
そのためにはちょこっとプログラムを組む必要があります。
ハンドシェイクリクエストを取得するアプリを作成する
C#を使用してハンドシェイクリクエストを取得するアプリを作成します。 コンソールアプリケーションプロジェクトを新規に作成し、main関数内に以下のようにコードを記述したら実行します。
static void Main(string[] args)
{
    // usingに以下の行を追加
    // using System.Net.Sockets;
    // using System.Net;
    // using System.Text.RegularExpressions;
    Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    // 仕様においてWebSocketのポートは80番ポート(wss://(SSL接続)の場合は443番ポート)でなければならないとなっていますが(MUSTではない)、
    // 80番ポートが(IISなどで)使用されている場合がありますので今回は8181番ポートを使用することにします。
    IPEndPoint ipe = new IPEndPoint(IPAddress.Any, 8181);
    listenerSocket.Bind(ipe);
    listenerSocket.Listen(100);
    while (true)
    {
        Socket workerSocket = listenerSocket.Accept();
        byte[] buffer = new byte[1024];
        int len = workerSocket.Receive(buffer);
        string handShake = Encoding.UTF8.GetString(buffer, 0, len);
    // GETリクエストで送られてくるパス部分を取得
        string browser = Regex.Match(handShake, @"^GET\s*/([^\s]+)", RegexOptions.IgnoreCase).Groups[1].Value;
        Console.WriteLine(browser + "のハンドシェイクリクエスト");
        Console.WriteLine(handShake);
        Console.WriteLine();
        workerSocket.Close();
    }
}

実行すると、下記のような警告ダイアログが表示される場合があります。表示された場合は"アクセスを許可する"ボタンをクリックしてください。
f:id:gtk2k:20120130154438p:image

実行したら、各ブラウザーからWebsocketの接続を試してみます。(各ブラウザーの開発ツールのコンソール表示までの手順は割愛させていただきます。)

Opera (ver12.00)
Operaはセキュリティの問題があるとしてデフォルトではWebSocketが無効となっていますのでまずはOperaでWebSocketを有効にしましょう。一応セキュリティの問題があることは認識しておいてください。こちら@komasshuさんがこのセキュリティ問題を詳しく解説しています。(kanasansoftさん情報提供ありがとうございます)
OperaのDragonflyコンソール画面にて new WebSocket('ws://localhost:8181/Opera') と入力してエンターキーを押します。
すると、ハンドシェイク取得アプリのコンソール画面に
Operaのハンドシェイクリクエスト
GET /Opera HTTP/1.1
Origin: http://yahoo.co.jp
Host: localhost:8181
Connection: Upgrade
Sec-WebSocket-Key2: 2 6 85)9C0e   4-0O UJ8P6
Sec-WebSocket-Key1: 17^a73.73ND   &_2111
Upgrade: WebSocket

@09\=p{~
といった感じで出力されると思います。これがハンドシェイクリクエストのデータです。
見ていただくと分かる通り、ハンドシェイクリクエストはHTTPのGETリクエスト形式で送られてきます。 ハンドシェイクリクエストを見てみると、Sec-WebSocket-Key1/Sec-WebSocket-Key2というフィールドが見られます。これはプロトコルバージョンhybi-00(hixi-76)〜hybi-03で採用されているハンドシェイク方法で使用されるフィールドですので、Operaはhybi-00(hixi-76)〜hybi-03のいずれかに対応したWebSocketクライアントが実装されているということがわかります。(以降、ハンドシェイク取得アプリは実行したまにしてください。)

Safari (ver5.1.7)
同様にSafariの開発ツールのコンソールにて new WebSocket('ws://localhost:8181/Safari') と入力してエンターキーを押します。
Safariのハンドシェイクリクエスト
GET /Safari HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: localhost:8181
Origin: http://www.yahoo.co.jp
Sec-WebSocket-Key1: 41Sb   1vT   7 A166 3  l70
Sec-WebSocket-Key2: _ 5i[5 ]5g4 k3\ tt037>6s

?,m?YZ??
こちらもOperaと同様、Sec-WebSocket-Key1,Sec-WebSocket-Key2が見られますので、Safariもプロトコルバージョンhybi-00(hixi-76)〜hybi-03のいずれかに対応したWebSocketクライアントが実装されているということがわかります。

Chrome (ver20.0.1132.57 m)
同様に開発ツールのコンソール画面にて new WebSocket('ws://localhost:8181/Chrome') と入力しエンターキーを押します。
Chromeのハンドシェイクリクエスト
GET /Chrome HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8181
Origin: http://www.yahoo.co.jp
Sec-WebSocket-Key: 8cRxNay03B6I/Iu/roDBfQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
すると、今度はSec-WebSocket-Key1やSec-WebSocket-Key2というものがなく、Sec-WebSoket-Keyというのが1つだけとなりました。また、Sec-WebSocket-Versionというが追加されています。
プロトコルバージョンhybi-04からハンドシェイク方法の大幅な仕様変更が行われました。この仕様変更により以前のプロトコルバージョンとは互換性がありません。Sec-WebSocket-Key1/Sec-WebSocket-Key2は廃止され、代わりにSec-WebSocket-Keyというフィールドが1つになりました。同様にhybi-04からSec-WebSocket-Versionというフィールドが追加されました。これでWebSocketクライアントがどのプロトコルバージョンに対応している("要求している"といったほうが正しいかな?)かがわかるようになりました。だた、このSec-WebSocket-Versionの値が実際のプロトコルバージョンと1:1に対応しているわけではありません。
Sec-WebSocket-Versionの値4567813
プロトコルバージョン(hybi-)0405060708〜1213〜RFC
これらのことにより、Chromeにはhybi-13〜RFC(hybi-17)のいずれかに対応したWebSocketクライアントが実装されているということがわかります。

Firefox (ver14.0.1)
Firefoxの場合は、Chromeなどの(JavaScriptがデバッグできる)開発ツールが実装されていないため、アドオンをインストールする必要があります。Firefoxの開発ツールアドオンではFirebugが有名でしょう。このFirebugを使用することとして話を進めます。
Firebugをインストールしていない場合は、アドオンマネージャで検索してインストールしてください。インストールするとブラウザーの右上にFirebugのアイコン f:id:gtk2k:20120131122659p:image が追加されます。そのアイコンをクリックするとFirebugが起動します。 起動するとFirebugのメニューに「コンソール」という項目がありますのでそこをクリックします。クリックすると一番下に(>>>)が表示されますので、そこに new MozWebSocket('ws://localhost:8181/Firefox') と入力してエンターキーを押します。
Firefoxのハンドシェイクリクエスト
GET /Firefox HTTP/1.1
Host: localhost:8181
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 13
Origin: http://www.yahoo.co.jp
Sec-WebSocket-Key: uqj8RVY6epCMoZIAjtNzkQ==
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Firefoxのハンドシェイクリクエストはにぎやかです。User-Agentなども送られてきます。見ていただくとわかるようにChromeと同様Sec-WebSocket-KeyやSec-WebSocket-Versionというフィールドが見られます。FirefoxのSec-WebSocket-Versionの値も13となっているので、こちらも同様にプロトコルバージョンhybi-13〜RFC(hybi-17)のいずれかに対応したWebSocketクライアントが実装されているということがわかります。

おまけにMacのMountain Lionに搭載されたSafari6のハンドシェイクリクエストも確認してみる
2012/07/25にMax OS Xの最新メジャーバージョンMountain Lionがリリースされました。Mountain LionではSafariの最新バージョンSafari6が搭載されています。
このSafari6のハンドシェイクリクエストも確認してみます。
Safari6のハンドシェイクリクエスト
GET /Safari HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8181
Origin: http://www.yahoo.co.jp
Sec-WebSocket-Key: 931/dHGCRqK2asjfEOUMsA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
ChromeやFirefoxと同様にSec-WebSocket-Versionが13となっており、プロトコルバージョンhybi-13〜RFC(hybi-17)のいずれかに対応したWebSocketクライアントが実装されていることがわかります。

2012/08/04追記 さらにおまけにOpera 12.50のハンドシェイクリクエストも確認してみる
ちなみにOperaの12.50にようやくRFC版が実装されるとのことです(Opera Developer News)。いままでデフォルトでは無効となっていたのですが12.50になるとデフォルトで有効となるようです。12.50のスナップショットのインストーラーでインストールしてハンドシェイクリクエストを確認してみます。
GET /Opera HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:8181
Origin: http://www.yahoo.co.jp
Sec-WebSocket-Key: Bv+wYgQ6D0M7fiSQu9q7bA==
Sec-WebSocket-Version: 13
こちらもChromeやFirefoxと同様にSec-WebSocket-Versionが13となっており、プロトコルバージョンhybi-13〜RFC(hybi-17)のいずれかに対応したWebSocketクライアントが実装されていることがわかります。


ついでにモバイル版のハンドシェイクリクエストも見てみましょう。

Android版 Chrome(ver18.0.1025123)
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 115.125.114.151:8181
Origin: http://yahoo.co.jp
Sec-WebSocket-Key: 28w71cHJ2aJcHyCZwar6+g==
Sec-WebSocket-Version: 13

Android版 Firefox Beta(ver15.0)
GET / HTTP/1.1
Host: localhost:8181
User-Agent: Mozilla/5.0 (Android; Tablet; rv:15.0) Gecko/15.0 Firefox/15.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 13
Origin: http://yahoo.co.jp
Sec-WebSocket-Key: 54z+S4sLcELj7LjU/D83fA==
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Android版 Opera Mobile(ver12.0.4)
GET / HTTP/1.1
Host: localhost:8181
Origin: http://yahoo.co.jp
Sec-WebSocket-Key1: '4 0531 `U18 248
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key2: V c26146e9!885O6

O&A8`:S1I

iPhone iOS Safari (ver iOS5.1.1)
GET /Safari HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: localhost:8181
Origin: http://yahoo.co.jp
Sec-WebSocket-Key1: 2  49  9}T  R8   /  82 6F,9,6
Sec-WebSocket-Key2: Z pA : /  1|\07059M K5   153|S

@'Ab9u1d
2012/09/28追記 iPhone iOS Safari (ver iOS6.0)
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8181
Origin: http://www.gtk2k.net
Sec-WebSocket-Key: pzqLZiggVY8oz8RFzq9Nbw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
iOS6.0になるとSafari6と同等のSafariが実装されました。そのためWebSocketもプロトコルバージョンhybi-13〜RFC(hybi-17)に対応したWebSocketが実装されました。
iOSにもChromeなどの他のブラウザーアプリがありますが、アプリのガイドラインにおいてWebkit Javascriptを使用しなければならいとなっているため、
他のブラウザーアプリでもSafariと同じと思われます。(ちなみにiPhoneのChromeのハンドシェイクリクエストを確認するとSafariと同じでした。(iOS6においても同様でした))
しかし、今回のiOS6に搭載されたApple独自のMapアプリがすこぶる不人気で、iOS6にアップデートするのを控える人たちが少なからずいるようです。
そのため、現在はiOS5とiOS6の2つが存在する状態となり、あまりよろしくない状況となっています。

まとめ

このように、ハンドシェイクで送られてきたデータから、ある程度どのプロトコルのバージョンに対応しているのかがわかりました。
ブラウザープロトコルバージョン
Opera(Windows版及びAndroid版) / Safari(Windows版およびiOS版)hybi-00(hixi-76)〜hybi-03
Chrome / Firefox / Safari6hybi-13〜RFC(hybi-17)
ご存知の方が多いと思いますが、実はWebSocketのWikiなどにはどのブラウザーがどのプロトコルバージョンに対応したWebSocketクライアントを実装しているのかを表にしてまとめていますので、こちらを参考にしていただければ具体的にどのプロトコルバージョンに対応しているのかがわかります。
更に、@koizukaさんが、プロトコルバージョンごとのデータのやり取りにおける差異を表にまとめてくれています。プロトコルの仕様を理解していないとこの表の見方がわかりづらいと思われますが、パッと見てどういった違いがあるのかはわかるかと思います。hybi-15までで止まっていますがhybi-16以降もハンドシェイクやデータのやり取り(データフレームの仕様)はhybi-13と同じです。
2012/08/04追記 OperaにもRFC版が実装されたことにより(Stable版はまだ)、これでようやく実装されているプロトコルのバージョンの違いがなくなる日がさらにいっそう近くなりました。

KanasansoftKanasansoft 2012/02/03 22:52 セキュリティ問題はここが詳しいです。
http://blog.livedoor.jp/kotesaki/archives/1600864.html

gtk2kgtk2k 2012/02/03 23:17 @Kanasansoft ありがとうございます。とても助かりました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証