Mensajería con Spring Websocket

Noé Montes AlvarezSpring Framework

En esta entrada vamos a hacer un pequeño ejemplo de comunicación basada en mensajes usando Spring Websocket.

¿Que es Websocket?

Websocket es una tecnología que proporciona un canal de comunicación bidireccional y full-duplex (capacidad de un sistema de enviar y recibir mensajes de manera simultánea) sobre un único socket TCP. La especificación del protocolo define dos nuevos esquemas URI. ws: para conexiones no cifradas, y wss: para conexiones cifradas. Una vez que se establece la conexión websocket, esta permanece hasta que el cliente o el servidor deciden finalizar esta conexión. Para mas información sobre Spring Websocket os animo a visitar la documentación de spring.io (https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html)

¿Que es STOMP?

Define un protocolo para clientes y servidores para que se comuniquen usando la semántica de mensajería. Proporciona una semántica de alto nivel que se asignan a las tramas de websocket. Algunos de estos tipos son:

  • connect
  • subscribe
  • unsubscribe
  • send…

En la página oficial de STOMP podéis encontrar mas información sobre este protocolo (https://stomp.github.io)

Aplicación de prueba

Como ejemplo de websocket, vamos a hacer un chat. El proyecto en sí consta de dos partes. Por un lado el servidor WebSocket y por otro lado un cliente que se conecta a al servidor para enviar y recibir mensajes. Espero que este código os sea útil. Let’s Go!

Antes de nada comentaros que el código fuente de este ejemplo lo podéis descargar de mi GitHub. El proyecto del servidor lo podéis encontrar en: https://github.com/dark-wave/websocket-message-server y el cliente en: https://github.com/dark-wave/websocket-message-client

Ahora sí, vamos al lío.

Dependencias

El proyecto está creado con la versión 2.4.2 de Spring Boot. Por el lado servidor tenemos que incluir la dependencia de websocket para acceder a todas las configuraciones necesarias.

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Por otro lado, en el lado de cliente vamos a incluir las siguientes dependencias.

<dependency>
     <groupId>org.webjars</groupId>
     <artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
     <groupId>org.webjars</groupId>
     <artifactId>sockjs-client</artifactId>
     <version>1.0.2</version>
</dependency>
<dependency>
     <groupId>org.webjars</groupId>
     <artifactId>stomp-websocket</artifactId>
     <version>2.3.3</version>
</dependency>
<dependency>
     <groupId>org.webjars</groupId>
     <artifactId>bootstrap</artifactId>
     <version>3.3.7</version>
</dependency>
<dependency>
     <groupId>org.webjars</groupId>
     <artifactId>jquery</artifactId>
     <version>3.1.1-1</version>
</dependency>


Configuración de WebSocket

El primer paso es realizar la configuración de nuestro websocket en el lado del servidor. Para ello creamos una clase de configuración (anotada con @Configuration). Esta clase la vamos a decorar, además, con la anotación @EnableWebSocketMessageBroker para habilitar un broker de mensajería sobre websocket usando un subprotocolo de mensajería de alto nivel. Para personalizar la configuración podemos implementar la interfaz WebSocketMessageBrokerConfigurer. Veamos el código de esta clase y luego seguimos comentando algún detalle de la misma.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/topic");
		config.setApplicationDestinationPrefixes("/app");
	}
	
	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/websocket-chat")
		.setAllowedOrigins("http://192.168.1.36:8080", "http://localhost:8080")
		.withSockJS();
	}
}

Esta clase sobreescribe dos métodos de la interfaz. El primero de ellos, configureMessageBroker, habilita un broker simple y permite configurar uno o mas prefijos para filtrar destinos de los mensajes en este ejemplo hemos definido un prefijo denominado /app.

El segundo método, registerStompEndpoints, registra los endpoints mapeando cada uno a una url y, en este caso, habilitando y configurando las funciones de respaldo de SockJS. Indicamos además los orígenes permitidos mediante setAllowedOrigins, ya que por defecto websocket y SockJS aceptan solo solicitudes del mismo origen, por lo que si el cliente y el servidor están en dominios diferentes deben configurarse en este punto para permitir la comunicación entre ambos.

Controller

Ahora que tenemos el websocket configurado necesitamos un punto de entrada para incluir nuevos mensajes a nuestro broker. Para ello creamos una clase controlador (decorada con @Controller).

@Controller
public class ChatController {
	private static final Logger LOG = LoggerFactory.getLogger(ChatController.class);
	
	@MessageMapping("/chat")
	@SendTo("/topic/messages")
	public Message chatWebSocket(@Payload Message message){
		LOG.info(message.toString());
		return message;
	}
}

El método chatWebSocket mapea todas las peticiones que entran por el endpoint «/chat» mediante la anotación @MessageMapping y el resultado del método lo envía a una cola de mensajería. Este método recibe como parámetro un @Payload lo que equivale al cuerpo del mensaje que se marea a un objeto de modelo java.

Aplicación cliente

Llegados a este punto ya tenemos el servidor configurado para poder comunicarse vía websocket con los posibles clientes que se conecten. Es un ejemplo muy sencillo que se podría mejorar incluyendo comunicación con base de datos, integración con Spring Security y mucho mas, pero como punto de entrada a esta arquitectura creo que está bien.

Para este apartado vamos a crear un cliente que envíe y escuche los mensajes que va a proporcionar nuestro servidor. Me voy a centrar en los métodos principales y para ver el formulario y el resto de funciones javascript podéis verlas en el código fuente completo que os dejo en GitHub.

En un primer paso nos tenemos que conectar a nuestro websocket (lógicamente). Para realizar nuestra conexión nos vamos a apoyar en el cliente de STOMP y en la librería SockJS. Veamos la función y seguimos comentando detalles.

function connect() {
    var socket = new SockJS('http://192.168.1.36:3000/websocket-chat');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Conectado: ' + frame);
        stompClient.subscribe('/topic/messages', function (message) {
            showMessage(JSON.parse(message.body));
        });
    });
}

En primer lugar definimos nuestro endpoint del servidor mediante la librería de SockJS y configuramos con ella nuestro cliente STOMP. Al llamar al método Connect enviamos un callback que se ejecutará si la conexión se establece de manera satisfactoria. De ser así ya nos podemos subscribir a nuestra cola de mensajes que ejecutará una función cada vez que se recibe un nuevo mensaje. En este caso llama a una función showMessage, que lo único que hace es dar un formato al texto del mensaje para mostrarlo en la vista html.

Una vez tenemos la conexión establecida y estamos escuchando los mensajes podemos enviar mensajes al servidor. Para enviar mensajes usamos el método send el cual envía un json al messageMapping de nuestro broker que a su vez lo enviará a la cola de mensajes tal y como vimos en el controller

function sendMsg(){
	stompClient.send("/app/chat", 
		{}, 
		JSON.stringify({
			'from': $("#inputName").val() != '' ? $("#inputName").val() : 'Anonymous',
			'content': $("#inputMsg").val() 
		}));
	$("#inputMsg").val("");
}

Finalmente cuando ya no queramos hacer uso de nuestro socket podemos cerrar la conexión

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Desconectado");
}

Pues nada más. Hasta aquí este pequeño ejemplo sobre mensajería con websocket. Como ya dije este ejemplo es muy básico y se puede completar con muchos más conceptos, pero bueno esos los podemos dejar para otro día.

Saludos!!