bodyclose は HTTP レスポンスボディが適切にクローズされているかを検出する静的解析ツールです。
解決する問題
http.Get などで取得したレスポンスの Body を閉じ忘れると、TCP 接続が再利用されず、コネクションリークやパフォーマンス低下を引き起こします。
1 2 3 4 5 6 7 8 9 10
| func fetchData(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("fetch %s: %w", url, err) }
return io.ReadAll(resp.Body) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func fetchData(url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("fetch %s: %w", url, err) } defer func() { if err := resp.Body.Close(); err != nil { slog.Error("close response body", "error", err) } }()
return io.ReadAll(resp.Body) }
|
設定
公式ドキュメント
設定オプションはありません。有効化するだけで動作します。
1 2 3 4 5 6
| version: "2"
linters: enable: - bodyclose
|
オプション
golangci-lint 経由では設定オプションはありません。
スタンドアロンで実行する場合は -check-consumption フラグが利用でき、Body が実際に読み取られているかも検証します。
サンプル
検出例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
func example1() { resp, _ := http.Get("https://example.com") _ = resp }
func example2() (*Data, error) { resp, err := http.Get("https://example.com") if err != nil { return nil, err } defer resp.Body.Close()
var data Data if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return nil, err }
return &data, nil }
|
修正例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func fetchUser(ctx context.Context, url string) (*User, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) }
resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer func() { if err := resp.Body.Close(); err != nil { slog.Error("close response body", "error", err) } }()
var user User if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { return nil, fmt.Errorf("decode response: %w", err) }
return &user, nil }
|
注意点
- カスタムの Body 消費パターン(独自のラッパー関数など)は検出できない場合がある。その場合は
//nolint:bodyclose で抑制する
resp.Body.Close() の呼び出し順序(エラーチェック前後)は検出対象外
参考リンク