Como funcionam os WebSockets: Um guia completo sobre conexões em tempo real
Nos primórdios da web, o navegador era um simples visualizador de documentos. Você solicitava uma página, o servidor a renderizava e a conexão era fechada. Esse ciclo de requisição-resposta é o núcleo do HTTP (Hypertext Transfer Protocol).
No entanto, à medida que as aplicações web evoluíram para experiências ricas e interativas — como chat em tempo real, painéis financeiros ao vivo, edição colaborativa e jogos multiplayer —, o modelo HTTP tradicional começou a mostrar suas limitações.
Para obter atualizações ao vivo, os desenvolvedores inicialmente dependiam de soluções alternativas:
- Short Polling: O navegador envia repetidamente requisições HTTP ao servidor a cada poucos segundos para perguntar por novos dados. Isso cria uma enorme sobrecarga de cabeçalhos e desperdiça recursos do servidor.
- Long Polling (Comet): O navegador envia uma requisição e o servidor a mantém aberta até que novos dados estejam disponíveis. Assim que os dados são enviados, a conexão é fechada e o navegador abre imediatamente uma nova requisição. Isso é complexo de gerenciar e ainda incorre em uma sobrecarga significativa na configuração da conexão.
Os WebSockets resolveram essas limitações introduzindo um protocolo padronizado para comunicação persistente, bidirecional e full-duplex sobre uma única conexão TCP.
O que é um WebSocket?
Os WebSockets (definidos na RFC 6455) operam em conjunto com o HTTP. Enquanto o HTTP é um protocolo sem estado onde apenas o cliente pode iniciar requisições, uma conexão WebSocket permanece aberta indefinidamente, permitindo que tanto o cliente quanto o servidor enviem dados um ao outro a qualquer momento com latência mínima.
Aqui está a regra fundamental dos WebSockets:
Uma vez estabelecida a conexão, qualquer um dos lados pode enviar mensagens a qualquer momento, sem iniciar uma nova solicitação de conexão.
Passo a Passo: O Ciclo de Vida da Conexão
Uma conexão WebSocket passa por três fases distintas: o Handshake (aperto de mão), a Transferência de Dados e o Fechamento.
1. O HTTP Handshake (Upgrade do Protocolo)
Como os firewalls e roteadores estão configurados para permitir o tráfego web padrão nas portas 80 (HTTP) e 443 (HTTPS), os WebSockets começam sua jornada como uma requisição HTTP/1.1 padrão. Isso é chamado de Upgrade Handshake.
A Requisição do Cliente
O cliente envia uma requisição HTTP GET com cabeçalhos específicos solicitando a mudança de protocolo:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
Upgrade: websocketeConnection: Upgrade: Informam ao servidor que o cliente deseja mudar de protocolo.Sec-WebSocket-Key: Um valor aleatório de 16 bytes codificado em Base64. Ele é usado para provar que o servidor recebeu o aperto de mão e entende o protocolo WebSocket.Sec-WebSocket-Version: Especifica a versão do protocolo WebSocket (geralmente 13).Origin: Usado pelo servidor para decidir se permite a conexão (verificação de segurança contra sites não autorizados).
A Resposta do Servidor
Se o servidor suportar WebSockets, ele valida a requisição e responde com um código de status HTTP 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
- Como o Servidor Calcula
Sec-WebSocket-Accept:- O servidor pega o
Sec-WebSocket-Keydo cliente (dGhlIHNhbXBsZSBub25jZQ==). - Ele o concatena com uma string mágica padrão GUID:
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11". - Calcula o hash SHA-1 da string combinada.
- Codifica o hash resultante em Base64.
- Se o cliente verificar que este valor corresponde às suas expectativas, o handshake é bem-sucedido, a conexão HTTP muda para um socket TCP bruto e ambos os lados passam a usar o protocolo WebSocket.
- O servidor pega o
2. Enquadramento e Transferência de Dados
Ao contrário do HTTP, que envia cabeçalhos em texto simples seguidos por um corpo, os WebSockets transmitem dados em pacotes binários estruturados chamados frames (quadros).
Um frame WebSocket tem um cabeçalho muito leve (variando de 2 a 14 bytes) seguido pela carga útil (payload). Esse cabeçalho contém:
- Bit FIN (1 bit): Indica se este é o frame final de uma mensagem.
- Opcode (4 bits): Define o tipo de frame:
0x1: Frame de texto (codificado em UTF-8)0x2: Frame binário0x8: Solicitação de fechamento de conexão0x9: Ping0xA: Pong
- Bit Mask (1 bit): Especifica se os dados da carga útil estão mascarados.
- Payload Length: O tamanho dos dados.
- Masking Key (4 bytes): Requisito Crucial de Segurança: Todos os frames enviados do cliente para o servidor devem ser mascarados (ofuscados por XOR) usando uma chave aleatória de 4 bytes. Isso impede que caches proxy leiam o tráfego ou executem ataques de envenenamento de cache. Frames de servidor para cliente não devem ser mascarados.
Heartbeats (Ping/Pong)
Para evitar que roteadores e balanceadores de carga fechem conexões ociosas, qualquer um dos lados pode enviar um frame de Ping. O lado receptor deve responder imediatamente com um frame de Pong contendo a mesma carga útil.
3. Fechando a Conexão
Para fechar uma conexão de forma limpa:
- Um peer envia um frame de
Closecontendo um código de status (por exemplo,1000para fechamento normal,1006para fechamento anormal) e um motivo opcional em texto. - O outro peer responde com seu próprio frame de
Close. - O socket TCP subjacente é fechado.
Exemplo de Código: Implementação de WebSockets em Node.js
Para ver os WebSockets em ação, vamos escrever uma aplicação simples em Node.js. Criaremos um servidor WebSocket local que devolve qualquer mensagem que receber, junto com um script cliente para se conectar a ele.
O Servidor WebSocket (server.js)
const { WebSocketServer } = require('ws');
const http = require('http');
// 1. Criar um servidor HTTP padrão
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('HTTP Server running. Use WebSocket to connect.\n');
});
// 2. Acoplar um servidor WebSocket ao servidor HTTP
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, req) => {
const clientIp = req.socket.remoteAddress;
console.log(`[Servidor] Novo cliente conectado de ${clientIp}`);
// Enviar mensagem de boas-vindas ao cliente
ws.send(JSON.stringify({ type: 'welcome', message: 'Conectado ao servidor WebSocket da Ghaznix!' }));
// Ouvir mensagens vindas deste cliente
ws.on('message', (message) => {
console.log(`[Servidor] Recebido: ${message}`);
try {
const data = JSON.parse(message);
ws.send(JSON.stringify({
type: 'echo',
message: `Eco do servidor: ${data.text.toUpperCase()}`,
timestamp: new Date().toISOString()
}));
} catch (e) {
ws.send(JSON.stringify({ type: 'error', message: 'Formato JSON inválido' }));
}
});
// Tratar desconexão do cliente
ws.on('close', (code, reason) => {
console.log(`[Servidor] Cliente desconectado (Código: ${code}, Razão: ${reason.toString() || 'Nenhuma'})`);
});
ws.on('error', (error) => {
console.error(`[Servidor] Erro de socket: ${error.message}`);
});
});
server.listen(8080, () => {
console.log('Servidor WebSocket escutando em ws://localhost:8080');
});
O Cliente do Navegador (JavaScript do lado do cliente)
Você pode executar este cliente diretamente no console do seu navegador:
// 1. Estabelecer conexão com o servidor
const socket = new WebSocket('ws://localhost:8080');
// 2. Manipulador de conexão aberta
socket.addEventListener('open', (event) => {
console.log('[Cliente] Conectado ao servidor.');
const payload = JSON.stringify({ text: 'olá, servidor!' });
socket.send(payload);
console.log(`[Cliente] Enviado: ${payload}`);
});
// 3. Ouvir mensagens do servidor
socket.addEventListener('message', (event) => {
const response = JSON.parse(event.data);
console.log('[Cliente] Mensagem recebida do servidor:', response);
});
// 4. Ouvir o fechamento da conexão
socket.addEventListener('close', (event) => {
console.log(`[Cliente] Conexão fechada (Código: ${event.code})`);
});
// 5. Ouvir erros
socket.addEventListener('error', (error) => {
console.error('[Cliente] Erro no WebSocket:', error);
});
HTTP vs. WebSockets: Uma Comparação Detalhada
| Característica | HTTP/1.1 | WebSockets |
|---|---|---|
| Comunicação | Unidirecional (iniciada pelo cliente) | Bidirecional (cliente ou servidor) |
| Modelo de Conexão | Requisição-Resposta (curta duração) | Persistente (longa duração) |
| Sobrecarga | Alta (cabeçalhos enviados a cada requisição) | Muito baixa (sobrecarga mínima de enquadramento) |
| Estado | Sem estado | Com estado (contexto da conexão é mantido) |
| Protocolo | http:// ou https:// |
ws:// ou wss:// |
| Ideal para | Obtenção de documentos, APIs REST | Chats em tempo real, painéis, feeds ao vivo |
Considerações de Segurança para WebSockets
Como os WebSockets ignoram o roteamento HTTP padrão após o handshake, eles introduzem vetores de segurança exclusivos:
- Use WebSocket Secure (
wss://): Sempre execute WebSockets sobre TLS/SSL (porta 443). O WSS criptografa a carga útil do frame, evitando interceptações e espionagem de intermediários. - Validação de Origem: Os WebSockets não são restringidos pela Same-Origin Policy (SOP). Sempre valide o cabeçalho
Originno servidor durante o handshake para impedir acesso não autorizado. - Autenticação no Handshake: Autentique os usuários antes que a conexão seja estabelecida. Isso é comumente feito transmitindo um token (como um JWT) nos parâmetros da consulta ou verificando cookies de sessão.
- Sanitização de Entradas: Trate cada mensagem recebida via WebSockets como entrada não confiável. Valide e sanitize as cargas úteis para evitar Cross-Site Scripting (XSS).
Resumo
Os WebSockets transformaram as aplicações web em tempo real ao eliminar a sobrecarga do polling HTTP tradicional. Ao manter uma única conexão TCP persistente, eles permitem a troca instantânea de mensagens bidirecionais, alimentando os modernos painéis ao vivo, jogos multiplayer e aplicativos de chat de hoje. Compreender o upgrade do HTTP, a arquitetura dos frames e as principais práticas de segurança garante que você crie serviços em tempo real rápidos e seguros.
Explore mais tutoriais e guias para desenvolvedores no Blog da Ghaznix →