メインコンテンツまでスキップ

HTTP プロトコルの発展史

· 約18分

HTTP はブラウザとサーバー間の最も主要な通信プロトコルです。HTTP はアプリケーション層(第 7 層)のプロトコルであり、アプリケーション層で生成されたデータはトランスポート層プロトコルを媒体として、インターネット上の他のホストへと伝送されます。その媒体こそが TCP プロトコルです(3.0 では UDP に変更されました)。TCP でコネクションを確立し、その上で対応するコンテンツを伝送する仕組みになっています。

20 世紀 60 年代、アメリカ国防総省の高等研究計画局(ARPA)が ARPA ネットを構築しました。これがインターネットの起源とされています。70 年代には、研究者たちが ARPA ネットでの実践と考察をもとに、有名な TCP/IP プロトコルを発明しました。このプロトコルは優れた階層構造と安定した性能を備えており、80 年代半ばには UNIX システムのカーネルに組み込まれ、より多くのコンピュータがネットワークに接続されるきっかけとなりました。

1989 年、ティム・バーナーズ=リー博士が論文を発表し、インターネット上にハイパーリンクドキュメントシステムを構築する構想を提唱しました。この論文の中で博士は、URI、HTML、HTTP という 3 つの重要な技術を確立しました。

この 3 つの技術によって、ハイパーテキストシステムはインターネット上で完璧に動作できるようになりました。リー博士はこのシステムを「World Wide Web」(万維網)と名付けました。こうして HTTP プロトコルはインターネット上で普及し始め、現在最もよく使われているのは HTTP/1.1 と HTTP/2.0 です。

実のところ、HTTP プロトコルの各世代の進化は、いずれも前世代の HTTP の欠点を改善する形で行われてきました。

HTTP/0.9

1991 年、HTTP(HyperText Transfer Protocol、ハイパーテキスト転送プロトコル)が正式に誕生しました。当時のバージョンは 0.9 です。名前からわかるように、このプロトコルの役割はハイパーテキストコンテンツである HTML を転送することでした。プロトコルはクライアントがリクエストを送り、サーバーがそれに応答するという通信モデルを定義しました。リクエストメッセージの内容はわずか 1 行、「GET + リクエストするファイルパス」だけです。メッセージタイプの区別がなかったため、テキスト情報しか転送できませんでした。

HTTP/1.0

インターネットの発展とブラウザの登場に伴い、単純なテキストコンテンツだけではユーザーのニーズを満たせなくなりました。ブラウザは HTTP を通じてスクリプト、スタイル、画像、音声、動画などさまざまな種類のファイルを転送したいと考えるようになり、1996 年にアップデートされた HTTP 1.0 では次のような機能が導入されました。

  • HEAD、POST などの新しいメソッドを追加
  • レスポンスステータスコードを追加し、想定されるエラー原因を示せるように
  • プロトコルバージョン番号の概念を導入
  • HTTP Header(ヘッダー)の概念を導入し、リクエストとレスポンスの処理をより柔軟に
  • 転送できるデータがテキストに限定されなくなった

中でも最も重要な変更はヘッダーの導入です。ヘッダーの内容はキーと値のペアの形式で設定されます。リクエストヘッダーは Accept フィールドで受け入れ可能なファイルタイプをサーバーに伝え、レスポンスヘッダーは Content-Type フィールドで返却するファイルのタイプをブラウザに伝えます。ヘッダーフィールドは異なる種類のファイル転送の問題を解決するだけでなく、キャッシュや認証情報など、他の多くの機能も実現できます。

HTTP/1.1

インターネットの急速な発展に伴い、HTTP/1.0 もニーズを満たせなくなりました。最も大きな課題はコネクションの問題です。具体的には、HTTP/1.0 では通信のたびにコネクション確立データ転送コネクション切断という 3 つのフェーズを経る必要がありました。1 つのページが多くの外部ファイルを参照している場合、このコネクションの確立と切断のプロセスが大量のネットワークオーバーヘッドを生んでしまいます。

HTTP/1.1 ではデフォルトで Connection: keep-alive が使われるようになり、コネクションの確立と解放を繰り返すオーバーヘッドが回避されました。ただしサーバーは、クライアントが各リクエストのレスポンス内容を区別できるように、クライアントのリクエスト順どおりに結果を返さなければなりません。ヘッダーフィールドによって現在のリクエストのデータをすべて受信し終えたかを判断する仕組みであり、2 つのレスポンスが並行して存在することは許されません。

HTTP/1.0 の問題を解決するため、1999 年にリリースされた HTTP/1.1 には次のような特徴があります。

  • 持続的接続(Connection: keep-alive):TCP コネクションの再利用を導入。1 つの TCP コネクションはデフォルトでは閉じられず、複数のリクエストで再利用できる
  • 並行コネクション:1 つのドメインに対するリクエストに複数の持続的接続を割り当て可能(持続的接続における「HOL ブロッキング(Head-of-Line Blocking)」問題を緩和)
  • パイプライン機構を導入し、1 つの TCP コネクションで複数のリクエストを同時に送信可能(レスポンスの順序はリクエストの順序と一致しなければならないため、あまり使われていない)
  • PUT、DELETE、OPTIONS、PATCH などの新しいメソッドを追加
  • キャッシュ用フィールドを新設(Cache-Control、ETag)
  • リクエストヘッダーに Range フィールドを導入し、レジューム(断点続伝)をサポート
  • レスポンスデータのチャンク転送(chunked)を許可し、大きなファイルの転送に有利に
  • Host ヘッダーを必須化し、バーチャルホスティングを可能に

HTTP/2.0

HTTP/1.1 は持続的接続によってコネクションの作成・切断を繰り返すことによる性能消費を大幅に減らしましたが、その並行処理能力には制限がありました。それは次の 2 点に表れます。

  • HTTP/1.1 で持続的接続を使う場合、1 つのコネクションでは同時に 1 つのリクエストしか処理できません。現在のリクエストが完了するまで、他のリクエストはブロック状態になります。この状況は「HOL ブロッキング」と呼ばれます
  • ブラウザはサーバーの負荷を軽減するため、同一ドメインに対する HTTP コネクション数を制限しており、一般的には 6 ~ 8 本です

2015 年に正式リリースされた HTTP/2 は、デフォルトで ASCII エンコードによる転送をやめ、転送効率を高めるためにバイナリデータに変更されました。

クライアントはリクエストを送信する際、各リクエストの内容を番号付きの異なるバイナリフレーム(Frame)にカプセル化し、これらのフレームをまとめてサーバーに送信します。サーバーはデータを受信すると、同じ番号のフレームを結合して完全なリクエスト情報に復元します。同様に、サーバーが結果を返し、クライアントがそれを受信する際も、このフレームの分割と結合のプロセスに従います。

バイナリフレーミングが導入されたことで、同一ドメインに対してクライアントはサーバーと 1 本のコネクションを確立するだけで通信のニーズを満たせるようになりました。このように 1 本のコネクションで複数のリクエストを送信する方式は「多重化(マルチプレキシング)」と呼ばれ、それぞれの経路は stream(ストリーム)と呼ばれます。

HTTP/2.0 の主な変更点は次のとおりです。

  • データはバイナリプロトコルで転送され、プレーンテキストではなくなった
  • 多重化を導入し、1.1 のパイプラインを廃止
  • 専用アルゴリズムでヘッダーを圧縮し、転送データ量を削減
  • データフレームに優先度を設定し、サーバーが特定のリクエストを優先処理できるように
  • サーバーからクライアントへのデータのプッシュ(サーバープッシュ)を許可
  • ヘッダーフィールドはすべて小文字に。疑似ヘッダーの概念を導入し、ヘッダーフィールドの前にコロン始まりで記述
  • セキュリティを強化し、「事実上」暗号化通信(TLS)を必須に

HTTP/2.0 はリリースから何年も経っていますが、HTTP/1.1 があまりにも定番かつ強力だったため、現在でも HTTP/2.0 の普及率はそれほど高くなく、多くのウェブサイトが依然として HTTP/1.1 を使っています。

HTTP/3.0

もちろん HTTP/2 も完璧ではありません。クライアントまたはサーバーの通信中にパケットロスが発生したり、どちらかのネットワークが中断したりすると、TCP コネクション全体が一時停止してしまいます。

HTTP/2 はバイナリフレーミングによる多重化を採用しているため、通常は 1 本の TCP コネクションだけで転送を行います。パケットロスやネットワーク中断が発生すると、後続のすべてのデータがブロックされてしまいます。一方 HTTP/1.1 の場合は複数の TCP コネクションを開けるため、どれか 1 本の TCP に問題が発生しても他のコネクションには影響せず、残りの TCP コネクションで正常にデータを転送し続けられます。この状況では HTTP/2 のパフォーマンスがかえって HTTP/1 に劣ってしまうのです。

2018 年、HTTP/3 は下層で依存していた TCP を UDP に変更し、この問題を根本的に解決しました。UDP は TCP と比べて、データ転送時にコネクションを確立する必要がなく、複数のパケットを同時に送信できるため転送効率が非常に高いのが最大の特徴です。欠点は、相手が確実にデータを受け取れることを保証する確認応答の仕組みがないことです。そこで Google は独自路線を取り、UDP ベースの QUIC プロトコルを開発して HTTP/3 に採用しました。現在、多くのブラウザやハードウェアベンダーが HTTP/3.0 プロトコルへの対応を始めています。

QUIC プロトコル

百度百科:QUIC_百度百科 (baidu.com)

QUIC は quick と同じ発音で、つまり速いという意味です。Google が提唱した UDP ベースの転送プロトコルであり、そのため QUIC は「Quick UDP Internet Connections(高速 UDP インターネット接続)」とも呼ばれています。QUIC の第一の特徴は速さです。なぜ速いと言えるのか、いったいどこが速いのでしょうか?

1) より高速なコネクション確立

ご存じのとおり、HTTP プロトコルはトランスポート層で TCP を使ってメッセージを転送しており、さらに HTTPS や HTTP/2.0 では TLS プロトコルによる暗号化も行われます。そのため、ハンドシェイクによるコネクション遅延が発生します。TCP の 3 ウェイハンドシェイク(1 RTT)に加えて TLS ハンドシェイク(2 RTT)が必要で、下図のようになります。

多くの短時間接続のシナリオでは、このハンドシェイク遅延の影響は大きく、しかも取り除くことができません。RTT との戦いは、人類と効率の果てなき闘いなのです。

これに対して QUIC のハンドシェイクはより高速です。トランスポート層プロトコルとして UDP を使うことで、3 ウェイハンドシェイクの時間遅延を削減しているからです。さらに QUIC の暗号化には TLS プロトコルの最新バージョンである TLS 1.3 が採用されています。従来の TLS 1.1-1.2 と比べて、TLS 1.3 はクライアントが TLS ハンドシェイクの完了を待たずにアプリケーションデータの送信を開始でき、1 RTT および 0 RTT をサポートするため、高速なコネクション確立を実現できます。

2) HOL ブロッキングがない

前述のとおり、HTTP/2.0 はアプリケーション層の HOL ブロッキング問題を解決しましたが、コネクション自体は依然として TCP に基づいており、トランスポート層でのリクエストブロッキング問題は解決できませんでした。

一方 UDP にはそもそもコネクション確立という概念がなく、さらに QUIC が使用する stream は互いに分離されているため、ある stream のデータ処理が他の stream をブロックすることはありません。したがって UDP を使うことで HOL ブロッキングは発生しなくなります。

3) 信頼性とより正確な RTT

TCP ではデータの信頼性を保証するために、シーケンス番号+確認応答番号の仕組みが使われています。synchronize sequence number を持つパケットがサーバーに送信されると、サーバーは一定時間内に応答を返します。この時間内に応答がなければ、クライアントはサーバーがパケットを受信して応答を返すまで、そのパケットを再送し続けます。

では、TCP はどのように再送タイムアウト時間を判断しているのでしょうか?

TCP は一般的に適応的再送アルゴリズムを採用しており、このタイムアウト時間はラウンドトリップタイム RTT に応じて動的に調整されます。しかし再送時には毎回同じシーケンス番号を使うため、応答が元のパケットに対するものなのか再送パケットに対するものなのかを区別できず、RTT の計算結果があまり正確でなくなってしまいます。

QUIC は TCP プロトコルを使っていませんが、信頼性も保証しています。QUIC が信頼性を実現する仕組みは Packet Number です。このシーケンス番号は synchronize sequence number の代替と考えることができ、こちらも単調増加します。syn と異なるのは、サーバーがパケットを受信したかどうかにかかわらず、この Packet Number は必ず +1 されるという点です。syn の場合は、サーバーが ack 応答を返した後にのみ +1 されます。

たとえば PN = 10 のパケットが送信中に何らかの理由でなかなかサーバーに届かなかった場合、クライアントは PN = 11 のパケットを再送します。しばらくしてクライアントが PN = 10 に対する応答を受け取り、応答メッセージを返送すると、このときの RTT は PN = 10 のパケットがネットワーク内で生存していた時間となり、比較的正確に計算できます。

QUIC はパケットの信頼性を保証しますが、では、データの信頼性はどのように保証されるのでしょうか?

QUIC は stream offset という概念を導入しました。1 つの stream で複数の stream offset を転送でき、各 stream offset は実質的に 1 つの PN で識別されるデータです。ある PN で識別されるデータが失われても、PN を +1 して再送されるのは元の PN が示していたのと同じデータであり、すべての PN で識別されるデータがサーバーに届いた時点で再構築が行われます。これによってデータの信頼性が保証されます。サーバーに到達した stream offset は順序どおりに組み立てられるため、データの順序性も同時に保証されます。

4) プラガブルな輻輳制御

よく知られているように、TCP プロトコルの具体的な実装はオペレーティングシステムのカーネルが担っており、アプリケーションは利用することしかできず、カーネルを変更することはできません。モバイル端末やますます多くのデバイスがインターネットに接続されるようになり、パフォーマンスは非常に重要な評価指標になってきました。モバイルネットワークは急速に発展していますが、ユーザー側の更新は非常に緩慢です。開発終了から何年も経っているにもかかわらず、いまだに多くの地域のコンピュータで XP システムが使われているのを目にします。サーバー側のシステムはユーザーのアップグレードには依存しませんが、OS のアップグレードは低レイヤーのソフトウェアやランタイムライブラリの更新を伴うため、やはり保守的で緩慢です。

QUIC プロトコルの重要な特徴のひとつが**プラガビリティ(差し替え可能性)**であり、動的な更新とアップグレードが可能です。QUIC はアプリケーション層で輻輳制御アルゴリズムを実装しており、OS やカーネルのサポートを必要としません。輻輳制御アルゴリズムを切り替える場合も、サーバーで再読み込みするだけでよく、停止や再起動は不要です。

5) フロー制御とセキュリティ

TCP のフロー制御はスライディングウィンドウによって実現されていることはご存じのとおりです。QUIC もフロー制御を実装しており、同様にウィンドウ更新 window_update を使って、受け入れ可能なバイト数を相手に伝えます。

TCP プロトコルのヘッダーは暗号化も認証もされていないため、転送中に改ざんされる可能性があります。これに対して QUIC では、メッセージのヘッダーはすべて認証され、メッセージ本体も暗号化処理されています。そのため QUIC のメッセージに何らかの変更が加えられれば、受信側は即座にそれを検知でき、セキュリティが保証されます。

6) QUIC の利点まとめ

まとめると、QUIC には次のような利点があります。

  • UDP プロトコルを使うため、3 ウェイハンドシェイクによるコネクション確立が不要で、TLS のコネクション確立時間も短縮される。
  • HOL ブロッキング問題を解決した。
  • 動的なプラガビリティを実現し、アプリケーション層で輻輳制御アルゴリズムを実装しているため、いつでも切り替えられる。
  • メッセージヘッダーとメッセージ本体をそれぞれ認証・暗号化処理し、セキュリティを保証する。
  • コネクションのスムーズなマイグレーションが可能。

コネクションのスムーズなマイグレーションとは、スマートフォンやモバイルデバイスが 4G 回線と WiFi などのネットワーク間を切り替える際に、切断や再接続が発生せず、ユーザーが何も意識することなくシームレスに信号を切り替えられることを指します。現在、QUIC プロトコルは RFC 9000 として標準化されています。

まとめ

HTTP の進化を振り返ってみましょう。0.9 は HTML テキストしか転送できず、1.0 はヘッダーを導入して転送コンテンツを多様化し、1.1 は持続的接続でコネクションを繰り返し確立するオーバーヘッドを解決し、2.0 はバイナリフレーミングと多重化で並行処理のボトルネックを突破しました。しかし下層が TCP である限り、トランスポート層の HOL ブロッキングは根絶できません。そこで HTTP/3 は UDP ベースの QUIC に切り替え、コネクション確立、信頼性のある転送、輻輳制御をすべてアプリケーション層に移して再設計しました。各世代のプロトコルは前世代の弱点を補う形で進化してきたのです。HTTP/3.0 が今後ますます多くのシーンで使われていくことでしょう。

COMMENTS