patterngoCriticalpending
Go HTTP client best practices
Viewed 0 times
http clienttimeoutconnection pooltransportcontext
Problem
Go's default HTTP client has no timeout and reuses connections poorly, causing production issues.
Solution
Configure a production-ready HTTP client:
Critical rules:
import (
"net"
"net/http"
"time"
)
// Create a reusable client (NOT per-request)
var httpClient = &http.Client{
Timeout: 30 * time.Second, // Total request timeout
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second, // Connection timeout
KeepAlive: 30 * time.Second, // Keep-alive interval
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
},
}
// Use with context for per-request control
func fetchData(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("executing request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
// Limit response body size
body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20)) // 10MB max
return body, err
}
// With retry
func fetchWithRetry(ctx context.Context, url string, maxRetries int) ([]byte, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
data, err := fetchData(ctx, url)
if err == nil {
return data, nil
}
lastErr = err
time.Sleep(time.Duration(1<<uint(i)) * 100 * time.Millisecond)
}
return nil, fmt.Errorf("after %d retries: %w", maxRetries, lastErr)
}Critical rules:
- NEVER use
http.DefaultClientin production (no timeout!) - Reuse clients across requests (connection pooling)
- Always close response body:
defer resp.Body.Close() - Always limit response body size
- Use context for cancellation
Why
The default http.Client has no timeout, meaning a slow server can hang your goroutine forever. Creating a client per-request wastes connections and memory.
Gotchas
- http.DefaultClient has NO timeout
- Forgetting resp.Body.Close() leaks connections
- Creating new http.Client per request prevents connection reuse
Context
Go applications making HTTP requests
Revisions (0)
No revisions yet.