Sabe, sempre curti a ideia do WebSocket: uma conexão entre dois sistemas, um cliente e um servidor, um elo que (quase) nunca acaba. Assim como Taís Araújo e Reynaldo Gianecchini em Da Cor do Pecado. Porém, assim como eles tiveram bons momentos, também tiveram alguns percalços (sim, eu lembro de você, Giovanna Antonelli, inclusive minha crush de infância), e você também vai ter utilizando WebSocket. Espero que não na mesma quantidade; afinal, ninguém quer um irmão gêmeo aparecendo do nada para estragar o sistema, não é mesmo?
Fazendo alguns estudos sobre o tema, vi que algumas dores muito comuns são:
Só dobrar a quantidade de réplicas escala? Spoiler... talvez dê merda.
Como seria a melhor forma de fazer a autenticação?
Se for preciso que uma informação de um cliente A chegue no cliente B, como faço isso se eles estão em conexões diferentes?
Escalando os minions
Pensando de uma forma simplificada, só aumentar a quantidade de CPU e memória (escalar verticalmente) já seria uma opção recomendada em vários casos. Porém, nem sempre isso é o suficiente e escalar horizontalmente se faz necessário. É para esse cenário, lidando com WebSocket, que temos que tomar cuidado.
Vamos pensar na forma como o WebSocket enxerga uma conexão: o cliente (provavelmente de um navegador ou smartphone) chama o servidor. Sendo aceita, uma conexão contínua entre o cliente e aquele servidor específico está estabelecida.
Veja bem a ênfase no servidor específico. Como as outras instâncias da aplicação saberiam dessa conexão que só essa parte do sistema tem conhecimento? É aí que mora a pegadinha. Caso isso não seja tratado em um ambiente com múltiplas instâncias, podemos ter o seguinte problema:
- Perda de Sessão na Reconexão: Em um cenário com balanceamento de carga, se a conexão do cliente cair rapidamente por instabilidade e ele tentar reconectar, ele pode cair em uma instância completamente diferente daquela que guardava os dados dele na memória. Além disso, bibliotecas modernas costumam usar requisições HTTP convencionais antes de abrir o WebSocket de fato, e essas requisições iniciais precisam bater na mesma máquina.
Obs.: Em alguns casos, mesmo sem a Perda de sessão momentânea a conexão pode ser refeita em outro servidor que não o inicial.
Há algumas formas de evitar isso e garantir que o cliente converse sempre com a instância certa. As estratégias mais comuns são:
Sticky cookies (ou headers): A abordagem mais famosa. O cliente recebe um cookie que sinaliza em que instância ele fez o primeiro contato. Assim, toda requisição ou tentativa de conexão que ele fizer vai bater sempre na mesma máquina que já o conhece.
Serviço de direcionamento (Discovery): Você pode ter um serviço à parte que passa um endereço específico daquela instância para aquele usuário.
Esquema de Hash: A partir de alguma informação fixa (como o ID da conta do usuário), o balanceador calcula um hash e direciona o tráfego daquele ID sempre para o mesmo servidor.
Independentemente da escolha, sempre deve haver uma preocupação em relação a isso, para que recursos como CPU, memória e rede não sejam desperdiçados.
Sabendo quem é quem
Uma preocupação que eu tive quando comecei a estudar foi:
- Quando e o que utilizar para autenticar esse usuário?
Pensando nisso, é possível perceber que o WebSocket não restringe de forma alguma a maneira como a autenticação será realizada. É possível utilizar tanto JWT quanto tokens estáticos, por exemplo.
Levando isso em consideração, temos outra dúvida: quando autenticar? Há algumas possibilidades:
Autenticar no Handshake: O protocolo WebSocket é compatível com o HTTP, tanto que a primeira solicitação é feita por uma requisição HTTP tradicional. Se o servidor aceita, retorna uma resposta do tipo 101 (Switching Protocols) e a troca de protocolo é feita. Logo nesse handshake inicial é possível checar (via headers ou cookies) se o usuário é válido e tem permissão de acesso. A vantagem é barrar o invasor antes mesmo de a conexão se firmar.
Utilizar uma conexão anônima: O servidor aceita o upgrade da requisição sem exigir nada, porém, já no primeiro momento pós-conexão, exige que o cliente envie uma mensagem de autenticação. Caso as credenciais falhem, o servidor "chuta" o usuário na hora para evitar conexões ociosas ou mal-intencionadas. O WhatsApp faz algo parecido, porém com detalhes criptográficos muito mais profundos. Se quiser se aprofundar, vale dar uma lida: WhatsApp End-to-End Encryption Overview.
Avalie o seu projeto e veja o que é mais seguro e eficiente para o seu caso.
Conectando pontes
Você configurou seu servidor, o proxy tá um brinco e a autenticação não passa ninguém sem CPF. Em muitos sistemas (como acompanhar o progresso de uma transferência de arquivo em background), isso já é o suficiente.
Porém, quando você quer que suas mensagens sejam repassadas para outros sistemas, ou até para o seu coleguinha que também está conectado no mesmo app que você, temos um desafio, pois as conexões são diferentes.
Aprofundando no problema: imagine um chat separado por canais onde um Cliente A manda uma mensagem e quer que todos daquele canal a recebam. Como fazer isso, sendo que os outros usuários podem estar conectados em servidores completamente diferentes?
A forma mais comum de resolver esse BO é utilizar o padrão de Pub/Sub (Publish/Subscribe), que é organizado em tópicos e inscritos:
Tópicos: São os canais responsáveis por receber o fluxo de dados (eventos ou mensagens). No nosso exemplo, cada tópico seria equivalente a um canal do chat.
Inscritos (Subscribers): São os ouvintes. No nosso caso, são os próprios servidores do backend. Cada instância do seu servidor se inscreve nos tópicos para saber o que está rolando naqueles canais.
Dessa forma o que temos é:
O Cliente A envia a mensagem para a Instância 1.
A Instância 1 publica a mensagem no Tópico central.
O Tópico repassa a mensagem para a Instância 2.
E, finalmente, a Instância 2 avisa aos clientes conectados nela que uma nova mensagem chegou.
Fica tudo em sincronia através de um lugar central, sem que um servidor precise conhecer a existência do outro.
Fechando a conta
Espero que tenha gostado dessa análise um pouco superficial, mas que, assim como um conselho de amigo, ajude a não cair em algumas ciladas comuns desse mundo maravilhoso de WebSockets.
Referências:
WebSocket API (WebSockets): https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
Pub/Sub Architecture: https://www.geeksforgeeks.org/system-design/what-is-pub-sub/


Top comments (0)