WebSockets na prática: comunicação em tempo real no navegador
Como os WebSockets abrem um canal permanente entre navegador e servidor para tempo real, quando usá-los e o que muda em relação ao HTTP tradicional.
Neste artigo
Um chat que mostra mensagens no instante em que chegam, um placar que atualiza sozinho, um documento colaborativo onde você vê o cursor do colega se mexer, uma cotação que pisca a cada mudança — todas essas experiências têm algo em comum que o modelo tradicional da web não entrega bem. O HTTP foi desenhado para um padrão de conversa em que o cliente pergunta e o servidor responde, sempre nessa ordem. O servidor nunca fala primeiro; ele só reage a pedidos. Isso funciona perfeitamente para carregar páginas e buscar dados, mas trava quando o que você precisa é o servidor avisar o cliente de algo no momento em que acontece, sem que o cliente tenha perguntado. É justamente esse cenário que os WebSockets resolvem.
Antes de os WebSockets existirem, os desenvolvedores contornavam essa limitação com truques. O mais comum era o cliente ficar perguntando repetidamente ao servidor "tem novidade? tem novidade?", em intervalos curtos, na esperança de pegar as atualizações logo depois de acontecerem. Essa técnica funciona, mas é ineficiente: gera muitas requisições que na maioria das vezes voltam sem nada, desperdiça recursos dos dois lados e ainda introduz um atraso entre o evento acontecer e o cliente descobrir. Os WebSockets oferecem uma solução estruturalmente diferente e muito mais adequada.
Neste artigo vamos entender o que muda com os WebSockets: como um canal permanente é aberto a partir de uma conexão HTTP comum, por que a comunicação passa a fluir nas duas direções livremente, quando essa ferramenta é a escolha certa e quais são as responsabilidades que ela transfere para o desenvolvedor. É uma mudança de modelo que resolve uma classe de problemas, mas que também traz obrigações novas.
O limite do modelo pergunta-resposta#
Para entender o valor dos WebSockets, é preciso primeiro sentir onde o HTTP aperta. No modelo tradicional, cada interação é uma requisição iniciada pelo cliente que recebe uma resposta e termina. A conexão serve àquela troca e depois a conversa acaba. Se, um segundo depois, o servidor tem uma informação nova para o cliente, ele não tem como entregá-la: não há canal aberto, e o servidor não pode iniciar contato. Ele precisa esperar o cliente perguntar de novo.
Essa assimetria — só o cliente inicia — é ótima para o caso geral da web, mas é exatamente o oposto do que aplicações em tempo real precisam. Nelas, os eventos importantes muitas vezes se originam no servidor ou em outros usuários: uma mensagem que alguém enviou, um preço que mudou, um jogador que se moveu. O cliente que vai receber essa informação não tem como saber que ela existe para ir buscá-la. Alguém precisa avisá-lo, e esse alguém é o servidor, que no modelo HTTP puro não tem voz própria.
Por que ficar perguntando não basta#
A saída de perguntar repetidamente sofre de um dilema insolúvel. Perguntar com frequência alta reduz o atraso, mas multiplica as requisições e a carga, a maioria delas retornando vazia. Perguntar com frequência baixa alivia a carga, mas aumenta o atraso entre o evento e a descoberta. Não existe intervalo que resolva os dois lados; é sempre uma troca ruim. Além disso, cada requisição carrega toda a bagagem do HTTP — cabeçalhos, estabelecimento de conexão — o que, repetido muitas vezes por segundo por muitos usuários, vira um desperdício considerável. Os WebSockets eliminam esse dilema ao substituir muitas perguntas por um canal que fica aberto.
O aperto de mão: de HTTP a WebSocket#
Os WebSockets começam, curiosamente, como uma requisição HTTP normal. O cliente faz uma requisição especial pedindo para "promover" aquela conexão de HTTP para WebSocket — um pedido de mudança de protocolo. Se o servidor concorda, ele responde com um status que confirma a promoção, e a partir daquele momento a mesma conexão de rede, que antes falava HTTP, passa a falar o protocolo WebSocket. Esse procedimento é o aperto de mão, e a beleza dele é reaproveitar a infraestrutura existente: a conexão nasce como HTTP, atravessa os mesmos caminhos e portas, e só então se transforma.
Depois do aperto de mão, a natureza da conexão muda completamente. Ela não fecha após uma troca; permanece aberta, viva, pronta para transportar dados a qualquer momento em qualquer direção. É uma conexão persistente — dura enquanto as duas partes quiserem — e bidirecional — tanto o cliente quanto o servidor podem enviar dados quando tiverem algo a dizer, sem esperar que o outro pergunte. Essa é a mudança fundamental: de trocas isoladas e iniciadas por um lado só, para um diálogo contínuo em que ambos falam livremente.
O canal permanente e bidirecional#
Com o canal aberto, a comunicação passa a funcionar por eventos. O cliente escuta as mensagens que chegam e reage a elas assim que aparecem; o servidor faz o mesmo. Quando algo acontece — uma nova mensagem no chat, uma mudança de preço —, o servidor simplesmente empurra os dados pelo canal, e o cliente recebe quase instantaneamente, sem ter perguntado nada. Da mesma forma, o cliente pode enviar dados a qualquer momento, e o servidor os recebe na hora. Não há mais o ritmo de pergunta e resposta; há um fluxo livre nos dois sentidos.
Essa persistência traz eficiência: uma vez estabelecida a conexão, enviar uma mensagem por ela é barato, sem a bagagem de cabeçalhos e estabelecimento que cada requisição HTTP carrega. Para aplicações que trocam muitas mensagens pequenas em alta frequência, a economia é enorme, tanto em atraso quanto em recursos. É por isso que jogos, edição colaborativa, notificações ao vivo e painéis que se atualizam sozinhos encontraram nos WebSockets o transporte natural.
O que muda para o desenvolvedor#
A mudança de modelo transfere responsabilidades. No HTTP, cada requisição é independente e curta, e se uma falha basta tentar de novo. Com um canal permanente, o desenvolvedor precisa lidar com o ciclo de vida da conexão: ela pode cair — a rede oscila, o usuário troca de Wi-Fi para dados móveis, um intermediário derruba conexões ociosas — e o código precisa perceber a queda e reconectar. Precisa também tratar o que fazer com as mensagens durante a reconexão, para não perder eventos importantes. Nada disso existe no modelo HTTP simples, onde a ausência de estado torna cada requisição autossuficiente. O canal persistente é mais poderoso, mas exige gerenciamento ativo.
Quando usar e quando não usar#
O poder dos WebSockets não os torna a resposta para tudo. A pergunta que decide é sobre a origem e a direção dos eventos. Se o servidor precisa avisar o cliente de coisas que o cliente não teria como saber que aconteceram, e isso ocorre com frequência ou precisa ser rápido, os WebSockets brilham. Chat, colaboração ao vivo, notificações push dentro da aplicação, dados de mercado, jogos multiplayer — todos têm esse perfil de eventos originados no servidor ou em outros usuários, chegando de forma imprevisível.
Por outro lado, se a interação é o cliente pedindo dados quando o usuário age — carregar uma página, enviar um formulário, buscar um resultado —, o HTTP tradicional é mais simples, mais robusto e mais adequado. Abrir um WebSocket para um padrão de pergunta e resposta ocasional é complexidade sem retorno: você assume o fardo de gerenciar conexões persistentes para um problema que uma requisição comum resolveria melhor. E para casos em que só o servidor precisa empurrar dados, sem o cliente nunca ter nada a dizer de volta, existem alternativas mais leves de fluxo em uma direção só, que não exigem toda a maquinaria bidirecional do WebSocket.
Uma alternativa em uma direção#
Vale mencionar que nem todo tempo real precisa das duas direções. Quando o fluxo é essencialmente do servidor para o cliente — um feed de notícias que atualiza, um progresso de tarefa longa, notificações — existe um mecanismo mais simples em que o servidor mantém uma conexão aberta e vai empurrando eventos por ela, enquanto o cliente apenas escuta. Ele reaproveita o HTTP de forma mais direta, reconecta sozinho quando cai e é mais fácil de operar do que um WebSocket completo. Escolher entre esse mecanismo unidirecional e o WebSocket bidirecional depende de o cliente precisar ou não também enviar dados pelo mesmo canal em alta frequência. Se só o servidor fala, a solução mais simples costuma ser preferível.
Escala e as questões que aparecem#
Adotar WebSockets muda também as considerações de infraestrutura, e vale ter consciência disso. Como cada cliente mantém uma conexão aberta e viva, o servidor precisa sustentar muitas conexões simultâneas, cada uma consumindo recursos enquanto existir, mesmo que fique em silêncio a maior parte do tempo. Isso é diferente do HTTP, em que conexões são efêmeras e liberadas logo. Em escala, gerenciar milhares ou milhões de conexões persistentes exige planejamento — de memória, de distribuição de carga, de como transmitir uma mesma mensagem para muitos clientes conectados a servidores diferentes.
Há também a questão da entrega. Um canal aberto não garante por si só que nenhuma mensagem se perca em uma queda momentânea. Aplicações que não podem perder eventos precisam de estratégias para detectar lacunas e recuperar o que foi perdido durante uma reconexão, o que adiciona lógica de numeração e reenvio de mensagens. São problemas solucionáveis, mas que não existiam no modelo mais simples e que fazem parte do custo de adotar tempo real. Entrar nesse território sabendo dessas responsabilidades evita a surpresa de descobri-las em produção.
Fechando#
Os WebSockets existem para resolver uma limitação específica e real do modelo tradicional da web: a incapacidade do servidor de falar primeiro. Ao promover uma conexão HTTP a um canal persistente e bidirecional, eles permitem que servidor e cliente conversem livremente, nos dois sentidos, sem o ritmo engessado de pergunta e resposta. Isso destrava toda uma categoria de experiências em tempo real que antes só se conseguia com truques ineficientes.
Mas esse poder vem com responsabilidades que o HTTP sem estado poupava: gerenciar o ciclo de vida da conexão, tratar quedas e reconexões, cuidar da entrega e sustentar muitas conexões vivas em escala. A decisão certa nasce de entender a natureza dos eventos da sua aplicação — de onde eles vêm e em que direção precisam fluir. Onde o servidor precisa empurrar eventos frequentes e imprevisíveis, e o cliente também precisa responder na mesma via, os WebSockets são a ferramenta exata. Onde o padrão é o cliente perguntando quando o usuário age, o HTTP continua sendo a escolha mais sábia. Conhecer os dois modelos, e o que cada um cobra, é o que permite usar tempo real onde ele agrega e evitá-lo onde ele só complica.