「続・ハイパフォーマンスWebサイト」に書かれたWebSocket記事への更新

続・ハイパフォーマンスWebサイト ―ウェブ高速化のベストプラクティス」というオライリー・ジャパンの書籍の付録に「Web高速化に対するGoogleのアプローチ」と題して、記事を執筆させてもらった。

続・ハイパフォーマンスWebサイト ―ウェブ高速化のベストプラクティス

この「続・ハイパフォーマンスWebサイト」はタイトルの通り、「ハイパフォーマンスWebサイト ―高速サイトを実現する14のルール」の続編だ。「ハイパフォーマンスWebサイト」がそうであったように、本書も実践的なWeb高速化テクニックについて書かれていて、Web開発者は必携の1冊だろう。

以下が目次。付録が日本語版でのみの特典で、Yahoo! JAPANさん、Mozilla Japanさん、そしてGoogleが高速化についてその考えやテクニックを書いている。私は最後の最後までほかの方が何を書かれているか知らなかったのだが、それぞれ違う観点から書かれていて、なかなか面白い。

1章 Ajaxアプリケーションとパフォーマンス
2章 応答性の高いウェブアプリケーション
3章 初期ロードの分割
4章 実行をブロックしないスクリプトのロード
5章 非同期のスクリプトの組み合わせ
6章 インラインスクリプトの適切な位置
7章 効率的な JavaScriptコード
8章 Comet
9章 gzip圧縮再考
10章 画像の最適化
11章 主ドメインの細分化
12章 ドキュメントのフラッシュ
13章 iframeの取り扱い
14章 CSSセレクタの単純化

付録A パフォーマンス関連ツール
付録B Yahoo! JAPANが実践する Webの高速化
B.1 CSSスプライト −事例: Yahoo! JAPANトップページ
B.2 画像の軽量化 −事例: Yahoo!ニュース
B.3 FirstByte−事例: Yahoo! JAPANウェブ検索ページ
B.4 消費リソースの分析 −事例: Yahoo!検索のフロントエンド
B.5 効率的な開発とパフォーマンスを意識した運用
付録 Cブラウザの最新技術を活用した Webの高速化
C.1 プラットフォームとしてのブラウザ
C.2 JavaScriptエンジンを意識した高速化
C.3 ブラウザの処理を意識した高速化
C.4 ブラウザの最新機能を活用した高速化
C.5 ユーザー体験を意識した高速化
付録 D Web高速化に対する Googleのアプローチ
D.1 Webの高速化
D.2 Web標準採用 /推進による高速化の利点
D.3 Web SocketsとSPDY
D.4 Steve Soudersとの一問一答


ちなみに、前編の「ハイパフォーマンスWebサイト」の目次は以下。

A章 フロントエンドのパフォーマンスの重要性
B章 HTTPの概要
1章 ルール1:HTTPリクエストを減らす
2章 ルール2:CDNを使う
3章 ルール3:Expiresヘッダを設定する
4章 ルール4:コンポーネントgzipする
5章 ルール5:スタイルシートは先頭に置く
6章 ルール6:スクリプトは最後に置く
7章 ルール7:CSS expressionの使用を控える
8章 ルール8:JavaScriptCSSは外部ファイル化する
9章 ルール9:DNSルックアップを減らす
10章 ルール10:JavaScriptを縮小化する
11章 ルール11:リダイレクトを避ける
12章 ルール12:スクリプトを重複させない
13章 ルール13:ETagの設定を変更する
14章 ルール14:Ajaxをキャッシュ可能にする
15章 米国トップ10サイトの分析

まさに、あわせて読みたい という感じだろう。

WebSocketの一部仕様変更の議論

この付録D「Web高速化に対するGoogleのアプローチ」(269p〜)でWebSocketの解説もしているのだが、執筆時から仕様が変更になっている*1。ここでその主な変更点を解説する。

まず、プロトコル名であるが、WebSocketsからWebSocketに最近では統一されつつある。ここでも、WebSocketで統一して書くこととする。

WebSocketハンドシェイク

P.275の「D.3.1 Web Socketsとは」でWebSocketのハンドシェイクでHTTPからWebSocketに通信がアップグレードされる様子を解説している。
以下がクライアントからサーバーに送られるパケットヘッダとして書籍に書いたものである。

GET /demo HTTP/1.1 
Upgrade: WebSocket
Connection: Upgrade 
Host: example.com 
Origin: http://example.com 
WebSocket-Protocol: sample

このフィールドのうち、HostとOriginの扱いがMustからShouldに変更になっている。すなわち、HostとOriginは無くても構わない。

また、WebSocket-Protocolというフィールド名がSec-WebSocket-Protocolに変更されている。フィールド名が変更されたことに加えて、Sec-WebSocket-Key1とSec-WebSocket-Key2という乱数で生成した文字列が入るフィールドが追加された。これは、いわゆるチャレンジアンドレスポンスのチャレンジにあたる値を送るフィールドである。これらのフィールドと次に説明する、8バイトのボディがサーバー側でレスポンスに生成に利用される。この一連のセキュリティ対策はWebSocketサーバーにWebSocket以外で接続するクロスプロトコルアタックを防ぐためにとられたものである。

最新の仕様に準拠したクライアントからサーバーへのヘッダの例は次のようになる。

GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
Sec-WebSocket-Protocol: sample
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
Origin: http://example.com

この例にあるように、フィールドの順番はランダムで構わなくなったのも変更点の1つだ。このヘッダの後、CRLFを挿入し、8バイトのボディが入る。

一方、サーバーからクライアントへのパケットヘッダは書籍では次のように書いた。

HTTP/1.1 101 Web Socket Protocol Handshake 
Upgrade: WebSocket
Connection: Upgrade 
WebSocket-Origin: http://example.com 
WebSocket-Location: ws://example.com/demo 
WebSocket-Protocol: sample 

まず、"HTTP/1.1 101 Web Socket Protocol Handshake"の部分であるが、ステータスコードのみが利用されるため、バージョン番号やステータスコード以降の文字列(理由フレーズ)は何でも構わない。また、WebSocket-Origin、WebSocket-Location、WebSocket-Protocolはそれぞれ、Sec-WebSocket-Origin、Sec-WebSocket-Location、Sec-WebSocket-Protocolとなった。

以下が最新仕様のヘッダの例となる。

HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/demo
Sec-WebSocket-Protocol: sample

このヘッダの後に、CRLFを2個挿入し、16バイトのボディが入るが、これがクライアントから送られたチャレンジであるSec-WebSocket-Key1とSec-WebSocket-Key2およびクライアントパケットのボディの8バイトデータから MD5をとって生成されたものとなる。

また、新たに、クロージングの際のハンドシェイクも追加された。セッションを終わらせるときには、このクロージングハンドシェイクを送る。送るのは、クライアントとサーバーのどちらであっても構わない。

セッションの終了を希望する側は長さが0x00の0xFFフレームを送信する。受け取った側はまったく同じ0xFFフレームを送信する。相手からの0xFF 0x00を受け取ったら、TCPを閉じる。これにより、クライアントとサーバーで同時にクロージングハンドシェイクを行ってしまった場合でも、きちんと処理されることが保証される。

このクロージングハンドシェイクはTCPのクロージングハンドシェイク(FIN/ACK)の置き換えとして機能する。プロキシなどが介在する場合など、エンドトゥエンドにおいてTCPのクロージングハンドシェイクが必ずしも信頼性が高いわけではない。そのため、このWebSocket側でのクロージングハンドシェイクが用意された。

以上がプロトコルでの変更点である。最新の仕様は http://www.whatwg.org/specs/web-socket-protocol/ から入手できる。

WebSocket API

同じくP.275の「D.3.1 Web Socketsとは」で、WebSocket APIについても解説した。インターフェイスを書籍では以下のように示した。

[Constructor(in DOMString url, in optional DOMString protocol)]
interface WebSocket {
  readonly attribute DOMString URL;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSED = 2;
  readonly attribute unsigned short readyState;
  readonly attribute unsigned long bufferedAmount;

  // networking
           attribute Function onopen;
           attribute Function onmessage;
           attribute Function onclose;
  boolean send(in DOMString data);
  void close();
};
WebSocket implements EventTarget;

これが以下のように変更になっている。

[Constructor(in DOMString url, in optional DOMString protocol)]
interface WebSocket {
  readonly attribute DOMString URL;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSING = 2;
  const unsigned short CLOSED = 3;
  readonly attribute unsigned short readyState;
  readonly attribute unsigned long bufferedAmount;

  // networking
           attribute Function onopen;
           attribute Function onmessage;
           attribute Function onerror;
           attribute Function onclose;
  boolean send(in DOMString data);
  void close();
};
WebSocket implements EventTarget;

http://dev.w3.org/html5/websockets/#the-websocket-interface から抜粋)

状態(ステート)として、CLOSINGが追加され、イベントハンドラとしてonerrorが追加されている。

CLOSING状態はプロトコルの変更点で説明した、クロージングハンドシェイクがかけられている状態を示す。また、onerrorイベントハンドラは エラーフレーム受信時に行う処理を指定することができる。

また、closeイベントハンドラでは、CloseEventがつかわれて、wasCleanというプロパティできちんととcloseできたかどうかが確認できるようになった。

*1:正確にはまだ仕様変更の議論がされており、まだ確定ではない