bodyclose

Table of Contents

  1. 解決する問題
  2. 設定
  3. オプション
  4. サンプル
    1. 検出例
    2. 修正例
  5. 注意点
  6. 参考リンク

bodyclose は HTTP レスポンスボディが適切にクローズされているかを検出する静的解析ツールです。

解決する問題

http.Get などで取得したレスポンスの Body を閉じ忘れると、TCP 接続が再利用されず、コネクションリークやパフォーマンス低下を引き起こします。

1
2
3
4
5
6
7
8
9
10
// Bad: Body を閉じていない
func fetchData(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}

// resp.Body.Close() がない → コネクションリーク
return io.ReadAll(resp.Body)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Good: defer で確実に閉じる
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
# .golangci.yml
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
// bodyclose が警告するパターン

// 1. Body を閉じていない
func example1() {
resp, _ := http.Get("https://example.com")
// resp.Body が閉じられていない
_ = resp
}

// 2. エラーパスで閉じ忘れ
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 // ここでは defer により閉じられるので OK
}

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
// 正しいパターン: defer で確実に閉じる
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() の呼び出し順序(エラーチェック前後)は検出対象外

参考リンク