Go incluye en la biblioteca estándar todo lo necesario para construir servidores HTTP de producción sin dependencias externas. El paquete net/http cubre desde un servidor básico de tres líneas hasta configuraciones con middleware, routing avanzado y graceful shutdown.
El servidor más sencillo
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hola, %s!", r.URL.Path[1:])
})
http.ListenAndServe(":8080", nil)
}
Handlers como tipos propios
Para estructuras más complejas, implementa la interfaz http.Handler con el método ServeHTTP. Esto permite inyectar dependencias como loggers o conexiones a bases de datos:
type servidor struct {
db *sql.DB
logger *log.Logger
}
func (s *servidor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/usuarios":
s.listarUsuarios(w, r)
case "/salud":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
default:
http.NotFound(w, r)
}
}
func (s *servidor) listarUsuarios(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// consultar s.db y escribir JSON...
fmt.Fprintln(w, `[{"id":1,"nombre":"Ana"}]`)
}
ServeMux para routing
Desde Go 1.22, el http.ServeMux soporta patrones con método HTTP y parámetros en la ruta:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /usuarios", listarUsuarios)
mux.HandleFunc("POST /usuarios", crearUsuario)
mux.HandleFunc("GET /usuarios/{id}", obtenerUsuario)
mux.HandleFunc("DELETE /usuarios/{id}", borrarUsuario)
http.ListenAndServe(":8080", mux)
}
func obtenerUsuario(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // nuevo en Go 1.22
fmt.Fprintf(w, "usuario %s", id)
}
Middleware: encadenar comportamiento
Un middleware en Go es una función que recibe un Handler y devuelve otro. Se encadenan envolviéndolos uno dentro de otro:
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s en %v", r.Method, r.URL.Path, time.Since(inicio))
})
}
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", handler)
// Encadenar: cors ? logging ? mux
handler := cors(logging(mux))
http.ListenAndServe(":8080", handler)
}
Respuestas JSON
json.NewEncoder escribe directamente al ResponseWriter sin crear un buffer intermedio:
type Usuario struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Email string `json:"email,omitempty"`
}
func responderJSON(w http.ResponseWriter, status int, datos any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(datos)
}
func listarUsuarios(w http.ResponseWriter, r *http.Request) {
usuarios := []Usuario{
{ID: 1, Nombre: "Ana"},
{ID: 2, Nombre: "Carlos", Email: "[email protected]"},
}
responderJSON(w, http.StatusOK, usuarios)
}
Ficheros estáticos
mux.Handle("GET /static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("./assets"))))
Graceful shutdown
Con http.Server configurado manualmente puedes detener el servidor esperando a que las peticiones en curso terminen antes de salir:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", handler)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("servidor: %v", err)
}
}()
// Esperar señal del sistema operativo
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("shutdown forzado:", err)
}
log.Println("servidor detenido correctamente")
}
