Use WebSocket quando o domínio exigir push real. Autentique no handshake; cada conexão é um lifecycle async com leitura, escrita e cancelamento via CancellationToken/drop do channel.
HTTP geral Axum: skill axum. Runtime/canais: tokio. Produção (shutdown, limites): rust-axum-production.
Stack e escopo
| Crate | Papel |
|---|---|
| axum | WebSocketUpgrade, Message, rota WS |
| tokio | Tasks, mpsc / broadcast |
| futures | StreamExt / SinkExt no socket |
| Hub | Registro de clients, fan-out |
Feature Axum: ws. Preserve a stack do projeto.
Upgrade e handler
use axum::{
extract::{
ws::{Message, WebSocket, WebSocketUpgrade},
State,
},
response::IntoResponse,
};
async fn ws_handler(
ws: WebSocketUpgrade,
State(hub): State<Hub>,
// extratores de auth (Query/Header/Extension) ANTES do upgrade
) -> impl IntoResponse {
ws.on_upgrade(move |socket| client_session(socket, hub))
}
async fn client_session(socket: WebSocket, hub: Hub) {
let (mut sender, mut receiver) = socket.split();
// registrar no hub; spawn write loop; ler até Close/erro
}
- Auth antes de
on_upgrade(cookie/header/query validados no handler HTTP). - Após o upgrade, não misture response HTTP normal no mesmo fluxo.
SameOrigin/ checagem deOriginquando o browser for cliente.
Modelo hub / client
Client --subscribe--> broadcast::Sender / Hub
Hub --fan-out--> Client mpsc (bounded)
Write task <-- mpsc / broadcast
Read task --> Hub / domain handlers
- Um writer por conexão (serialize writes numa task).
- Channel bounded para backpressure; política se cheio: drop mensagem ou desconectar.
tokio::select!entre leitura, eventos do hub e shutdown.
Ping, pong e mensagens
- Trate
Message::Ping/Pong/Close. - Envie pings periódicos se o domínio/proxy exigir keepalive.
- Texto JSON com
typediscriminado (serde); binário só com contrato claro. - Limite tamanho de frame (config do server / rejeição no app).
- Revalide authz por mensagem/canal quando o risco existir.
Authz e multi-tenant
- Identity amarrada à conexão no upgrade; não confie em
room_iddo client sem checar membership. - Rooms: mapa
room → subscribersno hub, ou canaisbroadcastpor room. - Não vaze eventos cross-tenant.
Shutdown gracioso
- Pare de aceitar upgrades (drain do server Axum/Hyper).
- Sinalize hub (
CancellationToken/ fechar sender). - Clients: enviar
Close, flush, drop tasks. JoinSet/ contagem até zerar (com timeout global).
Integração
- Compartilhe
State<Hub>noRouterAxum. - Não bloqueie o hub com I/O pesado —
spawnworkers /spawn_blockingsó se inevitável. - Multi-instância: hub in-process não basta; use Redis/NATS/etc. para fan-out horizontal.
Anti-padrões
- Upgrade sem auth nem checagem de Origin
UnboundedSendersem limite sob load- Várias tasks escrevendo no mesmo
SplitSinksem mutex/canal único - Ignorar
Message::Closee tasks órfãs - WS para CRUD simples que seria HTTP
unwrapem path de conexão
Critérios de conclusão
- Upgrade com auth (e Origin quando browser)
- Read/write loops claros; um writer por socket
- Backpressure ou desconexão sob sobrecarga
- Authz por room/canal quando aplicável
- Shutdown não deixa tasks órfãs
- Contrato de mensagem versionado e validado