Server-Sent Events: streaming de dados em tempo real sem WebSocket
1. Fundamentos do SSE: quando e por que usar em vez de WebSocket
1.1. O que são Server-Sent Events: fluxo unidirecional do servidor para o cliente
Server-Sent Events (SSE) é uma tecnologia que permite ao servidor enviar dados para o cliente de forma contínua e assíncrona, utilizando uma conexão HTTP persistente. Diferentemente do WebSocket, que estabelece um canal bidirecional completo, o SSE opera exclusivamente no sentido servidor → cliente. Isso significa que o cliente não precisa enviar requisições repetidas para obter atualizações — o servidor "empurra" os dados conforme eles se tornam disponíveis.
A especificação SSE é parte do HTML5 e é suportada nativamente por todos os navegadores modernos, exceto Internet Explorer. A implementação é surpreendentemente simples: o servidor define o Content-Type como text/event-stream e mantém a conexão aberta, enviando blocos de texto formatados.
1.2. Comparação direta: SSE vs WebSocket vs Polling vs Long Polling
| Característica | SSE | WebSocket | Polling | Long Polling |
|---|---|---|---|---|
| Direcionalidade | Servidor → Cliente | Bidirecional | Cliente → Servidor | Cliente → Servidor |
| Latência | Baixa (eventos em tempo real) | Muito baixa | Alta (intervalo fixo) | Média |
| Reconexão automática | Nativa (Last-Event-ID) | Manual | Não se aplica | Manual |
| Headers customizados | Não suporta nativamente | Suporta | Suporta | Suporta |
| Navegadores modernos | Sim (excluindo IE) | Sim | Sim | Sim |
O Polling tradicional faz requisições HTTP em intervalos fixos, gerando overhead desnecessário. O Long Polling mantém a conexão aberta até que o servidor tenha dados para enviar, mas ainda requer que o cliente inicie cada ciclo. O WebSocket oferece comunicação bidirecional com baixa latência, mas é mais complexo de implementar e pode ser bloqueado por firewalls corporativos. O SSE ocupa um ponto ideal: simplicidade do HTTP com capacidade de streaming em tempo real.
1.3. Casos de uso ideais
Os cenários perfeitos para SSE incluem:
- Notificações em tempo real: alertas de sistema, mensagens de aplicativo, notificações push
- Feeds de cotação: preços de ações, criptomoedas, taxas de câmbio
- Logs em tempo real: visualização de logs de servidor, monitoramento de aplicações
- Atualizações de dashboard: métricas de sistema, KPIs, status de serviços
- Progresso de tarefas: uploads, processamentos batch, deploys
2. Arquitetura e protocolo: como o SSE funciona por baixo dos panos
2.1. O formato text/event-stream
A comunicação SSE segue um formato de texto simples, onde cada mensagem é composta por campos opcionais:
event: update
data: {"message": "Novo dado disponível", "timestamp": 1700000000}
id: 42
retry: 3000
- event: Define o tipo do evento (opcional). Se omitido, dispara o evento
messagegenérico no cliente. - data: O payload da mensagem. Pode ser múltiplas linhas.
- id: Identificador único do evento, usado para reconexão automática.
- retry: Tempo em milissegundos que o cliente deve esperar antes de tentar reconectar.
2.2. Reconexão automática nativa com Last-Event-ID
Uma das vantagens mais poderosas do SSE é o mecanismo de reconexão automática. Quando a conexão cai, o navegador envia automaticamente o cabeçalho Last-Event-ID com o último id recebido. O servidor pode usar esse valor para retomar o fluxo a partir do ponto de interrupção, garantindo que nenhum evento seja perdido.
2.3. Limitações intrínsecas
- Número máximo de conexões por domínio: A maioria dos navegadores limita a 6 conexões SSE simultâneas por domínio (especificação HTTP/1.1).
- Ausência de suporte a headers customizados: A API
EventSourcenão permite definir headers personalizados, o que complica a autenticação. - Unidirecionalidade: O cliente não pode enviar dados de volta pela mesma conexão.
3. Implementação do lado do servidor: produzindo eventos em diferentes stacks
3.1. Servidor HTTP básico em Node.js (Express/Raw HTTP)
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
let eventId = 0;
const interval = setInterval(() => {
eventId++;
res.write(`id: ${eventId}\n`);
res.write(`event: heartbeat\n`);
res.write(`data: ${JSON.stringify({ time: new Date().toISOString() })}\n\n`);
}, 5000);
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
app.listen(3000);
3.2. SSE em Python (FastAPI/Starlette)
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import asyncio
import json
app = FastAPI()
async def event_generator(request: Request):
event_id = 0
while True:
if await request.is_disconnected():
break
event_id += 1
data = json.dumps({"event_id": event_id, "message": "Atualização em tempo real"})
yield f"id: {event_id}\nevent: update\ndata: {data}\n\n"
await asyncio.sleep(2)
@app.get("/events")
async def sse_endpoint(request: Request):
return StreamingResponse(
event_generator(request),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no"
}
)
3.3. Gerenciamento de múltiplos clientes
Para broadcast de eventos para múltiplos clientes, mantenha um registro de conexões ativas:
const clients = new Map();
app.get('/events', (req, res) => {
const clientId = Date.now();
clients.set(clientId, res);
req.on('close', () => {
clients.delete(clientId);
});
});
function broadcast(event, data) {
clients.forEach((res) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
});
}
4. Consumo no frontend: a API EventSource e suas armadilhas
4.1. Uso padrão
const eventSource = new EventSource('http://localhost:3000/events');
// Evento genérico (sem campo event)
eventSource.onmessage = (event) => {
console.log('Dados recebidos:', event.data);
};
// Evento nomeado
eventSource.addEventListener('update', (event) => {
const data = JSON.parse(event.data);
console.log('Update recebido:', data);
});
eventSource.addEventListener('heartbeat', (event) => {
console.log('Heartbeat:', event.data);
});
4.2. Tratamento de erros e reconexão manual
eventSource.onerror = (error) => {
console.error('Erro na conexão SSE:', error);
if (eventSource.readyState === EventSource.CLOSED) {
setTimeout(() => {
// Reconexão manual com novo EventSource
reconnect();
}, 5000);
}
};
4.3. Limitações do navegador
- CORS: Requer cabeçalhos
Access-Control-Allow-Originno servidor. - Cookies: Não são enviados automaticamente em conexões SSE cross-origin.
- Conexões simultâneas: Limite de 6 por domínio no HTTP/1.1.
5. Padrões avançados: estendendo o SSE para cenários complexos
5.1. SSE com autenticação via token na URL
const token = localStorage.getItem('auth_token');
const eventSource = new EventSource(`https://api.exemplo.com/events?token=${token}`);
5.2. Compressão e chunking
Para otimizar o throughput, agrupe múltiplos eventos em um único chunk:
// Servidor agrupa eventos
function sendBatch(res, events) {
const batch = events.map(e =>
`event: ${e.type}\ndata: ${JSON.stringify(e.payload)}\n`
).join('');
res.write(batch + '\n');
}
5.3. Fallback para ambientes sem suporte
function createSSEConnection(url) {
if (typeof EventSource !== 'undefined') {
return new EventSource(url);
}
// Fallback para Long Polling
return createLongPollingFallback(url);
}
6. Deploy, monitoramento e escalabilidade
6.1. Proxy reverso (Nginx) e configuração de buffer
location /events {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
}
6.2. Escalando conexões com Redis Pub/Sub
const redis = require('redis');
const subscriber = redis.createClient();
subscriber.subscribe('events');
subscriber.on('message', (channel, message) => {
broadcast('update', JSON.parse(message));
});
6.3. Métricas e health check
Implemente heartbeats para monitorar conexões ativas:
setInterval(() => {
console.log(`Conexões ativas: ${clients.size}`);
clients.forEach((res, id) => {
res.write(`event: heartbeat\ndata: {"alive": true}\n\n`);
});
}, 30000);
7. Comparação final e guia de decisão
7.1. Tabela comparativa
| Característica | SSE | WebSocket | gRPC Stream | GraphQL Subscriptions |
|---|---|---|---|---|
| Complexidade | Baixa | Média | Alta | Média |
| Suporte nativo | Sim (navegador) | Sim | Biblioteca | Biblioteca |
| Bidirecional | Não | Sim | Sim | Sim |
| Reconexão automática | Nativa | Manual | Manual | Manual |
| Headers customizados | Não | Sim | Sim | Sim |
7.2. Checklist para escolha
- Use SSE quando: Precisa de notificações unidirecionais, simplicidade de implementação, reconexão automática nativa.
- Use WebSocket quando: Precisa de comunicação bidirecional, jogos em tempo real, aplicações colaborativas.
- Use gRPC Stream quando: Precisa de alta performance, streaming bidirecional em microsserviços.
- Use GraphQL Subscriptions quando: Já utiliza GraphQL e precisa de atualizações em tempo real.
7.3. Exemplo híbrido: SSE para notificações + WebSocket para chat
// SSE para notificações do sistema
const notificationSource = new EventSource('/notifications');
notificationSource.addEventListener('alert', (event) => {
showNotification(event.data);
});
// WebSocket para chat em tempo real
const ws = new WebSocket('wss://chat.exemplo.com');
ws.onmessage = (event) => {
displayMessage(JSON.parse(event.data));
};
Referências
- MDN Web Docs: Server-Sent Events — Documentação oficial da Mozilla sobre a API EventSource e o formato text/event-stream
- HTML Living Standard: Server-Sent Events — Especificação oficial do W3C/WHATWG para Server-Sent Events
- Node.js SSE Example - DigitalOcean Community — Tutorial prático de implementação SSE com Node.js e Express
- FastAPI SSE Documentation — Documentação oficial do FastAPI sobre streaming de eventos e Server-Sent Events
- Nginx SSE Configuration Guide — Guia oficial da Nginx para configuração de proxy reverso com Server-Sent Events
- WebSocket vs SSE: A Complete Comparison — Artigo técnico comparando WebSocket e SSE da Ably, com casos de uso e benchmarks
- SSE Polyfill for Older Browsers — Polyfill oficial para suporte a EventSource em navegadores que não implementam nativamente SSE