Go: http.Get / http.DefaultClient はタイムアウトなし。自前の http.Client に Timeout を付ける
http.DefaultClient の Timeout はゼロ(無制限)なので、接続を受けたまま応答しないサーバーに当たるとゴルーチンが永遠に止まる。&http.Client{Timeout: ...} を作るか、リクエストに期限付きの context を渡す。
何が起きるか
http.Get / http.Post / http.Head は内部で http.DefaultClient を使う。Timeout が 0 なので、
- TCP 接続はできたがレスポンスヘッダが返ってこない
- ボディを少しずつしか送ってこない
といった相手に当たると、呼び出し元のゴルーチンが待ち続ける。バッチやワーカーでこれが積み重なると、ゴルーチンと接続が漏れて詰まる。
書き方
go
// パッケージ単位で 1 つ作って使い回す(Transport の接続プールを共有するため)
var httpClient = &http.Client{
Timeout: 10 * time.Second, // 接続〜ボディを読み終えるまでの合計
}
resp, err := httpClient.Get(url)リクエストごとに期限を変えたいときは context を使う。
go
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := httpClient.Do(req)注意点
Client.Timeoutはボディの読み取りも含む合計時間。大きなファイルのダウンロードやストリーミングでは途中で切れるので、その用途はTimeoutを付けず context やTransport側のResponseHeaderTimeoutなどで制御する。http.Clientはリクエストごとに作らない。接続の再利用が効かなくなる(Go: 同じホストへ大量に呼ぶなら MaxIdleConnsPerHost を上げる(既定は 2) も参照)。- サーバー側のタイムアウトは Go: http.Server には必ず ReadHeaderTimeout を設定する。
出典
- @amitsinha__07 · 2026-10-24
- net/http ドキュメント
Client.Timeout: pkg.go.dev/net/http#Client