Go: Comunicación por HTTP

Golang y el paquete net/http te permiten enviar y recibir peticiones HTTP, también crear servicios API REST sin necesidad de frameworks.

Aprende Go GRATIS y certifícate

El paquete net/http de Go es una herramienta fundamental para desarrolladores que buscan crear servidores y clientes HTTP eficientes. Este paquete forma parte de la biblioteca estándar de Go y ofrece una amplia gama de funcionalidades para manejar comunicaciones HTTP de manera efectiva. A continuación, exploraremos en profundidad cómo utilizar el paquete net/http para construir aplicaciones web sólidas y escalables.

Introducción al paquete net/http

El paquete net/http proporciona las capacidades necesarias para:

  • Crear servidores HTTP.
  • Desarrollar clientes HTTP.
  • Manipular solicitudes y respuestas HTTP.
  • Implementar middleware y manejadores personalizados.

Al ser parte de la biblioteca estándar, no es necesario instalar dependencias externas para comenzar a utilizarlo.

Creación de un servidor HTTP básico

Para iniciar un servidor web en Go, se puede utilizar la función http.ListenAndServe. A continuación se muestra un ejemplo básico:

package main

import (
    "fmt"
    "net/http"
)

func manejadorSaludo(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "¡Bienvenido al servidor HTTP en Go!")
}

func main() {
    http.HandleFunc("/", manejadorSaludo)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println("Error al iniciar el servidor:", err)
    }
}

Este código inicia un servidor que escucha en el puerto 8080 y responde con un mensaje de bienvenida a cualquier solicitud entrante.

Manejo de solicitudes y respuestas

El paquete net/http utiliza las estructuras http.Request y http.ResponseWriter para representar solicitudes y respuestas HTTP respectivamente.

Estructura de http.Request

La estructura http.Request contiene información sobre la solicitud entrante, como:

  • Método HTTP (GET, POST, etc.).
  • URL solicitada.
  • Encabezados y cookies.
  • Cuerpo de la solicitud.

Uso de http.ResponseWriter

El http.ResponseWriter permite enviar respuestas al cliente. Se puede escribir directamente en él para enviar el cuerpo de la respuesta y establecer encabezados HTTP.

func manejadorDatos(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"mensaje": "Datos procesados correctamente"}`))
}

Rutas y enrutamiento

Aunque el paquete net/http no incluye un enrutador sofisticado, permite asociar manejadores a rutas específicas usando http.HandleFunc o http.Handle.

http.HandleFunc("/usuarios", manejadorUsuarios)
http.HandleFunc("/productos", manejadorProductos)

Para aplicaciones más complejas, es común utilizar enrutadores externos, pero es posible implementar enrutamiento básico con las herramientas estándar.

Manejadores personalizados y la interfaz http.Handler

Un manejador en Go es cualquier objeto que implemente la interfaz http.Handler, es decir, que tenga un método ServeHTTP.

type miManejador struct{}

func (h *miManejador) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Este es un manejador personalizado utilizando http.Handler")
}

func main() {
    manejador := &miManejador{}
    http.Handle("/personalizado", manejador)
    http.ListenAndServe(":8080", nil)
}

Este enfoque permite mayor flexibilidad y reutilización de código en aplicaciones de gran escala.

Middleware y composición de manejadores

Los middleware son componentes que se ejecutan antes o después de los manejadores principales, permitiendo agregar funcionalidades transversales como autenticación, registro de solicitudes o manejo de errores.

func registroMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Solicitud recibida:", r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

func manejadorPrincipal(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Respuesta del manejador principal")
}

func main() {
    http.Handle("/", registroMiddleware(http.HandlerFunc(manejadorPrincipal)))
    http.ListenAndServe(":8080", nil)
}

Servir archivos estáticos

Para servir archivos estáticos, como archivos HTML, CSS o imágenes, se utiliza http.FileServer.

func main() {
    fs := http.FileServer(http.Dir("./public"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))
    http.ListenAndServe(":8080", nil)
}

Este código sirve los archivos ubicados en el directorio public bajo la ruta /static/.

Manejando parámetros de consulta y formularios

Parámetros de consulta

Los parámetros de consulta en la URL pueden obtenerse a través de r.URL.Query().

func manejadorConsulta(w http.ResponseWriter, r *http.Request) {
    valores := r.URL.Query()
    id := valores.Get("id")
    fmt.Fprintf(w, "El ID proporcionado es: %s", id)
}

Procesamiento de formularios

Para procesar datos enviados mediante formularios (método POST), se utiliza r.ParseForm().

func manejadorFormulario(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        http.Error(w, "Error al procesar el formulario", http.StatusBadRequest)
        return
    }
    nombre := r.FormValue("nombre")
    correo := r.FormValue("correo")
    fmt.Fprintf(w, "Nombre: %s, Correo: %s", nombre, correo)
}

Subida de archivos al servidor

El manejo de archivos enviados desde formularios se realiza mediante r.FormFile.

func manejadorSubida(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Método no permitido", http.StatusMethodNotAllowed)
        return
    }
    archivo, encabezado, err := r.FormFile("archivo")
    if err != nil {
        http.Error(w, "Error al obtener el archivo", http.StatusBadRequest)
        return
    }
    defer archivo.Close()

    // Procesar el archivo (por ejemplo, guardarlo en el servidor)
    rutaDestino := "./uploads/" + encabezado.Filename
    ficheroDestino, err := os.Create(rutaDestino)
    if err != nil {
        http.Error(w, "Error al guardar el archivo", http.StatusInternalServerError)
        return
    }
    defer ficheroDestino.Close()
    io.Copy(ficheroDestino, archivo)

    fmt.Fprintf(w, "Archivo subido exitosamente: %s", encabezado.Filename)
}

Implementación de clientes HTTP

Además de crear servidores, el paquete net/http permite realizar solicitudes HTTP como cliente.

func realizarSolicitud() {
    resp, err := http.Get("https://api.ejemplo.com/datos")
    if err != nil {
        fmt.Println("Error en la solicitud:", err)
        return
    }
    defer resp.Body.Close()

    cuerpo, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error al leer la respuesta:", err)
        return
    }

    fmt.Println("Respuesta del servidor:", string(cuerpo))
}

Para solicitudes más complejas, se puede utilizar http.Client y construir solicitudes con http.NewRequest.

Configuración de tiempo de espera y transporte

Es posible establecer configuraciones personalizadas en el cliente HTTP, como tiempos de espera y proxies.

func clientePersonalizado() {
    transport := &http.Transport{
        Proxy: http.ProxyFromEnvironment,
    }
    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    req, err := http.NewRequest("GET", "https://api.ejemplo.com/datos", nil)
    if err != nil {
        fmt.Println("Error al crear la solicitud:", err)
        return
    }

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error en la solicitud:", err)
        return
    }
    defer resp.Body.Close()
    // Procesar la respuesta
}

Uso de contexto para cancelación y plazos

El uso del paquete context permite manejar cancelaciones y plazos de tiempo en solicitudes.

func solicitudConContexto() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, "GET", "https://api.ejemplo.com/lento", nil)
    if err != nil {
        fmt.Println("Error al crear la solicitud:", err)
        return
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        fmt.Println("Error en la solicitud:", err)
        return
    }
    defer resp.Body.Close()
    // Procesar la respuesta
}

Implementación de HTTPS y manejo de certificados

Para servir contenido sobre HTTPS, se utilizan las funciones ListenAndServeTLS y se proporcionan los certificados SSL.

func main() {
    http.HandleFunc("/", manejadorSeguro)
    if err := http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil); err != nil {
        fmt.Println("Error al iniciar el servidor HTTPS:", err)
    }
}

Es importante gestionar correctamente los certificados y asegurar las conexiones para proteger la información transmitida.

Mejoras de rendimiento con HTTP/2

El soporte para HTTP/2 en Go es automático cuando se sirve sobre TLS usando ListenAndServeTLS. HTTP/2 mejora el rendimiento mediante multiplexación de solicitudes y reducción de latencia.

Consejos de seguridad en aplicaciones web

Al desarrollar aplicaciones web, es crucial seguir prácticas de seguridad para proteger el servicio y a los usuarios.

Validación y saneamiento de entradas

Siempre validar y sanear los datos recibidos de los usuarios para prevenir inyecciones y otros ataques.

import "html"

func manejadorSeguro(w http.ResponseWriter, r *http.Request) {
    entrada := r.FormValue("entrada")
    entradaSegura := html.EscapeString(entrada)
    fmt.Fprintf(w, "Entrada segura: %s", entradaSegura)
}

Uso de encabezados de seguridad

Establecer encabezados que indiquen a los navegadores cómo manejar el contenido.

func seguridadMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Strict-Transport-Se
Empezar curso de Go

Lecciones de este módulo de Go

Lecciones de programación del módulo Comunicación por HTTP del curso de Go.

Ejercicios de programación en este módulo de Go

Evalúa tus conocimientos en Comunicación por HTTP con ejercicios de programación Comunicación por HTTP de tipo Test, Puzzle, Código y Proyecto con VSCode.