読者です 読者をやめる 読者になる 読者になる

ブログのしゅーくりーむ

技術的なメモとかライフログとか。

週報仲間というやつを試してみる

プライベートにおける作業、たとえば英語の勉強だとかちょっとしたプロダクトを実装してみるとか、はたまたプラモデルを組み立てるだとかを実行しモチベーションを維持するのは、どうにも難しいように思えます。 僕の最近の実体験でいうと、去年電子ピアノを購入して趣味で弾いてみていたのですが、うまくモチベーションを管理できず、現在では電子ピアノは触れられることなく部屋を彩るオサレなインテリアのポジションに収まってしまっています(モッタイナイ!) プライベートでやってる作業なんてものは、多くの場合できなかった場合でも人に迷惑を掛けることもなく義務感も生じにくいため、「やめた」と言いやすいですよねー、困ったことに。

さて最近週報仲間なんてエントリを読みまして、これがモチベーション維持に使えそうなんじゃないかなーと思っています。(この話、最近の Rebuild.fm でも紹介されてた気がする!) 今のところ仲間内で毎週日曜に一週間内でやったこと、翌週やること、その他雑感を書いていって、各週の計画や振り返りを共有することによって、そこはかとない作業への義務感を自身に植えつけられ、作業実行やモチベーション維持をスムーズに行える効果が期待できるかなーと。 ......実際のところ、この手法が自分に合っているかはしばらく継続してみないと分からないので、まずは取り組んでみて 1,2 ヶ月後くらいに効果を評価してみようと考え中です。

HTTP/2 のコネクション再利用について確認してみる

はじめに

本記事は http2 Advent Calendar 2015 の 12 日目の記事となります。

本記事では HTTP/2 における TCP コネクション再利用とその周辺仕様を確認してみようと思います。コネクション再利用の挙動を理解することは、実際の Web サイトにおける HTTP/2 のデプロイの助けになると思われます。

HTTP/1.1, HTTP/2 での TCP コネクション管理

よく訓練された方々には既知のことかとは思いますが、 HTTP/2 において HTTP/1.1 の頃にあった性能向上の為のハックであるドメインシャーディングは好ましくないテクニックになってしまいます。 HTTP/1.1 において今日のブラウザは 1 ドメインあたり概ね 6 TCP コネクションほど張って、並列にリクエストを送るような振る舞いをします。このため Web サイトなどで参照するドメインを分割することで HTTP リクエストの並列度を向上する手法がよく取られていました。 しかしながら HTTP/2 において HTTP リクエストの処理は 1 本の TCP コネクションの上で、ストリームという論理単位で HTTP リクエストが並列処理されるようになり、このようなハックは不要になりました。むしろ参照するドメイン名が集約されていた方が、 TCP コネクションを張る回数が削減できて性能の向上に貢献できます。

これは悩ましい問題です。今日までに HTTP/1.1 を前提としたチューニングがされた Web ページの構造を HTTP/2 向けに構築し直すのは骨が折れる作業といえます。また、依然として HTTP/1.1 ユーザも数多くいるわけで、現在 HTTP/2 前提の Web ページ構成にしてしまって良いのかどうかの判断も難しいといえます。 本記事ではこの課題に対する一つの対策となりうる、 HTTP/2 のコネクション再利用呼ばれる振る舞いについて記述します。

HTTP/2 におけるコネクション再利用

RFC7540 Section 9.1.1 Connection Reuse の節には下のような記述があります。

   Connections that are made to an origin server, either directly or
   through a tunnel created using the CONNECT method (Section 8.3), MAY
   be reused for requests with multiple different URI authority
   components.  A connection can be reused as long as the origin server
   is authoritative (Section 10.1).  For TCP connections without TLS,
   this depends on the host having resolved to the same IP address.

   For "https" resources, connection reuse additionally depends on
   having a certificate that is valid for the host in the URI.  The
   certificate presented by the server MUST satisfy any checks that the
   client would perform when forming a new TLS connection for the host
   in the URI.

https://tools.ietf.org/html/rfc7540#section-9.1.1

HTTP/2 において、 オリジンが authoritative であれば既に張られているコネクションを再利用できるのです。 再利用できるかどうかの基準については、再利用しようとしているコネクションにおいて、リクエスト対象のドメイン名がサーバ証明書の subjectAltName にマッチするかどうかで判断します。 例えば、まず https://a.example.com/ にリクエストを投げるために a.example.com にコネクションを張った際にサーバ証明書の subjectAltName に "*.example.com" が含まれている場合、続いて https://b.example.com/ にリクエストを投げる際に以前使ったコネクションを再利用することができます。

f:id:syu_cream:20151211224939p:plain

この仕様により、冒頭で述べた HTTP/1.1 の既存のハックの改修を行わずに HTTP/2 のコネクション集約の恩恵を受けられる可能性が出てきます。 これを利用して、例えば利用ドメインと証明書を集約して持つエッジサーバなどを構築し、 TCP コネクションを束ねて受けてしまうなどの対応が行えます。

Chrome における コネクション再利用

Google Chrome は既にコネクション再利用のロジックが入っているようです。いくつかの Google の Web ページを閲覧した後 HTTP/2 セッションのステータスを chrome://net-internals/#http2 で確認したところ、下記のようになっていました。

f:id:syu_cream:20151211214255p:plain

いくつかのドメイン名の HTTP/2 セッションが集約されていることがわかります。 ただし、 サーバ証明書の subjectAltName としては *.google.com が含まれているのですが、これにマッチして集約されることが期待されるいくつかのセッションが分割されています。

コネクション再利用の落とし穴

このコネクション再利用ですが、サーバとしては望まないコネクションの再利用が行われる可能性があります。 RFC7540 で記述されている通り SNI を元にリクエスト転送先オリジンサーバを決定している HTTP/2 プロキシサーバが存在するかもしれませんし、サーバ証明書としてはワイルドカードの subjectAltName を持っているものの、実際にはレスポンスを返せない場合があります。 このような場合には、サーバはクライアントに対し 421(Misdirected Request) を送って通知することが可能です。

f:id:syu_cream:20151211224943p:plain

ただし、クライアントがこの挙動を考慮して「コネクションを再利用してリクエストを投げてみて 421 が返されてから、再度コネクションを張り直してリクエストを投げ直す」ような挙動をした場合、もちろんレイテンシが向上してしまいます。 この問題を解決するために、 Origin フレーム という HTTP/2 の拡張フレームの Internet-Draft も存在します。 Origin フレームを用いることでサーバ証明書としては集約されてしまうオリジンがあったとしても、サーバが実際に authoritative なオリジンを指定することができ、これにより上記のレイテンシ向上問題を回避することができます。 (Origin フレームは拡張仕様であるためサーバ、クライアント両者が対応していなければならず、また大量のオリジンに対して authoritative であるサーバは Origin フレームにそれらを列挙し送信すると、その送信自体のコストが大きくなってしまいそうな欠点はありますが。。。)

f:id:syu_cream:20151211224947p:plain

おわりに

HTTP/2 のコネクション再利用が期待できると、現在の Web にデプロイした際に HTTP/2 の恩恵が受け易くなると思われます。 ただし現状クライアントの実装と周辺仕様の充実、さらに実際に運用した上で得られた知見が必要である状態だと思われます。

Apache Traffic Server の設定やちょっとした制御ロジックを mruby で書けるようになるかもしれないプラグイン書いてる

mod_mrubyngx_mrubyh2o_mrubylibvmod_mruby など、さまざまな HTTP サーバの設定などを mruby スクリプトで書ける世の中になりつつありますね。 そんな風潮の後押しもあり、近頃、題意の通り mruby で ATS(Apache Traffic Server) の設定やヘッダの加工などのロジックを mruby で記述可能にするプラグインts_mruby をちまちまと開発しています。

ts_mruby によって例えば下記のようなロジックが mruby で書けるようになります。

  • リクエストヘッダを付与したり削除する
if server_name == "NGINX"
  Server = Nginx
elsif server_name == "Apache"
  Server = Apache
elsif server_name == "ApacheTrafficServer"
  Server = ATS
end

# rewrite request headers
r = Server::Request.new
conn = Server::Connection.new
r.headers_in["X-ATS-Plugin"] = "ts_mruby"
r.headers_in["X-Forwarded-For"] = conn.remote_ip
r.headers_in.delete("Cookie")
  • 特定 IP 以外からのリクエストを弾く
if server_name == "NGINX"
  Server = Nginx
elsif server_name == "Apache"
  Server = Apache
elsif server_name == "ApacheTrafficServer"
  Server = ATS
end

whitelist = [
  "127.0.0.1"
]

# deny if client IP isn't listed in whitelist
conn = Server::Connection.new
unless whitelist.include?(conn.remote_ip)
  Server::echo "Your access is not allowed ..."
  Server::return Server::HTTP_FORBIDDEN
end

ATS でプラグインをロードする際、グローバルプラグイン(ATS 全体に作用するプラグイン)や Remap プラグイン(各リクエストをプロキシする際に作用するプラグイン)という形態を採ることになるのですが、 ts_mruby ではそのどちらもサポートしています。 全リクエストで動作させたいのか、特定 VirtualHost や パスだけに対して動作させたいのか、など要件によって使い分けることが可能です。

  • グローバルプラグインとして使う場合、 plugin.config に下のように記述
# write here if you hope to apply all of requests
ts_mruby.so etc/trafficserver/unified_hello.rb
  • Remap プラグインとして使う場合、 remap.config に下のように記述
# write here if you hope to apply only accesses to /test
map /test http://127.0.0.1/ @plugin=ts_mruby.so @pparam=etc/trafficserver/unified_hello.rb

現状サポートしている ts_mruby の mruby 向けクラス、メソッド は mod_mruby, ngx_mruby と共通の名前、機能にするよう気をつけています。 mruby を HTTP サーバ設定の DSL として使うこともできるようになるかもしれません(ただし各サーバ固有の機能の部分はどうしても出てくるわけで、その部分をどう扱うか悩みどころです)

ts_mruby は現状、細々と個人で開発をしている状態です。なにかご意見や Pull-Request などいただけると幸いです。

JMeter で HTTP/2 リクエストを送れるようにするプラグインを書いてみた

表題の通り、 JMeter で HTTP/2 リクエストを送ることを可能にする HTTP2 サンプラーを追加するプラグインを書いてみました。

github.com

但し、今のところ単純な GET リクエストを、しかも 1 request / connection で送る程度のことしかできず、パフォーマンステストをする上で必要な機能が十分にあるとは言えない状態です。 HTTP ヘッダマネージャによるリクエストヘッダの編集、アサーションでのレスポンスコード、レスポンスメッセージ、レスポンスヘッダのチェックはできるので、何か単純なサーバの動作確認を実施する程度になら使えるかも知れません。

プラグインを導入すると、下図のように HTTP/2 Sampler という項目が追加されます。 HTTP/2 Sampler を追加して項目を埋めていくことで HTTP/2 リクエストの送信が実現できます。 f:id:syu_cream:20150706002423p:plain

HTTP サンプラーで使われるヘッダマネージャを、 HTTP/2 Sampler でも使用できます。 f:id:syu_cream:20150706002039p:plain

HTTP/2 Sampler では現在、サーバの IP 、ポート、パスを指定できるようにしています。 今後必要な項目を用意していきたいところです。(レイアウトが残念なことになっているのでそれもどうにかしたい f:id:syu_cream:20150706002832p:plain

テスト結果はアサーションで検査したりリスナーで一覧、保存できたりします。 jtl ファイルに出力させれば、 Jenkins の Performance Plugin に解釈させることも可能なはず。 f:id:syu_cream:20150706002817p:plain

RFC 7541 (HPACK) の日本語訳を公開しました

表題の通り、 RFC7541 "HPACK: Header Compression for HTTP/2"日本語訳されたドキュメント を公開しました。 今のところ GitHub の僕のリポジトリにて編集管理を行っています。(将来的には別の管理の仕方をするかもしれません)

前回日本語訳を行った時点(draft-10) から RFC になるまでの、記述上の細かい修正の追従が主になります。また、前回イマイチだった箇所の日本語訳も少し修正しています。

問題点の指摘や翻訳の改善の Pull-Request 、大歓迎です。 こちら よりお願いします。

末筆ですが、やっと HTTP/2 , HPACK の RFC が出ましたね!これまで変更を追従してきた皆様お疲れ様でした。

HPACK draft-10 の日本語訳を公開しました

表題の通り、 HPACK draft10日本語訳されたドキュメント を公開しました。 今のところ GitHub の僕のリポジトリにて編集管理を行っています。(将来的には別の管理の仕方をするかもしれません)

問題点の指摘や翻訳の改善の Pull-Request 、大歓迎です。こちら よりお願いします。

HTTP Alternative Services について

本稿は HTTP2 Advent Calendar 2014 20 日目の記事です。 本稿では HTTP/2 周辺のトピックでもやや地味な部類に入るであろう、 HTTP Alternative Services について簡単に触れていきます。

  • 2014-12-21 19:20 用語の修正

概要

HTTP Alternative Services とは、 HTTP で配信するリソースを他のプロトコル、ホスト、ポート番号でもサービス提供ができることを通知する仕組みです。 用途としては下記のようなものが想定されます。

  • メンテナンス等のためサーバをダウンさせる前に、他のサービス可能なサーバを提示する
  • 新しいプロトコルへの切り替えを提案する(HTTP/1.1 から HTTP/2 へ、など)
  • 日和見暗号(サーバ認証を行わず http スキームで暗号化通信する仕組み) の使用を提案する
  • SNI などをサポートするクライアントとそうでないクライアントを分離する
  • 他ホストにリクエストを振り分けて負荷分散する

HTTP Alternative Services は HTTP/2 とは分離された Internet-Draft になっており、 2014年12月現在、 5番目のドラフトが出ている 状態です。

HTTP Alternative Services メッセージの表現方法

HTTP Alternative Services ではサーバからクライアントへ代替が存在する旨のメッセージを通知することで使用することができます。 このメッセージでは最初にリクエストを受け取った OriginRFC6454 にあるようにスキーム、ホスト、ポートの組で識別する)を代替する Alternative service を通知するような内容になります。

例えば Origin が下記の通りだったとして、

 ("http", "www.example.com", "80")

Origin が下記のようなメッセージを送ることで、「new.example.com:81 に HTTP/2 で話すことで Origin と同様のリソースを受け取ることができる」ことを通知できます。

("h2", "new.example.com", "81")

このメッセージの表現方法について、現行のHTTP Alternative Services のドラフトでは下記2つの方法が提示されています。

  • Alt-Svc HTTP ヘッダフィールド を使用する
  • HTTP/2 において ALTSVC フレーム という拡張フレームを使用する

Alt-Svc HTTP ヘッダフィールド

Origin は、下記のようなフォーマットの Alt-Svc ヘッダを送ることで Alternative service の存在をクライアントに通知することができます。 ここでプロトコル名として提示するのは、 ALPNプロトコル識別子と同様のものになります。

Alt-Svc: h2="new.example.org:80"

Alt-Svc ヘッダの内容はキャッシュされます。デフォルトでは 24 時間後にフレッシュされますが、 Alt-Svc ヘッダの ma(max-age) パラメータによってキャッシュが失効されるまでの時間を指定することもできます。

# 1時間後に失効
Alt-Svc: h2=":443"; ma=3600

なお、クライアントは Alt-Svc を使って Alternative service にリクエストを投げる際は Alt-Used HTTP ヘッダフィールド を付加する必要があります。(MUST) これは Alternative service 切り替えのループが発生することを防ぐことと、サーバ側で負荷分散する際に Alt-Svc が使われたリクエストなのかどうかを識別することが目的です。

Alt-Svc を使って振られた Alternative service はリクエストをうまく捌けない際に 421 Misdirected Request HTTP ステータスコード を返します。 クライアントは 421 を受け取った際には Alt-Svc キャッシュから該当エントリを消す必要があります。(MUST)

ALTSVC フレーム

HTTP/2 で Alternative Services を使用する場合は、 ALTSVC フレームで Alt-Svc ヘッダと同内容のメッセージを表現します。 ALTSVC フレームは Optional なフレームタイプであり、 最新の HTTP/2 のドラフト では定義されておらず、これをサポートしていないクライアントはサーバから送られてきた ALTSVCフレームを無視しても問題ありません。 (ちなみに draft-11 から draft-13 の間は ALTSVC フレームタイプが HTTP/2 のドラフト内で定義されていたりしました)

HTTP Alternative Services の活用例

http スキームで暗号通信を行う 日和見暗号 でも HTTP Alternative Services を使用します。 Origin は日和見暗号が利用可能であればレスポンスに HTTP-TLS ヘッダを付与します。 クライアントはこれを ":443" の Alternative service として記憶することで、次回以降にユーザが http スキームでリクエストを投げる際に HTTP over TLS で通信可能な Alternative service を選択することができます。

f:id:syu_cream:20141220153046p:plain

HTTP/2 通信開始前は基本的に ALPN や HTTP Upgrade でクライアントとサーバの間でお互いに HTTP/2 を解釈可能だということを確認します。 HTTP Alternative Services を使って事前に HTTP/2 で通信可能な alternative services がいることを知っていれば、このようなプロトコルネゴシエーションをスキップして直ぐに HTTP/2 通信を開始することもできます。

f:id:syu_cream:20141220153103p:plain

  • 負荷分散する

alternative service として別ホストを指定することもできるため、 Origin が高負荷な場合は HTTP Alternative Services を使って別ホストにリクエストを振ることで負荷分散することも可能です。

f:id:syu_cream:20141220153112p:plain

これは個人の意見ですが、 HTTP Alternative Services を使って負荷分散するのは難しいのではと思っています。 まず Origin は Alternative services の負荷状況を把握しておく必要があると考えられます。でないと誤って高負荷な Alternative services にリクエストを振り続けてしまう可能性があります。 更に HTTP Alternative Services は Optional な機能のため、これで負荷をどの程度分散できるかはどの程度ブラウザが対応してくれるかに大きく依存します。

HTTP Alternative Services 周辺事情のこれまで

HTTP Alternative Services 周辺のこれまでのおおまかな流れをまとめてみました。

  • Alternate-Protocol ヘッダフィールド

Alternative Services の前身となるのは、 SPDYの仕様に含まれる Alternate-Protocol ヘッダフィールド であると思われます。 Alternate-Protocol ヘッダは、リクエストが指定ポートで他のプロトコルでも捌けることを通知するためのヘッダです。

  • Encryption for HTTP URIs Using Alternate Services

こちらの文書日和見暗号を実現するのに、 Alt-Svc ヘッダを使う提案がされています。

  • HTTP Alternate Services

こちらの文書 で、 Alternative Services が日和見暗号から分離されています。

  • HTTP/2 ALTSVC フレーム

http2 draft-11 で、 HTTP/2 で Alternative Services を使用するためのフレームタイプが定義されました。 しかしながら http2 draft-13 でこれは除去され、 HTTP Alternative Services draft-02 に分離されました。

GitHub の issues で議論された内容を元に、 HTTP Alternative Services draft-05 まで更新されています。

おわりに

HTTP Alternative Services の理解があまりなかったため、今回仕様と経緯を調査しまとめてみました。補足や誤り指摘等大歓迎です。何かありましたらコメント欄にでも書いて頂ければ幸いです。

ちなみに今回 HTTP Alternative Services に対応している実装が存在するのか気になって少々調べてみたのですが、少なくともコードベースでは Chromium, FireFox, nghttp2 には対応する実装がありそうでした。 どこまで機能するのかは未調査です。後ほど余裕があった際にでも調べます。

余談ですが, 現在の仕様では "HTTP2.0" ではなく "HTTP/2" もしくは "HTTP2" が正しい名称です.