Cabeceras HTTP y Response models

Intermedio
FastAPI
FastAPI
Actualizado: 01/07/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Leer y enviar cabeceras HTTP

Las cabeceras HTTP son metadatos que acompañan cada petición y respuesta en el protocolo HTTP. Funcionan como información adicional que describe aspectos técnicos de la comunicación, como el tipo de contenido, la codificación de caracteres o preferencias del cliente.

En FastAPI, trabajar con cabeceras es sencillo gracias a las utilidades integradas que nos permiten tanto leer cabeceras de las peticiones entrantes como enviar cabeceras personalizadas en nuestras respuestas.

Leer cabeceras de peticiones

Para acceder a las cabeceras que envía el cliente, utilizamos la clase Header de FastAPI. Esta función nos permite extraer valores específicos de las cabeceras HTTP de forma similar a como trabajamos con parámetros de consulta:

from fastapi import FastAPI, Header

app = FastAPI()

@app.get("/info")
def obtener_info_cliente(user_agent: str = Header()):
    return {"navegador": user_agent}

El parámetro user_agent captura automáticamente el valor de la cabecera User-Agent que envía el navegador. FastAPI convierte automáticamente los guiones de las cabeceras HTTP a guiones bajos en los nombres de parámetros.

Podemos hacer que una cabecera sea opcional proporcionando un valor por defecto:

@app.get("/preferencias")
def obtener_preferencias(
    accept_language: str = Header(default="es-ES"),
    accept_encoding: str = Header(default="gzip")
):
    return {
        "idioma_preferido": accept_language,
        "codificacion_aceptada": accept_encoding
    }

Trabajar con cabeceras personalizadas

Las aplicaciones web frecuentemente utilizan cabeceras personalizadas para transmitir información específica. Estas cabeceras suelen comenzar con X- por convención:

@app.post("/procesar")
def procesar_datos(
    x_request_id: str = Header(),
    x_client_version: str = Header(default="1.0")
):
    return {
        "id_peticion": x_request_id,
        "version_cliente": x_client_version,
        "estado": "procesado"
    }

Validar cabeceras con tipos

Al igual que con otros parámetros en FastAPI, podemos validar el formato de las cabeceras utilizando tipos de Python:

from typing import Optional

@app.get("/configuracion")
def obtener_configuracion(
    x_max_results: int = Header(default=10),
    x_timeout: Optional[float] = Header(default=None)
):
    config = {"resultados_maximos": x_max_results}
    
    if x_timeout:
        config["timeout"] = x_timeout
    
    return config

Enviar cabeceras en respuestas

Para incluir cabeceras personalizadas en nuestras respuestas, utilizamos la clase Response de FastAPI:

from fastapi import Response

@app.get("/archivo")
def descargar_archivo(response: Response):
    response.headers["Content-Type"] = "application/pdf"
    response.headers["Content-Disposition"] = "attachment; filename=documento.pdf"
    response.headers["X-Custom-Header"] = "valor-personalizado"
    
    return {"mensaje": "Archivo preparado para descarga"}

También podemos establecer cabeceras directamente en el decorador de la ruta usando el parámetro response_headers:

@app.get(
    "/api/datos",
    response_headers={
        "X-API-Version": "2.1",
        "Cache-Control": "no-cache"
    }
)
def obtener_datos():
    return {"datos": [1, 2, 3, 4, 5]}

Ejemplo práctico: API con cabeceras

Veamos un ejemplo que combina la lectura y envío de cabeceras en una API que gestiona información de usuarios:

from fastapi import FastAPI, Header, Response
from typing import Optional

app = FastAPI()

@app.get("/usuario/{user_id}")
def obtener_usuario(
    user_id: int,
    response: Response,
    x_client_id: str = Header(),
    accept_language: str = Header(default="es-ES")
):
    # Simular datos del usuario
    usuario = {
        "id": user_id,
        "nombre": "Juan Pérez",
        "idioma": accept_language
    }
    
    # Establecer cabeceras de respuesta
    response.headers["X-Request-Client"] = x_client_id
    response.headers["X-User-Language"] = accept_language
    response.headers["Cache-Control"] = "max-age=300"
    
    return usuario

Este endpoint requiere la cabecera X-Client-ID para identificar al cliente que hace la petición, mientras que Accept-Language es opcional. En la respuesta, incluimos información sobre el cliente y configuramos el cacheo del resultado durante 5 minutos.

Las cabeceras HTTP nos proporcionan un mecanismo flexible para intercambiar metadatos entre cliente y servidor, mejorando la comunicación y permitiendo implementar funcionalidades avanzadas en nuestras APIs.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Definir modelos de respuesta con Pydantic

Los modelos de respuesta en FastAPI nos permiten estructurar y validar los datos que devolvemos desde nuestros endpoints. Utilizando Pydantic, podemos definir esquemas claros que garantizan la consistencia de nuestras respuestas y mejoran la documentación automática de la API.

Hasta ahora hemos devuelto diccionarios simples desde nuestros endpoints, pero cuando las aplicaciones crecen, necesitamos una estructura más robusta que defina exactamente qué campos contiene cada respuesta y qué tipos de datos esperamos.

Crear modelos básicos de respuesta

Un modelo de respuesta es una clase que hereda de BaseModel de Pydantic y define la estructura de los datos que devuelve un endpoint:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UsuarioResponse(BaseModel):
    id: int
    nombre: str
    email: str
    activo: bool

@app.get("/usuarios/{user_id}", response_model=UsuarioResponse)
def obtener_usuario(user_id: int):
    # Simular datos del usuario
    return {
        "id": user_id,
        "nombre": "Ana García",
        "email": "ana@ejemplo.com",
        "activo": True
    }

El parámetro response_model en el decorador le dice a FastAPI que valide y serialice la respuesta según el modelo definido. Esto garantiza que siempre devolvamos datos con la estructura correcta.

Modelos con campos opcionales

Frecuentemente necesitamos campos que pueden estar presentes o no en la respuesta. Utilizamos Optional para definir estos campos:

from typing import Optional
from datetime import datetime

class ProductoResponse(BaseModel):
    id: int
    nombre: str
    precio: float
    descripcion: Optional[str] = None
    fecha_creacion: datetime
    en_stock: bool

@app.get("/productos/{producto_id}", response_model=ProductoResponse)
def obtener_producto(producto_id: int):
    return {
        "id": producto_id,
        "nombre": "Laptop Gaming",
        "precio": 1299.99,
        "fecha_creacion": datetime.now(),
        "en_stock": True
        # descripcion se omite, será None
    }

Modelos anidados

Los modelos de respuesta pueden contener otros modelos, permitiendo estructuras de datos complejas:

class DireccionResponse(BaseModel):
    calle: str
    ciudad: str
    codigo_postal: str

class UsuarioCompletoResponse(BaseModel):
    id: int
    nombre: str
    email: str
    direccion: DireccionResponse
    activo: bool

@app.get("/usuarios/{user_id}/completo", response_model=UsuarioCompletoResponse)
def obtener_usuario_completo(user_id: int):
    return {
        "id": user_id,
        "nombre": "Carlos López",
        "email": "carlos@ejemplo.com",
        "direccion": {
            "calle": "Calle Mayor 123",
            "ciudad": "Madrid",
            "codigo_postal": "28001"
        },
        "activo": True
    }

Listas de modelos

Para endpoints que devuelven múltiples elementos, utilizamos listas de modelos:

from typing import List

@app.get("/usuarios", response_model=List[UsuarioResponse])
def listar_usuarios():
    return [
        {
            "id": 1,
            "nombre": "María Rodríguez",
            "email": "maria@ejemplo.com",
            "activo": True
        },
        {
            "id": 2,
            "nombre": "Pedro Martín",
            "email": "pedro@ejemplo.com",
            "activo": False
        }
    ]

Personalizar la serialización

Pydantic nos permite personalizar cómo se serializan los datos utilizando el decorador field_serializer:

from pydantic import field_serializer

class PedidoResponse(BaseModel):
    id: int
    total: float
    fecha_pedido: datetime
    estado: str

    @field_serializer('total')
    def serializar_total(self, valor: float) -> str:
        return f"€{valor:.2f}"

    @field_serializer('fecha_pedido')
    def serializar_fecha(self, valor: datetime) -> str:
        return valor.strftime("%d/%m/%Y %H:%M")

@app.get("/pedidos/{pedido_id}", response_model=PedidoResponse)
def obtener_pedido(pedido_id: int):
    return {
        "id": pedido_id,
        "total": 156.75,
        "fecha_pedido": datetime.now(),
        "estado": "enviado"
    }

Excluir campos sensibles

Los modelos de respuesta nos permiten filtrar información sensible que no debe exponerse en la API:

class UsuarioPrivadoResponse(BaseModel):
    id: int
    nombre: str
    email: str
    # Excluimos campos como password, tokens, etc.

@app.get("/perfil", response_model=UsuarioPrivadoResponse)
def obtener_perfil():
    # Los datos internos pueden tener más campos
    datos_completos = {
        "id": 1,
        "nombre": "Usuario Ejemplo",
        "email": "usuario@ejemplo.com",
        "password_hash": "hash_secreto",  # Este campo no aparecerá en la respuesta
        "token_interno": "token_secreto"   # Este tampoco
    }
    
    return datos_completos  # FastAPI filtra automáticamente

Respuestas con metadatos

Para APIs más sofisticadas, podemos crear modelos que incluyan metadatos adicionales junto con los datos principales:

class PaginacionResponse(BaseModel):
    total: int
    pagina: int
    por_pagina: int
    paginas_total: int

class UsuariosConPaginacionResponse(BaseModel):
    usuarios: List[UsuarioResponse]
    paginacion: PaginacionResponse

@app.get("/usuarios/paginados", response_model=UsuariosConPaginacionResponse)
def listar_usuarios_paginados(pagina: int = 1, limite: int = 10):
    # Simular datos paginados
    usuarios_simulados = [
        {"id": i, "nombre": f"Usuario {i}", "email": f"user{i}@ejemplo.com", "activo": True}
        for i in range(1, 6)
    ]
    
    return {
        "usuarios": usuarios_simulados,
        "paginacion": {
            "total": 50,
            "pagina": pagina,
            "por_pagina": limite,
            "paginas_total": 5
        }
    }

Los modelos de respuesta con Pydantic nos proporcionan control total sobre la estructura de nuestras respuestas, garantizando consistencia, validación automática y documentación clara de nuestra API. Esta aproximación hace que nuestro código sea más mantenible y que los consumidores de la API sepan exactamente qué esperar de cada endpoint.

Aprendizajes de esta lección

  • Comprender qué son las cabeceras HTTP y cómo leerlas en FastAPI.
  • Aprender a enviar cabeceras personalizadas en las respuestas HTTP.
  • Definir y utilizar modelos de respuesta con Pydantic para estructurar y validar datos.
  • Implementar modelos con campos opcionales, anidados y listas para respuestas complejas.
  • Personalizar la serialización y excluir campos sensibles en los modelos de respuesta.

Completa FastAPI y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración