Módulo 1 · Fundamentos que sostienen todo

Lección 01 — HTTP a fondo

Métodos, códigos de estado con criterio, CORS, cookies seguras, caché y TLS — verificado con curl.

Publicada
En esta lección
  1. Objetivos
  2. 1. Anatomía de una petición HTTP
  3. 2. Cabeceras que dominas o sufres
  4. 3. Keep-alive, HTTPS y TLS
  5. 4. HTTP en DRF: lo que el framework hace (y no debe hacer) por ti
  6. Autoevaluación (respóndeme en el chat)

Stack: Django + DRF · Proyecto: TicketFlow Estado: Impartida — ejercicios pendientes de entrega Prerrequisito: Lección 00 — Entorno y primer endpoint


Objetivos

Al terminar esta lección podrás:

  1. Describir la anatomía exacta de una petición y una respuesta HTTP.
  2. Elegir método y código de estado correctos justificándolo (entrevistas).
  3. Explicar qué cabeceras controlan CORS, caché, cookies y compresión, y verificarlas con curl.
  4. Razonar sobre keep-alive, HTTPS/TLS y por qué importan en producción.
  5. Aplicar todo esto en DRF: por qué Response elige códigos por ti y cuándo no dejarle que lo haga.

1. Anatomía de una petición HTTP

HTTP es un protocolo de texto (ahora binario en HTTP/2) sobre TCP, sin estado: cada petición lleva toda la información necesaria. Estructura:

http
POST /api/events/42/reserve HTTP/1.1      ← método + ruta + versión
Host: ticketflow.example.com              ← obligatoria en HTTP/1.1
Content-Type: application/json            ← qué envío
Authorization: Bearer eyJhbGci...         ← quién soy
Idempotency-Key: 7f3c9a2e                 ← anti-duplicados (Lección 14)
Content-Length: 58

{"seat_id": 1051, "quantity": 2}          ← cuerpo (opcional)

Y la respuesta:

http
HTTP/1.1 201 Created
Location: /api/reservations/9172/         ← dónde quedó lo creado
Content-Type: application/json
Cache-Control: no-store

{"id": 9172, "status": "pending_payment", "seats": [1051, 1052]}

Concepto clave para entrevistas: HTTP es sin estado (stateless) pero puede mantener sesión mediante cookies o tokens. No confundas estado del protocolo con estado de la aplicación.

Métodos: seguro, idempotente, cacheable

MétodoSeguroIdempotenteUso típico en TicketFlow
GETlistar eventos, ver disponibilidad
POSTcrear reserva, iniciar compra
PUTreemplazar un evento completo
PATCH*editar precio de un evento
DELETEcancelar reserva

\* PATCH es idempotente solo si el parche es absoluto ({"price": 20}), no relativo ({"price": "+2"}).

  • Seguro = no modifica estado del servidor (el cliente puede repetirlo sin miedo).
  • Idempotente = repetir N veces produce el mismo resultado que una vez. Crucial en pagos y webhooks (Lección 14).

Códigos de estado: los que de verdad usarás

CódigoSignificadoEjemplo TicketFlow
200 OKéxito genéricoGET de disponibilidad
201 Createdrecurso creadoPOST de reserva (con Location)
204 No Contentéxito sin cuerpoDELETE de reserva
400 Bad Requestpetición mal formada / inválidaasientos negativos
401 Unauthorizedno autenticado (el nombre engaña)sin token
403 Forbiddenautenticado pero sin permisocomprador intenta editar evento
404 Not Foundno existe (o no quieres decir que existe)—
409 Conflictchoque de estadoasiento ya reservado
422 Unprocessableválido en forma, inválido en negocioevento ya pasado
429 Too Many Requestsrate limitbot comprando todo
500bug nuestro¡nuestro fallo, no del cliente!

Regla de oro: los 4xx son culpa del cliente; los 5xx, del servidor. Devolver 500 por una validación fallida es un bug de tu API.

2. Cabeceras que dominas o sufres

CORS (Cross-Origin Resource Sharing)

El navegador bloquea por defecto las respuestas de otro origen. CORS es cómo el servidor autoriza esos orígenes. Petición preflight OPTIONS con:

http
Access-Control-Allow-Origin: https://ticketflow.app
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type, Idempotency-Key

En DRF lo gestiona django-cors-headers (lo instalamos en los ejercicios). El CORS se valida en el servidor, no es una medida de seguridad del cliente: si la API pública no tiene CORS, es que no quiere navegadores de otros orígenes; curl ignora CORS siempre.

Cookies y atributos de seguridad

http
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
  • HttpOnly → JavaScript no puede leerla (mitiga XSS robando sesión).
  • Secure → solo viaja por HTTPS.
  • SameSite=Lax|Strict|None → mitiga CSRF restringiendo envíos cross-site.
  • Caduca: Expires/Max-Age; sin ellas es cookie de sesión (muere al cerrar navegador).

Caché HTTP

http
Cache-Control: max-age=60, public          # política fuerte (recomendada)
ETag: "v17"                                # identificador de la versión del recurso
  • Cliente repite petición con If-None-Match: "v17" → si no cambió, servidor responde 304 Not Modified sin cuerpo: ahorro de ancho de banda.
  • Cache-Control: no-store para datos sensibles (pagos). no-cache = "puedes cachear pero valida siempre" (nombre tramposo).
  • Caché compartida (CDN/proxy) frente a privada: private evita que un CDN cachee respuestas con datos de usuario.

Negociación y cuerpo

  • Content-Type: application/json (petición) y respuesta. DRF hace content negotiation: con Accept: application/json vs text/html (el navegador te muestra el HTML navegable del APIView).
  • Accept-Language, Accept-Encoding: gzip, br → compresión de respuesta (la hace el servidor o el proxy, Lección 02).

3. Keep-alive, HTTPS y TLS

Keep-alive y el coste de las conexiones

HTTP/1.0 abría una conexión TCP por petición (costoso: handshake TCP + TLS). HTTP/1.1 introdujo conexiones persistentes (Connection: keep-alive por defecto): muchas peticiones reutilizan la conexión. HTTP/2 va más allá con multiplexado (varias peticiones en paralelo por una sola conexión).

En producción tu Django no habla directamente con el mundo: Gunicorn/Uvicorn detrás de Nginx mantiene keep-alive hacia el cliente y conexiones eficientes hacia la app (Lección 02).

HTTPS/TLS en 4 pasos

  1. Handshake: cliente y servidor negocian versión y cifrados.
  2. Certificado: el servidor presenta su certificado firmado por una CA en la que el cliente confía.
  3. Claves: mediante (clásicamente) intercambio Diffie-Hellman se deriva una clave simétrica de sesión.
  4. Cifrado simétrico para todos los datos (rápido).

Esto da confidencialidad (nadie lee), integridad (nadie altera) y autenticidad (el servidor es quien dice ser). En 2026 el estándar es TLS 1.3: 1 round-trip (o 0 en reconexión) y cifrados modernos.

HSTS: cabecera Strict-Transport-Security que le dice al navegador "solo hables HTTPS conmigo durante X tiempo", impidiendo ataques de downgrade.

4. HTTP en DRF: lo que el framework hace (y no debe hacer) por ti

python
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(["POST"])
def reserve(request):
    if request.method == "POST":
        # DRF: request.data ya parsea JSON según Content-Type
        # Response sin status= devolvería 200: ¡para crear un recurso di 201!
        return Response({"status": "pending_payment"}, status=status.HTTP_201_CREATED)

Puntos que un senior controla:

  • Códigos explícitos: el default silencioso de Response es 200. Para POST que crea → 201; para acciones de negocio → 200; para "aceptado para procesar en cola" → 202 (Lección 06).
  • request.data vs request.query_params: cuerpo vs query string. Confundirlos es fuente de bugs.
  • Errores consistentes: DRF devuelve {"detail":...} o {"campo": [errores]}; definiremos un formato de error propio y centralizado (Lección 26).
  • Cabeceras de respuesta: Response(headers={"Location":...}), Cache-Control en listados cacheables.
  • 401 vs 403 en DRF: NotAuthenticated (401) vs PermissionDenied (403). DRF lo resuelve, pero debes saber explicarlo.

Autoevaluación (respóndeme en el chat)

  1. ¿Por qué POST /reserve no es idempotente y qué consecuencias tiene si el cliente reintenta por timeout?
  2. Cliente envía credenciales válidas pero su rol no permite esa acción. ¿401 o 403? ¿Por qué?
  3. ¿Qué cabecera(s) usarías para que un listado de eventos públicos se cachee 60s en CDN pero los datos de pago nunca?
  4. ¿Qué pasa exactamente cuando el navegador hace un preflight y el servidor no permite la cabecera Idempotency-Key?
  5. ¿Por qué devolver 500 cuando un JSON viene mal formado es un error del backend, no del cliente?

Continúa con los ejercicios. Las soluciones solo tras intentarlo.