DesenvolvimentoDestaque PrincipalDestaque SecundárioJavaScriptTutoriais

Server-Sent Events: streaming em tempo real com EventSource

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.

Precisando de uma hospedagem de site ou de um servidor dedicado personalizado, seja para ambiente de teste, desenvolvimento ou de produção? E com um suporte de especialistas, que ti, ajudam a resolver os problemas o mais rápido possível? A SoloWeb tem o prazer em ti, ajudar com isso. Entre em contato conosco e faça uma cotação agora mesmo, acesse: www.soloweb.com.br.

FALE COM A SOLOWEB

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 EventSource apontando para um endpoint.
  • ✅ O servidor responde com 200 OK e Content-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

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