Patrones de Servidores HTTP, Handlers y ServeMux
net/http enruta las peticiones a través de ServeMux a implementaciones de Handler.
Busca en todas las páginas de la documentación
net/http enruta las peticiones a través de ServeMux a implementaciones de Handler.
Go 1.22 modernizó la coincidencia de patrones para que las rutas REST sensibles a métodos funcionen sin un enrutador de terceros.
Un servidor HTTP escucha en un puerto, analiza las peticiones y las despacha a los handlers.
http.Server envuelve el listener con controles de tiempo de espera y TLS.
http.ServeMux mapea patrones de host, método y ruta a handlers.
Los handlers implementan ServeHTTP o usan el adaptador HandlerFunc.
El middleware envuelve a los handlers para añadir logging, autenticación y métricas antes de que se ejecute tu lógica de negocio.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
mux := http.NewServeMux()
mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, `{"id":%q}`, id)
})
srv := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
log.Fatal(srv.ListenAndServe())Cuándo usar esto:
package main
import (
"encoding/json"
"log"
"net/http"
"strings"
"time"
)
type user struct {
ID string `json:"id"`
Name string `json:"name"`
}
var users = map[string]user{
"1": {ID: "1", Name: "Ada"},
"2": {ID: "2", Name: "Lin"},
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("POST /api/users", createUser)
api := stripPrefix("/api", mux)
srv := &http.Server{
Addr: ":8080",
Handler: logging(api),
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
u, ok := users[id]
if !ok {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(u)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var in user
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
in.ID = "3"
users[in.ID] = in
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(in)
}
func stripPrefix(prefix string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, prefix)
if p == "" {
p = "/"
}
r2 := r.Clone(r.Context())
r2.URL.Path = p
h.ServeHTTP(w, r2)
})
}
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}Lo que esto demuestra:
ServeMux dedicado.r.PathValue para segmentos {id}./api.http.Server orientados a producción.ServeMux coincide con el patrón aplicable más largo considerando las reglas de método y host.ServeHTTP del handler ganador se ejecuta en el goroutine de la conexión a menos que tú mismo generes trabajo.ResponseWriter almacena en búfer las cabeceras hasta la primera Write o WriteHeader explícita.| Patrón | Coincide |
|---|---|
/files/ | Cualquier método, rutas bajo /files/ |
GET /users/{id} | Solo GET, captura id |
GET /items/{id}/ | GET con segmento de barra final |
{$} | Comodín para rutas no coincidentes (usar con cuidado) |
// Evita registrar en el mux global en librerías:
// http.HandleFunc(...) // malo en paquetes reutilizables
mux := http.NewServeMux() // bueno: local, testeable
// Rutas con ámbito de host:
mux.HandleFunc("GET example.com/admin/", adminHandler)| Tipo | Usar cuando |
|---|---|
http.HandlerFunc | Handlers de función simple |
Struct con ServeHTTP | Handlers que necesitan dependencias (DB, configuración) |
http.FileServer | Archivos estáticos |
http.StripPrefix + mux interno | Montar sub-aplicaciones |
http.DefaultServeMux en librerías - Las importaciones de terceros pueden registrar rutas conflictivas. Solución: Pasar http.NewServeMux() desde main.ReadHeaderTimeout - Los clientes lentos pueden mantener conexiones sin enviar cabeceras. Solución: Establecerlo en cada http.Server de producción.Write bloquea el estado a 200. Solución: Llamar a WriteHeader o http.Error antes de los bytes del cuerpo.r.PathValue requiere patrones de Go 1.22+. Solución: Actualizar Go o usar un enrutador como chi./users y /users/ son patrones diferentes. Solución: Registrar ambos o redirigir consistentemente.| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| chi | Quieres grupos de rutas y pilas de middleware en handlers estándar | Necesitas cero dependencias |
| gorilla/mux | Las rutas regex y las restricciones de host son centrales | Los patrones de Go 1.22 ya son suficientes |
| gin / echo | Binding, validación y grandes ecosistemas de middleware | Quieres mínima magia y tipos estándar |
ServeMux crudo | API pequeña, pocas rutas, solo librería estándar | Enrutamiento complejo de regex o multi-inquilino por host |
Los patrones pueden incluir métodos HTTP y comodines {nombre}.
r.PathValue lee los segmentos capturados.
La coincidencia de prefijos antigua todavía funciona para patrones no calificados.
Registra patrones separados por método.
Los pares método/ruta no registrados devuelven 404; las comprobaciones explícitas de método dentro de un handler comodín pueden devolver 405.
Crea múltiples valores http.Server compartiendo el mismo handler, cada uno con su propio goroutine ListenAndServe.
Usa mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("public")))).
Establece cabeceras de caché en el middleware envolvente si es necesario.
Sí, cuando los handlers necesitan dependencias como *sql.DB.
Almacena las dependencias en el struct e implementa ServeHTTP una vez por tipo de ruta.
Los patrones pueden incluir un prefijo de host como api.example.com/.
Útil cuando un proceso sirve múltiples hosts virtuales.
Envuelve un handler y limita los bytes del cuerpo de la petición leídos.
Combínalo con http.MaxBytesReader para endpoints JSON que aceptan cuerpos POST.
Usa httptest.NewRequest con método y ruta, luego mux.ServeHTTP(rec, req).
Verifica el estado y el cuerpo con httptest.ResponseRecorder.
Sí, cada petición típicamente se ejecuta en su propio goroutine.
Protege el estado compartido del handler con mutexes o confina el estado al ámbito de la petición.
Ejecuta un segundo listener en :80 que devuelva http.Redirect a URLs TLS, o termina TLS en el balanceador de carga.
No con ServeMux simple.
Usa puertos separados, cmux, o un gateway que enrute por protocolo.
Handle registra un http.Handler.
HandleFunc registra una función a través del adaptador HandlerFunc.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (GC por defecto 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: 16 jul 2026