Upgrade to Pro — share decks privately, control downloads, hide ads and more …

なぜレスポンスボディを読み切るのか - Go 1.27で進化した net/http のコネクシ...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

なぜレスポンスボディを読み切るのか - Go 1.27で進化した net/http のコネクション再利用メカニズム_Ryu

Go Night Talks2026 – After ConferenceにてRyuさんが登壇した資料です。
https://upsider.connpass.com/event/403598/

Avatar for UPSIDER, Inc.  Tech&Product div.

UPSIDER, Inc. Tech&Product div.

September 15, 2026

More Decks by UPSIDER, Inc. Tech&Product div.

Transcript

  1. Presenter Profile 古田 龍平 Ryuhei Furuta) 株式会社UPSIDER 2020/04 〜 2025/01

    ヤフー株式会社(→ LINEヤフー株式会社) レコメンデーション基盤の開発・運用 2025/02 〜 現在 株式会社 UPSIDER PRESIDENT CARD (経営者のための法人カード)の開発・運用 趣味 思い入れのある Go package スポーツ観戦 httputil pkg © 2026 UPSIDER.inc
  2. でもパフォーマンスは... ボディを読まないでクローズすると、毎回 TCPコネクションを張り直す 同じ URL に 20 回リクエスト / 接続の再利用回数を

    httptrace で数える 01. close only 02. drain + close ボディを読まずに Close ボディを Read / Drain してから Close reused = 0 new = 20 ⚠ 毎リクエストで TCP コネクションを張り直す reused = 19 new = 1 ✅ コネクションがプールに回収され再利用される © 2026 UPSIDER.inc
  3. 同じコードでも Go 1.27 でこの挙動変わりました 同じ URL に 20 回リクエスト /

    接続の再利用回数を httptrace で数える 01. close only 02. drain + close ボディを Read / Drain してから Close ボディを Read / Drain してから Close reused = 19 new = 1 ✅ コネクションがプールに回収され再利用される reused = 19 new = 1 ✅ コネクションがプールに回収され再利用される © 2026 UPSIDER.inc
  4. HTTP/1 原理 HTTP/1 はTCPコネクション内でバイト列を流すだけ レスポンス1 レスポンス2 HTTP/1.1 200 OK..Content-Length: 5.....helloHTTP/1.1

    200 OK..Content-Length: 5...world ⚠ この位置に区切りのバイトは無い TCP が運んでいるのは順序付きバイト列 データの境界を決めるのはアプリケーション層 バケットの切れ目と HTTP の切れ目は一致しない Content-Length 分数えるか chuncked の終端を検知する © 2026 UPSIDER.inc
  5. HTTP/1 原理 実測:HTTP/1 のコネクションは区切りのないバイト列 2つのリクエストをまとめて書き込み、返ってきたバイトを一切解釈せずにダンプ 01. 生データの受信 RAW HEX &

    ASCII DUMP (157 BYTES) バイトの連続ストリーム ソケットから取得したそのままのバイナリ。改行 コードやヘッダー情報がすべて一続きで受信さ れる。 02. 境界の検知は不可能 区切り文字( Delimiter)なし レスポンス1 (hello) の終端直後に レスポンス2 HTTP/1.1 が隙間なく続く。 03. アプリ層の責務 Content-Length による切り出し TCP自体はメッセージの区切りを知らない。アプ リ側がバイト数をカウントして分割処理を行う。 © 2026 UPSIDER.inc
  6. HTTP/1 原理 読み残していると、次のレスポンスが解釈できない レスポンス1 レスポンス2 HTTP/1.1 200 OK..Content-Length: 5.....helloHTTP/1.1 200

    OK..Content-Length: 5...world ⚠ ここから次のヘッダを読み出そうとして壊れる malformed HTTP version "helloHTTP/1.1" 影響 読み残しがあるとコネクションは再利用できない → 破棄 © 2026 UPSIDER.inc
  7. Transport 実装 Transport がTCPコネクション生成・プール管理・データ読み書きを担当 Client 内の Transport が各 HTTP リクエスト・レスポンスを処理。

    Client Transport RoundTripper Transport RoundTripperインタフェースの実装 コネクションプール コネクション生成 persistConn (アクティブ) dialConn persistConn Idle Connection) 1. TCP / TLS ダイヤル ✔ ヒット時:そのまま再利用 ソケット接続(net.Dial) ❌ 未ヒット時:コネクション生成 writeLoop リクエストの書き込み Photo 受領したリクエストをソケットへ書き出し。 役割 / 処理 HTTP リクエスト・レス ポンスの処理を Transport へ移譲 2. persistConn 生成 persistConn Idle Connection) `writeLoop`と`readLoop`並行起動 readLoop レスポンスの読み出し ソケットからのレスポンスを呼び出し元へ返却。 3. アクティブ化 persistConn Idle Connection) コネクションを有効化する 使用後にコネクションプールへ返却 © 2026 UPSIDER.inc
  8. readLoop 実装 readLoop では EOF まで読み切ったかどうかをチャネル経由で受け渡し bodyEOFSignal EOF 読み切り成功 (struct)

    レスポンスを正常に読み切った コネクションを再利用 resp.Body レスポンスボディのデータストリーム (io.ReadCloser) fn EOF 到達時 / 読み切り完了時に 実行されるフック関数 ✔ レスポンスデータを最後まで正常に読み切り完了 Photo コネクションは正常状態として Keep-Alive Idle プールへ再利用用に返却 waitForBodyRead Channel) 途中クローズ / キャンセル リクエストのキャンセル コネクションのクローズ コネクションを破棄 earlyCloseFn 途中でクローズされた場合に コネクション切断を行う関数 ✖ 途中でボディ読み出しが中断またはキャンセルされた場合 残データが残ると次のレスポンス解釈が破損するためコネクションを破棄 © 2026 UPSIDER.inc
  9. Go 1.27 での変更 クローズ時にレスポンスボディを読み切ろうとするようになった bodyEOF まで読み切っていない かつ Content-Length がクローズ後の読み切りサイズ以下 HTTP/1

    Response.Body now automatically drains any unread content upon being closed, up to a conservative limit, to allow better connection reuse. https://go.dev/doc/go1.27 HTTP/1のResponse.Bodyをクローズ(close)した際、接続を効率的に再利用できるよう、未読のコンテンツが保守的な上限値まで自動的に読み飛ばされる(ドレ インされる)ようになりました。 © 2026 UPSIDER.inc
  10. Summary 本セッションのまとめ POINT 01 HTTP/1 は区切りのないバイト列 ボディの終端=次のレスポンスの開始位置。データを最後まで読み切らないと、コネクションを安全に再利用することができない。 POINT POINT 02

    01 HTTP/1.1 は区切りのないバイト列 判断しているのは readLoop ボディの終端=次のレスポンスの開始位置。 bodyEOFSignal(response.Body のラッパー)が判定を担当。waitForBodyRead Channel に流すことで EOF 到達を制御。 データを最後まで読み切らないと、コネクションを安全に再利用することができません。 POINT 03 Go 1.27 クローズ時の自動 drain 対応 レスポンスボディのクローズ時に一定条件まで自動で drain 実行。条件上限: 256 KB / 待機時間: 50ms POINT 04 HTTP/2 では元々不要で、 HTTP/1 との振る舞いの一貫性のなさを解消する変更だった HTTP/2 はフレームおよびストリーム指向のプロトコル。ストリーム独立管理のため、このようなバイト列の読み切り制御は不要。 © 2026 UPSIDER.inc
  11. Appendix References RELEASE NOTE Go 1.27 release note https://go.dev/doc/go1.27 GO

    REVIEW Go Code Review CL 737720 https://go-review.googlesource.com/c/go/+/737720 GITHUB ISSUE golang/go #77370 https://github.com/golang/go/issues/77370 ARTICLE Golangのnet/httpの実装から HTTP1.1クライアントでの TCPコネクションの挙動を確かめる https://gehirn.kotaro7750.net/posts/20250321-golang-http-client-behavior/ SAMPLE CODE sample project https://github.com/Ryuheeeei/go-night-talks-2026 © 2026 UPSIDER.inc