WebSockets HTML5 em Java com Jetty: Web em tempo real
![WebSockets HTML5 em Java com Jetty: Web em tempo real](https://www.alura.com.br/artigos/assets/code/websockets-html5-em-java-com-jetty-web-em-tempo-real.1731021821.png)
Navegadores são bons em fazer requisições para o servidor. Mas e o contrário? Fazer o servidor enviar dados pro navegador em momentos arbitrários sempre foi um trabalho. Ajax reverso, comet, long polling são algumas das gambiarrastécnicas usadas. Mas o HTML5 trouxe uma grande novidade: a API de WebSockets.
WebSockets permitem abrir uma conexão com o servidor remoto e trafegar dados arbitrariamente do servidor para o cliente e vice-versa. Dá pra fazer muita coisa com isso. Um chat em tempo real, um mecanismo de sincronização, e até streaming de dados binários.
No último QCon SP 2012, usei WebSockets na minha palestra de Web Mobile para sincronizar os slides de apresentação com os celulares, tablets e notebooks da platéia. Todo mundo abriu uma página com os slides e, conforme a palestra andava, eu mudava o slide no telão e imediatamente a platéia via o novo slide em seus dispositivos, junto com notas e exemplos adicionais.
Os slides foram feitos em HTML, CSS e JS, e o mecanismo de sincronizar meu slide com os mais de 200 dispositivos conectados ao mesmo tempo foi WebSockets. Minha máquina no telão enviava para o servidor qual era o slide atual e este distribuía a informação pra todo mundo em tempo real.
JavaScript no cliente
O código na página é muito simples. Você abre a conexão com o servidor e pode receber ou enviar mensagens. O envio é uma simples chamada de método e o recebimento, um callback JavaScript assíncrono. Há ainda um callback pra você saber quando a conexão for aberta.
No caso do sincronizador de slides, a versão mobile aberta nos dispositivos dos usuários recebia o ID do slide a ser mostrado:
var ws = new WebSocket('ws://meuservico.com/websockets');
ws.onopen = function() { console.log('Conexão aberta com sucesso'); };
ws.onmessage = function(message) { var slide = document.getElementById(message); mostrarSlide(slide); };
Já a máquina principal, que faz a sincronização, envia qual é o slide atual pra todo mundo:
function mostrarSlide(slide) { // lógica de exibir slide...
// sincroniza dispositivos ws.send(slide.id); }
Repare como usei um protocolo bem simples, trocando apenas os IDs dos slides. Isso facilitou em ocupar menos banda e evitar deixar a rede pesada no momento da palestra.
![Imersão dev Back-end: mergulhe em programação hoje, com a Alura e o Google Gemini. Domine o desenvolvimento back-end e crie o seu primeiro projeto com Node.js na prática. O evento é 100% gratuito e com certificado de participação. O período de inscrição vai de 18 de novembro de 2024 a 22 de novembro de 2024. Inscreva-se já!](assets/imersao-dev-back-end/imersao-dev-back-end-gemini-banner-corpo-mobile.png)
Servidor WebSockets com Jetty 8
O cliente JavaScript é bastante simples, mas a complexidade maior acaba ficando no servidor. WebSockets são um novo protocolo de comunicação em cima do HTTP e porta 80, e portanto exigem um servidor compatível. Há uma implementação de WebSockets muito boa no Jetty 8 para usarmos em Java, mas há outras para diversas linguagens - como o socket.io para Node.JS.
Com Jetty 8, é possível usar WebSockets através de uma Servlet especial, a WebSocketServlet
. Herdamos dessa classe e sobrescrevemos o método doGet
como numa Servlet comum. Mas, além disso, devemos sobrescrever o método doWebSocketConnect
que faz a conexão no protocolo de WebSockets em si.
Sua implementação desse método deve devolver um objeto do tipo WebSocket
que você vai criar. Para trabalhar com mensagens texto - como no nosso caso - implementamos a interface OnTextMessage
. Essa interface nos dá três métodos: onOpen
, onMessage
e onClose
.
Servidor de sincronização com Java
A ideia do sincronizador de slides é simples: guardar todos os usuários conectados numa lista e, quando chegar uma nova mensagem, reenviamos pra todo mundo. Para saber qual usuário é o principal, que comanda a sincronização, vou usar um parâmetro na URL:
@WebServlet(urlPatterns="/sincronizar") public class SincronizadorServlet extends WebSocketServlet { // lista de todos os usuários conectados. // cuidado com acesso concorrente, por isso uso aqui a CopyOnWriteArraySet private final Set<SyncWebSocket> usuarios = new CopyOnWriteArraySet<SyncWebSocket>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
// implementação do doGet aqui. // pode até deixar em branco, sem resposta.
} @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String arg1) {
// faz a conexão via WebSockets e devolve um novo cliente. // devolvemos um objeto da nossa classe SyncWebSocket que implementa a lógica. // repare que ela terá acesso à lista de usuários e // ao parâmetro para saber se é o usuário principal ou não. return new SyncWebSocket(usuarios, request.getParameter("principal") != null); } }
Cada cliente é representado pela minha classe SyncWebSocket
que recebe as mensagens do usuário principal e dispara para todos os outros.
public class SyncWebSocket implements OnTextMessage { private final Set<SyncWebSocket> usuarios; private final boolean principal; private Connection connection; public SyncWebSocket(Set<SyncWebSocket> usuarios, boolean principal) { this.usuarios = usuarios; this.principal = principal; }
public void onOpen(Connection connection) { // novo usuário conectado. adicionar na lista compartilhada usuarios.add(this);
// guarda a Connection pra enviar mensagens depois this.connection = connection; } public void onClose(int arg0, String arg1) { // remove usuário da lista quando sai usuarios.remove(this); }
public void onMessage(String message) { // só recebe mensagens se esse for o usuário principal. if (principal) {
// envia a mensagem pra todo mundo, sincronizando os clientes for (SyncWebSocket usuario: usuarios) { try { usuario.connection.sendMessage(message); } catch (IOException e) { usuarios.remove(usuario); usuario.connection.close(); } } } else { throw new RuntimeException("Você não pode mandar mensagens!"); } } }
Para subir esse código, você vai precisar do Jetty 8. No meu caso, precisei também copiar os JARs de websockets e outras libs do Jetty pra dentro do WEB-INF/lib do projeto.
É um exemplo simples de sincronização, mas muito mais é possível com WebSockets. Dá pra mandar mensagens binárias, e enviar e receber mensagens ao mesmo tempo.
Mais WebSockets e o futuro
WebSockets são suportadas nas últimas versões de todos os navegadores, incluindo os mobile. Há um detalhe apenas com relação à versão do protocolo que é suportada. Tivemos várias revisões do protocolo que foi sendo refinado com o tempo fechando bugs importantes de segurança. A versão final do RFC ainda não é suportada por todo mundo e pode gerar alguns problemas de compatibilidade para coisas mais complicadas.
A transmissão de dados binários foi adicionada recentemente ao protocolo e também não é suportada em todos os navegadores.