HTML5 Server-Sent Events (SSE): Guia | Web Academy
Tradicionalmente na web, a comunicação segue o modelo estrito de Requisição e Resposta: o navegador do cliente precisa pedir ativamente uma informação para que o servidor possa responder. No entanto, para feeds de notícias, placares esportivos, painéis financeiros e notificações, essa mecânica é ineficiente. A especificação do HTML5 introduziu os Server-Sent Events (SSE), permitindo que o servidor mantenha uma conexão HTTP persistente aberta e “empurre” dados instantaneamente para o navegador (Push Unidirecional) no momento exato em que um evento acontece.
📑 Índice do Conteúdo
1. Adeus ao Polling Primitivo: A Eficiência do SSE
Antes do SSE, os desenvolvedores eram obrigados a recorrer a técnicas ineficientes como o Short Polling (disparar requisições fetch() a cada 3 segundos via setInterval) ou o Long Polling (manter a requisição suspensa até haver resposta).
Esse modelo tradicional gerava problemas graves de infraestrutura:
- Desperdício colossal de largura de banda com o reenvio repetitivo de cabeçalhos HTTP e cookies.
- Sobrecarga inútil da CPU do servidor processando milhares de requisições que retornavam respostas vazias.
- Latência artificial na entrega de informações críticas ao usuário.
💡 A Elegância do Server-Sent Events
Com o SSE, o cliente faz uma única requisição HTTP inicial. O servidor responde com o tipo especial Content-Type: text/event-stream e mantém o canal aberto. A partir desse instante, sempre que surgir um dado novo, o servidor apenas transmite o fluxo de bytes pela conexão já estabelecida.
2. A Anatomia do Protocolo SSE (text/event-stream)
Diferente de protocolos binários complexos, o SSE é composto por texto puro formatado em UTF-8. Cada mensagem transmitida pelo servidor obedece a regras simples:
HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache
Connection: keep-alive
data: {"ativo": "BTC/USD", "preco": 68450.00}
event: alerta
data: Volatilidade alta detectada no mercado!
id: 1042
retry: 5000
Campos Padronizados pelo Protocolo:
data:O corpo da mensagem ou payload útil (pode ser texto simples ou uma string JSON).event:O nome de um evento customizado. Permite escutar tipos diferentes de ações no cliente.id:Identificador único do evento. Se a internet do usuário oscilar e cair, o navegador envia automaticamente o cabeçalhoLast-Event-IDna reconexão, garantindo que nenhuma mensagem seja perdida!retry:Tempo em milissegundos que o navegador deve aguardar antes de tentar reconectar automaticamente caso a conexão seja interrompida.\n\n(Dupla quebra de linha): Sinaliza ao navegador o término do pacote e dispara o processamento do evento.
3. A API EventSource no Navegador (Cliente)
No JavaScript do front-end, consumir uma transmissão SSE é extremamente simples através da classe nativa EventSource:
// 1. Abre a conexão com o endpoint de streaming do servidor
const streamNoticias = new EventSource('/api/cotacoes-tempo-real');
// 2. Conexão aberta com sucesso
streamNoticias.onopen = function() {
console.log('✅ Conexão SSE estabelecida com sucesso.');
};
// 3. Recebimento de mensagens genéricas (campos 'data:')
streamNoticias.onmessage = function(event) {
const dados = JSON.parse(event.data);
console.log('Nova cotação recebida:', dados);
};
// 4. Escuta de eventos tipados específicos (campo 'event: alerta')
streamNoticias.addEventListener('alerta', function(event) {
console.warn('⚠️ Alerta prioritário do sistema:', event.data);
});
// 5. Tratamento de falhas e reconexão automática
streamNoticias.onerror = function(erro) {
console.log('Conexão perdida. O navegador tentará reconectar sozinho...');
};
// 6. Fechamento manual da conexão quando o usuário sair da tela
// streamNoticias.close();
4. Código Prático Completo: Painel de Cotações em Tempo Real
Abaixo implementamos uma interface de monitoramento com controle de status visual de conexão e exibição de dados recebidos via streaming:
<div class="wzw-sse-card">
<div class="sse-header">
<h3>📈 Painel de Cotações em Tempo Real (SSE)</h3>
<span id="statusBadge" class="badge-conectando">Conectando...</span>
</div>
<div class="price-display">
<span class="currency-label">Bitcoin (BTC/BRL):</span>
<span id="valorPreco" class="price-value">R$ 0,00</span>
</div>
<div class="log-container">
<h4>Histórico de Atualizações Push:</h4>
<ul id="listaHistorico" class="sse-logs"></ul>
</div>
<button id="btnDesconectar" class="btn-sse">Interromper Transmissão</button>
</div>
.wzw-sse-card {
padding: 24px;
background-color: #0f172a;
border: 1px solid #1e293b;
border-radius: 8px;
color: #f8fafc;
max-width: 520px;
}
.sse-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.price-display {
padding: 16px;
background-color: #1e293b;
border-radius: 6px;
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.price-value {
font-size: 1.5rem;
font-weight: 700;
color: #10b981;
}
.badge-conectando { background: #f59e0b; color: #000; padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: bold; }
.badge-online { background: #10b981; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: bold; }
.badge-offline { background: #ef4444; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: bold; }
.sse-logs {
list-style: none;
padding: 0;
max-height: 140px;
overflow-y: auto;
font-size: 0.85rem;
color: #94a3b8;
}
.sse-logs li {
padding: 6px 0;
border-bottom: 1px solid #1e293b;
}
.btn-sse {
margin-top: 16px;
padding: 10px 16px;
background-color: #ef4444;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
// Script de Demonstração (Simulador de Servidor Local para Testes)
const statusBadge = document.getElementById('statusBadge');
const valorPreco = document.getElementById('valorPreco');
const listaHistorico = document.getElementById('listaHistorico');
const btnDesconectar = document.getElementById('btnDesconectar');
// Em um ambiente de produção real, aponte para sua URL de API (ex: '/api/stream-cotacoes')
// const stream = new EventSource('/api/stream-cotacoes');
// Para demonstração imediata no navegador, simulamos o comportamento de recebimento:
statusBadge.className = 'badge-online';
statusBadge.textContent = '● Transmitindo ao Vivo';
function adicionarLog(texto) {
const li = document.createElement('li');
li.textContent = `[${new Date().toLocaleTimeString()}] ${texto}`;
listaHistorico.prepend(li);
}
// Simulação de eventos empurrados pelo servidor a cada 3 segundos
let precoSimulado = 380250.00;
const simuladorInterval = setInterval(() => {
const variacao = (Math.random() - 0.48) * 1200;
precoSimulado += variacao;
valorPreco.textContent = precoSimulado.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
adicionarLog(`Cotação atualizada: ${precoSimulado.toFixed(2)} BRL`);
}, 3000);
btnDesconectar.addEventListener('click', () => {
clearInterval(simuladorInterval);
// stream.close();
statusBadge.className = 'badge-offline';
statusBadge.textContent = 'Desconectado';
adicionarLog('Conexão encerrada pelo usuário.');
btnDesconectar.disabled = true;
});
5. Comparativo: Server-Sent Events vs WebSockets
Ambas são tecnologias de tempo real do ecossistema HTML5 moderno, mas com arquiteturas projetadas para necessidades diferentes:
| Critério | Server-Sent Events (SSE) | WebSockets (WS) |
|---|---|---|
| Direção do Fluxo | Unidirecional (apenas Servidor → Cliente) | Totalmente Bidirecional (Cliente ↔ Servidor) |
| Protocolo | HTTP / HTTP/2 padrão (porta 80 ou 443) | Protocolo TCP próprio com handshake (ws:// ou wss://) |
| Reconexão Automática | Nativa pelo navegador (sem nenhuma linha de código) | Manual (requer bibliotecas ou tratamento via código) |
| Compatibilidade com Proxies e Firewalls | 100% transparente em qualquer proxy ou CDN existente | Pode ser bloqueado por firewalls e proxies corporativos restritivos |
| Cenário Perfeito | Feeds de notícias, dashboards, cotações, notificações | Jogos online multiplayer, salas de bate-papo, edição colaborativa |
💡 Dica Pro: O Limite de Conexões HTTP/1.1 vs HTTP/2
No protocolo legado HTTP/1.1, os navegadores impõem um limite rígido de apenas 6 conexões simultâneas abertas por domínio. Se você abrir 6 abas com SSE, a 7ª aba travará sem conseguir carregar imagens ou páginas!
A solução moderna: Certifique-se de que seu servidor web utilize HTTP/2 ou HTTP/3. Nesses protocolos, múltiplas conexões e streams são multiplexados dentro de uma única conexão TCP física, eliminando totalmente o limite de abas.
6. Ferramentas Relacionadas & Próximos Passos
Continue seu domínio de tecnologias interativas com as ferramentas do nosso ecossistema:
- Central do Desenvolvedor Web: Conversores JSON, geradores de payload e utilitários para depuração de rede.
- Hub Master de Imagens: Otimize ícones de alerta e status visual para dashboards.
- Lição 39: Web Workers & Multithreading: Combine o recebimento de fluxos SSE com processamento em segundo plano.
- Lição 41: WebSockets & Comunicação em Tempo Real: Conheça o protocolo bidirecional completo para jogos e chats.
7. Referências Técnicas & Especificações Oficiais
- WHATWG HTML Living Standard: Server-Sent Events Specification
- MDN Web Docs: Using Server-Sent Events
- W3C Server-Sent Events Recommendation