前回のHTTP/2.0接続試験参加(標準化作業編)に続き、今回お届けするのは技術解説編。既存のSPDYでは使われていないようなHTTP/2.0で新しく議論された技術仕様、相互接続試験のポイントとなった技術要素などを中心にレポートします。
HTTP/2.0相互接続試験で重要な技術要素の概要
SPDYを技術ベースにして検討されているHTTP/2.0仕様は、現在60ページ弱の分量です。従来のHTTP/1.1と異なりバイナリー通信を基本とするため、その多くはフレームフォーマットの説明に割かれています。HTTP/2.0で新しく導入されたヘッダ圧縮の仕様(HPACK)は、現在HTTP/2.0の仕様と分離されていますが、将来的には統合することも検討されています。
HTTP/2.0は、既存のHTTP/1.1のセマンティクスを保持したままフレームやストリームといった単位でバイナリー通信を行うので、この点過去のSPDYの開発経験や運用実績を活かすことができます。他方、既存のSPDYでは使われていないようなHTTP/2.0で新しく議論された技術仕様については、まだ未知数な部分が多く残っています。ここでは後者に該当する項目、先に述べた相互接続試験のポイントとなった2つの技術要素について、簡単に解説します。
初期接続:HTTP/2.0の接続方法は3種類で2段階
相互接続試験における一番最初の技術的なハードルは、初期接続の確立です。将来HTTP/2.0の仕様化が完了すると、ある日突然今利用している全てのウェブサーバやブラウザーを一斉にHTTP/2.0対応に変えなければならなくなる、というわけではありません。HTTP/1.1とHTTP/2.0はインターネット上で共存できるよう設計されており、皆さんはHTTP/1.1をこれからもずっと使い続けることができるでしょう。その共存を可能にする技術が、HTTP/2.0の初期接続仕様です。ブラウザやサーバは、この初期接続を通じて相手がHTTP/2.0を使うことができるのかどうかを知り、従来のHTTP/1.1を使うのか、それとも新しいHTTP/2.0で接続するのかを正確に判断することができるのです。
初期接続の第1段階
HTTP/2.0の初期接続は、2段階のステップに分かれています。 第1段階は、次の3種類の接続方法を規定しています(図1)。
1. TLS+ALPN
ALPN(Alternate Protocol Negotiation)は、TLSのハンドシェイク時に拡張フィールドにクライアントから利用可能なプロトコルリスト送信し、そのリストを受け取ったサーバが利用プロトコルを決定する方法です。 SPDYでは、クライアント側が利用プロトコルを決定するNPN(Next Protocol Negotiation)仕様を利用していました。IETFのtlsワーキンググループの議論の過程で、TLSハンドシェイク後のプロトコル選択方法としてNPNではなく、ALPNを採用することが決定されました。ALPN仕様の採用には様々な理由がありますが、セキュリティ通信の多くは基本的にサーバ側が決定権を持つというのが理由の一つです。 クライアント・サーバ間でTLSのネゴシエーションが終り、ALPNでその後続けてHTTP/2.0通信を行うことが決まると、次に2段階目の初期接続に移ります。
今回8月の接続試験時は、opensslのALPN対応パッチの導入がまだ公表されていなかったため、iij-http2 を含めたいくつかの実装は、ALPNの代替としてNPNを利用して試験しました。
2. HTTP Upgrade
従来のHTTP/1.1リクエストを行うと同時にクライアントから Upgrade ヘッダを送信して HTTP/2.0 接続に切り替える方法です。これは、WebSocketで行われている方法と同じ切り替え方式になります。サーバが、HTTP/2.0に対応していれば、ステータスコード 101 (Switching Protocols)をクライアントに返し、第2段階目の初期接続に移ります。Upgrade時にクライアントからHTTP/1.1で送信されたHTTPリクエストは、サーバ内部で最初のHTTP/2.0のリクエストとして扱われます。サーバ側では、Upgradeの有無によってHTTP/1.1とHTTP/2.0の両方を処理可能にする必要があるため、 iij-http2ではクライアントのみこの接続方式を実装しました。
3. Direct接続
あらかじめクライアントの接続先がHTTP/2.0に対応していることを知っていれば、(1)や(2)の初期接続を行わずいきなり第2段階目の初期接続から開始することも可能です。どのようにして事前にHTTP/2.0対応であることを知ることができるのかは、HTTP/2.0仕様とは別に決めることになっています。現在のところ、DNSレコードを見て判断する方法やAlternate-Protocolヘッダを見てリダイレクションを行う方法などが候補とされていますが、具体的な仕様の検討はまだ始まっていません。
初期接続の第2段階
第2段階目の初期接続は、HTTP/2.0通信を開始する最終確認です。このステップでは、24バイトのコネクションヘッダというバイト列(505249202a20485454502f322e300d0a0d0a534d0d0a0d0a) をクライアントからサーバに送信します(図2) 。
これは、 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n という文字列を表したものです(注1)。もし第1段階を経由して間違ってHTTP/1.1のサーバにアクセスしてしまっても、HTTP/1.1のサーバは PRIというメソッドとHTTP/2.0というサポートされていないバージョンを持ったHTTPリクエストを受け取ったと解釈し、400 (Bad Request) のエラーを返して直ちにコネクションを切断するでしょう。一方、 HTTP/2.0のサーバは、このバイト列を受け取れば確かにHTTP/2.0のクライアントからの接続であることの確証が取れますので、安心してHTTP/2.0の処理を継続することができます。
このバイト列を受け取ったことを確認したら、クライアント・サーバ双方で設定情報を通知するSETTINGSフレームを送信し、HTTP/2.0の初期情報を交換します。これをSETTINGSフレームによって、通常データのフロー制御を行う初期ウィンドウサイズや同時オープンできるストリーム数の最大数などの設定情報が定まります。
以上、第2段階までのHTTP/2.0初期接続のステップを完了して初めて通常のHTTPリクエスト・レスポンスといった処理を開始することができます。今回の相互接続試験では、試験対象の実装がサポートしている初期接続に対してちゃんと仕様が想定している通りに動作し、お互いに第二段階まで問題なく完了できるのかといったところが一つの技術的なマイルストーンでした。
実装者からのフィードバック議論において、既に大規模にSPDY向けにTLS+NPNを実サービスに展開しているGoogleなどから、HTTP/2.0でTLS+ALPNが採用されたため、今後どう両者を共存させながら大規模試験をしていくのか、そして最終的にどのようにしてSPDYからHTTP/2.0に移行していくか、という運用上の問題が懸念として示されました。これに伴いHTTP/2.0に関する dev ops の議論を行う場を新たに作る提案がなされています。
ヘッダ圧縮(HPACK):脆弱性を克服、これまでのWebにはない新しい試み
現状HTTP/1.1で利用されているヘッダ情報は、毎回同じデータを送るように冗長になっている場合が多く、その容量も次第に増加しています。特にネットワーク帯域が制限されているモバイル環境では、冗長なHTTPヘッダ情報のやり取りは帯域を圧迫し、その影響は無視できないものになっています。この状況を解決するためSPDYでは、deflateという汎用的な圧縮アルゴリズムを利用してヘッダ情報の通信容量を圧縮しました。
しかし2012年9月に、deflateを用いた場合にヘッダ情報が暗号化された通信上でも漏えいできることをセキュリティ研究者により明らかにされました。彼らはCRIMEというツールを利用することにより、特定の環境下で短時間にクッキーなどの重要なヘッダ情報を取得することを示しました。現在SPDYを利用しているブラウザーでは、この脆弱性に対応するために暫定的にヘッダ圧縮をしない対策がとられています。しかしモバイル環境などでヘッダ情報の送受信を削減することは急務の課題です。
皮肉なことにこの脆弱性は、データ圧縮アルゴリズムによって送信データが最適化されてしまったことを手掛かりにヘッダ情報を取得する手法を使っています。このため一般的な情報圧縮手法では脆弱性を回避できない可能性もあり、HTTP/2.0ではHTTPのリクエスト・レスポンスに特化した目的でヘッダ情報のデータ量を削減する新たな仕様を導入する必要に迫られました。
当初新たなヘッダ圧縮手法として、 Delta2 と Header-Diff という2つの仕様が候補として検討が進められていました。調整の結果、第2回の中間会議の直前にこの2つを合わせた Header-Compression 仕様(現HPACK仕様)が両仕様の共著として提出されました。最終的にこれを実装仕様として採用することが決定し、相互接続試験では一部を改良した仕様(Header-Compression-01)を使ってテストが行われました。
HTTPリクエストとレスポンスは、HTTPヘッダのやり取りをせずに実現できるものではありません。相互試験の参加者は、Header-Compressionのリリース後初めてこのヘッダ圧縮仕様を実装します。今回の相互接続試験のもう一つの技術トピックは、この新しいヘッダ圧縮の仕様を使って本当にHTTPリクエスト・レスポンスのやり取りが実現可能か実証する所にありました。
HPACKの仕様は、これまでHTTPで用いられてきた通信手順と全く異なり、新しいコンセプトに基づくものです。その手順は細かく規定されていますが、全てを説明するとわかりにくくなるため、若干正確性を犠牲にしますが一部簡略化してその仕組みの概要を解説します。
この仕様の基本は、サーバ・クライアントがヘッダ情報を記載したヘッダテーブルとreference set という2つのデータ集合を状態として保持することにあります。このヘッダテーブルとreference setは、HTTPリクエスト・レスポンスそれぞれに用意されるものです。ヘッダテーブルは、初期状態としてあらかじめ数十種類のヘッダ情報が番号付きで登録されています。 reference set は、1つ前のHTTPリクエスト・レスポンスの送受信によって有効になったヘッダ情報を保持しています(図3)。reference set の初期状態は空です。 クライアントとサーバは、HTTPリクエスト・レスポンスが行われる度に reference set やヘッダテーブルに対してどういうヘッダ情報を追加・削除・変更するのかといった差分操作をやり取りします。この差分操作を表すやり方は、その種類や操作によって4種類のエンコーディング方式が定義されています(注2)。受信側は、エンコードされた差分操作のデータを解析し、記載された操作に従ってヘッダテーブルやreference set を更新します。その結果、サーバとクライアントは同じヘッダテーブルと reference set を持ち、ブラウザやWebアプリケーションはこの新しい reference set を有効になヘッダ情報として利用することになります。
具体的な例を見たほうがよりわかりやすいでしょう。一例としてまずクライアントが一番最初に
:scheme http
:host serverA
:path /
xmyhoge hoge
のヘッダ(空白を除いて39バイト)情報をサーバにHTTPリクエストとして送信する場合を考えます。この場合、次の5つのステップでヘッダ情報の差分操作として表します。
- 0番のヘッダテーブルをそのまま使う
- 2番のヘッダテーブルを serverAに書き換える
- 3番のヘッダテーブルをそのまま使う
- 4番のヘッダテーブルをそのまま使う
- xmyhogeは新しくヘッダテーブルに採番して値にhogeを入れ込む
この差分操作を規定されたエンコード書式で表すと、
0x80
0x03 0x02 0x07 serverA
0x83
0x84
0x40 0x07 xmyhoge 0x04 hoge
のような形式になります。これは全27バイトのデータ容量で、HTTP/2.0のフレームに付与してクライアントからサーバへ送信されます。従来のヘッダ情報の送信(39バイト)よりもデータ容量が減っていることがわかります。このヘッダ操作データを受け取ったサーバは、エンコード情報を解析し、初期状態が空のreference setとヘッダテーブルに対して1から5の差分操作を行います。結果、クライアントと同じ有効なヘッダ情報を持つreference setを作成し、Webアプリケーションに渡します(図4)。
次にクライアントが2回目のリクエストする際には、この新しいreference setに対して、ヘッダの追加、削除、変更という差分操作情報をサーバに送信します。もしクライアントが、
:scheme http
:host serverA
:path /foo.html
xmyhoge hoge
のヘッダ情報(47バイト)を2回目に送信する場合は、今保持するreference setに対して:pathのヘッダ情報だけ更新を指示する操作情報のみサーバに送ることになります(3番のヘッダテーブルを/foo.htmlに書き換える操作)。 このエンコード方式は、
0x04 0x03 0x09 /foo.html
と表すことができます。これは、わずか12バイトです。このデータをクライアントからサーバに送信し、サーバは、reference set とヘッダテーブル:pathの値を更新します。HPACKを使わなければ47バイトのヘッダ情報を送付しなければならないことが、HPACKの利用で12バイトを送付するだけに縮小できるのです(図5)。
もし3回目のリクエストが2回目と同じヘッダ情報を持つリクエストなら、更新操作は必要なくなり、送付する差分操作情報は0バイトになります。クライアントは、ペイロードを空にしてフレームヘッダだけを送信すればよいのです(図6)。 このように冗長で繰り返し同じヘッダ情報を送付する場合に、HPACKはその効果が最大限に発揮される仕様であると言えます。
この方式は、SPDYで利用されていたdeflateの圧縮アルゴリズムと全く異なるものであるため先ほどのCRIME攻撃の脆弱性は存在しないものと考えられています。当初この仕様を見て実装を始めた時には、その変わり様に非常に驚きました。しかし、実際に相互接続試験では初めての実装試験であるにもかかわらず、各実装に対して問題なくHTTPリクエスト・レスポンスのやり取りができることを確認できました。
しかし様々な課題がまだ残っています。例えばメモリの対策のためヘッダテーブルは容量を制限しなければなりません。その容量を超えた場合の更新処理はどうなるのか。また実利用のヘッダ情報を使っても長時間継続してヘッダ情報の更新を行った場合でも、クライアント・サーバ間でヘッダ情報の整合性を保てるのか、といったような課題が議論され今後更なる仕様の見直しと試験を続けていく必要がある、ということが合意されました。
このように従来とは全く異なった手法でヘッダ情報がやり取りされるということは、HTTP/2.0の大きな特徴です。今後HTTP/2.0の導入や運用を行う際には、これまでの古い知識にとらわれず新しく頭を切り替えてHTTP/2.0に取り組んでいく必要がありそうです。
まだまだある、技術的なチャレンジ
上記2つの技術トピックスは、初回の相互接続試験で特に問題になりそうなものだけを解説しました。これ以外にも、
- クライアント・サーバ間でフレームの送受信の競合状態が発生しても問題は発生しないか
- リクエストの予約という形の新しいサーバプッシュ機能はクライアントへキャッシュを正常に送り込めるのか
- フロー制御や優先度設定はいろんな環境でも有効に機能するのか
などなど、他にも解決しなければならない技術的課題がまだ数多く残っています。
とはいえ、近い将来インターネットで主流となる新しいプロトコルを世界中の優秀な技術者と一緒に作り上げていく過程は、エンジニアとして非常に貴重な機会であり、とてもワクワクする瞬間です。このレポートを読んでいただいている方に、このワクワク感が少しでも伝わればうれしいことだと思っています。
(注: 本記事は、2013年8月時点でのHTTP/2.0仕様ドラフトに基づくものです。今後仕様が変更され記述内容と異なる可能性がありますので、ご注意ください。)
(注1) これ(PRI SM)は、あのスノーデン事件が起きた時にエディターがシャレで付けた文字列です。現時点でこの文字列を正式に決めるようとすると議論がまとまらない可能性があるので仮の値になってます。このようにどうでもいいことで議論が発散することをbikeshed(自転車置き場)と呼んでワーキンググループ内で議論する時に注意しています。
(注2) この4種類のエンコーディング方式は、
- Indexed Header Representation
- ヘッダテーブルの数字指定だけで有効・無効をトグルさせる操作方式
- Literal Header without Indexing
- ヘッダを文字列指定してヘッダテーブルの更新しない操作方式
- Literal Header with Incremental Indexing
- ヘッダを文字列指定してヘッダテーブルの最後に追加する操作方式
- Literal Header with Substitution Indexing
- ヘッダを文字列指定して既存のヘッダテーブルの値を更新する操作方式