Chat em tempo real e dashboards live com WebSockets

Chat em tempo real e dashboards live com WebSockets
Introdução
Nos últimos anos, a expectativa dos usuários por interatividade instantânea cresceu exponencialmente. Seja em um aplicativo de mensagens corporativas, em um sistema de alertas de estoque ou em painéis de monitoramento que exibem métricas em tempo real, a latência percebida pode ser o diferencial entre a adoção ou o abandono da solução.
Para atender a esse requisito, a tecnologia de WebSockets se consolidou como a escolha mais robusta quando a necessidade é comunicação full‑duplex (bidirecional) entre cliente e servidor. Diferente das requisições HTTP tradicionais, que seguem o modelo request/response, um canal WebSocket permanece aberto, permitindo que ambas as partes enviem mensagens a qualquer momento, sem overhead de novas conexões.
Neste artigo, vamos:
Dica: Embora o foco seja o uso de WebSockets, as ideias aqui podem ser adaptadas a outras bibliotecas como Socket.IO ou protocolos como STOMP sem grandes mudanças na lógica de negócio.
1. Conceitos fundamentais dos WebSockets
1.1 Handshake e upgrade da conexão
A negociação de um WebSocket inicia-se com um handshake HTTP padrão. O cliente envia um GET com o cabeçalho Upgrade: websocket e o servidor responde com 101 Switching Protocols se aceitar a conexão.
GET /ws/chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Se tudo ocorrer bem, o canal passa a operar em modo binário ou texto, com mensagens delimitadas por frames. Essa troca ocorre uma única vez, reduzindo o custo de estabelecimento comparado a múltiplas requisições HTTP.
1.2 Modelo de mensagem
- Texto: ideal para JSON, XML ou simples strings.
- Binário: usado quando se envia arquivos, imagens ou blobs compactados.
1.3 Comparativo rápido
| Técnica | Latência média | Complexidade | Compatibilidade |
|---|---|---|---|
| WebSocket | < 10 ms | Média | Navegadores modernos |
| Server‑Sent Events (SSE) | 30‑50 ms | Baixa | Navegadores modernos (unidirecional) |
| Long‑polling | 200‑500 ms | Alta | Todos (fallback) |
WebSockets são a única solução bidirecional com latência mínima, tornando‑os ideais para casos onde o servidor precisa “empurrar” informações ao cliente em tempo real.
2. Arquitetura recomendada para chat, notificações e dashboards
2.1 Componentes principais
2.2 Fluxo de mensagens
[Cliente] -- (WebSocket) --> [Servidor A] -- (Redis Pub/Sub) --> [Servidor B] -- (WebSocket) --> [Cliente]
- O cliente abre a conexão e envia seu token.
- O servidor valida o token e associa a conexão a um channel interno (ex.:
user:1234). - Quando o cliente envia uma mensagem, o servidor publica no broker (ex.:
chat:room42). - Todos os servidores que têm assinantes naquele canal recebem a mensagem e a repassam via WebSocket.
2.3 Por que usar um broker?
- Escalabilidade horizontal: novas réplicas do servidor podem ser adicionadas sem perder a consistência das mensagens.
- Persistência opcional: alguns brokers permitem armazenar mensagens até que o consumidor esteja online (útil para notificações offline).
- Desacoplamento: a lógica de entrega fica separada da lógica de negócio, facilitando manutenção.
3. Implementando um chat simples com FastAPI + WebSockets
Pré‑requisitos: Python 3.9+,
fastapi,uvicorn,python‑dotenv. Opcionalmente,redispara broker.
3.1 Estrutura de pastas
chat-app/
├─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ router.py
│ └─ utils.py
├─ .env
└─ requirements.txt
3.2 Código do servidor
requirements.txt
fastapi
uvicorn[standard]
python-dotenv
redis
app/main.py
``python
import os
import json
import uuid
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
import redis.asyncio as aioredis
load_dotenv() REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
app = FastAPI(title="Chat em tempo real")
Permitir chamadas do front-end (ajuste conforme seu domínio)
app.add_middleware( CORSMiddleware, allow_origins=[""], allow_credentials=True, allow_methods=[""], allow_headers=["*"], )
redis_client = aioredis.from_url(REDIS_URL, decode_responses=True)
class ConnectionManager: """Gerencia conexões WebSocket por sala.""" def __init__(self): self.active_connections: dict[str, set[WebSocket]] = {}
async def connect(self, websocket: WebSocket, room: str): await websocket.accept() self.active_connections.setdefault(room, set()).add(websocket)
def disconnect(self, websocket: WebSocket, room: str): self.active_connections[room].remove(websocket) if not self.active_connections[room]: del self.active_connections[room]
async def broadcast(self, room: str, message: dict): if room not in self.active_connections: return data = json.dumps(message) for connection in self.active_connections[room]: await connection.send_text(data)
manager = ConnectionManager()
@app.websocket("/ws/chat/{room_id}") async def chat_endpoint(websocket: WebSocket, room_id: str): """ Endpoint WebSocket para salas de chat. O cliente deve enviar um JSON com a chave username` na primeira mensagem. """ await manager.connect(websocket, room_id)
try: # Primeiro payload: identificação do usuário init_msg = await websocket.receive_text() init_data = json.loads(init_msg) username = init_data.get("username", f"user_{uuid.uuid4().hex[:6]}")
# Notifica a entrada na sala await manager.broadcast( room_id, {"type": "join", "user": username, "msg": f"{username} entrou na sala."} )
# Loop principal de recebimento while True: raw = await websocket.receive_text() payload = json.loads(raw) await manager.broadcast( room_id, {"type": "msg", "user": username, "msg": payload.get("msg", "")} ) except WebSocketDisconnect: manager.disconnect(websocket, room_id) await manager.broadcast( room_id, {"type


