Conheça o Tyrus – sua implementação para WebSocket com backend em Node e Java

Aqui na Caelum, adoramos destrinchar as tecnologias novas que entram na especificação da plataforma Java. No JavaEE 6, lançado em 10/12/2009, falamos muito sobre CDI, e talvez tenha sido a especificação mais proeminente da tecnologia. Ano passado (12/06/2013), foi lançado o JavaEE 7, e já começamos a discorrer sobre algumas melhorias que entraram na especificação, como o JMS 2.0 por exemplo.

O objetivo deste artigo é estudar um pouco mais a API de WebSocket. Já discutimos a especificação anteriormente, então a ideia aqui é tentar aprofundar um pouco mais. Talvez essa seja uma API tão discutida quanto foi o CDI,e ela nos faz rever alguns conceitos sobre REST também. Além disso, vários frameworks já estão suportando e sugerindo melhorias à especificação.

Para implementar o servidor vamos usar duas tecnologias: NodeJS e JavaEE 7 (GlassFish 4.0).

Server side – NODE

Node é uma implementação que permite executar JavaScript no lado do servidor. Ele utiliza um modelo orientado a eventos assíncrono. Possui vantagens em relação a um servidor Java e foram discutidas na revista MundoJ. Para trabalhar com WebSocket em NodeJS, existem várias implementações como Socket.IO e Socket.JS. Neste artigo vamos utilizar a segunda.

var http = require('http');
var sockjs = require('sockjs');
var clients = {};

var sockjs_echo = sockjs.createServer();

function broadcast (message, exclude) {
    console.log("mensagem a ser enviada: "+ message);
    for ( var i in clients ) {
     if ( i != exclude ) {
        console.log("Cliente selecionado para enviar mensagem"+ clients[i]);
        clients[i].write( message );
     }

    }
}

sockjs_echo.on('connection', function(conn) {
 //guarda cliente conectado
 clients[conn.id] = conn;

 //Quando receber uma mensagem, faz o broadcast
 conn.on('data', function(data) {
     broadcast(data);
 });
});

//criando um servidor http para ouvir o serviço
var server = http.createServer();

//criando o canal de comunição websocket caelum
sockjs_echo.installHandlers(server, {prefix:'/caelum'});

server.listen(8082, '127.0.0.1');

Para executar esse código você precisa estar com o Node e o SocketJS instalado. Depois é só executar no terminal o comando: node <nomeArquivo>.js

Server side – JavaEE (Glassfish 4.0)

Em Java, você deve criar um projeto JavaEE e ter o GlassFish 4.0 instalado.

O código é bem simples e auto-explicativo, basta utilizar anotações com @ServerEndpoint e anotações de callback como @OnMessage, @OnOpen.

@ServerEndpoint("/caelum")
public class CaelumServer {
 	@OnOpen
	public void onOpen(Session cliente) {
		System.out.println("Novo cliente" + cliente);
	}

	@OnClose
	public void onClose(Session cliente) {
		System.out.println("Cliente desconectado" + cliente);
	}

	@OnMessage
	public void message(String message, Session client) throws IOException {

		Set<Session> openSessions = client.getOpenSessions();
		for (Session session : openSessions) {
			session.getBasicRemote().sendText(message);
		}
	}
}

O lado do servidor está pronto, temos o mesmo serviço WebSocket implementado por duas tecnologia diferentes e podendo ser acessado por um cliente JavaScript ou Java.

Conheça o Tyrus

O Projeto Tyrus é a implementação referência para a API de WebSocket. Assim como o Weld, o projeto possui implementações que podem ser utilizadas para um servlet container ou standalone.

Vamos implementar um cliente Tyrus para acessar os dois serviços que criamos. Para isso, crie um projeto maven e coloque a seguinte dependência.

<dependency>
  <groupId>org.glassfish.tyrus.bundles</groupId>
  <artifactId>tyrus-standalone-client</artifactId>
  <version>1.4</version>
</dependency>

Vamos criar duas classes de Cliente. A primeira apenas recebe a mensagem, já a segunda envia e recebe também. Para isso basta utilizarmos a anotação @ClientEndPoint.

@ClientEndpoint
public class ChatClientReceiver {
	@OnOpen
	public void onOpen(Session session) {
		System.out.println("Client Receiver conectado: " + session);
	}

	@OnMessage
	public void processMessage(String message) {
		System.out.println("Recebendo no Client Receiver: " + message);
	}
}

@ClientEndpoint
public class ChatClientSender {
	@OnOpen
	public void onOpen(Session session) {
		try {
			System.out.println("Client Sender conectado: " + session);
			session.getBasicRemote().sendText("Mensagem Enviada Via WebSocket");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@OnMessage
	public void processMessage(String message) {
		System.out.println("Recebendo no Client Sender: "+ message);
	}
}

Agora vamos criar uma classe que usa a API do Tyrus para acionar os clientes

public class TestClient {
	public static void main(String[] args) {
		WebSocketContainer container = ContainerProvider.getWebSocketContainer();
		String uri = "ws://127.0.0.1:8082/caelum/websocket";
		// String uri = "ws://127.0.0.1:8080/WebSocket/caelum";
		try {
			container.connectToServer(ChatClientReceiver.class, URI.create(uri));
			Thread.sleep(5000);
			container.connectToServer(ChatClientSender.class, URI.create(uri));
		} catch (DeploymentException | IOException | InterruptedException e) {
			e.printStackTrace();
		}
	}
}

O código é bem simples, a única peculiaridade é que a primeira URI acessa o WebSocket Node.JS, já a segunda o Java. O sleep na Thread é só para dar um tempo e termos certeza que o primeiro CaelumClientReceiver conectou-se ao serviço.

Concluindo, um dos artigos mais legais que eu já li e que me motivou a fazer parte da equipe Caelum foi como montar um serviço JAX-WS sem servidor de aplicação, a ideia aqui é mostrar que com WebSocket também podemos fazer o mesmo. Também é importante salientar a integração entre tecnologias diferentes. No exemplo, trocamos uma simples mensagem, mas nada impediria de ser um JSON.

9 Comentários

  1. Lucas Martins 10/02/2014 at 11:57 #

    é curioso como é difçil alguem falar do Tyrus, seja por aqui ou lá fora! Parabens pelo post

  2. Rafael Ponte 10/02/2014 at 14:47 #

    Muito bacana o post, Lacerda.

    Não tive a oportunidade de trabalhar com WebSocket ainda, mas acho a tecnologia é muito interessante. No mais, é sempre bom se manter atualizado!

    Parabéns pelo post!

  3. Raphael Maquiné 18/02/2014 at 18:00 #

    Já utilizei websocket com node mas olhando a api pra java até que ficou muito boa.

  4. Minato 12/03/2014 at 08:56 #

    Como ele funciona em um ambiente balanceado? Notei que a conexão é via IP, caso conecte em um NAT ou url, conhece alguma forma de gerenciamento da mensageria?

  5. Raphael Lacerda 12/03/2014 at 14:37 #

    Boa pergunta Minato. No meu trabalho não temos um balancer na frente, então não passei ainda por isso, mas googlando um pouquinho, vi que já tem algumas dúvidas sobre o balancer http://stackoverflow.com/questions/19659177/websocket-under-load-balance-environment

    e parece que o HAProxy consegue fazer
    http://blog.exceliance.fr/2012/11/07/websockets-load-balancing-with-haproxy/

  6. Richard Madureira 09/06/2014 at 12:23 #

    Olá Raphael!
    Uma duvida…

    Na classe principal, você mostra como abrir a conexão (utilizando “connectToServer()”), mas não fala da desconxão. Verifiquei na documentação da Classe WebSocketContainer que não há um método para realizar a desconexão…
    Como faço para realizar a desconexão via java?

    Parabéns pelo post.
    Muito massa mesmo!

    Obrigado!

  7. Raphael Lacerda 09/06/2014 at 18:09 #

    O close está na session

    https://www.openshift.com/blogs/how-to-build-java-websocket-applications-using-the-jsr-356-api

  8. RobertoW 09/08/2016 at 10:38 #

    Olá, estou precisando desenvolver uma aplicação webscokets com java se, teria alguma solução?

Deixe uma resposta