Websockets con Node.js

Implementación de WebSockets con Node.js

WebSocket es un protocolo de comunicación sobre una conexión TCP que establece un canal bidireccional o full-duplex con mínima latencia entre el servidor y el navegador. El navegador utiliza javascript y la API de Websocket soportada por todos los navegadores y del lado del servidor, vamos a implementar el esquema de Websockets con Node.js

El protocolo Websocket fue estandarizado por la IETF e 2011 como RFC 6455 y la API de Websocket por la W3C.

Este protocolo es diferente al protocolo HTTP aunque ambos son de la capa 7 en el modelo OSI y dependen de la capa 4 TCP. Websocket esta diseñado para operar sobre HTTP en los puertos 443 y 80 al mismo tiempo de soportar proxies e intermediarios.

Funcionalidad

El punto interesante es que el protocolo Websocket habilita la interacción entre un navegador web o cualquier otra aplicación cliente y un servidor web con baja sobrecarga facilitando la transferencia de datos en tiempo real desde y hacia el servidor. Esto se hace posible con una forma estandarizada de enviar contenido del servidor al cliente sin que el cliente inicie la petición y permite enviar mensajes de regreso y hacia adelante manteniendo la conexión abierta. Por lo que se puede establecer una comunicación bidireccional entre el cliente y el servidor.

Websockets con Node.js

En consecuencia, la mayoría de los navegadores soportan el protocolo Websocket a través de la directiva ws:// y para el protocolo seguro la directiva es wss:// como esquemas de URI (Uniform Resource Identifier).

Para su implementación se utiliza Javascript del lado del cliente en una página web que invoca funciones a través de los eventos de la página y del lado del servidor se pueden utilizar diversos lenguajes de programación entre los cuales podemos mencionar C#, Java, PHP, Python y por supuesto Javascript con Node y en nuestro caso utilizaremos el paquete de websockets.

Para el manejo de los Sockets, del lado del cliente existen dos grupos de funciones: 1) las requeridas para administrar el ciclo de vida del objeto y 2) los callbacks, que son las funciones que el navegador va a invocar cuando detecte un evento relacionado con el Websocket.

Ciclo de vida

En primer lugar se crea el WebSocket, lo más recomendable es que sea al terminar de cargarse la página, dentro de la función onload() del objeto window.

var socket = new WebSocket("ws://servidor.com/socketserver");

El objeto Websocket recibe en el constructor únicamente la URL del servidor al que nos vamos a conectar. La directiva ws:// reemplaza a http:// y si requerimos un canal seguro, entonces usamos wss://

Para enviar mensajes al servidor utilizamos el método send:

socket.send("Mensaje para el servidor");

El método send recibe una cadena con el mensaje que se enviará al servidor.

Si deseamos enviar un objeto JSON podemos hacerlo de la siguiente forma:

var mensaje = {
          nombre: "Enrique Martínez",
          correo: "enrique@gmail.com",
          edad: 30
        };
socket.send(JSON.stringify(mensaje));

Con la función JSON.stringify convertimos el objeto JSON en una cadena, que es lo que recibe el método send. Cuando sea necesario cerrar la conexión de manera explicita invocamos:

socket.close();

Eventos

Debido a que los Websockets funcionan de manera asíncrona, es decir, que la aplicación no se queda esperando en una línea a que llegue el mensaje del servidor, sino que en el momento en que llega un mensaje del servidor, el navegador lo notifica invocando un función de callback. De esta manera la aplicación no se queda bloqueada y sigue respondiendo a las interacciones del usuario mientras se interactúa con el servidor.

Los eventos del Websocket son los siguientes:

  • onmessage
  • onopen
  • onclose
  • onerror

Las funciones de callback que se implementan para cada uno de los eventos reciben el objeto event, con el cual obtenemos la información correspondiente al evento. El mensaje recibido desde el servidor lo obtenemos con event.data

socket.onopen = function(evt){ alert("Conexión establecida"); ...};

socket.onmessage = function(evt){ alert("Mensaje redibido: " + evt.data); ...};

socket.onclose = function(evt){ alert("Conexión cerrada"); ...};


Ejercicio Práctico Websockets con Node.js

En primer lugar, empezaremos con la página html que realiza la conexión al servidor de websockets. El archivo tiene el nombre: index.html y se puede colocas en la carpeta public dentro del proyecto de node o puede ser un archivo de una aplicación o sitio web distinto.

Cliente WebSockets



    
        <meta charset="utf-8">
        <title>Mensajes con WebSockets</title>
        <script src="js/funciones.js"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
    
    
        <div class="container">
            <h2>Implementando Websockets con Node.js</h2>
            <div class="row mt-4">
                <div class="col-sm-6">
                    <label class="form-label">Mensajes del servidor</label>
                    <textarea id="mensajes" rows="10" cols="10" class="form-control"></textarea>
                </div>
                <div class="col-sm-5">
                    <form onsubmit="enviarTexto(event)">
                        <label class="form-label">Texto a enviar</label>
                        <input type="text" id="texto" name="texto" class="form-control">
                        <button type="submit" id="enviar" class="btn btn-primary mt-2">Enviar</button>
                    </form>
                </div>
            </div>
        </div>
    

En la sección del head se esta incorporando un archivo de javascript ubicado en la carpeta js con el nombre funciones.js, posteriormente se incorpora una etiqueta link para agregar la hoja de estilos de Bootstrap directamente del cdn de bootstrap para darle formato a la página sin agregar tanto código.

El cuerpo de la página creamos dos columnas con el modelo de cajas de Bootstrap. En la columna de la izquierda colocamos un Textarea para ir agregando y mostrando los mensajes que se reciben del servidor, el ID del textarea es mensajes.

En la columna del lado derecho colocamos un formulario con un campo de texto y un botón. El ID del campo de texto es texto y en el se escribirán los mensajes que se envían al servidor. Para ello el formulario invocará la función enviarTexto() al momento de realizar la acción submit dando click en el botón, el cual de inicio se encuentra deshabilitado.

La siguiente imagen muestra el despliegue del archivo index.html

El siguiente paso ahora es crear el archivo de Javascript, que en este caso se llama funciones.js y se encuentra dentro de la carpeta js.

En el archivo funciones.js se crean las funciones enviarTexto(), init(), wsConnect(), onOpen(), onClose(), onMessage(), onError() y doSend()

Al terminar de cargar la ventana se ejecuta la función init() la cual invoca la conexión con servidor, si en algún momento se desconecta, la función onClose realiza el reintento de conexión cada 2 segundos. Si la conexión tiene éxito se habilita el botón.

archivo: funciones.js

// Se invoca cuando se oprime el botón Enviar
function enviarTexto(event){
    event.preventDefault();
    var campo = event.target.texto;
    // Enviamos el valor del campo al servidor
    doSend(campo.value);
    // Vaciamos el campo
    campo.value="";
}

// La función init se ejecuta cuando termina de cargarse la página
function init() {
    // Conexión con el servidor de websocket
    wsConnect();
}

// Invoca esta función para conectar con el servidor de WebSocket
function wsConnect() {
    // Connect to WebSocket server
    websocket = new WebSocket("ws://localhost:3000");

    // Asignación de callbacks
    websocket.onopen = function (evt) {
        onOpen(evt)
    };
    websocket.onclose = function (evt) {
        onClose(evt)
    };
    websocket.onmessage = function (evt) {
        onMessage(evt)
    };
    websocket.onerror = function (evt) {
        onError(evt)
    };
}

// Se ejecuta cuando se establece la conexión Websocket con el servidor
function onOpen(evt) {
    // Habilitamos el botón Enviar
    document.getElementById("enviar").disabled = false;
    // Enviamos el saludo inicial al servidor
    doSend("Hola");
}

// Se ejecuta cuando la conexión con el servidor se cierra
function onClose(evt) {

    // Deshabilitamos el boton
    document.getElementById("enviar").disabled = true;

    // Intenta reconectarse cada 2 segundos
    setTimeout(function () {
        wsConnect()
    }, 2000);
}

// Se invoca cuando se recibe un mensaje del servidor
function onMessage(evt) {
    // Agregamos al textarea el mensaje recibido
    var area = document.getElementById("mensajes")
    area.innerHTML += evt.data + "\n";
}

// Se invoca cuando se presenta un error en el WebSocket
function onError(evt) {
    console.log("ERROR: " + evt.data);
}

// Envía un mensaje al servidor (y se imprime en la consola)
function doSend(message) {
    console.log("Enviando: " + message);
    websocket.send(message);
}


// Se invoca la función init cuando la página termina de cargarse
window.addEventListener("load", init, false);

Servidor Websockets

La implementación del servidor la hacemos con Node.js para lo cual primero creamos el proyecto y la estructura del mismo, posteriormente instalamos las librerías requeridas.

Creamos la carpeta del proyecto y en la ventana de línea de comandos, dentro de la carpeta del proyecto, ejecutamos el comando para la creación de la aplicación con Node:

npm init -y

Una vez terminada la ejecución del init, creamos la carpeta src y dentro de ella la carpeta public y el archivo index.js

Posteriormente, instalamos las librerías que vamos a requerir:

npm install express cors websockets

Para el proyecto, instalamos express y cors para crear el servidor http y para el servidor de Websockets, instalamos la librería websockets. Puedes consultar la documentación adicional de la librería websockets en este enlace: https://github.com/theturtle32/WebSocket-Node/blob/HEAD/docs/index.md

Una vez completada la instalación de las librerías, colocamos en la carpeta public el archivo index.html y la carpeta js con su correspondiente archivo funciones.js

El archivo index.js que se encuentra dentro de la carpeta src tendrá lo siguiente:

// importamos las librerías requeridas
const path = require("path");
const express = require('express');
const cors = require('cors');
const app = express();
const server = require('http').Server(app);
const WebSocketServer = require("websocket").server;

// Creamos el servidor de sockets y lo incorporamos al servidor de la aplicación
const wsServer = new WebSocketServer({
    httpServer: server,
    autoAcceptConnections: false
});

// Especificamos el puerto en una varibale port, incorporamos cors, express 
// y la ruta a los archivo estáticos (la carpeta public)
app.set("port", 3000);
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, "./public")));

function originIsAllowed(origin) {
    // Para evitar cualquier conexión no permitida, validamos que 
    // provenga de el cliente adecuado, en este caso del mismo servidor.
    if(origin === "http://localhost:3000"){
        return true;
    }
    return false;
}

// Cuando llega un request por sockets validamos el origen
// En caso de origen permitido, recibimos el mensaje y lo mandamos
// de regreso al cliente
wsServer.on("request", (request) =>{
    if (!originIsAllowed(request.origin)) {
        // Sólo se aceptan request de origenes permitidos
        request.reject();
        console.log((new Date()) + ' Conexión del origen ' + request.origin + ' rechazada.');
        return;
      }
    const connection = request.accept(null, request.origin);
    connection.on("message", (message) => {
        console.log("Mensaje recibido: " + message.utf8Data);
        connection.sendUTF("Recibido: " + message.utf8Data);
    });
    connection.on("close", (reasonCode, description) => {
        console.log("El cliente se desconecto");
    });
});


// Iniciamos el servidor en el puerto establecido por la variable port (3000)
server.listen(app.get('port'), () =>{
    console.log('Servidor iniciado en el puerto: ' + app.get('port'));
})

Los mensajes por Websockets que recibe el servidor, los imprime en consola y los manda de regreso al cliente.

Puedes ver la implementación de este ejemplo en el siguiente video

Suscríbete también al canal de Youtube

2.2 20 votes
Article Rating
Subscribe
Notify of
guest
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
omperales
omperales
2 years ago

Me parece que la linea “npm install express cors websockets ” tiene un typo, viendo el video se instalo “websocket” en lugar de “websockets”

Danilo gonzalez
Danilo gonzalez
1 year ago

como hacer esto sin el boton.O sea escribir algo en un texarea y se refleje instantaneamente en el otro texarea.

Abraham
Abraham
1 year ago
Cannot GET /

me aparece este erro cada que intento ingresar con localhost:3000

6
0
Would love your thoughts, please comment.x
()
x

JacobSoft

Recibe notificaciones de los nuevos artículos y tutoriales cada vez que se incorpore uno nuevo

Gracias, te has suscrito al blog y al newsletter

There was an error while trying to send your request. Please try again.

JacobSoft utilizará la información que proporcionas para estar encontacto contigo y enviarte actualizaciones.