Quando WebSocket é demais, ou por que SSE está de volta à moda
\n\nImagine: você precisa enviar dados do servidor para o cliente em tempo real. Cotações de moedas, notificações de novas mensagens, logs do servidor, progresso do processamento de arquivos. A primeira coisa que vem à mente é o WebSocket. Mas e se você não precisar de um canal bidirecional? E se o cliente apenas escuta e o servidor apenas fala?
\n\nÉ aqui que entram em cena os Server-Sent Events (SSE) — um protocolo que permite ao servidor enviar dados ao cliente através de uma conexão HTTP comum. Sem handshakes extras, sem protocolos complexos, sem dores de cabeça com proxies e balanceadores de carga. E sim, em Rust isso funciona de forma simplesmente divina.
\n\nNeste artigo, vamos explorar como configurar SSE em Rust do zero, usando frameworks populares. Você verá exemplos funcionais, conhecerá as armadilhas e entenderá quando SSE é a sua melhor escolha.
\n\nO que são Server-Sent Events e por que Rust?
\n\nSSE é um padrão W3C (EventSource API) que permite ao servidor enviar dados em streaming para o cliente via HTTP. O cliente se conecta a um endpoint e o servidor começa a enviar mensagens no formato text/event-stream. Tudo funciona sobre HTTP/1.1 ou HTTP/2 comum, não requer proxies especiais e é suportado por todos os navegadores modernos.
Por que Rust? Porque Rust nos oferece:
\n- \n
- Assincronicidade sem surpresas — Tokio e async-std permitem manter milhares de conexões SSE em uma única thread \n
- Segurança de memória — sem vazamentos, sem race conditions em streaming multithread \n
- Consumo mínimo de recursos — uma conexão SSE consome alguns kilobytes \n
- Ecossistema — Actix Web, Rocket, Axum — todos suportam SSE nativamente ou com extensões mínimas \n
Como funciona o SSE: analisando o protocolo
\n\nO cliente cria um EventSource no navegador, especificando uma URL. O servidor responde com o cabeçalho Content-Type: text/event-stream e começa a enviar linhas no formato:
data: Olá, mundo!\\n\\n\n\nCada mensagem é um bloco onde data: contém a carga útil. As mensagens são separadas por dois caracteres de nova linha. É possível transmitir JSON, números, strings — o que você quiser. Além disso, existem os campos id, event e retry.
Aqui está um exemplo mínimo de mensagem SSE:
\n\nid: 1\nevent: update\ndata: {"status": "processing", "progress": 42}\n\n\n\nO cliente em JavaScript recebe isso como um evento update com o campo data contendo JSON.
Escrevendo um servidor SSE em Rust com Actix Web
\n\nVamos começar com o framework mais popular — Actix Web. Instale as dependências:
\n\n[dependencies]\nactix-web = "4"\nactix-rt = "2"\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"\n\nAgora vamos escrever um endpoint simples que enviará a hora atual a cada 2 segundos:
\n\nuse actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, middleware};\nuse actix_web::rt::time;\nuse futures::stream::StreamExt;\nuse std::time::Duration;\n\n#[get("/events")]\nasync fn stream_events(req: HttpRequest) -> HttpResponse {\n let mut rx = tokio::sync::broadcast::channel::<String>(100).1;\n\n let stream = async_stream::stream! {\n loop {\n let message = format!("data: {}\\n\\n", chrono::Local::now());\n yield Ok::<_, actix_web::Error>(\n web::Bytes::from(message)\n );\n time::sleep(Duration::from_secs(2)).await;\n }\n };\n\n HttpResponse::Ok()\n .insert_header(("Content-Type", "text/event-stream"))\n .insert_header(("Cache-Control", "no-cache"))\n .insert_header(("Connection"