Capa de aplicación — HTTP, DNS, TLS

"Si entendés HTTP y DNS, entendés cómo funciona el 90% de Internet."

Qué vas a aprender en este capítulo

La capa de aplicación es la que tu programa habla. Vas a aprender HTTP (protocolo de la web), DNS (cómo se traducen nombres a IPs), y TLS (la magia detrás del candadito en el navegador). Vas a construir un mini servidor HTTP desde cero — el proyecto-hilo del libro — y al cerrar, vas a entender qué pasa, byte por byte, cuando hacés una petición a un sitio web.

5.1 La idea: protocolos en texto

💡 Intuición

Los protocolos de aplicación clásicos (HTTP, SMTP, FTP) son texto plano legible. Eso era radical en los 90s — antes los protocolos eran binarios y opacos.

Cuando entendés HTTP, podés escribir un cliente HTTP en cualquier lenguaje, en menos de 10 líneas, sin biblioteca. Solo abriendo un socket TCP y mandando texto.

Cliente envía:
    GET / HTTP/1.1
    Host: example.com
    
Servidor responde:
    HTTP/1.1 200 OK
    Content-Type: text/html
    
    <html>...</html>

Eso es una página web. Es así de simple, en su esencia.

Conociendo este protocolo, podés:

  • Diagnosticar problemas (curl, telnet).
  • Implementar APIs REST.
  • Optimizar rendimiento (caching, keep-alive).
  • Hackear (consensuadamente — en CTFs).

DNS y TLS son más complejos pero igualmente fundamentales.

5.2 HTTP — anatomía

📐 Fundamento

HTTP (HyperText Transfer Protocol) es stateless (sin estado por defecto), texto, request-response.

Request

GET /productos/queso HTTP/1.1
Host: pupuseria.com
User-Agent: Mozilla/5.0
Accept: text/html
Cookie: session=abc123

Estructura:

  1. Línea de request: MÉTODO RUTA VERSIÓN.
  2. Headers: clave-valor, uno por línea.
  3. Línea en blanco (separador).
  4. Cuerpo opcional (POST, PUT con body).

Response

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Set-Cookie: nuevo=xyz
Server: nginx/1.21

<html>...</html>

Estructura:

  1. Línea de status: VERSIÓN CÓDIGO MENSAJE.
  2. Headers.
  3. Línea en blanco.
  4. Cuerpo opcional.

Métodos HTTP

Método Para qué Idempotente Body
GET Obtener recurso No
POST Crear / acción No
PUT Actualizar (reemplazar)
PATCH Actualizar (parcial) No siempre
DELETE Eliminar No
HEAD Como GET pero sin body No
OPTIONS Capacidades del servidor No

Idempotente = ejecutar varias veces da el mismo resultado. Importante para reintentos.

Códigos de estado

1xx Informativos (raros en HTTP/1.1).

2xx Éxito:

  • 200 OK — todo bien.
  • 201 Created — recurso creado (típico tras POST).
  • 204 No Content — éxito sin contenido.

3xx Redirecciones:

  • 301 Moved Permanently — el recurso se movió, actualizá tu link.
  • 302 Found — temporal.
  • 304 Not Modified — no cambió desde tu última visita (caching).

4xx Errores del cliente:

  • 400 Bad Request — la request está malformada.
  • 401 Unauthorized — necesitás autenticarte.
  • 403 Forbidden — autenticado pero sin permiso.
  • 404 Not Found — recurso no existe.
  • 429 Too Many Requests — rate limit.

5xx Errores del servidor:

  • 500 Internal Server Error — el servidor reventó.
  • 502 Bad Gateway — proxy/load balancer no puede hablar con el backend.
  • 503 Service Unavailable — servidor saturado o en mantenimiento.
  • 504 Gateway Timeout — proxy esperó demasiado.

Memorizá: 200=OK, 301=movido, 404=no existe, 500=server roto. Los demás se buscan.

5.3 HTTP/1.1, HTTP/2, HTTP/3

📐 Fundamento

HTTP/1.0 (1996)

Una conexión TCP por request. Lento — abrir/cerrar TCP cada vez.

HTTP/1.1 (1997)

Keep-alive por defecto: una conexión TCP sirve varias requests. Pipelining: mandar varias requests sin esperar (poco usado en práctica).

Aún así, una request a la vez por conexión — head-of-line blocking.

HTTP/2 (2015)

  • Binario (no texto) — más eficiente de parsear.
  • Multiplexación: muchas requests en paralelo sobre la misma conexión TCP.
  • Compresión de headers (HPACK).
  • Server push: el servidor puede enviar recursos antes de que el cliente los pida.

Resuelve el head-of-line blocking de HTTP/1.1, aunque hereda el de TCP.

HTTP/3 (2022)

Reemplaza TCP por QUIC (sobre UDP). Resuelve definitivamente HOL blocking — cada stream es independiente.

  • Más rápido para abrir conexión (0-RTT en vuelta de cliente conocido).
  • Mejor en redes inestables (mobile, WiFi).
  • TLS 1.3 obligatorio.

Hoy en 2026, los grandes sites (Google, Facebook, Cloudflare) usan HTTP/3. El navegador lo negocia automáticamente.

Para escribir tu servidor

En la práctica, escribís contra HTTP/1.1 y un proxy frontal (nginx, Caddy) maneja HTTP/2 y HTTP/3 hacia los clientes.

5.4 DNS — del nombre a la IP

📐 Fundamento

Internet rutea por IPs, pero los humanos recordamos nombres. DNS (Domain Name System) traduce.

Estructura jerárquica

                     . (raíz)
                    /  |  \
                  com  org  sv     ← TLDs
                  /     \    \
              google   wiki   gob
              ↓
        google.com    ← dominio
              ↓
        www.google.com  ← FQDN

Cada nivel está delegado al siguiente: la raíz no sabe IPs de Google, sabe quién es responsable del .com. El servidor de .com sabe quién es responsable de google.com. Y así.

Tipos de registros

Tipo Contenido
A IPv4 (google.com → 142.250.x.x)
AAAA IPv6
CNAME Alias (www.google.com → google.com)
MX Servidor de email
NS Servidor DNS responsable de un dominio
TXT Texto arbitrario (verificación, SPF, DKIM)
PTR Reverse (IP → nombre)
SRV Servicios (XMPP, SIP, etc.)

Resolución típica

Tu PC quiere www.google.com:

  1. Caché local. Si tu PC ya lo resolvió hace poco, listo.
  2. DNS resolver del ISP (típicamente). Le preguntás "¿IP de www.google.com?".
  3. El resolver, si no la tiene cacheada:
    • Pregunta a la raíz: "¿quién maneja .com?"
    • La raíz responde: "los servidores de .com están en estas IPs".
    • Pregunta a .com: "¿quién maneja google.com?"
    • .com responde: "los NS de google.com son ns1.google.com, ns2.google.com".
    • Pregunta a ns1.google.com: "¿qué IP tiene www.google.com?"
    • ns1 responde: "142.250.x.x".
  4. Resolver te lo devuelve y lo cachea.
  5. Tu PC abre TCP a esa IP.

Latencia típica: primer DNS lookup ~30-100 ms. Después, cachéa todo, instantáneo.

Inspeccionar DNS

$ dig www.google.com

;; ANSWER SECTION:
www.google.com.    300  IN  A  142.250.10.139

;; Query time: 18 msec
;; SERVER: 192.168.1.1#53

Trace (ver toda la cadena):

$ dig +trace google.com
.      518400  IN  NS  a.root-servers.net.
              ...
com.   172800  IN  NS  a.gtld-servers.net.
              ...
google.com.  172800  IN  NS  ns1.google.com.
              ...
www.google.com. 300  IN  A  142.250.10.139

Ahí ves cada nivel de la jerarquía respondiendo.

DNS-over-HTTPS (DoH)

DNS clásico es sin cifrar — tu ISP puede ver qué sitios resolvés. DoH envía DNS por HTTPS, ocultándolo. Cloudflare (1.1.1.1), Google (8.8.8.8) lo soportan.

Trade-off: privacidad mayor, pero centralizás la confianza en pocos resolvers.

5.5 TLS / HTTPS

📐 Fundamento

TLS (Transport Layer Security, antes SSL) es el protocolo que cifra HTTP. HTTPS = HTTP + TLS.

Tres garantías

  1. Confidencialidad. Nadie en el medio puede leer.
  2. Integridad. Nadie puede modificar sin que se note.
  3. Autenticidad. Sabés que estás hablando con el servidor real (no un impostor).

Handshake (simplificado)

Cliente                                        Servidor
   |                                                |
   |─ "ClientHello" (versiones, ciphers)──────────>|
   |                                                |
   |<─ "ServerHello" + Certificado ────────────────|
   |                                                |
   |  (cliente verifica el certificado)             |
   |                                                |
   |─ Intercambio de claves (ECDHE) ──────────────>|
   |                                                |
   |─── Cambio a cifrado ──────────────────────────|
   |                                                |
   |── Datos HTTP cifrados ─────────────────────  ─>|

Tras el handshake, todos los bytes van cifrados simétricamente con la clave acordada.

Certificados

Un certificado dice: "este servidor afirma ser google.com, y la entidad X (CA) lo respalda con su firma".

CAs (Certificate Authorities) confiables vienen pre-instaladas en tu navegador y SO. Cuando ves un certificado:

  1. ¿Lo firma una CA conocida? Si no → alerta.
  2. ¿Está vigente? Si no → alerta.
  3. ¿El nombre del cert coincide con el dominio que pediste? Si no → alerta.
  4. ¿Está revocado (CRL u OCSP)? Si sí → alerta.

Let's Encrypt revolucionó esto al hacer certificados gratis y automatizados. Hoy >90% de la web usa HTTPS.

TLS 1.3

Versión actual (2018). Más simple, más rápida, más segura:

  • 1-RTT handshake (vs 2-RTT antes).
  • 0-RTT en reconexiones.
  • Solo cifradores modernos (eliminados los rotos).

Ver el certificado

En el navegador: clic en el candado. Línea de comandos:

$ openssl s_client -connect google.com:443 -servername google.com
...
subject=/CN=*.google.com
issuer=/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
...

-servername es crítico — habilita SNI, que permite múltiples sitios HTTPS en una sola IP.

5.6 Mini servidor HTTP

🛠️ Avance del proyecto

Cierre del proyecto-hilo. Un servidor HTTP que sirve páginas:

# mini-server.py - servidor HTTP educativo

import socket
from datetime import datetime
from urllib.parse import unquote


HOST, PORT = '', 8080


def parse_request(raw: bytes):
    """Parsea una request HTTP/1.1 muy básica."""
    text = raw.decode('utf-8', errors='replace')
    if '\r\n\r\n' not in text:
        return None
    head, body = text.split('\r\n\r\n', 1)
    lines = head.split('\r\n')
    if not lines:
        return None
    metodo, ruta, version = lines[0].split(' ', 2)
    headers = {}
    for linea in lines[1:]:
        if ':' in linea:
            k, v = linea.split(':', 1)
            headers[k.strip().lower()] = v.strip()
    return {
        'metodo': metodo,
        'ruta': unquote(ruta),
        'version': version,
        'headers': headers,
        'body': body,
    }


def build_response(status: int, body: str, content_type='text/html'):
    """Construye una response HTTP/1.1."""
    mensajes = {200: 'OK', 404: 'Not Found', 500: 'Internal Server Error'}
    msg = mensajes.get(status, '')
    body_bytes = body.encode('utf-8')
    headers = [
        f"HTTP/1.1 {status} {msg}",
        f"Content-Type: {content_type}; charset=utf-8",
        f"Content-Length: {len(body_bytes)}",
        f"Date: {datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')}",
        "Server: mini-server/0.1",
        "Connection: close",
        "",
        "",
    ]
    return ('\r\n'.join(headers)).encode('utf-8') + body_bytes


def routear(req):
    """Ruteador trivial."""
    if req['ruta'] == '/':
        return build_response(200, """
            <h1>Pupuseria La Esquina</h1>
            <p>Bienvenidos a nuestro mini-servidor.</p>
            <ul>
              <li><a href='/menu'>Menu</a></li>
              <li><a href='/contacto'>Contacto</a></li>
            </ul>
        """)
    if req['ruta'] == '/menu':
        return build_response(200, """
            <h1>Menu</h1>
            <ul>
              <li>Pupusa de queso ........ $0.50</li>
              <li>Pupusa revuelta ........ $0.60</li>
              <li>Pupusa de chicharron ... $0.60</li>
            </ul>
        """)
    if req['ruta'] == '/contacto':
        return build_response(200, "<h1>Contacto</h1><p>San Miguel</p>")
    return build_response(404, "<h1>404</h1><p>Recurso no encontrado.</p>")


def manejar(cliente):
    try:
        raw = cliente.recv(8192)
        req = parse_request(raw)
        if req is None:
            cliente.send(build_response(400, "<h1>400 Bad Request</h1>"))
            return
        print(f"[{req['metodo']}] {req['ruta']}")
        respuesta = routear(req)
        cliente.send(respuesta)
    except Exception as e:
        print(f"Error: {e}")
        cliente.send(build_response(500, "<h1>500</h1>"))
    finally:
        cliente.close()


def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((HOST, PORT))
    s.listen(50)
    print(f"Mini-server escuchando en http://localhost:{PORT}")
    while True:
        cliente, addr = s.accept()
        manejar(cliente)


if __name__ == '__main__':
    main()

Probalo:

$ python mini-server.py
Mini-server escuchando en http://localhost:8080

Visitá http://localhost:8080 en tu navegador. Funciona. Sirve HTML, hace ruteo básico, devuelve códigos de estado correctos.

Lo que sigue para hacerlo "real":

  • Multi-cliente (threads o async).
  • Servir archivos estáticos.
  • Métodos POST con form data.
  • Cookies y sesiones.
  • HTTPS (envolvé el socket con ssl.wrap_socket).

Pero conceptualmente, esto es un servidor HTTP. nginx, Apache y caddy hacen lo mismo, optimizado.

5.7 Resumen de lo aprendido

📐 Fundamento

Cuando hacés curl https://www.google.com:

  1. Tu cliente consulta DNS para resolver www.google.com142.250.x.x (UDP/53).
  2. TCP three-way handshake con esa IP en puerto 443.
  3. TLS handshake — intercambio de certificados, claves.
  4. HTTP request cifrada: GET / HTTP/1.1.
  5. Servidor de Google procesa, busca el HTML.
  6. HTTP response cifrada: 200 OK, html, etc.
  7. Cierre TCP (FIN-ACK).
  8. Tu cliente muestra el contenido.

Cada paso involucra:

  • Capa física (cobre, fibra, radio).
  • Capa enlace (Ethernet, WiFi, MAC, switches).
  • Capa red (IP, routing, NAT, ICMP).
  • Capa transporte (TCP).
  • Capa aplicación (DNS, TLS, HTTP).

Todo eso, en menos de un segundo, miles de millones de veces por hora, alrededor del planeta.

5.8 Cierre del libro

Llegaste al final de Redes I. Repaso:

  1. Modelos y capas — el truco arquitectónico.
  2. Capa física y enlace — cables, frames, switches.
  3. Capa de red — IP, routing, NAT.
  4. Capa de transporte — TCP, UDP, sockets.
  5. Capa de aplicación — HTTP, DNS, TLS.

Ya entendés Internet. Quizás no en cada detalle, pero la arquitectura entera es accesible para vos.

Próximos pasos:

5.9 Ejercicios

✏️ Ejercicio 5.1 — HTTP a mano con netcat

Hacé una request HTTP a mano:

$ nc google.com 80
GET / HTTP/1.1
Host: google.com

(Después de "Host:", presioná Enter dos veces.)

¿Qué respuesta obtenés?

✏️ Ejercicio 5.2 — DNS trace

Ejecutá dig +trace tu-universidad.edu.sv (reemplazá por un dominio real). ¿Cuántos niveles ves? Identificá: raíz, TLD (.sv), dominio.

✏️ Ejercicio 5.3 — Examinar certificados

Visitá un sitio HTTPS, hacé clic en el candadito, mirá el certificado. Identificá:

a. ¿Quién firmó (issuer)? b. ¿Cuándo expira? c. ¿Para qué dominios vale?

Después, ejecutá:

$ openssl s_client -connect <sitio>:443 -showcerts < /dev/null

¿Coincide con lo que viste?

✏️ Ejercicio 5.4 — Extender el mini-server

Tomá el mini-server de la sección 5.6 y agregá:

a. Servir archivos estáticos desde una carpeta ./public/. b. Soporte para POST con form data. c. Multi-cliente con threads.

5.10 Para profundizar


Definiciones nuevas: HTTP, request, response, método (GET/POST/etc.), código de estado, headers, HTTP/1.1, HTTP/2, HTTP/3, DNS, TLD, FQDN, registro DNS, resolver, CA, certificado, TLS, HTTPS, SNI, mini-server.