Un middleware en Go es una función que envuelve un handler HTTP para añadir comportamiento común antes o después de atender una petición.
La petición atraviesa varias capas antes de llegar al handler final. Cada una puede medir tiempo, validar un token, escribir registros o cortar la petición si algo no cuadra.
La buena noticia es que en Go no necesitamos nada especial. Con net/http, un middleware es simplemente una función que recibe y devuelve un http.Handler.
Recordando los handlers
Un handler HTTP en Go implementa esta interfaz:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}Y una función normal puede convertirse en handler con http.HandlerFunc.
func hola(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hola"))
}
handler := http.HandlerFunc(hola)Estructura de un middleware
La forma habitual es esta:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Antes del handler
next.ServeHTTP(w, r)
// Después del handler
})
}next representa el siguiente paso de la cadena. Puede ser otro middleware o el handler final.
Middleware de logging
Vamos con un ejemplo práctico: registrar método, ruta y duración.
package main
import (
"log"
"net/http"
"time"
)
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
defer func() {
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(inicio))
}()
next.ServeHTTP(w, r)
})
}Y lo usamos así:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hola"))
})
servidor := logging(mux)
log.Fatal(http.ListenAndServe(":8080", servidor))
}Todas las rutas del mux pasan ahora por el middleware.
Middleware de autenticación
Un middleware también puede cortar la petición antes de llegar al handler.
func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer secreto" {
http.Error(w, "No autorizado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Fijaos en el return. Si el token no vale, respondemos el error y no llamamos a next.
Este ejemplo de token es solo didáctico. En una aplicación real validaríamos JWT, sesiones, claves de API o el mecanismo que toque, y nunca pondríamos secretos hardcodeados.
Encadenar middlewares
Podemos envolver varias capas:
servidor := logging(auth(mux))La petición pasará primero por logging, luego por auth y finalmente por mux.
Si queréis que se lea mejor, podéis crear una pequeña función chain.
func chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}Uso:
servidor := chain(mux, logging, auth)El orden importa. No es lo mismo registrar todas las peticiones que registrar solo las autorizadas.
Middleware de recuperación
El servidor de net/http ya recupera los pánicos de los handlers, registra una traza y corta la respuesta o la conexión. Un middleware propio permite controlar el registro e intentar enviar una respuesta 500.
import "runtime/debug"
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic atendiendo %s: %v\n%s", r.URL.Path, err, debug.Stack())
http.Error(w, "Error interno", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}Esto no convierte panic en una herramienta normal de errores. Si el handler ya había enviado las cabeceras, no podremos cambiar el código de estado a 500. Además, recuperar el pánico no arregla el bug: debemos registrarlo y corregir su causa.