Clientes HTTP, Transporte y Agrupación de Conexiones
El tráfico HTTP saliente en Go fluye a través de http.Client, que delega la gestión de conexiones a http.Transport.
Busca en todas las páginas de la documentación
El tráfico HTTP saliente en Go fluye a través de http.Client, que delega la gestión de conexiones a http.Transport.
Ajustar los tiempos de espera y los tamaños de los pools evita fugas de goroutines, bloqueos de TLS y agotamiento de conexiones bajo carga.
http.Client es la API de alto nivel para enviar solicitudes.
Su campo Transport implementa RoundTripper y es responsable del dial TCP, TLS, la actualización a HTTP/2 y la reutilización de conexiones inactivas.
Client.Timeout limita toda la solicitud, incluida la lectura del cuerpo.
Un control más fino reside en Transport: DialContext, TLSHandshakeTimeout, ResponseHeaderTimeout y los límites de conexiones inactivas.
Cada cuerpo de respuesta debe cerrarse para que las conexiones regresen al pool.
Tarjeta de referencia rápida - lista para copiar y pegar.
var apiClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 5 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := apiClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()Cuándo usar esto:
http.Get/http.Post en rutas de código de producción.package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"time"
)
type API struct {
base string
client *http.Client
}
func NewAPI(base string) *API {
return &API{
base: base,
client: &http.Client{
Timeout: 8 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 3 * time.Second,
ResponseHeaderTimeout: 4 * time.Second,
MaxIdleConns: 50,
MaxIdleConnsPerHost: 8,
IdleConnTimeout: 60 * time.Second,
},
},
}
}
func (a *API) Fetch(ctx context.Context, path string) (map[string]any, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, a.base+path, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
resp, err := a.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(b))
}
var out map[string]any
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return nil, err
}
return out, nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
api := NewAPI("https://httpbin.org")
data, err := api.Fetch(ctx, "/get")
if err != nil {
log.Fatal(err)
}
fmt.Println(data["url"])
}Lo que esto demuestra:
http.Client dedicado con ajuste explícito de Transport.NewRequestWithContext para la propagación de cancelación.defer resp.Body.Close() en cada ruta de éxito y error.Client.Do llama a Transport.RoundTrip, que establece una conexión o reutiliza una inactiva.Body.Close().MaxIdleConnsPerHost limita la reutilización por host de destino; las conexiones excesivas se cierran después de la respuesta.| Capa | Campo | Cubre |
|---|---|---|
| Solicitud completa | Client.Timeout | Desde el dial hasta la lectura del cuerpo |
| Dial TCP | Dialer.Timeout | Conexión inicial |
| TLS | TLSHandshakeTimeout | Solo handshake |
| Tiempo de espera del servidor | ResponseHeaderTimeout | Después de enviar la solicitud, antes de las cabeceras |
| Contexto | req.Context() | Cancelación del llamador |
// Inyectar tracing o autenticación sin reemplazar Transport por completo:
type roundTripper struct { base http.RoundTripper }
func (rt roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("Authorization", "Bearer "+token)
return rt.base.RoundTrip(req)
}
client.Transport = roundTripper{base: client.Transport}| Parámetro | Tipo | Descripción |
|---|---|---|
Client.Timeout | time.Duration | Cero significa sin tiempo de espera general |
MaxIdleConnsPerHost | int | Por defecto 2 en versiones anteriores de Go; aumentar para hosts activos |
IdleConnTimeout | time.Duration | Cierra las conexiones inactivas en pool |
DisableKeepAlives | bool | Fuerza una nueva conexión por solicitud |
http.DefaultClient en servidores - Sin tiempo de espera y transporte compartido en todo el proceso. Solución: Construir clientes a nivel de paquete o inyectados por dependencia.resp.Body - Fuga conexiones hasta el agotamiento del pool. Solución: defer resp.Body.Close() inmediatamente después de un Do exitoso.io.Copy(io.Discard, resp.Body) antes de cerrar en rutas de error.Client.Timeout - Puede ocultar la necesidad de ResponseHeaderTimeout en servidores lentos. Solución: Superponer tiempos de espera de transporte para casos de cabeceras colgadas.http.DefaultTransport - Afecta a todo el proceso, incluidas las bibliotecas. Solución: Clonar los valores predeterminados en una nueva estructura Transport.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
resty / heimdall | Se desean ayudantes de reintentos y backoff | Quieres cero dependencias |
| Cliente gRPC | Contratos fuertes, streaming | El par solo habla REST |
net/http + RoundTripper personalizado | Control total, solo stdlib | Necesitas un framework de cliente HTTP completo |
| Sidecar de Service Mesh | mTLS y reintentos en el plano de datos | Llamadas JSON internas simples son suficientes |
Client.Timeout es fijo por instancia de cliente.
El deadline del contexto varía por llamada y se compone con la cancelación del padre.
Usar ambos: techo del cliente más presupuesto de contexto por solicitud.
Los límites predeterminados de conexiones inactivas por host son pequeños.
Un QPS alto a una API sin aumentarlo causa un desgaste excesivo de TCP.
Sí.
Los clientes son seguros para uso concurrente una vez configurados.
Crear uno por dependencia de upstream al inicio del proceso.
Establecer TLSNextProto a un mapa vacío en Transport para conexiones TLS, o configurar ForceAttemptHTTP2 a false.
Implementar lógica de reintento en un RoundTripper personalizado o en un wrapper del llamador.
Respetar Idempotency-Key y solo reintentar métodos seguros o rutas conocidas como idempotentes.
Sí, hasta 10 redirecciones por defecto.
Establecer CheckRedirect en el cliente para personalizar o deshabilitar.
Usar bytes.NewReader con NewRequestWithContext, establecer Content-Type, y pasar el lector como cuerpo.
MaxConnsPerHost (Go 1.11+) limita las conexiones concurrentes por host.
Combinar con limitación de tasa del lado del cliente para protección.
Establecer Transport.TLSClientConfig con el certificado del cliente cargado y el pool de CA.
Rotar certificados a través de la devolución de llamada personalizada GetClientCertificate.
Sí, si las políticas de tiempo de espera coinciden.
Diferentes valores de Client.Timeout pueden compartir un Transport.
Envolver RoundTripper para registrar el método, URL, estado y duración.
Evitar registrar cuerpos con secretos.
La solicitud en curso se aborta y devuelve context.Canceled.
Aún así, cierre cualquier cuerpo de respuesta parcial si Do devuelve una respuesta.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (Valor predeterminado de GC Green Tea, go fix modernizers - verificar parche en build), chi (última - verificar en build), gin (última - verificar en build), echo (última - verificar en build), google.golang.org/grpc (última - verificar en build), sigs.k8s.io/controller-runtime (última - verificar en build), kubebuilder (última - verificar en build), tinygo (última - verificar objetivos de placa en build), wazero (última - verificar en build), y golangci-lint (última - verificar conjunto de linters en build).
Revisado por Chris St. John·Última actualización: 16 jul 2026