Introducción

En los entornos de trabajo tener una comunicación en tiempo real con cientos de personas, cada vez es más importante para optimizar nuestro tiempo y recursos.

Por ello, vamos a mostrar cómo utilizar la librería Socket.IO para montar un servidor en Java que envíe notificaciones a los distintos usuarios conectados a él.

Esto nos puede servir tanto para crear un chat con mensajes directos y salas, como un sistema de gamificación para informar de que hacen el resto de los usuarios conectados o enviar comunicaciones de carácter informativo.

La librería de Socket.IO para Java que utilizaremos está basada en la de Socket.IO escrita en Javascript.

¿Qué es Socket.IO?

Socket.IO es una librería basada en eventos que permite la comunicación bidireccional y de baja latencia entre cliente y servidor en tiempo real. Está constituido sobre Engine.IO, abstrayéndonos de la capa de transporte, ya que incluye AJAX long-polling y WebSocket, en una única API. Por ello,permite a los desarrolladores enviar y recibir mensajes sin preocuparse por la compatibilidad de navegadores y dispositivos.

Tecnologías utilizadas

  • Java 11
  • Socket.io 4.0.1
  • Engine.io 6.2.1
  • Jetty server 9.4.44.v20210927
  • Junit 5.10.0
  • Mockito 4.0.0
  • Maven

Dependencias de Maven

Antes de comenzar a codificar, tenemos que añadir las dependencias para utilizar Socket.IO. También añadiremos las librerías necesarias para tener un servidor Jetty embebido donde tendremos el servicio de mensajería disponible.

Estas serían las principales dependencias:

<!-- Jetty embedded server dependencies -->

<dependency>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-server</artifactId>

<version>9.4.44.v20210927</version>

</dependency>

<dependency>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-servlet</artifactId>

<version>9.4.44.v20210927</version>

</dependency>

<dependency>

<groupId>org.eclipse.jetty</groupId>

<artifactId>jetty-servlets</artifactId>

<version>9.4.44.v20210927</version>

</dependency>

<dependency>

<groupId>org.eclipse.jetty.websocket</groupId>

<artifactId>websocket-server</artifactId>

<version>9.4.44.v20210927</version>

</dependency>

<!– Socket.io dependencies –>

<dependency>

<groupId>io.socket</groupId>

<artifactId>socket.io-server</artifactId>

<version>4.0.1</version>

</dependency>

<dependency>

<groupId>io.socket</groupId>

<artifactId>engine.io-server</artifactId>

<version>6.2.1</version>

</dependency>

 

<dependency>

<groupId>io.socket</groupId>

<artifactId>engine.io-server-jetty</artifactId>

<version>6.2.1</version>

</dependency>

 

<dependency>

<groupId>org.json</groupId>

<artifactId>json</artifactId>

<version>20230618</version>

</dependency>

Integración y uso de Socket.IO

Preparación del terreno

Lo primero que haremos, es crear un proyecto en nuestro IDE. El proyecto que creemos puede tener la versión de Java que consideremos. Nosotros hemos creado una demo con la versión 11 de Java, aunque también se pueden usar versiones superiores. 

Después añadiremos las dependencias al proyecto para poder usarlas.

Creación de un servidor Jetty embebido con Socket.IO

Con todo esto hecho, vamos a crear un servidor Jetty e implementar Socket.IO en él.

Empezaremos definiendo las opciones que usará EngineIOServer. Esta clase es parte de la librería Engine.IO Java que nos provee la comunicación entre cliente y servidor, cross-browser/cross-device.

Las opciones mencionadas se crean mediante la clase EngineIOServerOptions. Podemos inicializar el objeto con las opciones por defecto utilizando el método newFromDefault(), de la siguiente manera:

EngineIoServerOptions engineIoServerOptions = 
EngineIoServerOptions.newFromDefault();

En la configuración de las opciones debemos tener en cuenta la configuración del CORS (Cross-origin resource sharing). Por defecto, todos los orígenes están permitidos, pero podemos restringir la lista de orígenes usando el método setAllowedCorsOrigins() del objeto EngineIoServerOptions si lo deseamos.

Una vez tenemos el objeto EngineIoServerOptions, podemos instanciar el objeto EngineIoServer y el objeto SocketIOServer de esta forma:

EngineIoServer engineIoServer = new 
EngineIoServer(engineIoServerOptions);
SocketIoServer socketIoServer = new SocketIoServer(engineIoServer);

Ya tendríamos configurada la parte de Socket.IO

Ahora vamos a crear el servidor Jetty. Para ello, vamos a crear un socket con la IP y el puerto en el que queramos que el servidor escuche y estableceremos una instancia del servidor con estos datos.

InetSocketAddress inetAddress = new InetSocketAddress(hostname, port);
Server server = new Server(inetAddress);

Para poder conectar y realizar peticiones a Socket.IO necesitamos añadir en el servidor un servlet para manejar la ruta de contexto «/socket.io», siendo el contexto por defecto que utiliza la librería Socket.IO.

Por lo tanto, vamos a crear un objeto tipo ServletContextHandler y añadirle el servlet que se encargue de todas las peticiones que enviadas a «/socket.io».

ServletContextHandler servletContextHandler = new 
ServletContextHandler(ServletContextHandler.SESSIONS);
servletContextHandler.addServlet(new ServletHolder(new HttpServlet() {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
        mEngineIoServer.handleRequest(new HttpServletRequestWrapper(request) {
            @Override
            public boolean isAsyncSupported() {
               return false;
            }
         }, response);
     }
}), "/socket.io/*");

Como estamos estableciendo comunicación entre sistemas a través de sockets, vamos a interceptar todas las peticiones que empiecen por «/socket.io», para convertirlas de una conexión HTTP a una conexión WebSocket a través del objeto WebSocketUpgradeFilter.

WebSocketUpgradeFilter webSocketUpgradeFilter = 
WebSocketUpgradeFilter.configure(servletContextHandler);
webSocketUpgradeFilter.addMapping(new ServletPathSpec("/socket.io/*"),
                    (servletUpgradeRequest, servletUpgradeResponse) ->
new JettyWebSocketHandler(mEngineIoServer));

Después de haber configurado el objeto ServletContextHandler, necesitamos añadirlo como handler del servidor Jetty embebido:

server.setHandler(servletContextHandler);

Con esto, hemos creado un servidor Jetty embebido y hemos implementado Socket.IO en Java. Sólo necesitamos iniciar el servidor Jetty embebido usando el método start() del objeto Server.

server.start();

Ahora ya tenemos un servidor con Socket.IO para recibir y enviar mensajes.

Conclusión

En esta primera publicación hemos aprendido:

  • Cómo configurar un servidor Jetty embebido
  • Integrar Socket.IO en el servidor

Próximamente publicaremos la segunda parte de este post, en el que os contaré los siguientes puntos: 

  • Cómo crear eventos
  • Unir y abandonar salas
  • Difundir mensajes de distintas formas
  • Utilizar Postman para recibir y enviar mensajes

Fuentes