Cadenas de Middleware y Contexto de Solicitud
El middleware envuelve http.Handler para ejecutar lógica transversal antes y después de tus manejadores de ruta.
Busca en todas las páginas de la documentación
El middleware envuelve http.Handler para ejecutar lógica transversal antes y después de tus manejadores de ruta.
Los metadatos del ámbito de la solicitud fluyen a través de r.Context() usando claves de contexto tipadas y no exportadas.
Una función de middleware acepta un manejador interno y devuelve un nuevo manejador que delega a él.
Las cadenas anidan envoltorios: el middleware más externo ve la solicitud primero y la respuesta última.
r.Context() es el portador estándar para cancelación, plazos y metadatos de solicitud.
Usa r.WithContext para adjuntar valores de forma inmutable; nunca modifiques la solicitud en su lugar sin clonarla.
Bibliotecas como chi proporcionan asistentes Use, pero el modelo subyacente es idéntico a la composición de la biblioteca estándar.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
func chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
func requestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get("X-Request-ID")
if id == "" {
id = uuid.NewString()
}
ctx := context.WithValue(r.Context(), reqIDKey{}, id)
w.Header().Set("X-Request-ID", id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Cuándo usar esto:
package main
import (
"context"
"log"
"net/http"
"time"
)
type ctxKey int
const userKey ctxKey = 1
type statusWriter struct {
http.ResponseWriter
code int
}
func (w *statusWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}
func chain(h http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
for i := len(mws) - 1; i >= 0; i-- {
h = mws[i](h)
}
return h
}
func recoverer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if v := recover(); v != nil {
log.Printf("panic: %v", v)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func authenticate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userKey, "demo-user")
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func accessLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw := &statusWriter{ResponseWriter: w, code: http.StatusOK}
start := time.Now()
next.ServeHTTP(sw, r)
log.Printf("%s %s %d %s", r.Method, r.URL.Path, sw.code, time.Since(start))
})
}
func userFrom(ctx context.Context) (string, bool) {
u, ok := ctx.Value(userKey).(string)
return u, ok
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /me", func(w http.ResponseWriter, r *http.Request) {
user, ok := userFrom(r.Context())
if !ok {
http.Error(w, "no user", http.StatusInternalServerError)
return
}
w.Write([]byte(user))
})
handler := chain(mux, recoverer, authenticate, accessLog)
http.ListenAndServe(":8080", handler)
}Lo que esto demuestra:
statusWriter que captura el estado HTTP para los registros.http.Handler sobre el manejador interno.next.ServeHTTP.r.WithContext devuelve una copia superficial de la solicitud con un nuevo árbol de contexto.Solicitud -->
recoverer -->
authenticate -->
accessLog -->
mux / manejador de negocio
Respuesta <--
El middleware externo debe incluir recuperación y rastreo; la autenticación se sienta dentro de la recuperación pero fuera de la lógica de negocio.
// Equivalente en chi (manejadores stdlib debajo):
// r.Use(middleware.Recoverer, middleware.RequestID)
// r.Get("/me", handler)
// Prefiere accesores:
func User(ctx context.Context) (string, bool) {
u, ok := ctx.Value(userKey).(string)
return u, ok
}| Haz | No Hagas |
|---|---|
| Tipos de clave no exportados en el paquete propietario | Claves de cadena en bibliotecas |
| IDs de solicitud, principal de autenticación, idioma | Estructuras grandes o parámetros opcionales |
| Documenta claves en el comentario del paquete | Almacena *sql.DB en el contexto |
type ctxKey int.r.URL sin Clonar - Carrera cuando el middleware comparte solicitudes. Solución: Usa r.Clone(ctx) al alterar la URL o las cabeceras para sub-enrutadores.next.ServeHTTP - Descarta solicitudes silenciosamente. Solución: Usa linting o prueba el middleware con httptest afirmando que el cuerpo se alcanzó.context.Background() aguas abajo - Pierde la cancelación ante la desconexión del cliente. Solución: Empieza desde r.Context() para todos los contextos hijos.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Use de chi | Registro de middleware más limpio | Solo stdlib sin dependencias |
Use de gin / Pre de echo | Vinculación y grupos nativos del framework | Necesitas exportaciones de http.Handler simples |
| Cabeceras de malla de servicios | Propagación de identidad entre servicios | Monolito único con autenticación en proceso |
| Verificaciones manuales por manejador | Una o dos rutas | Reglas transversales duplicadas en todas partes |
Aplica los envoltorios de adentro hacia afuera: el último middleware en la lista se sitúa más cerca del manejador de ruta.
Un asistente chain que invierte un slice mantiene las llamadas legibles.
La capa más externa para que cada middleware y manejador interno estén cubiertos.
Registra trazas de pila en el servidor; devuelve un 500 genérico a los clientes.
El contexto transporta metadatos transversales a través de capas que no comparten una estructura.
Las entradas de negocio pertenecen a parámetros tipados del manejador o cuerpos de solicitud.
Sí, el código después de next.ServeHTTP se ejecuta al salir.
Capturar el estado requiere envolver ResponseWriter antes de llamar al siguiente.
Siempre deriva contextos hijos de r.Context(), no de Background().
El middleware puede adjuntar plazos más cortos para sub-llamadas internas.
Están bien si aún exponen http.Handler y documentan las claves de contexto.
Verifica que los errores de validación de tokens se corten antes que los manejadores.
Crea un manejador interno simulado que establezca una cabecera o escriba un marcador.
Controla el middleware con httptest.NewRequest y afirma el comportamiento de corte.
Monta sub-muxes con manejadores envueltos diferentes: http.StripPrefix más cadenas distintas.
Los grupos de rutas de chi resuelven esto con bloques Route.
Las cabeceras funcionan para metadatos de borde entrantes.
El contexto mantiene los accesores en proceso seguros en cuanto a tipos sin exponer cabeceras internas aguas abajo.
No hay un límite estricto, pero las pilas profundas ocultan errores de ordenación.
Prefiere una pila estándar documentada más envoltorios específicos de ruta de forma escasa.
Gin lleva su propio *gin.Context, pero c.Request.Context() sigue siendo la raíz de cancelación de la biblioteca estándar.
Sincroniza valores importantes en context.Context para bibliotecas compartidas.
Registra /health en un mux separado sin middleware de autenticación, o enseña al middleware de autenticación a permitir ciertas rutas.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado Green Tea GC, 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: 19 jul 2026