Saltar al contenido principal

📋 Encabezados HTTP

[... Previous sections remain the same as in the previous snippet ...]

Envío de cookies almacenadas por el cliente

GET /api/perfil HTTP/1.1
Host: example.com
Cookie: sessionId=abc123; userId=456
// El navegador envía cookies automáticamente
fetch('https://example.com/api/perfil', {
credentials: 'include' // Incluir cookies
});

Referer

URL de la página anterior

GET /api/productos/123 HTTP/1.1
Host: example.com
Referer: https://example.com/productos

Propósito:

  • Análisis de fuentes de tráfico
  • Verificación de seguridad
  • Registro

Otros encabezados de solicitud

Origin
├─ Origen desde donde se inició la solicitud
└─ Usado para verificación CORS

If-None-Match
├─ Comparar con valor ETag
└─ Validación de caché

If-Modified-Since
├─ Verificar si se modificó después de una fecha
└─ Validación de caché

Range
├─ Solicitar solo una parte de los datos
└─ Ejemplo: bytes=0-1023

📤 Encabezados de respuesta (Response Headers)

Content-Type

Tipo de medio del cuerpo de la respuesta

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"id": 123,
"name": "María González"
}
fetch('https://api.example.com/users/123')
.then(response => {
console.log(response.headers.get('Content-Type'));
// "application/json; charset=utf-8"
return response.json();
});

Content-Length

Tamaño del cuerpo de la respuesta (bytes)

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 256

{"id":123,"name":"María González"}

Almacenar cookies en el cliente

HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
Set-Cookie: userId=456; Max-Age=3600
// Establecer cookie en servidor (Node.js/Express)
res.cookie('sessionId', 'abc123', {
httpOnly: true, // Bloquear acceso de JavaScript
secure: true, // Solo HTTPS
sameSite: 'strict',
maxAge: 3600000 // 1 hora
});

Atributos de cookies:

HttpOnly
├─ Sin acceso por JavaScript
└─ Defensa contra XSS

Secure
├─ Solo enviar por HTTPS
└─ Defensa contra ataques de intermediario

SameSite
├─ Strict: Solo en el mismo sitio
├─ Lax: Permitir algunos sitios cruzados
└─ None: Permitir todos los sitios cruzados (Secure obligatorio)

Max-Age / Expires
├─ Max-Age: 3600 (en segundos)
└─ Expires: Wed, 21 Oct 2025 07:28:00 GMT

Domain / Path
├─ Domain: example.com (incluye subdominios)
└─ Path: / (toda la ruta)

Cache-Control

Política de caché

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Wed, 26 Jan 2025 10:00:00 GMT
fetch('https://api.example.com/users')
.then(response => {
const cacheControl = response.headers.get('Cache-Control');
console.log(cacheControl); // "public, max-age=3600"
});

Directivas de caché:

Cache-Control: no-cache
├─ Validar con el servidor antes de usar caché
└─ Garantizar datos más recientes

Cache-Control: no-store
├─ No cachear
└─ Datos sensibles (contraseñas, etc.)

Cache-Control: public, max-age=3600
├─ Permitir caché pública
├─ Cachear durante 1 hora
└─ Recursos estáticos (imágenes, CSS, etc.)

Cache-Control: private, max-age=3600
├─ Cachear solo en navegador
└─ Datos específicos de usuario

Cache-Control: must-revalidate
├─ Validar obligatoriamente al expirar caché
└─ Prevenir datos antiguos

Location

Ubicación de redirección o recurso creado

HTTP/1.1 201 Created
Location: /api/users/123

HTTP/1.1 301 Movido Permanentemente
Location: https://example.com/nueva-pagina
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'María González' })
})
.then(response => {
if (response.status === 201) {
const location = response.headers.get('Location');
console.log('Nuevo recurso:', location); // "/api/users/123"
}
});

Access-Control-* (CORS)

Intercambio de recursos de origen cruzado

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
// Configuración CORS en servidor (Node.js/Express)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://example.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});

Encabezados CORS:

Access-Control-Allow-Origin
├─ Especificar origen permitido
├─ *: Permite todos los orígenes (cuidado con seguridad)
└─ https://example.com: Solo ese origen

Access-Control-Allow-Methods
├─ Métodos HTTP permitidos
└─ GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers
├─ Encabezados de solicitud permitidos
└─ Content-Type, Authorization

Access-Control-Allow-Credentials
├─ Permitir inclusión de cookies
└─ true (no válido con Origin *)

Access-Control-Max-Age
├─ Tiempo de caché de resultados Preflight (segundos)
└─ 86400 (24 horas)

🔐 Encabezados de seguridad

Strict-Transport-Security (HSTS)

Forzar HTTPS

HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
// Configuración en servidor (Node.js/Express)
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});

Content-Security-Policy (CSP)

Defensa contra ataques XSS

HTTP/1.1 200 OK
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com

Directivas CSP:

default-src 'self'
├─ Política predeterminada: solo mismo origen
└─ Valor predeterminado para todos los tipos de recursos

script-src 'self' https://trusted.com
├─ Limitar fuentes de scripts
└─ Defensa contra XSS

style-src 'self' 'unsafe-inline'
├─ Limitar fuentes de hojas de estilo
└─ Permitir estilos en línea

img-src * data:
├─ Limitar fuentes de imágenes
└─ Permitir todos los orígenes, URI de datos

frame-ancestors 'none'
├─ Bloquear inserción en iframes
└─ Defensa contra clickjacking

X-Content-Type-Options

Prevenir suplantación MIME

HTTP/1.1 200 OK
X-Content-Type-Options: nosniff

X-Frame-Options

Defensa contra clickjacking

HTTP/1.1 200 OK
X-Frame-Options: DENY

Opciones:

DENY
├─ Bloquear totalmente inserción en iframes
└─ Más seguro

SAMEORIGIN
├─ Permitir iframes solo del mismo origen
└─ Comúnmente usado

ALLOW-FROM https://example.com
├─ Permitir solo de un origen específico
└─ Compatibilidad con navegadores antiguos

📊 Encabezados personalizados

Prefijo X- (no estándar, heredado)

GET /api/users HTTP/1.1
Host: example.com
X-API-Key: abc123xyz
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
fetch('https://api.example.com/users', {
headers: {
'X-API-Key': 'abc123xyz',
'X-Request-ID': crypto.randomUUID()
}
});

Encabezados personalizados comunes:

X-API-Key
├─ Autenticación con clave API
└─ Alternativa: Encabezado Authorization

X-Request-ID
├─ ID único para rastreo de solicitudes
└─ Registro, depuración

X-Forwarded-For
├─ IP original tras proxy
└─ Ejemplo: X-Forwarded-For: 203.0.113.1, 198.51.100.178

X-Real-IP
├─ IP real del cliente
└─ Usado en Nginx

X-Correlation-ID
├─ Rastreo entre microservicios
└─ Sistemas distribuidos

💡 Ejemplo práctico

Ejemplo completo de solicitud/respuesta

Solicitud:
POST /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
Accept: application/json
Accept-Language: es-ES,es;q=0.9
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Length: 58
Origin: https://example.com
Referer: https://example.com/register

{
"name": "María González",
"email": "maria@example.com"
}

Respuesta:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Content-Length: 156
Location: /api/users/123
Cache-Control: no-cache, no-store, must-revalidate
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
X-Request-ID: 550e8400-e29b-41d4-a716-446655440000
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
"id": 123,
"name": "María González",
"email": "maria@example.com",
"createdAt": "2025-01-26T10:00:00Z"
}

🤔 Preguntas frecuentes

Q1. Reglas para nombres de encabezados personalizados

R:

Pasado (prefijo X-):
X-API-Key: abc123
X-Custom-Header: valor

Actualidad (recomendado):
API-Key: abc123
Custom-Header: valor

Razones:
- Prefijo X- no recomendado por RFC 6648
- Evitar confusión al estandarizar
- Algunos encabezados X- ampliamente usados se mantienen

Excepciones:
X-Forwarded-For - Muy usado
X-Real-IP - Estándar Nginx
X-Request-ID - Estándar de registro/rastreo

Q2. Límites de tamaño de encabezados

R:

Límites típicos:
├─ Apache: 8KB (predeterminado)
├─ Nginx: 4KB-8KB
├─ Node.js: 80KB (http.maxHeaderSize)
└─ Cloudflare: 32KB

Si se excede:
- 431 Campos de encabezado de solicitud demasiado grandes
- 413 Entidad de solicitud demasiado grande

Soluciones:
1. Usar cuerpo en lugar de encabezados
2. Reducir longitud de tokens
3. Modificar configuración de servidor (con precaución)

❌ Ejemplo incorrecto:
Authorization: Bearer <token JWT de 5KB>

✅ Ejemplo correcto:
- Usar tokens cortos
- Guardar ID de sesión en servidor

Q3. ¿Distinguen mayúsculas y minúsculas?

R:

Nombre de encabezado: No distingue mayúsculas/minúsculas
├─ Content-Type = content-type = CONTENT-TYPE
└─ HTTP/2 convierte todo a minúsculas

Valor de encabezado: Distingue mayúsculas/minúsculas
├─ Content-Type: application/json (Correcto)
├─ Content-Type: Application/JSON (Incorrecto)
└─ El valor es sensible a mayúsculas/minúsculas

Recomendaciones:
- Nombre de encabezado: Cada palabra con inicial mayúscula (Content-Type)
- Valor: Seguir especificación

Q4. ¿Es seguro incluir información sensible en encabezados?

R:

✅ Uso seguro de encabezados:
Authorization: Bearer <token>
├─ Usar HTTPS obligatorio
├─ Enmascarar en logs
└─ Tiempo de expiración corto

❌ Uso inseguro:
Authorization: Bearer <token> (HTTP)
├─ Transmisión sin cifrar
└─ Vulnerable a ataques de intermediario

X-API-Key: <secreto>
├─ Exposición en logs de URL
└─ Permanece en historial del navegador

Consideraciones:
1. HTTPS obligatorio
2. Excluir encabezados sensibles de logs de servidor
3. Visible en herramientas de desarrollador
4. Transmitido a proxies y CDNs

Q5. ¿Qué es una solicitud Preflight?

R:

Preflight = Solicitud previa CORS

¿Cuándo ocurre?
├─ Métodos distintos de GET, POST, HEAD (PUT, DELETE, etc.)
├─ Uso de encabezados personalizados
└─ Content-Type distintos de valores estándar (application/json, etc.)

Proceso:
1. Solicitud OPTIONS (Preflight)
OPTIONS /api/users HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

2. Respuesta del servidor
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

3. Solicitud real
POST /api/users HTTP/1.1
Content-Type: application/json
Authorization: Bearer token

Optimización:
- Caché con Access-Control-Max-Age (24 horas)
- Cumplir condiciones de solicitud simple (evitar Preflight)

🎓 Prácticas

1. Depuración de encabezados

// Registro de encabezados de solicitud/respuesta
async function debugFetch(url, options = {}) {
console.log('=== Solicitud ===');
console.log('URL:', url);
console.log('Método:', options.method || 'GET');
console.log('Encabezados:', options.headers || {});

const response = await fetch(url, options);

console.log('=== Respuesta ===');
console.log('Estado:', response.status, response.statusText);
console.log('Encabezados:');
response.headers.forEach((valor, clave) => {
console.log(` ${clave}: ${valor}`);
});

return response;
}

// Uso
await debugFetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ name: 'María González' })
});

2. Middleware de encabezados (Express)

// Establecer encabezados de seguridad automáticamente
function encabezadosSeguridad(req, res, next) {
res.set({
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Content-Security-Policy': "default-src 'self'"
});
next();
}

// Configuración CORS
function encabezadosCORS(req, res, next) {
res.set({
'Access-Control-Allow-Origin': req.headers.origin || '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
});

if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
}

app.use(encabezadosSeguridad);
app.use(encabezadosCORS);

🔗 Documentos relacionados

🎬 Conclusión

¡Los encabezados HTTP son un medio fundamental para intercambiar metadatos entre clientes y servidores! El uso adecuado de encabezados mejora la seguridad, el rendimiento y la compatibilidad.

Siguiente paso: Lea Cookies y sesiones para aprender a superar la naturaleza sin estado de HTTP.