middlewares-go-net-http

Middlewares en Go con net/http para logging y auth

  • 3 min

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)
}
Copied!

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)
Copied!

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
    })
}
Copied!

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)
    })
}
Copied!

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))
}
Copied!

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)
    })
}
Copied!

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))
Copied!

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
}
Copied!

Uso:

servidor := chain(mux, logging, auth)
Copied!

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)
    })
}
Copied!

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.