2014年11月14日

HTTP/2

HTTP/2 への取り組みの続報と ATS Summit 参加報告

  • このエントリーをはてなブックマークに追加

はじめに

こんにちは。システム統括本部 プラットフォーム開発本部 大久保 諒です。本稿では以前本ブログ記事「Yahoo! JAPANにおけるHTTP/2への取り組み」にてお伝えした、オープンソースのHTTPプロキシサーバーであるApache Traffic Server(以下、ATS)のHTTP/2サポートに向けた活動の続報をお伝えします。具体的には前回の記事以降に発生したATSコミュニティとのやり取りやそれを受けての実装方針の変更、ATSコミッタと密に交流するためATS Summitというイベントに参加した件について記述致します。HTTP/2やATSに関する基礎的な内容については以前の記事にて紹介させていただいていますので、本稿では割愛させていただきます。ぜひ本ブログの以前の記事、HTTP/2 入門前回の記事をご覧の上で本稿を読んでいただければ幸いです。

ATSのHTTP/2サポートに向けた新たな活動

まず前回以降のアップデートについて紹介致します。

結論から申しあげますと、前回の記事で紹介したパッチはマージされない方向に進み、コミッタとの相談の結果新たにスクラッチで実装を進めることになりました。現在はコミッタと協力し新たな実装を進めております。その過程でいくつかのパッチを投稿し、そのうち半数程度は既にマージされています。実装の進捗としては、ある程度簡単な動作が確認できるプロトタイプが出来上がっている状態になっています。

以前投げたパッチのその後

2014年6月時点では、前回の記事でご紹介した通りオープンソースのHTTP/2ライブラリnghttp2を利用する形で実装を進め、パッチを投稿しました。しかしその後コミッタと相談した結果、性能面の懸念やプラグイン向けのフックの提供の容易さから外部ライブラリに依存しない形での実装が望まれ、投稿したパッチはマージされない運びとなってしまいました。

この方針が決定した後の7月上旬に、コミッタの一人のJames PeachによりHTTP/2フレーム処理の基礎部分のコードがコミットされました。この時のコードはHTTP/2セッションの開始と終了、フレームヘッダのパース、SETTINGSフレームのペイロードのパース、GOAWAYフレームの送信処理などHTTP/2フレーム処理の共通部分に関する内容であり、HTTP/2リクエストを処理可能なレベルまでは実装が進んでいませんでした。

このコミットを受けて、私も引き続きATSのHTTP/2実装を進める旨を伝え、Jamesの実装を拡張する形で作業に着手し、その過程でいくつかのパッチを投稿しました。投稿したパッチの内容としては下記のものになります。

  • HTTP/2 draft-14への追従
  • HPACKエンコーダ/デコーダ(詳細は後述)
  • プロトタイプ実装(詳細は後述)

これらのHTTP/2サポートに関するパッチ投稿や議論については、前回に引き続きATSのバグ管理がされているJIRAのチケット上で行っております。

HPACKエンコーダ/デコーダの実装について

上記の方針が決定してから、最初にHTTP/2の仕様の中でもHPACKと呼ばれる独自のヘッダ圧縮形式に関する実装に着手しました。HPACKのドラフトはHTTP/2とは分離されており、2014年11月現在draft-09が最新です。HPACKでは下記のような圧縮を行うことでヘッダサイズを削減します。

  • よく使用されるヘッダのフィールド名もしくはフィールド名と値のペア(仕様であらかじめ定められている)をインデックスで参照可能にする
  • 以前送ったヘッダのフィールド名もしくはフィールド名と値のペアをインデックスで再参照可能にする
  • ヘッダのフィールド名もしくは値をハフマン符号化する

HTTP/2でもHTTP/1.xと同様リクエストやレスポンスのメッセージがやり取りされる際にボディの前にヘッダをやり取りします。(HTTP/2ではHEADERSフレームとして表現されます)クライアントから送信されるリクエストヘッダの内容を解釈する為にHPACKデコーダを、クライアントにレスポンスヘッダを渡すためにHPACKエンコーダを実装する必要があります。

HPACKエンコーダについては上記の圧縮方式をサポートしない最低限の実装を、デコーダについては上記全ての圧縮形式を網羅してかつヘッダフィールドとインデックスを対応付けるテーブルのサイズ変更機能の実装を行いパッチを投稿しました。(*1)このパッチについては既にマージされています。

プロトタイプについて

上記のHPACKエンコーダ、デコーダ実装に合わせて、下記のような機能追加を行いHTTP/2リクエストを処理可能なプロトタイプの実装を行いました。

  • 受信したHEADERS,DATA,CONTINUATIONフレームのハンドリング
  • HTTP/2とHTTP/1.1のヘッダフォーマット相互変換
  • オリジンサーバーへのリクエストのフォワードとそれに対するレスポンスのハンドリング
  • HEADERS,DATAフレーム送信

ここでは簡単にプロトタイプ実装でHTTP/2リクエストを処理できることを確認しました。実験環境としてローカルでATSとnginxを動作させ、ATSに到達したリクエストをnginxに転送するように設定した状態でnghttpでリクエストを送った際の出力を確認しました。nginxはここでは数行程度のシンプルなHTMLを返すようにします。

下記出力内容により、正常にリクエストヘッダを解釈し、nginxへリクエストを転送し、レスポンスをクライアントに返すことができているのが分かります。

$ nghttp -v https://127.0.0.1:4430/
[  0.002][NPN] server offers:
          * h2-14
          * http/1.1
          * http/1.0
The negotiated protocol: h2-14
[  0.003] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.003] send HEADERS frame <length=33, flags=0x05, stream_id=1>
          ; END_STREAM | END_HEADERS
          (padlen=0)
          ; Open new stream
          :authority: 127.0.0.1:4430
          :method: GET
          :path: /
          :scheme: https
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/0.6.0
[  0.003] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>
          (niv=0)
[  0.004] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.004] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
          ; ACK
          (niv=0)
[  0.007] recv (stream_id=1, noind=0) :status: 200
[  0.007] recv (stream_id=1, noind=0) server: ATS/5.2.0
[  0.007] recv (stream_id=1, noind=0) date: Tue, 04 Nov 2014 03:19:40 GMT
[  0.007] recv (stream_id=1, noind=0) content-type: text/html
[  0.007] recv (stream_id=1, noind=0) content-length: 95
[  0.007] recv (stream_id=1, noind=0) last-modified: Tue, 04 Nov 2014 03:18:10 GMT
[  0.007] recv (stream_id=1, noind=0) etag: "54584572-5f"
[  0.007] recv (stream_id=1, noind=0) accept-ranges: bytes
[  0.007] recv (stream_id=1, noind=0) age: 0
[  0.007] recv HEADERS frame <length=203, flags=0x04, stream_id=1>
          ; END_HEADERS
          (padlen=0)
          ; First response header
[  0.007] recv (stream_id=1, length=95, srecv=95, crecv=95) DATA
<html>
<head><title>Hello, World.</title></head>
<body>
<h1>Hello, World.</h1>
</body>
</html>
[  0.007] recv DATA frame <length=95, flags=0x01, stream_id=1>
          ; END_STREAM
[  0.007] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])

こちらのプロトタイプ実装のパッチもJIRAに投稿済です。パッチのファイル名はh2_prototype.patchです。ただし現状十分にデバッグできておらず、HTTP/2レスポンスを返せるまで最低限必要な実装しか行っていないので、今後作り込む必要があります。

さて、実装の詳細に絡む話になってしまいますが、このプロトタイプの実装を進める上で「ATSが受けたHTTP/2リクエストをどのようにオリジンサーバーに転送するか」が課題となりました。ATSにおいてHTTP/1.1のトランザクションはこのドキュメントの図のようなフローで処理されます。これは大ざっぱに説明しますと下記のようになります。

  1. リクエストヘッダを読み、設定ファイルで対応付けられているオリジンサーバーを決定する
  2. キャッシュを探索する。キャッシュヒットすればそれを読み込む
  3. (キャッシュミスした場合は)オリジンサーバーからコンテンツを取得する。オリジンサーバーからのレスポンスがキャッシュ可能であれば新たにキャッシュする
  4. クライアントにレスポンスを返す

これらのトランザクション状態の管理や処理は現状、主にHttpSMHttpTransactクラスによって担われています。

しかしながらHttpSMとHttpTransactはクライアントとATS間、ATSとオリジンサーバー間どちらもHTTP/1.xが使用されることを想定した実装がされており、今回のプロトタイプでサポートしたい「クライアントとATS間のみHTTP/2を使用し、ATSとオリジンサーバー間はHTTP/1.xを使用する」という構造にすることが容易ではありません。またこれらの実装はコードが複雑かつ行数も多く、既存の振る舞いを壊さずにクライアントとATS間のプロトコルを切替可能なよう修正するのは非常に難しい課題といえます。
(コミッタの一人曰く、HttpSMとHttpTransactに関しては“Be VERY cautious when changing this code”)

この問題はSPDY対応の際にも直面しており、その際にはFetchSMを使用することになりました。FetchSMはプラグイン用に提供されているAPIの内部処理で使われており、これによりHTTP/1.1リクエストを発行することが可能です。現状ATSではSPDYリクエストが来た際にFetchSMを用いて自身にHTTP/1.1リクエストを投げ直すことでオリジンサーバーへのリクエストの転送を賄っています。FetchSMを用いることで実装上の問題は回避されるのですが、同時にいったんリクエストとレスポンスをHTTP/1.1でやり取りする際に無駄なメモリ間コピーやヘッダのパース処理などが発生しているという懸念点が存在します。

今回作成したプロトタイプではSPDYと同様FetchSMを使用する形で実装しています。これはHttpSMやHttpTransactの構造を修正する困難さと、それらを含めた今後のHTTP/2実装の進め方に関して後述するATSSummitというイベントでコミッタ達と合意が取れたことによります。

Fall 2014 ATS Summit 参加報告

2014年10月21日(火),22日(水)に、ATSコミッタやATSユーザーが集まり今後の方針などについて議論するATS Summitというイベントがアメリカカリフォルニア州サニーベールのLinkedInのオフィスにて開催されました。このイベントにYahoo! JAPANから私を含めて2名が参加し、HTTP/2の今後の実装方針(特に上で紹介したATSとオリジンサーバーの通信方法)について相談する目的で発表を行いました。ここでは興味深かった発表内容と私の行った発表、雑感について簡単に記述します。

Introductions to ATS core

発表資料

Yahoo! Inc.のAlan M. Carrollによるセッション。ATSのプロセスや周辺ツールの基本構成から始まり、コア部分(上で挙げたようなHTTPのトランザクション処理やキャッシュ管理、オリジンサーバーのドメインの名前解決、イベントシステム周りなど)の実装に関する丁寧な説明がされました。

Debugging ATS

発表資料

LinkedInのBrian Geffonと、Yahoo! Inc.のBryan Callによるセッション。

ATSの設定値やViaヘッダによる簡単なデバッグ方法から、GDBによるデバッグ、メモリリーク発見方法などコミッタのプラクティスが共有されました。

HTTP/2

前半では私がHTTP/2実装の進捗報告と、FetchSMを用いた実装方針の是非、リリーススケジュールに関しての相談を行いました。この時発表した資料はSlideShareにて公開済です。

相談内容については、下記のような議論がされました。

  • FetchSMを使うことに関する懸念点の確認。どのようなオーバーヘッドがあるかの認識合わせ。
  • FetchSMを使用する方針でOK。既存のHttpSM周辺の実装を修正するのは非常に難しい。
  • うまく実装できれば次のメジャーリリース(ATS-6)ではサポートできるはず
  • その他HTTP/2に関する基本要素や今後仕様がどう固まっていくかに関する質疑

HTTP/2 セッションの発表風景

後半では現在のATSのHTTP/2フレーム処理部分の実装を書いたJamesによる、現在の実装の説明が行われました。現在HTTP/2に関するソースコードはproxy/http2に配置されおり、Jamesによってここで実装されているHttp2Frame(HTTP/2フレームを表現)、Http2SessionAccept(HTTP/2セッションを受け入れた際の処理を担う)、Http2ClientSession(HTTP/2フレームのヘッダやネットワークI/Oの管理を担う)、Http2ConnectionState(HTTP/2セッションの状態管理を担う)の4つのコンポーネントに関する説明がされました。

また発表の前後に、ATSコミュニティのchairのLeif Hedstromが、JIRAに投稿したプロトタイプのパッチを適用したATSを、ドキュメントをホストしているhttps://docs.trafficserver.apache.org/に導入してみるという豪快な場面もありました。

雑感

ATS Summitの感想としては「刺激的だった」との一言に集約されます。そもそもOSSのコミッタとじかに話す経験がなかったので、コミッタとの濃い技術の話を英語で行う事にも大きな新鮮味を感じていました。(同時に力量不足も痛感しました)

ちなみに今回が私にとっての初の渡米だったため、風景やら食文化やら多くのものが新鮮に感じました。下記の写真はその新鮮さに当てられ調子に乗って注文した、In-N-Out Burgersの裏メニューのバーガーを収めたものです。

4x4バーガーとアニマルスタイル

謝辞

(*1):HPACKデコーダの実装作業についてはサマーインターンシップ2014に参加してくれたD.A.くんに大部分を担当してもらいました。彼にはインターンシップにはおよそ10日間ほどの非常に限られた期間の中で英語の仕様のドラフトを読み、上記のデコーダの実装を行い、日本でHTTP/2に関する活動をしている有志によって提供されているhpack-test-caseのいくつかのテストケースを用いて正常にデコードが可能なことを確認してもらいました。彼の貢献により本ブログで紹介した活動がスムーズに行えました。この場を借りて御礼申しあげます。

Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。

  • このエントリーをはてなブックマークに追加