Propagación de Cancelación en Manejadores HTTP
Cada manejador HTTP debe tratar r.Context() como la raíz de su árbol de trabajo y pasarlo sin cambios a las operaciones de I/O descendentes.
Busca en todas las páginas de la documentación
Cada manejador HTTP debe tratar r.Context() como la raíz de su árbol de trabajo y pasarlo sin cambios a las operaciones de I/O descendentes.
Cuando el cliente se desconecta o el servidor aplica un tiempo de espera, ese contexto se cancela y los llamadores cooperativos deben detenerse.
net/http adjunta un context.Context por solicitud a cada *http.Request.
Los manejadores pasan r.Context() a las consultas de bases de datos, llamadas HTTP salientes y stubs gRPC para que las solicitudes abandonadas no sigan consumiendo recursos.
Los envoltorios de frameworks en chi, gin y echo aún exponen el mismo contexto de solicitud subyacente.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
func usersHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
users, err := store.ListUsers(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return // cliente desconectado; no escribir 500
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(users)
}Cuándo usar esto:
r.Context() como primer argumento hacia abajo.context.Canceled cuando la respuesta no puede llegar al cliente.WithTimeout más corto solo en límites internos intencionales, no en la raíz del manejador.package main
import (
"context"
"database/sql"
"encoding/json"
"errors"
"log"
"net/http"
"time"
_ "github.com/mattn/go-sqlite3"
)
type Store struct{ db *sql.DB }
func (s *Store) SlowQuery(ctx context.Context) (string, error) {
var out string
err := s.db.QueryRowContext(ctx, `SELECT 'ok'`).Scan(&out)
return out, err
}
func handler(store *Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
val, err := store.SlowQuery(ctx)
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]string{"status": val})
}
}
func main() {
db, _ := sql.Open("sqlite3", ":memory:")
defer db.Close()
mux := http.NewServeMux()
mux.Handle("/api", handler(&Store{db: db}))
srv := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
log.Fatal(srv.ListenAndServe())
}Lo que esto demuestra:
r.Context(), no de context.Background().QueryRowContext respeta la cancelación cuando el cliente aborta.context.Canceled y context.DeadlineExceeded se cortocircuitan sin un 500 engañoso.ReadHeaderTimeout a nivel de servidor complementa los presupuestos por manejador.http.Server crea un contexto de solicitud cuando acepta una conexión.ResponseWriter después de la cancelación pueden fallar silenciosamente; protege con verificaciones de contexto antes de trabajos costosos.(w, r) y pasar r.Context() a next.ServeHTTP sin reemplazarlo a menos que agregue valores o plazos.| Framework | Acceso al contexto de solicitud | Patrón de Middleware |
|---|---|---|
| net/http | r.Context() | func(next http.Handler) http.Handler |
| chi | r.Context() | middleware.Timeout envuelve el contexto hijo |
| gin | c.Request.Context() | c.Request.WithContext(ctx) para reemplazar |
| echo | c.Request().Context() | middleware.TimeoutWithConfig |
| Capa | Regla |
|---|---|
| Manejador | Iniciar desde r.Context() |
| Servicio | Aceptar ctx como primer parámetro |
| SQL | Usar QueryContext, ExecContext |
| HTTP Saliente | http.NewRequestWithContext |
| gRPC | Los métodos Stub aceptan ctx |
| Goroutines | Pasar ctx; detener en Done() |
// Middleware que agrega un valor de ID de solicitud - aún preserva la cancelación del padre
func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := uuid.NewString()
ctx := context.WithValue(r.Context(), requestIDKey, id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}r.Context().r.Context() o separar explícitamente con context.WithoutCancel solo para limpieza asíncrona intencional.ctx.Done() entre fragmentos.defer r.Body.Close() en manejadores que leen cuerpos.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
BaseContext del Servidor | Valores para todo el proceso en todas las solicitudes | Cancelación por solicitud (usar contexto de solicitud) |
context.WithoutCancel | Registro de auditoría después de la respuesta | I/O normal del manejador |
Canal done manual | Código heredado | Nuevos manejadores HTTP |
| Timeout corto solo en el manejador | Proteger una única consulta lenta | Reemplazar el contexto de solicitud completo |
| Cola de trabajadores desacoplada de HTTP | Trabajos asíncronos que sobreviven a la solicitud | El usuario espera un resultado síncrono |
En la desconexión del cliente, la finalización del manejador o la configuración de tiempos de espera del servidor.
El momento exacto depende de los campos de http.Server y el comportamiento de la capa TLS.
Envolver con WithValue, WithTimeout o WithCancel y llamar a r.WithContext(child).
Nunca sustituir context.Background() a mitad de cadena.
Usa httptest más un padre cancelable o cierra el cliente grabador.
Ver el artículo de pruebas en esta sección.
c.Set almacena claves locales de gin; usa c.Request.Context() para la propagación de cancelación.
Envuelve manejadores con un contexto de tiempo de espera y retorna 503 al expirar.
Aún así, pasa el contexto envuelto hacia abajo.
Sí - http.NewRequestWithContext(r.Context(), ...) vincula la vida útil del cliente a las llamadas de dependencia.
Registra en nivel de depuración cuando errors.Is(err, context.Canceled) y no se ha iniciado una respuesta.
Evita el ruido de nivel de error para el comportamiento normal del cliente.
Solo con context.WithoutCancel para tareas intencionales de "disparar y olvidar".
Por defecto: detén el trabajo cuando la solicitud finaliza.
Los manejadores de actualización aún comienzan desde r.Context(); la vida útil de la conexión puede extenderse más allá.
Gestiona la cancelación de WS con señales de cierre de conexión por separado.
Envuelve r.Context() con un plazo más corto y se cancela cuando se excede.
El código descendente debe respetar el contexto envuelto.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea, go fix modernizers - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026