URL に GET リクエストを送信し、HTTP ステータスコードを返す次の関数を考えてみましょう。HTTP/1(GODEBUG=http2client=0)を使用して index.golang.org に対してこの関数を 100 回呼び出すと、作者のマシンでは Go 1.26 で約 710 ミリ秒 かかります。Go 1.27 では同じプログラムが約 150 ミリ秒 で完了し、ほぼ 5 倍高速です。
この差は接続の確立コストから生じます。新しい接続でリクエストを送るには、TCP ハンドシェイク(1 往復時間、すなわち RTT)、TLS ハンドシェイク(TLS 1.2 では 2 RTT)、そして HTTP リクエスト自体が必要で、合計で約 3 RTT かかります。リージョン間の通信では片道の遅延が 50 ms 以上になることも多く、接続の確立だけで約 300 ms かかることもあります。TLS 1.3 を使用し比較的低遅延の index.golang.org であっても、新しい接続を開くコストは依然として大きく感じられます。
このコストを避けるため、HTTP クライアントとサーバーは接続の再利用を試みます。HTTP/1.0 のクライアントとサーバーは Connection: keep-alive ヘッダーでこれを有効にしますが、HTTP/1.1 の接続はデフォルトで永続的です。HTTP/2 は単一の TCP 接続上で複数のリクエストとレスポンスを多重化します。
ただし HTTP/1 では再利用が失敗することがあります。同じ接続上のレスポンスは順番に送られるため、net/http は現在のレスポンスボディを最後まで読み終えてからでないと、次のレスポンスの開始位置を特定できません。この例ではレスポンスボディが読まれないため、resp.Body.Close を呼び出すと net/http は未読のボディを見つけ、接続を安全に再利用できません。Go 1.26 までは、これによりリクエストごとに新しい接続が必要でした。
出典: Hacker News · 要約:HeadlinesBriefing