Problem Statement
The default http.DefaultClient has no timeout and poor connection pool settings. Configure a production-ready HTTP client that won't leak connections or hang forever.
The Default Client Trap
// ❌ NEVER use in production
resp, err := http.Get("https://slow-api.com/data")
// If server never responds, this blocks FOREVER
// No timeout, no connection limitProduction HTTP Client
import (
"net"
"net/http"
"time"
)
func NewHTTPClient() *http.Client {
transport := &http.Transport{
// Connection pool settings
MaxIdleConns: 100, // Total idle connections
MaxIdleConnsPerHost: 10, // Per-host idle connections
MaxConnsPerHost: 100, // Max connections per host
IdleConnTimeout: 90 * time.Second, // Close idle connections
// Timeouts for connection establishment
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // Connection timeout
KeepAlive: 30 * time.Second, // TCP keepalive
}).DialContext,
// TLS handshake timeout
TLSHandshakeTimeout: 10 * time.Second,
// Response header timeout
ResponseHeaderTimeout: 10 * time.Second,
// Expect: 100-continue timeout
ExpectContinueTimeout: 1 * time.Second,
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second, // Total request timeout
}
}Connection Pool Sizing
// If you talk to ONE microservice heavily:
transport := &http.Transport{
MaxIdleConnsPerHost: 100, // Increase from default 2!
MaxConnsPerHost: 100,
}
// If you talk to MANY different hosts:
transport := &http.Transport{
MaxIdleConns: 1000, // Total pool size
MaxIdleConnsPerHost: 10,
}Request-Level Timeout with Context
func fetchWithTimeout(client *http.Client, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("request timed out")
}
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}Common Production Issues
| Issue | Cause | Fix |
|---|---|---|
| Connection leak | Not closing resp.Body | Always defer resp.Body.Close() |
| Too many TIME_WAIT | Low MaxIdleConnsPerHost | Increase to 10-100 |
| Request hangs | No timeout set | Set Client.Timeout |
| DNS stale | Cached DNS entries | Limit connection age |