Server-Sent Events: streaming HTTP unidireccional sin WebSocket
@programacionLo esencial
- SSE usa el tipo MIME text/event-stream sobre una conexión HTTP normal, sin protocolo aparte como WebSocket.
- El navegador reconecta solo tras una caída y reenvía el header Last-Event-ID para retomar donde quedó.
- La API EventSource está soportada de forma nativa en todos los navegadores modernos desde hace más de una década.
- A diferencia de WebSocket, SSE es unidireccional: el servidor envía datos y el cliente no responde por el mismo canal.
- En HTTP/1.1 el navegador limita a 6 conexiones simultáneas por dominio; HTTP/2 elimina esa restricción.
- El formato de mensaje usa líneas data:, event:, id: y retry: separadas por una línea en blanco.
- Express, FastAPI y Spring lo implementan solo con headers y un stream de texto, sin librerías extra.
Qué es Server-Sent Events
Gmail actualiza tu bandeja sin que refresques la página y ChatGPT muestra el texto letra por letra mientras se genera: ambos casos usan el mismo mecanismo, Server-Sent Events (SSE), un protocolo que corre sobre HTTP plano.
SSE no reemplaza a HTTP: lo extiende. El servidor abre una respuesta normal, pero en lugar de cerrarla después de enviar el contenido, la mantiene abierta y sigue escribiendo datos cuando hay algo nuevo. Está definido en la especificación de WHATWG como parte del estándar HTML.
Cómo funciona el protocolo
El cliente hace una petición GET normal. El servidor responde con el header Content-Type: text/event-stream y deja la conexión abierta. A partir de ahí, cada bloque de texto que el servidor escribe llega al cliente como un evento.
app.get('/eventos', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const intervalId = setInterval(() => {
res.write(`data: ${JSON.stringify({ hora: new Date().toISOString() })}\n\n`);
}, 3000);
req.on('close', () => clearInterval(intervalId));
});Este servidor Express manda la hora cada 3 segundos. La línea clave es el doble salto de línea (\n\n): marca el final de un evento y le dice al navegador que ya puede entregarlo a la aplicación.
El formato del mensaje: data, event, id y retry
Cada evento SSE se arma con líneas de texto plano. Ninguna requiere comillas ni escapado especial más allá de un salto de línea al final del bloque.
event: actualizacion
id: 42
retry: 5000
data: {"progreso": 87}
El campo id es el que habilita la reconexión: si la conexión se corta, el navegador reenvía automáticamente el header Last-Event-ID con ese valor en el siguiente intento, y el servidor puede decidir desde dónde retomar el envío.
EventSource en el navegador
Del lado del cliente no hace falta ninguna librería. La API EventSource viene incluida en el navegador y maneja sola la reconexión.
const fuente = new EventSource('/eventos');
fuente.addEventListener('message', (evento) => {
const payload = JSON.parse(evento.data);
console.log('Hora del servidor:', payload.hora);
});
fuente.addEventListener('error', () => {
console.warn('Conexion perdida, el navegador reintentara automaticamente');
});La documentación completa de la interfaz, incluyendo los estados de conexión (CONNECTING, OPEN, CLOSED), está en MDN.
SSE vs WebSocket: cuándo usar cada uno
WebSocket abre un canal bidireccional: cliente y servidor se mandan mensajes por igual. SSE solo permite que el servidor hable. Si tu aplicación necesita que el cliente responda por el mismo canal (chat, juegos en tiempo real, colaboración simultánea), WebSocket es la opción correcta.
Si el flujo es de un solo sentido (notificaciones, progreso de una tarea, un feed de precios, tokens de un modelo de lenguaje generándose), SSE resuelve el mismo problema con menos código: no hay handshake especial, no hay librería de cliente adicional y el navegador reconecta solo.
Limitaciones y el gotcha de HTTP/1.1
Bajo HTTP/1.1 los navegadores limitan a 6 conexiones simultáneas por dominio, y una pestaña con varias conexiones SSE abiertas puede agotar ese límite y bloquear otras peticiones. Con HTTP/2, las conexiones se multiplexan sobre un único socket TCP y esa restricción desaparece.
Otro gotcha frecuente: proxies como nginx bufferean la respuesta por defecto, así que los eventos no llegan hasta que el buffer se llena. Hay que desactivarlo explícitamente con el header X-Accel-Buffering: no para que el streaming funcione en producción.
Conclusión
SSE no compite con WebSocket, cubre un caso más simple: cuando solo el servidor tiene algo que decir, usar HTTP plano con reconexión automática es menos infraestructura que mantener un socket bidireccional.
📖 Versión extendida con más detalle: https://elsolitario.org/2026/08/04/server-sent-events-streaming-http-explicado/?utm_source=telegraph&utm_medium=instant_view&utm_campaign=programacion