net/http en Go: crear un servidor HTTP en pocas líneas y ampliarlo

La biblioteca estándar de Go incluye un servidor HTTP completo y listo para producción en el paquete net/http. No necesitas ningún framework externo para construir una API REST funcional, gestionar rutas con parámetros, escribir middleware o hacer peticiones como cliente HTTP.

Servidor básico con HandleFunc

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hola desde Go")
    })

    http.HandleFunc("/salud", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status":"ok"}`))
    })

    fmt.Println("Servidor en :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

ServeMux propio para evitar el mux global

El mux global de http es conveniente para scripts rápidos, pero en producción es mejor usar un ServeMux propio para evitar efectos secundarios de paquetes que registren handlers en el global:

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("GET /usuarios", listarUsuarios)
    mux.HandleFunc("POST /usuarios", crearUsuario)
    mux.HandleFunc("GET /usuarios/{id}", obtenerUsuario)

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }

    if err := srv.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

Desde Go 1.22, el mux estándar admite métodos en la ruta (GET /ruta) y parámetros con {nombre}.

Handlers como tipos

type UsuariosHandler struct {
    db *sql.DB
}

func (h *UsuariosHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        h.listar(w, r)
    case http.MethodPost:
        h.crear(w, r)
    default:
        http.Error(w, "método no permitido", http.StatusMethodNotAllowed)
    }
}

func (h *UsuariosHandler) listar(w http.ResponseWriter, r *http.Request) {
    // consultar h.db y responder
}

mux.Handle("/usuarios", &UsuariosHandler{db: db})

Middleware

Un middleware en Go es una función que envuelve un http.Handler y devuelve otro:

func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        fmt.Printf("%s %s %vn", r.Method, r.URL.Path, time.Since(start))
    })
}

func auth(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if !validarToken(token) {
            http.Error(w, "no autorizado", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// aplicar middlewares
handler := logging(auth(mux))

Leer y escribir JSON

type Producto struct {
    ID     int     `json:"id"`
    Nombre string  `json:"nombre"`
    Precio float64 `json:"precio"`
}

func crearProducto(w http.ResponseWriter, r *http.Request) {
    var p Producto
    if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
        http.Error(w, "JSON inválido", http.StatusBadRequest)
        return
    }

    // procesar p...

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(p)
}

http.Client: hacer peticiones

func obtenerJSON(url string, destino any) error {
    cliente := &http.Client{Timeout: 10 * time.Second}

    resp, err := cliente.Get(url)
    if err != nil {
        return fmt.Errorf("GET %s: %w", url, err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("status inesperado: %d", resp.StatusCode)
    }

    return json.NewDecoder(resp.Body).Decode(destino)
}

Siempre configura un Timeout en el cliente. El http.DefaultClient no tiene timeout y puede bloquear las goroutines indefinidamente.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP