Paquete http en Go: Guía Completa
El paquete net/http proporciona una implementación HTTP para clientes y servidores web en Go. Este documento ofrece una visión detallada de sus características más importantes.
1. Servidor HTTP Básico
1.1 Iniciar un Servidor
package main
import (
"fmt"
"log"
"net/http"
)
func holaHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "¡Hola, mundo!")
}
func main() {
http.HandleFunc("/hola", holaHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
1.2 Servidor con Configuración Personalizada
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/usuarios", usuariosHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
2. Enrutamiento
2.1 Mux Básico
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/api/", apiHandler)
mux.HandleFunc("/api/productos/", productosHandler)
log.Fatal(http.ListenAndServe(":8080", mux))
}
2.2 Manejador Personalizado
type usuarioHandler struct {
db *sql.DB
}
func (h *usuarioHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
h.listar(w, r)
case "POST":
h.crear(w, r)
default:
http.Error(w, "Método no permitido", http.StatusMethodNotAllowed)
}
}
func main() {
db, _ := sql.Open("postgres", "...")
mux := http.NewServeMux()
mux.Handle("/usuarios/", &usuarioHandler{db: db})
http.ListenAndServe(":8080", mux)
}
3. Manejo de Solicitudes
3.1 Análisis de Parámetros
func buscarHandler(w http.ResponseWriter, r *http.Request) {
// Parámetros de consulta URL
query := r.URL.Query()
termino := query.Get("q")
limite, _ := strconv.Atoi(query.Get("limite"))
// Parámetros de ruta
id := strings.TrimPrefix(r.URL.Path, "/productos/")
fmt.Fprintf(w, "Búsqueda: %s, límite: %d, ID: %s", termino, limite, id)
}
3.2 Lectura del Cuerpo
func crearUsuarioHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Método no permitido", http.StatusMethodNotAllowed)
return
}
// Limitar tamaño del body
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1MB
var usuario struct {
Nombre string `json:"nombre"`
Email string `json:"email"`
}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&usuario); err != nil {
http.Error(w, "JSON inválido: "+err.Error(), http.StatusBadRequest)
return
}
// Validar campos
if usuario.Nombre == "" || usuario.Email == "" {
http.Error(w, "Campos requeridos", http.StatusBadRequest)
return
}
// Procesar...
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"status": "creado"})
}
4. Middleware
4.1 Logging Middleware
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
// Envolver ResponseWriter para capturar código de estado
ww := newWrappedResponseWriter(w)
// Llamar al siguiente handler
next.ServeHTTP(ww, r)
// Loggear después
log.Printf(
"%s %s %d %s %s",
r.Method,
r.URL.Path,
ww.status,
time.Since(inicio),
r.RemoteAddr,
)
})
}
// Uso:
mux := http.NewServeMux()
mux.HandleFunc("/api", apiHandler)
loggedMux := loggingMiddleware(mux)
http.ListenAndServe(":8080", loggedMux)
4.2 Encadenamiento de Middleware
func chain(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for _, m := range middlewares {
h = m(h)
}
return h
}
// Uso
handler := chain(
mux,
loggingMiddleware,
authMiddleware,
corsMiddleware,
)
5. Cliente HTTP
5.1 Cliente Simple
func obtenerDatos() ([]byte, error) {
resp, err := http.Get("https://api.ejemplo.com/datos")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", resp.Status)
}
return io.ReadAll(resp.Body)
}
5.2 Cliente Personalizado
func clientePersonalizado() {
cliente := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
IdleConnTimeout: 90 * time.Second,
},
}
req, err := http.NewRequest("GET", "https://api.ejemplo.com", nil)
if err != nil {
log.Fatal(err)
}
req.Header.Add("Authorization", "Bearer token123")
req.Header.Add("Content-Type", "application/json")
resp, err := cliente.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Procesar respuesta...
}
6. Mejores Prácticas
6.1 Manejo Seguro de Errores
func apiHandler(w http.ResponseWriter, r *http.Request) {
usuario, err := obtenerUsuario(r.Context(), 123)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
http.Error(w, "Usuario no encontrado", http.StatusNotFound)
case errors.Is(err, context.DeadlineExceeded):
http.Error(w, "Timeout de base de datos", http.StatusGatewayTimeout)
default:
// No exponer detalles del error interno
log.Printf("Error interno: %v", err)
http.Error(w, "Error del servidor", http.StatusInternalServerError)
}
return
}
json.NewEncoder(w).Encode(usuario)
}
6.2 Separación de Responsabilidades
// handlers/usuarios.go
package handlers
import (
"net/http"
"miapp/models"
"miapp/services"
)
type UsuarioHandler struct {
service *services.UsuarioService
}
func NewUsuarioHandler(service *services.UsuarioService) *UsuarioHandler {
return &UsuarioHandler{service: service}
}
func (h *UsuarioHandler) Listar(w http.ResponseWriter, r *http.Request) {
usuarios, err := h.service.ListarTodos(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, usuarios)
}
// Función helper para respuestas JSON
func writeJSON(w http.ResponseWriter, data any) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
Esta guía cubre los aspectos más importantes del paquete net/http de Go, desde la configuración básica de servidores hasta técnicas avanzadas de manejo de solicitudes y respuestas.