Como usar server-sent events para streaming de dados ao cliente
1. Fundamentos de Server-Sent Events (SSE)
Server-Sent Events (SSE) representam uma tecnologia de streaming unidirecional que permite ao servidor enviar dados automaticamente para o cliente através de uma conexão HTTP persistente. Diferentemente dos WebSockets, que oferecem comunicação bidirecional completa, o SSE é otimizado para cenários onde apenas o servidor precisa enviar atualizações contínuas ao cliente.
A principal diferença entre SSE, WebSockets e polling tradicional está no modelo de comunicação:
- Polling tradicional: O cliente faz requisições periódicas ao servidor, mesmo quando não há dados novos, gerando overhead desnecessário.
- WebSockets: Conexão bidirecional full-duplex, ideal para aplicações como chats e jogos multiplayer.
- SSE: Conexão unidirecional do servidor para o cliente, utilizando protocolo HTTP padrão, com reconexão automática nativa.
O mecanismo de funcionamento do SSE baseia-se em uma conexão HTTP persistente onde o servidor mantém o response aberto e envia dados no formato text/event-stream. O navegador oferece suporte nativo através da API EventSource, que gerencia automaticamente a reconexão em caso de falha.
2. Configuração do Servidor para SSE
Para implementar SSE no servidor, três headers HTTP são obrigatórios:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
A estrutura de mensagens SSE segue um formato específico:
event: atualizacao
data: {"mensagem": "Nova atualização disponível"}
id: 12345
retry: 3000
Cada campo tem função específica:
- event: Nome do evento personalizado (opcional)
- data: Conteúdo da mensagem (obrigatório)
- id: Identificador único para controle de reconexão (opcional)
- retry: Tempo em milissegundos para tentar reconexão (opcional)
O gerenciamento de conexões abertas exige implementação de heartbeat para evitar timeouts de proxy e balanceadores de carga. Um heartbeat típico envia um comentário a cada 30 segundos:
:heartbeat
3. Implementação de um Endpoint SSE Básico
Exemplo de implementação em Node.js com Express:
const express = require('express');
const app = express();
app.get('/stream', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const intervalId = setInterval(() => {
const data = {
timestamp: new Date().toISOString(),
valor: Math.random() * 100
};
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 2000);
const heartbeatId = setInterval(() => {
res.write(':heartbeat\n\n');
}, 30000);
req.on('close', () => {
clearInterval(intervalId);
clearInterval(heartbeatId);
res.end();
});
});
app.listen(3000, () => {
console.log('SSE server running on port 3000');
});
O tratamento de desconexão é crucial para evitar vazamento de memória. O evento close na requisição permite limpar os intervalos e encerrar a resposta adequadamente.
4. Cliente: Consumindo SSE no Frontend
O consumo de SSE no frontend utiliza a API EventSource:
const eventSource = new EventSource('/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Dados recebidos:', data);
};
eventSource.onopen = () => {
console.log('Conexão SSE estabelecida');
};
eventSource.onerror = (error) => {
console.error('Erro na conexão SSE:', error);
};
Para manipular eventos nomeados, utilize addEventListener:
eventSource.addEventListener('atualizacao', (event) => {
const data = JSON.parse(event.data);
atualizarDashboard(data);
});
A reconexão automática é gerenciada pelo navegador, mas você pode controlar o ponto de retomada usando last-event-id. O servidor deve verificar este header e retransmitir eventos perdidos:
app.get('/stream', (req, res) => {
const lastId = req.headers['last-event-id'];
if (lastId) {
// Retransmitir eventos a partir do ID especificado
}
// Continuar com o stream normal
});
5. Padrões Avançados de Streaming com SSE
Para enviar dados estruturados complexos, utilize JSON:
event: usuario_atualizado
data: {"id": 42, "nome": "Maria", "ultimo_acesso": "2024-01-15T10:30:00Z"}
id: 789
O uso de IDs de evento permite garantir entrega ordenada e retomada de conexão. O servidor deve incrementar o ID a cada evento e armazenar os últimos eventos em buffer:
let eventId = 0;
const eventBuffer = [];
app.get('/stream', (req, res) => {
// Verificar last-event-id e retransmitir eventos perdidos
const lastId = parseInt(req.headers['last-event-id']) || 0;
const missedEvents = eventBuffer.filter(e => e.id > lastId);
missedEvents.forEach(event => {
res.write(`id: ${event.id}\ndata: ${event.data}\n\n`);
});
// Continuar com novos eventos
});
Implemente filtros por tipo de evento no cliente para processamento seletivo:
const filtros = {
critico: (event) => mostrarAlerta(event.data),
informativo: (event) => atualizarLog(event.data),
metrica: (event) => atualizarGrafico(event.data)
};
eventSource.addEventListener('mensagem', (event) => {
const data = JSON.parse(event.data);
if (filtros[data.tipo]) {
filtros[data.tipo](event);
}
});
6. Segurança e Performance em SSE
A autenticação em SSE apresenta limitações porque o EventSource não suporta headers personalizados. As alternativas incluem:
- Token na URL:
new EventSource('/stream?token=seu_token') - Cookies: O servidor pode validar cookies de sessão
- Autenticação inicial: Estabelecer sessão via POST antes de abrir o SSE
Para limitar conexões simultâneas, implemente um contador no servidor:
let activeConnections = 0;
const MAX_CONNECTIONS = 100;
app.get('/stream', (req, res) => {
if (activeConnections >= MAX_CONNECTIONS) {
res.status(503).end('Servidor sobrecarregado');
return;
}
activeConnections++;
req.on('close', () => {
activeConnections--;
});
// Configurar stream
});
Estratégias de cache e compressão melhoram a performance:
app.get('/stream', (req, res) => {
res.setHeader('Content-Encoding', 'gzip');
// Configurar stream com compressão
});
7. Casos de Uso Reais e Comparação com Alternativas
SSE é ideal para cenários específicos:
- Notificações em tempo real: Alertas de sistema, notificações push
- Feeds de log: Streaming de logs de aplicação para dashboards
- Atualizações de cotação: Preços de ações, criptomoedas
- Métricas de servidor: CPU, memória, tráfego de rede
Comparação com alternativas:
| Característica | SSE | WebSockets | Long Polling |
|---|---|---|---|
| Direção | Servidor → Cliente | Bidirecional | Cliente → Servidor |
| Reconexão automática | Nativa | Manual | Manual |
| Suporte HTTP/2 | Sim | Limitado | Sim |
| Complexidade | Baixa | Alta | Média |
| Latência | Baixa | Muito baixa | Alta |
Exemplo completo de streaming de métricas para dashboard:
// Servidor
app.get('/metricas', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
});
const monitor = setInterval(() => {
const metricas = {
cpu: os.loadavg()[0],
memoria: process.memoryUsage().heapUsed / 1024 / 1024,
conexoes: activeConnections
};
res.write(`event: metrica\ndata: ${JSON.stringify(metricas)}\n\n`);
}, 1000);
req.on('close', () => clearInterval(monitor));
});
// Cliente
const metricasSSE = new EventSource('/metricas');
metricasSSE.addEventListener('metrica', (event) => {
const dados = JSON.parse(event.data);
atualizarDashboard(dados);
});
Referências
- MDN Web Docs: Server-Sent Events — Documentação completa da API EventSource e especificação SSE
- HTML Living Standard: Server-Sent Events — Especificação oficial do W3C para SSE
- Node.js Documentation: EventSource — Documentação oficial da implementação EventSource no Node.js
- Express.js Guide: Server-Sent Events — Guia de boas práticas para implementação SSE com Express
- Web.dev: Stream updates with Server-Sent Events — Tutorial prático do Google sobre implementação de SSE
- NGINX: Server-Sent Events (SSE) Configuration — Guia de configuração de SSE com NGINX como proxy reverso
- Stack Overflow: SSE vs WebSockets comparison — Discussão técnica detalhada comparando SSE e WebSockets