インターネットの普及に貢献してきた HTTP プロトコル、その新バージョンである HTTP/3 (http バージョン3) の仕様策定が進行中です。2020/9/25 に draft version 31 が公開されています。
本記事では HTTP の歴史を踏まえ、HTTP/3 の特徴を見ていきます。
HTTP 仕様の構造
HTTP の RFC 仕様には主に以下の2つの要素があります。
- Syntax (構文)
- Semantics (意味)
Syntax は、ネットワークを経由してどのように効率的に相手に情報を伝えるかの手段を示します。
Cache や Keep-Alive に関する規定や次に示す Semantics をどのように乗せるかというパケットフォーマット等の規定がされています。
HTTP/1.1 の RFC 仕様としては RFC 7230 があります。
Semantics は、相手に伝える情報そのものを示します。
GET や POST、CONNECT等のリクエスト用メソッドや、200 (ok) 等のレスポンスコード、ヘッダフィールド等に関する規定がされています。
HTTP/1.1 の RFC 仕様としては RFC7231 があります。メソッド一覧やレスポンスコード一覧を確認したいときはこの RFC の目次が見易くて分かり易いので良いと思います。
また、メソッドとレスポンスコードの一覧と意味については以下の記事も併せてご参照下さい。
HTTP のプロトコルスタックの遷移と比較
HTTP/1.0 から HTTP/3 にかけてプロトコルスタックや機能がどのように変わったのかを以下に示します。
HTTP/1.0 から HTTP/1.1 への変化
HTTP/1.0 が規定されているのは RFC1945 (1996年) です。この頃はインターネットが流行し始めたてで、試行錯誤を行っていた時代です。次の年の 1997年に HTTP/1.1 (RFC2068) が公表され、その後 2 回の改訂が行われました。最終版は前述の RFC7230 と RFC7231 です。
HTTP/1.0 から 1.1 への大きな変化の 1 つは「Keep Alive」の正式サポートです。
HTTP/1.0 では 1 つの TCP コネクションに 1 つのリクエストしかできませんでした。
ですが HTTP/1.1 では 1 つの TCP コネクションに「順番に」複数のリクエストを行うことができるようになりました。
その他、メソッドやレスポンスコードの追加、Host 属性によるバーチャルドメインサポート等が含まれています。
HTTP/1.1 から HTTP/2 への変化
HTTP/1.1 では「順番に」リクエストを送信することができるようになりましたが、1 つのリクエストが詰まると後のリクエストもその分遅れてしまうことが問題視されました。
このような「キューの中の 1 つが遅れた際に引きずられて後続も遅れる」事象を一般に HoL (Head of Line) Blocking と呼ばれています。
この問題のため、HTTP/1.1 に準拠したブラウザでは KeepAlive が使えても、複数の TCP コネクションで対応することが多いです。
この問題を解消しようと、HTTP/2 では 1 つの TCP コネクションの中に「並列に」複数のリクエストを行うことができるようになりました。この仕組みをStream (ストリーム)と呼びます。
ブラウザは、複数のリクエストの各々に対して 1 つのストリームを割り当て、それらをまとめて送信します。Web サーバはそのストリームを識別し、各々に対してレスポンスを返します。
HTTP/2 では HTTP/1.1 の Semantics は変わっていません。つまり、メソッドやレスポンスコード、ヘッダフィールドの意味などは変化していません。HTTP/2 ではストリームに代表される、Syntax 部分が大きく改良されています。
HTTP/2 のその他の改良点としては、HTTP レベルでフロー制御や優先制御が実装されたことと、PUSH 機能(クライアントからのリクエスト無しに、必要と思われるファイル(リンクされている .css や画像ファイル等) をサーバから気を利かせて送信する)が実装されたことです。
HoL Blocking 問題 再び
ですがこの HTTP/2 をもっても、HoL Blocking に引っ掛かることが分かりました。
そもそも HTTP/2 は TCP 上で処理するため、セグメンテーションされた TCP ペイロードをホスト側で組み立てる際には結局、TCP のシーケンス番号の順番に並び替えされるため、1 つのパケットが欠けていると以降のパケットの処理が出来ないためです。
これは TCP 通信用のライブラリの問題です。これにより、せっかくストリームにより並列に複数リクエストを出したとしても、パケットロスが 1 つあったら以降のレスポンスは全て揃ったとしてもロスしたパケットが再送されてくるまでそのレスポンスは表示できないのです。
つまり、HTTP レベルの HoL Blocking が解消されても、TCP レベルの HoL Blocking が解消されていなかったのです。
そのため、結局 HTTP/1.1 のときと変わらず、多くのブラウザは 1 つの Web サーバへのアクセスに対して複数の TCP コネクションを確立する実装となっています。
一方、時を同じくして Google は独自の検証により『TCP の 3way handshake とか TLS のネゴシエーションって何往復もするけど、これって実は Web 表示速度に大きく影響してるね。帯域を増やすより往復時間 (RTT: Rount Time Trip) や往復回数を減らすほうが効果的だね。』ということを突き止めました。
Google はその考えを反映した SPDY, QUIC といったプロトコルを独自開発していました。(今回の話の本流からはずれますが、TLS 1.2 から 1.3 への変化もこの流れに乗っています)
こういった背景もあり、脱 TCP の流れが出来上がり、そしてめでたく HTTP/3 では UDP 上(正確には QUIC 上) で動作することが決まったのです。
HTTP/2 から HTTP/3 への変化
HTTP/3 は QUIC という新プロトコル上で動作することが決まっています。QUIC は UDP 上で動作します。HTTP/3 の Semantics は HTTP/1.1 や HTTP/2 から変わっていません。
なので HTTP/3 の主な変化は Syntax 部分であり、その大きな要素は『HTTP/2 の TCP+TLS から UDP+QUIC への移行とそれに伴う適合化』です。
HTTP/2 で出来たばかりのストリームとフロー制御については HTTP/3 からは無くなり、QUIC 側で任されることになりました。
これにより、TCP+TLS で持っていた機能のほとんどが UDP+QUIC で賄えるようになりました。
- TCPコネクション ⇒ QUIC ConnectionID
- TCPシーケンス番号(ack/再送) ⇒ QUIC Packet Numbers
- TCPフロー制御 (Window) ⇒ QUIC フロー制御 (Window)
UDP になったことで以下のことが期待されます。
TCP 3way Handshake 削除による速度向上
前述の通り、今の時代では帯域を拡大するよりも通信の往復回数を減らすほうが速度向上に効果的です。TCP 3way Handshake が無くなることによって往復回数を減らすことができます。
以下に『TCP + TLS v1.3』と『UDP + QUIC + TLS v1.3』の比較を示します。
上図は QUIC セッション確立のために1往復使う『1-RTT』という方法ですが、セッション再開時はさらに再開メッセージとともに実データ (HTTP/3 等) を送る『0-RTT』という方法も用意されています。
ただし、これはセキュリティがやや甘くなるため、利用するかしないかはアプリケーション作成者に委ねられます。
TCP HoL Blocking の解消
QUIC からアプリケーション(Web サーバ)にデータが渡される単位が、(QUIC Connection 単位ではなく) QUIC ストリーム単位になります。
クライアントの IP 変化時のローミング
スマートフォンでの利用シーンとして、SIM から無線 (あるいはその逆) に切り替わることがよくあります。このとき、スマートフォンの IP が変化します。
TCP の場合、TCP コネクションを確立し直さなければなりません。(TCP コネクションは送信元/宛先の IP/ポート番号の組合せで識別するため。)
ですが UDP を使った QUIC の場合、[QUIC Connection ID] によりクライアント⇔サーバのコネクションを識別するので、IP が変化してもコネクションが継続します。
その他の機能
HTTP/3 には優先制御機能も付いています。(これは HTTP/2 で追加された機能で、HTTP/3 にも継承されています。)
クライアント(ブラウザ等)がサーバに対して、「並列して送ったリクエストの中でどういう優先度でレスポンスして欲しいか」を伝えることです。
サーバはこの要望を加味してレスポンスすることができます。ただし、そこはサーバ側のさじ加減次第ですし、最悪無視することもできます。
また、フロー制御機能もあります。これは TCP フロー制御とほぼ同じで、受け入れ可能なバッファ量を適時、相手に伝える (Sliding Window) ことで通信を効率化する方法です。
フロー制御は QUIC コネクション単位と QUIC ストリーム単位の両方で使われます。
コメント
いつもわかりやすい内容有難うございます。