no-image

HTTP/2を広く浅く知る

先日、会社の合同の勉強会があったので、みんなが知って得するような話題で、自分の最近の興味とも合うということで、HTTP/2をテーマに選び簡単に調べてみました。

「HTTP/2」。

名前は聞いたことあるけど、よくわからない。
僕の中ではそういう位置づけにあったのでちょうどいいと思いました。

以下のリンクは勉強会のときのプレゼン資料です。
発表時間は20分ほどでした。

【参考】プレゼン資料

今回はこのスライドに少し肉付けしたような記事を書こうかと思います。

この記事は、10分くらいで読める程度にHTTP/2についてさらっと知れるように書いています。
一つ一つの項目も深めればいくらでも深まるようなもので、詳細に関しては色々なサイトで解説されているので、この記事内の参考リンクなどを御覧ください

HTTP/2の概要

HTTP1.1は1997年にRFCで策定され、それからWebサイトが肥大化し、1つのページを表示するために大量のリソースを得る必要が出てきました。

そこで、パフォーマンス向上を目的とし、GoogleによってSPDYという独自のプロトコルが作られました。
これがなかなか良い感じだったみたいで、多くのブラウザがサポートしたり、TwitterやFacebookなどの大きいサービスが採用し始めたこともあり、これを改良したHTTP/2立ち上がりました。

HTTP/1.1時代の課題を解決し、HTTP/1.1の様式を保持した拡張版としてHTTP/2が定義されていき、2015年2月17日に発表されました。

HTTP/1.1の課題

HTTP/1.1には課題がありました。
ここではそのうちの2つを紹介します。

HoLブロッキング問題

HTTP/1.1では、「HTTPパイプライン」という機能が導入されました。
これは、同時に複数のリクエストを送ることができる機能です。
今までは、1つのリクエストを送ったあと、1つのレスポンスが返ってくるのを待つ必要があったのですが、これのおかげでレスポンスを受け取る前に次のリクエストを送れるようになり、時間の大幅な短縮ができるようになりました。

一見便利そうな機能ですが、一つ大きな問題点がありました。
それは、「送ったリクエストの順番通りにレスポンスを受け取らないといけない」というものです。

つまり、レスポンスを待たずにA,B,C,..と複数のリクエストを送れるまでは良いのですが、例えばこのAが処理に時間のかかるものの場合、結局それ以降のB,C,..も処理待ち状態になってしまい、結局全体の速度が遅くなってしまいます。

これのことを「HoL(Head of Line)ブロッキング問題」と呼びます。

煩雑なテキスト

HTTP/1.1以前では、データは文字ベースで通信されていました。
HTTPリクエストの中を見てみると、ヘッダがあり、空行を挟んでボディがあり、ヘッダを見るとスペースがあったり、コロンがあったり、改行があったり、、、。

これは結構煩雑です。
煩雑だとミスが起きやすくなりますね。

HTTP/2についての以下のスライドのp.15に煩雑さを表したページがありました。

 

HTTP/2の特徴

HTTP/2には新たに追加されたいろいろな機能があるのですが、そのうちのいくつかを紹介します。

バイナリフレーミングレイヤー

一言で言うと、より効率的に情報をやりとりするためのもので、クライアント/サーバー間でどのようにデータを通信するかを指示するものです。

HTTP/2での通信のデータは大まかに「ストリーム」、「メッセージ」、「フレーム」というものに分類できます。
まずはこれらがどんなものなのかをひとつずつ見てみます。

以下の図のようなイメージです。

【画像引用元】HTTP/2 の内幕

ストリーム

通信するときはクライアント/サーバー間で一つの接続が確立しますが、この中に双方向のフレームのやりとりをする経路のようなもの作り、これのことをストリームと呼びます。
一つの接続の中に複数のストリームを同時に送信することができます。

メッセージ

ストリームの中に流れるフレームの集合です。
リクエスト、レスポンスなどの論理的なHTTPメッセージのことです。

フレーム

HTTP/2での通信の最小単位です。
それぞれがヘッダーを持ち、その他にも、フレーム長、フレームタイプ、フラグなどを格納する部分があります。
フレームタイプは0~9の10種類あり、フレームの目的などを識別します。
例えば、エラーを示す「RST_STREAM」や、ストリームに優先順位を振る「PRIORITY」(後述)などがあります。
このフレームは、それぞれがバイナリフォーマットにエンコードして送信されます。
フレームタイプの一覧はいろいろなところで紹介されているので、ここでは割愛します。

 

リクエストとレスポンスの多重化

このような仕組みが導入されたことにより、リクエストとレスポンスの多重化が実現しました。

一つの接続内にストリームを複数作ることで、どこかのストリームがリクエストしている間に、他のストリームでレスポンスを受け取るといった、並列にデータの通信ができるようになりました。

任意のタイミングでリクエストを送ることができ、100以上ものリクエストの送信が可能になりました。

これにより、上述したHTTP/1.1の問題点である、HoLブロッキング問題も解決されました。

 

ストリームの優先度制御

さきほどの、「フレーム」の説明でサラッと書いたフレームタイプの中に「PRIORITY」というものがありました。
これを利用することで、ストリームに優先順位を振ることができます。

これの何が嬉しいかというと、これを使うことで優先順位の高いものからリソースを取得することができます。

例えば、Webサイトなどの場合、htmlファイルを真っ先に取得し、次にcssで整形をし、それよりも後に画像などを表示することで、ユーザーの体感時間が減り、UXの向上に繋がります。

各ストリームに「重み」と「依存関係」を指定し優先度ツリーを作ることで、優先順位を制御することができます。
ツリーの作り方など、詳しい解説は参考リンクのサイトにおまかせしますが、簡単に説明します。

重み

ストリームに1~256の数値で重みを振ることで、リソースの配分を指定できます。
例えば、A,B,Cのデータがあり、それぞれ8,16,32と重み付けをすると、1:2:4の割合でリソースが割り当てられます。

依存関係

リソース同士にも依存関係があります。
「このファイルAよりも先に絶対にファイルBを処理してないといけない」といった状況がそうです。
こういった依存関係がリソース同士にある場合には、それを指定することで先にAを処理することができます。

【参考】HTTP/2 の概要  |  Web  |  Google Developers

 

ストリーム及び接続のフロー制御

フロー制御は一言で言うと、データの「フロー」を「制御」することです。
わかりにくいですね。

HTTP/2では一つの接続内に複数のストリームを作ることができると言いましたが、いくつかあるストリームの内の一つが、巨大なデータを転送しようとし、リソースを占有してしまうと、他のストリームの通信がブロックされてしまいます。

これを防ぐのが、フロー制御です。

受信側「送信側!もっとデータの量減らして!データの送信ストップして!」

というように、受信側が制御します。

例えば動画サイトだと、サーバーはクライアントにデータを送信し続けますが、ユーザーが一時停止したときに、サーバーに対してデータの送信を停止するように通知します。

先ほど紹介したフレームタイプの内の一つである「WINDOW_UPDATE」などを使って制御するのですが、技術的な話については以下の参考サイトが詳しいので参考にしてみてください。

【参考】

サーバープッシュ

次はサーバープッシュという機能についてです。
これめっちゃすごいんですよ!!

一言で言うと、サーバーが次に来るリクエストを先読みしてレスポンスを返すものです。

最近は膨大なリソースを必要とするWebサイトが増えてきました。
たくさんの画像を表示したり、たくさんのcssやjsなどを読み込んだりしていますが、これは1ファイルを読み込むごとに通信が走っています。

従来のこの方法と、サーバープッシュを使った方法と簡単な例を見て比較してみます。
以下のような「index.html」というファイルがあり、これをサーバーから受け取る状況を考えます。

従来の場合

  1. クライアントがindex.htmlをくださいとサーバーにリクエストを送る
  2. サーバーがindex.htmlを返す
  3. 受け取ったブラウザがこのファイルを上からパースしていく
  4. 上から見ていくと9行目にpngファイルを見つけたので、このomosiro.png画像をくれとサーバーにリクエストを送る
  5. サーバーが画像を返す

サーバープッシュを用いた場合

  1. クライアントがindex.htmlをくださいとサーバーにリクエストを送る
  2. サーバーはindex.htmlとomosiro.pngを返す
  3. クライアントはomosiro.pngをキャッシュにセットする
  4. ブラウザがindex.htmlを上からパースしていく
  5. 9行目にpngファイルを発見
  6. リクエストを送る前にキャッシュを見てみるとomosiro.pngがあるのでこれを表示

こんな感じです。めっちゃすごくないですか!?
画像に関しては0RTTで表示できていることになります。

これはフレームタイプの「PUSH_PROMISE」を使うのですが、技術的な話は以下のサイトを参考にしてください。

【参考】HTTP/2 の概要  |  Web  |  Google Developers

ヘッダの圧縮

HTTP通信にはヘッダを伴いますが、プレーンのまま送るとサイズが大きく、Cookieなどが含まれていると数KBになることもあります。

これではいかんということで、SPDY時代にもzlibやgzipといった圧縮の手法を使って85~88%ほど圧縮したりしていたのですが、セッションハイジャッキングするCRIMEという攻撃手法が見つかり、無効とされました。

HTTP/2ではHPACKという新たな圧縮形式を使用しています。

使用頻度の高いヘッダフィールドに予め番号を振っておき、その数値をやりとりするだけで解釈する静的テーブルや、一度送ったヘッダフィールドに数値を振り、次回からはその数値でやりとりをする動的テーブルを利用したりしています。

また、ハフマン符号化というアルゴリズムを用いて圧縮しています。
ハフマン符号化は各文字の使用頻度からツリーを作成し、それぞれに2進数の数値を振ることで、使用頻度の高い文字はより少ないbitで表現することができます。

このページを見てみると、「a」という文字は「00011」という数値で表現されていることがわかります。
つまり、通常8bitでのところを、5bitで一文字を表現できているので圧縮されていますね。

ハフマン符号化のアルゴリズムについては以下の記事がわかりやすかったです。

【参考】HTTP2のヘッダ圧縮 Huffman Encode の原理とメリット・デメリット – Qiita

他にもHPACKに関してはとても詳しい日本語の情報がいくつも出てきたので紹介しておきます。

【参考】

デモサイト

これで、HTTP/2の機能の紹介は終わりますが、HTTP/1.1とHTTP/2の速度を比較するデモサイトがあったので紹介しておきます。

Chromeのデベロッパーツールを開き、「network」のタブを開いて、一覧の上部で右クリックし、「Protocol」にチェックマークをつけると、プロトコルを確認することができます。

「http1.1」や「spdy」や「h2」という表示が確認できるかと思います。

 

導入方法

どうやったら導入できるのかが気になるところですが、以下のサイトで紹介されていました。

クライアント側が新しめのブラウザを使っていれば対応済みで、サーバー側はいくつかの設定をする必要があるようです。

【参考】で、 HTTP2.0 対応って何をすればいいの? – dskst’s diary

参考

最後に、全体的に参考にしたサイトと書籍を載せておきます。