Patrones de Middleware y Decorador
El middleware en Go envuelve un http.Handler con otro http.Handler para agregar comportamiento transversal: registro, autenticación, recuperación y métricas.
Busca en todas las páginas de la documentación
El middleware en Go envuelve un http.Handler con otro http.Handler para agregar comportamiento transversal: registro, autenticación, recuperación y métricas.
Es la forma práctica del patrón decorador en un lenguaje sin herencia.
Cada middleware recibe el siguiente manejador, devuelve un nuevo manejador y decide si ejecutar lógica antes, después o alrededor de la llamada interna.
El servidor net/http invoca el manejador más externo; el middleware se anida hacia adentro hasta que se ejecuta el manejador de ruta.
El mismo modelo de composición aparece en chi, gin, echo e interceptores gRPC con ayudantes de registro específicos del framework.
Tarjeta de receta de referencia rápida: lista para copiar y pegar.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("handled", "path", r.URL.Path, "ms", time.Since(start).Milliseconds())
})
}Cuándo usar esto:
package main
import (
"context"
"log"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type ctxKey int
const userKey ctxKey = 1
func withUser(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 greet(w http.ResponseWriter, r *http.Request) {
user, _ := r.Context().Value(userKey).(string)
w.Write([]byte("hello " + user))
}
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Recoverer)
r.Use(withUser)
r.Get("/greet", greet)
srv := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}Lo que esto demuestra:
r.Use registra middleware para las rutas registradas en ese enrutadorhttp.Error antes de llamar a nextcontext.ContextSolicitud -->
Middleware RequestID
--> Middleware Recoverer
--> Middleware withUser
--> manejador greet
<-- withUser (después de que next retorna)
<-- Recoverer
<-- RequestID
Respuesta <--next.ServeHTTP a menos que maneje completamente la solicitudResponseWriter captura el estado y el tamaño del cuerpo para métricastype statusWriter struct {
http.ResponseWriter
code int
}
func (w *statusWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}http.ResponseWriter para delegar la mayoría de los métodosWriteHeader y Write para observar el comportamientohttp.ResponseController (Go 1.20+) para flush/hijack cuando sea necesario| Framework | Registro | Tipo de manejador |
|---|---|---|
| net/http | Anidamiento manual | http.Handler |
| chi | r.Use(mw) | http.Handler |
| gin | engine.Use(mw) | gin.HandlerFunc |
| echo | e.Use(mw) | echo.MiddlewareFunc |
| gRPC | grpc.ChainUnaryInterceptor | función interceptora |
next: la solicitud se cuelga o devuelve una respuesta vacía. Solución: siempre llame a next.ServeHTTP a menos que haya escrito la respuesta completa."user". Solución: constantes de clave tipada privada por paquete.Flusher/Hijacker se pierden si el envoltorio no las implementa. Solución: use middleware.NewWrapResponseWriter de chi o implemente interfaces opcionales.Recoverer en el exterior; autenticación antes de los manejadores que necesitan identidad.main.| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| Verificaciones de manejador en línea | Una ruta necesita autenticación | Muchas rutas comparten la preocupación |
| Cadena de middleware HTTP | Preocupaciones de transporte transversales | Ramificación principal del negocio |
| Estructuras decoradoras que incrustan Handler | Servidores personalizados con valores predeterminados | Servidor de archivos estático simple |
| Malla de servicios / autenticación de puerta de enlace | Política de borde centralizada | La simplicidad del desarrollo local importa |
Sí, en su función: los interceptores unarios/de flujo gRPC envuelven los manejadores de RPC de la misma manera que el middleware HTTP envuelve ServeHTTP.
Use httptest.NewRecorder y httptest.NewRequest; pase un manejador next simulado que establezca una bandera cuando se llame.
Sí, pero lea y restaure con cuidado; prefiera limitar el tamaño del cuerpo a nivel del servidor (MaxBytesReader).
El middleware valida las credenciales y adjunta la identidad al contexto; los servicios aplican las reglas de autorización sobre los recursos.
No hay un máximo fijo, pero si el orden es difícil de razonar, agrupe las preocupaciones relacionadas o documente una pila estándar en main.
En chi, sí para las rutas registradas en el enrutador; las rutas no coincidentes acceden al manejador NotFound después de la misma pila Use.
Registre la ruta en un subenrutador sin ese middleware, o use no-op dentro del middleware basándose en el prefijo de la ruta.
Prefiera slog con atributos específicos del ámbito de la solicitud (ID de solicitud del contexto) para registros estructurados.
gin envuelve http.Request en Context; el middleware todavía se compone pero usa la API de gin en lugar de manejadores sin procesar.
Sí: envuelva el mux: logging(mux) o registre manejadores por método envueltos individualmente.
mainVersiones de la pila: Esta página fue escrita para Go 1.26.x (predeterminado GC de Green Tea, go fix modernizers - verifique el parche en la compilación), chi (última versión - verifique en la compilación), gin (última versión - verifique en la compilación), echo (última versión - verifique en la compilación), google.golang.org/grpc (última versión - verifique en la compilación), sigs.k8s.io/controller-runtime (última versión - verifique en la compilación), kubebuilder (última versión - verifique en la compilación), tinygo (última versión - verifique los objetivos de la placa en la compilación), wazero (última versión - verifique en la compilación) y golangci-lint (última versión - verifique en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026