Imagine acompanhar a importação de um catálogo com milhares de produtos sem precisar apertar F5 a cada dez segundos. O navegador mostra o percentual avançando, a etapa atual e o total processado, tudo em tempo real, enquanto o servidor continua trabalhando. É exatamente para cenários assim que existem os Server-Sent Events (SSE): um padrão da web para enviar atualizações contínuas do servidor para o cliente por uma única conexão HTTP.
Neste guia técnico vamos além da definição: você vai entender o formato do stream, escrever um consumidor com EventSource, montar o endpoint no servidor, lidar com reconexão, heartbeat, proxies e escolher com segurança entre SSE, WebSocket e polling.
🚀 Precisa de uma solução personalizada para o seu projeto?
Fale com a equipe da SOLOWEB e tire sua ideia do papel com infraestrutura e desenvolvimento sob medida.
O que são Server-Sent Events
SSE é um mecanismo de streaming unidirecional sobre HTTP. O cliente abre um pedido GET comum e o servidor responde com o tipo text/event-stream, mantendo a resposta aberta. A partir daí, cada novidade é escrita como um bloco de texto terminado por uma linha em branco. O navegador interpreta esses blocos e dispara eventos JavaScript automaticamente.
A grande vantagem está na simplicidade: não existe handshake especial, upgrade de protocolo nem biblioteca obrigatória no front-end. Um EventSource resolve a conexão, o parser do formato e a reconexão. Do lado do servidor, basta escrever texto no formato correto e garantir que nada fique preso em buffers intermediários.
Como o fluxo funciona na prática
- ✅ O cliente cria um
EventSourceapontando para um endpoint. - ✅ O servidor responde com
200 OKeContent-Type: text/event-stream. - ✅ Cada evento é enviado como linhas de campo (
event,data,id,retry) e finalizado com\n\n. - ✅ O navegador dispara listeners nomeados ou o evento genérico
message. - ✅ Se a conexão cair, o próprio navegador tenta reconectar e envia o header
Last-Event-ID.
O canal é de mão única: o servidor fala, o cliente escuta. Se o navegador precisar enviar comandos, isso é feito por requisições HTTP separadas. Essa limitação é, ao mesmo tempo, a maior virtude do SSE, porque elimina toda a complexidade de gerenciar um protocolo bidirecional quando o objetivo é apenas receber atualizações.
O formato do stream: texto simples e legível
O corpo da resposta é texto UTF-8. Cada mensagem é composta por uma ou mais linhas no padrão campo: valor, separadas por uma linha vazia. Os campos principais são:
| Campo | Função |
|---|---|
data |
Conteúdo do evento. Linhas data consecutivas são unidas com quebra de linha. |
event |
Nome do evento. Sem ele, o navegador dispara message. |
id |
Identificador usado na retomada após queda de conexão. |
retry |
Intervalo sugerido, em milissegundos, para a reconexão automática. |
Uma linha iniciada por dois-pontos é um comentário e não gera evento. Parece detalhe, mas é a base de uma técnica essencial: enviar : ping periodicamente para manter a conexão viva e evitar timeouts de proxies.
Na prática: acompanhando uma importação no navegador
Vamos usar um caso comum em painéis administrativos: acompanhar o andamento de uma importação de catálogo. O servidor envia três tipos de evento — progresso, concluido e falha — e o cliente atualiza a interface sem polling.
const barra = document.querySelector('#barra');
const fonte = new EventSource('/api/importacoes/42/eventos');
fonte.addEventListener('progresso', (evento) => {
const dados = JSON.parse(evento.data);
barra.style.width = dados.percentual + '%';
barra.textContent = dados.etapa;
});
fonte.addEventListener('concluido', (evento) => {
console.log('Importação finalizada:', JSON.parse(evento.data));
fonte.close();
});
fonte.onerror = () => {
console.warn('Conexão interrompida. O navegador tentará reconectar.');
};
Repare que progresso e concluido são eventos nomeados, escutados com addEventListener. Se o servidor omitir o campo event, o callback onmessage é acionado. Os dois estilos podem conviver no mesmo stream, o que ajuda a separar mensagens de sistema de mensagens de negócio.
O evento.data é sempre uma string. Se você trafega JSON, precisa fazer o parse no cliente. Também vale guardar evento.lastEventId quando a ordem e a retomada forem importantes.
Do outro lado: escrevendo eventos no servidor
O endpoint precisa de três coisas: o tipo de conteúdo correto, a garantia de que a resposta será enviada em pedaços e a limpeza dos recursos quando o cliente desconectar. Um exemplo completo em PHP, com headers, retomada por Last-Event-ID e heartbeat:
<?php
header('Content-Type: text/event-stream; charset=utf-8');
header('Cache-Control: no-cache, no-transform');
header('X-Accel-Buffering: no');
header('Connection: keep-alive');
while (ob_get_level() > 0) {
ob_end_flush();
}
function enviarEvento(string $tipo, array $dados, int $id): void
{
echo "id: {$id}\n";
echo "event: {$tipo}\n";
echo 'data: ' . json_encode($dados, JSON_UNESCAPED_UNICODE) . "\n\n";
flush();
}
$sequencia = (int) ($_SERVER['HTTP_LAST_EVENT_ID'] ?? 0);
$ultimoSinal = time();
while (!connection_aborted()) {
$sequencia++;
enviarEvento('progresso', [
'percentual' => min(100, $sequencia * 5),
'etapa' => 'Processando lote ' . $sequencia,
], $sequencia);
if ($sequencia >= 20) {
enviarEvento('concluido', ['total' => 1200], $sequencia);
break;
}
if (time() - $ultimoSinal >= 15) {
echo ": keep-alive\n\n";
flush();
$ultimoSinal = time();
}
sleep(1);
}
Cabeçalhos que evitam dor de cabeça
O Content-Type é obrigatório. O Cache-Control: no-cache impede cache de respostas dinâmicas, e o no-transform desencoraja intermediários de reescrever o corpo. Já o X-Accel-Buffering: no é um pedido explícito para o Nginx não segurar os pacotes. Em HTTP/1.1, Connection: keep-alive mantém o canal aberto.
Em PHP, também é essencial encerrar os buffers de saída e chamar flush() depois de cada evento. Sem isso, o servidor pode acumular mensagens e entregá-las todas de uma vez, o que anula a experiência de tempo real. O connection_aborted() dentro do laço encerra o processamento quando o navegador fecha a aba, liberando o processo PHP.
Reconexão, Last-Event-ID e heartbeat
A reconexão é um dos maiores atrativos do SSE porque é nativa. Quando a conexão cai, o navegador espera o intervalo definido em retry (ou alguns segundos por padrão) e tenta novamente. Se o último evento tinha um id, a nova requisição inclui o header Last-Event-ID.
Isso permite retomada elegante: o servidor consulta o histórico a partir daquele identificador e continua de onde parou. Para que funcione, os IDs precisam ser estáveis e ordenáveis — números sequenciais, timestamps com precisão ou identificadores de eventos persistidos. Não adianta enviar IDs aleatórios e esperar mágica.
Outro ponto crítico é o heartbeat. Muitos proxies, balanceadores e CDNs encerram conexões ociosas. Envie um comentário a cada 15 ou 20 segundos, como : keep-alive, para mostrar que o canal está vivo. O cliente ignora o comentário, mas a infraestrutura entende que há tráfego.
SSE, WebSocket ou polling?
Não existe vencedor absoluto. Existe encaixe. A tabela abaixo resume as diferenças que mais importam na hora de decidir:
| Abordagem | Direção | Melhor cenário |
|---|---|---|
| SSE | Servidor → cliente | Notificações, progresso, dashboards, logs e streaming de texto |
| WebSocket | Bidirecional | Chat, jogos, edição colaborativa e dados binários |
| Polling | Cliente → servidor | Compatibilidade legada ou atualizações pouco frequentes |
O SSE costuma vencer quando o fluxo é predominantemente do servidor para o navegador. Ele reaproveita autenticação, TLS, logs e métricas do HTTP, além de sobreviver melhor a ambientes corporativos restritivos. O WebSocket faz sentido quando as duas pontas conversam o tempo todo ou quando há frames binários. Já o polling continua útil como plano B, mas tende a desperdiçar requisições e a introduzir atraso.
Boas práticas para produção
- ✅ Heartbeat obrigatório: comentários periódicos mantêm vivos proxies e balanceadores.
- ✅ Sem buffering: desative compressão e buffering no caminho inteiro, inclusive CDN.
- ✅ Timeouts coerentes: o timeout de leitura do proxy precisa ser maior que o intervalo do heartbeat.
- ✅ Limpeza no disconnect: cancele timers, assinaturas e consultas quando o cliente sair.
- ✅ IDs confiáveis: use identificadores persistentes para permitir retomada correta.
- ✅ Backpressure: se o cliente for lento, descarte ou agrupe eventos em vez de acumular memória.
- ✅ Observabilidade: monitore conexões abertas, duração média e taxa de reconexão.
Autenticação e segurança
O EventSource envia cookies same-origin automaticamente. Em origens diferentes, é preciso habilitar withCredentials e configurar CORS com cuidado, sem usar * quando há credenciais. Evite colocar tokens de longa duração na URL, pois eles aparecem em logs e históricos; prefira cookies HttpOnly ou um ticket de curta duração. Valide a origem, limite conexões por usuário e nunca transmita dados sensíveis que o cliente não deveria ver.
Proxy, HTTP/2 e escala
Em HTTP/1.1, navegadores limitam conexões simultâneas por domínio, o que pode incomodar quando muitas abas mantêm streams abertos. HTTP/2 resolve isso com multiplexação e é a configuração recomendada. No Nginx, garanta proxy_buffering off e timeouts adequados para a rota de streaming. Em ambientes com várias instâncias, use um back-end de publicação (Redis, NATS ou fila equivalente) para que qualquer nó consiga entregar eventos ao cliente, independentemente de onde a conexão caiu.
Quando SSE é a escolha certa
Alguns cenários em que o SSE brilha:
- 🎯 Progresso de tarefas longas: importações, exportações, geração de relatórios e processamento em lote.
- 🎯 Notificações e alertas: avisos internos, status de pedidos e eventos de conta.
- 🎯 Dashboards operacionais: métricas e indicadores que mudam com frequência.
- 🎯 Streaming de texto: respostas de modelos de linguagem e logs em tempo real.
- 🎯 Feeds e monitoramento: acompanhamento de filas, deploys e sistemas.
Evite SSE quando o cliente precisa enviar mensagens com alta frequência, quando o tráfego é binário ou quando o volume de conexões abertas inviabiliza a arquitetura sem um plano claro de escala. Nesses casos, WebSocket ou uma combinação de HTTP com webhooks costuma ser mais adequada.
Perguntas frequentes
SSE funciona em todos os navegadores?
Os navegadores modernos oferecem suporte consolidado ao EventSource. Ainda assim, vale testar webviews embarcados e clientes antigos. Aplicações nativas podem consumir o stream com qualquer biblioteca HTTP, já que o formato é apenas texto.
SSE é a mesma coisa que streaming HTTP?
É um formato específico de streaming sobre HTTP. Existem outras abordagens, como NDJSON ou respostas chunked arbitrárias, mas elas não acionam automaticamente o parser e a reconexão do EventSource.
Posso enviar dados binários?
Não diretamente. O stream é texto UTF-8. Se precisar trafegar binário, codifique em base64 ou avalie WebSocket.
Como testar um endpoint SSE pelo terminal?
Use uma ferramenta que não bufferize a saída, como curl -N -H "Accept: text/event-stream" https://seu-dominio.com/eventos. Você verá os eventos chegando em tempo real.
Conclusão
Server-Sent Events entregam muito com pouco: um endpoint HTTP, um formato de texto simples e a API EventSource no navegador. Para atualizações que fluem do servidor para o cliente, é uma alternativa enxuta, resiliente e fácil de operar. O próximo passo é mapear seu caso de uso, definir a estratégia de heartbeat e retomada e revisar o caminho entre aplicação, proxy e CDN.
Se a sua equipe precisa de uma solução personalizada, seja para construir esse tipo de integração, seja para preparar a infraestrutura que vai sustentá-la, fale com a SOLOWEB em www.soloweb.com.br.
Esperamos ter ajudado com mais esta dica, e siga nos, nas redes sociais para mais tutoriais, e se precisar de nossa ajuda estamos a disposição: www.soloweb.com.br.
E lembrando que a SOLOWEB além de oferecer Hospedagem de Sites, Servidores Dedicados, Servidores VPS com o menor custo do Brasil, também desenvolve soluções de software e realiza gerenciamento e monitoramento de servidores para sua empresa, faça uma cotação sem custo, acesse: www.soloweb.com.br
