Go: resp.Body は読み切ってから閉じる(keep-alive 接続を再利用させる)
defer resp.Body.Close() だけで、ボディを最後まで読まずに return すると、http.Transport がその接続を再利用できないことがある。毎回 TCP + TLS のハンドシェイクからやり直しになる。
書き方
go
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() {
io.Copy(io.Discard, resp.Body) // 残りを読み捨てる
resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %s", resp.Status) // 読まずに早期 return しても上で読み切る
}
return json.NewDecoder(resp.Body).Decode(&out)json.Decoder は JSON の値を読み終えたところで止まるので、末尾の改行などが残っていることがある。読み捨てておくと確実。
注意点
- 巨大なボディや終わらないストリームを無制限に読み捨てると、そのほうが遅い・危険。
io.Copy(io.Discard, io.LimitReader(resp.Body, 64<<10))のように上限を付け、超えたら接続を捨てる判断もあり。 - ステータスコードがエラーのときのボディも同じ。エラー処理の分岐で Close し忘れないよう、
deferはerrチェックの直後に置く。 - 接続が再利用されているかは
net/http/httptraceのGotConn(GotConnInfo.Reused)で確認できる。 - HTTP/2 では1本の接続を多重化するので影響の出方は違うが、ボディを閉じるのは同じく必須。