Capítulo 17: Comunicação em Tempo Real Bidirecional com WebSockets
Especialização em Backend com Python & FastAPI • FastAPI & Python 3.12+ • Pydantic v2, SQLModel, Async/Await e Microsserviços
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
B --> C["Controller / Handler"]
C --> D["Service Layer (Regras de Negócio)"]
D --> E["Repository / ORM (Persistência)"]
E --> F["Banco de Dados / Cache"]
subgraph ARQ["Arquitetura do Capítulo"]
G["Conceito: Comunicação em Tempo Real Bidirecional com WebSockets"]
H["Segurança, Validação e Resiliência"]
I["Alta Performance e Escalabilidade"]
end
D --> ARQ
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
🏛️ 1. HTTP request-response vs. canal full-duplex
Todo endpoint HTTP tradicional segue o ciclo requisição→resposta: o cliente pergunta, o servidor responde, a conexão termina (ou fica ociosa em keep-alive). Para o servidor empurrar dados sem o cliente perguntar (cotações mudando, notificações, chat, dashboards ao vivo), a alternativa ingênua é polling repetido — desperdício de banda e latência perceptível. O WebSocket (ws:// ou wss:// sobre TLS) resolve isso fazendo um handshake HTTP inicial (Upgrade: websocket) e depois mantendo uma única conexão TCP aberta, bidirecional e full-duplex: qualquer lado pode enviar uma mensagem a qualquer momento, sem novo handshake.
No FastAPI, uma rota WebSocket usa o decorador @app.websocket(...) em vez de @app.get/@app.post, recebe um objeto WebSocket e segue um ciclo de vida explícito — aceitar, trocar mensagens em loop, tratar desconexão:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI()
@app.websocket("/ws")
async def websocket_echo(websocket: WebSocket) -> None:
await websocket.accept() # completa o handshake HTTP -> WS
try:
while True:
texto = await websocket.receive_text()
await websocket.send_text(f"Eco: {texto}")
except WebSocketDisconnect:
# o cliente fechou a aba ou caiu a conexão: sempre trate, ou o processo
# tentará usar um socket morto na próxima iteração do loop.
print("Cliente desconectado")
Um detalhe importante: Depends() funciona em rotas WebSocket para injetar dependências no momento da conexão (ex.: uma sessão de banco), mas o modelo de autorização é diferente do HTTP — não há “response” intermediária para devolver um 401 elegante; a prática correta é validar credenciais antes de accept() e, se inválidas, chamar await websocket.close(code=1008) (Policy Violation) sem nunca aceitar o handshake.
👥 2. ConnectionManager: broadcast para múltiplos clientes
Cada conexão WebSocket ativa é isolada por padrão. Para funcionalidades como chat ou notificações em grupo, o padrão é manter uma lista (ou dicionário) de conexões ativas em um objeto ConnectionManager, com métodos para registrar, remover e transmitir (broadcast):
from fastapi import WebSocket
class ConnectionManager:
def __init__(self) -> None:
self.active_connections: list[WebSocket] = []
async def connect(self, websocket: WebSocket) -> None:
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket) -> None:
self.active_connections.remove(websocket)
async def broadcast(self, mensagem: str) -> None:
for connection in self.active_connections:
await connection.send_text(mensagem)
manager = ConnectionManager()
@app.websocket("/ws/chat/{cliente_id}")
async def chat_endpoint(websocket: WebSocket, cliente_id: str) -> None:
await manager.connect(websocket)
try:
while True:
mensagem = await websocket.receive_text()
await manager.broadcast(f"{cliente_id}: {mensagem}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"{cliente_id} saiu do chat")
🌐 3. Escalando horizontalmente: Pub/Sub com Redis
O ConnectionManager acima funciona apenas dentro de um único processo. Em produção, geralmente há múltiplas réplicas do servidor FastAPI atrás de um load balancer — se o cliente A está conectado à réplica 1 e o cliente B à réplica 2, um broadcast local nunca chegaria a B. A solução é desacoplar o broadcast da memória do processo usando Redis Pub/Sub (ou Redis Streams): cada réplica publica mensagens em um canal Redis e também assina esse canal; ao receber uma mensagem publicada por qualquer réplica (inclusive por ela mesma), repassa para seus próprios clientes WebSocket conectados localmente. Isso torna o sistema de tempo real horizontalmente escalável sem estado compartilhado direto entre processos.
🔗 Recursos Pedagógicos do Capítulo 17
| Recurso Didático | Finalidade | Link de Acesso |
|---|---|---|
| 📊 Slides de Aula | Apresentação visual interativa com Dark Mode e suporte a teclado | Ver Slides |
| 🧠 Quiz Formativo | Teste interativo de fixação com feedback imediato por alternativa | Fazer Quiz |
| 💻 Exemplos de Código | Demonstrações funcionais com código executável | Ver Exemplos |
| 🧩 Exercícios em 4 Níveis | Lista progressiva de fixação com gabarito em bloco colapsável | Resolver Exercícios |
| ⬅️ Capítulo Anterior | 📚 Sumário de Tópicos | Próximo Capítulo ➡️ |