4/13/2015

HTTP/2解説 (http2プロトコル)

ダニエル・スタインバーグ氏のHTTP/2解説書の超訳。第6章のhttp2プロトコル。

6. http2プロトコル

歴史や政治的な背景はここまでにする。それでは、プロトコルの仕様に飛び込んでみよう。まず、http2を作成する上でのこまごました考え方を説明する。

6.1. バイナリ

http2はバイナリプロトコルである。

一瞬で十分に理解させよう。前からインターネットプロトコルに関わっている人なら、おそらく、これに対して本能的に強く反応するだろう。反論する根拠として、text/asciiで作られたプロトコルが如何に便利か、そしてあなたが何度もサーバにtelnetして手でリクエストを入力してきたかを詳しく説明するだろう。

http2は簡単にフレーミングできるようバイナリである。実際は一般的なテキストベースのプロトコルやHTTP 1.1で、複雑なものの一つはフレームの始まりと終わりだと理解してほしい。任意で入れられる空白、同じことを書くのに色々な書き方があることから移行することで、実装がよりシンプルになる。

また、HTTP1では紛らわしいほど混在していたフレーミングからプロトコル部分を切り離すことがとても簡単になる。

プロトコルが圧縮機能を持つことや、ほとんどがTLSとなる通信では回線上でテキストが読めなくなるため、テキストの価値を減少する。http2でプロトコルレベルで何が流れているかを把握するには、Wiresharkなどのツールを使うことに慣れる必要がある。

プロトコルのデバッグは、curlのようなツールを使ったり、Wiresharkのhttp2 dissector などでネットワークストリームを解析したりすることが必要になる。

6.2. バイナリフォーマット

http2はバイナリフレームを送信する。送信されるものには異なるフレームタイプがあり、タイプ、長さ、フラグ、ストリーム識別子、フレームペイロードと全て同じ構造を持つ。http2の仕様の中では10のフレームが定義されており、HTTP 1.1の機能でいうところのDATAとHEADERSに相当する2つのフレームがもっとも重要なものとなる。このあと、より詳しくいくつかのフレームを説明する。

6.3. 多重ストリーム

http2で送信される各フレームにあって、前節のバイナリフォーマットで説明したストリーム識別子は、ストリームに関係付けられる。そして、クライアントとサーバ間で交換されるフレームの双方向シーケンスはそれぞれ独立している。

(写真)

一つのhttp2接続は、複数のストリームからエンドポインドのインターリービングフレームでも、複数同時にストリームをオープンできる。ストリームが確立され、一方向で使われる、あるいはクライアントかサーバのどちらで共有され、どちらかのエンドポイントからでもクローズできる。ストリームの中で送られるフレームの順番が重要である。

多重ストリームというは、たくさんのストリームが同じ接続で混在されパッケージ化していることを意味する。2つ以上の個々のデータ列が一つのストリームとなり、向こう側で再び分割される。ここに2つの列車がある。

(写真)

そして、それらは多重化の慣習で同じ接続の中に詰め込まれる。

(写真)

http2で、数十から百程度の同時ストリームに気付くだろう。そのため、新しいストリームの作成負担は非常に低くなる。

6.4. プライオリティと依存性

各ストリームはプライオリティを持ち、相手先に伝えられ、そのストリームが重要だと見なされる。

プロトコルの中でどのようにプライオリティが働くかの詳細は、数回変更され、まだ議論されている。要するにクライアントがどのストリームがもっとも重要かを指定でき、依存関係のパラメータがあって、一つのストリームは別のストリームに左右される。

プライオリティは実行中に動的に変更でき、ユーザが画像全体のページをスクロールダウンする際に、どのイメージがもっとも重要かを指定することをブラウザが可能になる。あなたがタブを切り替えたら、ストリームの新しいセットが優先付けされ、いきなりはっきり見えるようになる。

6.5. ヘッダ圧縮

HTTPはステートレスなプロトコルである。簡単に言えば、全てのリクエストは、サーバが前のリクエストの情報やメタデータを保持せずに、サーバがリクエストを処理するのに必要となるだけの情報が必要となる。http2はその枠組みを変更していない。それもまた取り入れている。

これはHTTP繰り返しになる。ウェブページにある画像のようにクライアントが同じサーバから多くのリソースを要求する時、全てが全く同一の巨大な連続したリクエストになる。ほとんど同一の連続したリクエストは時には圧縮が強く求められる。

私が以前述べたように、ウェブページ毎のオブジェクト数は増加している上に、クッキーの利用やリクエストのサイズも徐々に増加している。クッキーも全てのリクエストに含まれる必要があり、普通は多くのリクエストで同じである。

HTTP 1.1のリクエストサイズは実際に徐々に大きくなっており、時には初期TCPウィンドウよりも大きくなっている。そのため、リクエストを全て送ってしまう前に、サーバからACKを返す必要があり、それらを送るのにとても遅くなってしまう。圧縮を支持するもう一つの意見である。

6.5.1. 圧縮は扱いにくい題材である

HTTPSとSPDYの圧縮機能は、BREACHCRIME attackで脆弱性があることが見つかった。ストリームの中にテキストを挿入することで、出力がどのように変化するかが分かるので、攻撃者は何が送られたかを解読することができる。

これらの攻撃の一つでも脆弱にならずにプロトコルで動的コンテンツを圧縮するのは、考察と熟慮が要求される。これはHTTPbisチームがやろうとしたことである。

HPACKというHTTP/2のヘッダ圧縮では、http2ヘッダをとりわけ巧妙に作られた圧縮フォーマットで、別のインターネットドラフトで規定され、厳密にやり取りされる。新しい形式(別のカウンタと共に)は、この圧縮をエクスプロイトすることが難しくなるように、特定のヘッダやフレームの任意のパディングを圧縮せずに中間者に尋ねるビットのようなものを調整する。

HPACKの作者の一人であるRoberto Peon氏の言葉を借りると、HPACKは情報をリークする適合実装が難しくなる、とても速く安価でエンコード/デコードできる、受け手が圧縮したコンテキストサイズを制御できる、プロキシー再インデックスを許可する(例えば、プロキシーの中でフロントエンドとバックエンドの間で状態を共有)、そしてハフマンコードで文字列を高速圧縮するよう設計された。

6.6. Rest - 考え方を変更

HTTP 1.1が持つ欠点の一つは、HTTPメッセージがあるサイズのContent-Lengthで送れた時、それを簡単に止めることができないという点である。時々TCP接続が切断するが(常にではないが、ここではなぜかの長ったらしい理由は飛ばすことにする)、再び新しいTCPハンドシェイクをネゴシエートする必要が生じてしまう。

あなたは、むしろ単にメッセージを停止する方を選び、新しく開始する。これは、http2のRST_STREAMフレームで行うことができる。バンド幅が無駄になるのを防ぐのに役立ち、接続を壊す必要性を取り除ける。

6.7. サーバプッシュ

これは「キャッシュプッシュ」として知られる機能である。ここでのアイデアは、もしクライアントがリソースXを要求すると、サーバはクライアントがリソースZを欲していることを知り、その要求が無くてもクライアントに送信する。キャッシュの中にZを置くことでクライアントを助け、要求があった時に、そこにあるようにする。

サーバプッシュは、たとえクライアントがそれを実行しても、クライアントは明示的にサーバを許可しなければならない。特定のリソースを欲していないなら、自らの選択でRST_STREAMでプッシュされたストリームを早急に終了できる。

6.8. フローコントロール

http2の個々のストリームは、相手方がデータ送信を許可するフローウィンドウを自身で広告する。もし、あなたがどのようにSSHが働くかを知っているなら、これはスタイルと精神(style and spirit)でとてもよく似ている。

入方向データに適合するようたくさんの部屋を持つ必要があるため、双方の全てのストリームは相手に伝えなければならない。そして、相手方はウィンドウが拡張されるまで、多量のデータを送ることを許可するしかない。データフレームのみでフロー制御される。