Publicar y compartir una plantilla en Node.js

0 acciones
0
0
0
0

Introducción

El patrón Pub/Sub es un patrón versátil de mensajería unidireccional en el que un publicador produce datos/mensajes y los suscriptores se suscriben para recibir tipos específicos de mensajes. Puede implementarse mediante una arquitectura peer-to-peer o un intermediario de mensajes para mediar las comunicaciones.


La imagen superior muestra el modelo de publicación/suscripción punto a punto, donde un editor envía mensajes directamente a los suscriptores sin intermediarios. Los suscriptores necesitan conocer la dirección o el punto final del editor para recibir el mensaje.


En la imagen superior, el modelo Pub/Sub utiliza un agente de mensajes como punto central para el envío de mensajes entre publicadores y suscriptores. El agente media en el intercambio de mensajes y los distribuye de los publicadores a los suscriptores. Los nodos suscriptores se suscriben directamente al agente en lugar del publicador. La presencia de un agente mejora el aislamiento de los nodos del sistema, ya que tanto el publicador como los suscriptores interactúan únicamente con él. En este tutorial, creará una aplicación de chat en tiempo real para demostrar este patrón con más detalle.

Requisitos previos
  • Node.js (versión >= 12) está instalado en su sistema operativo.
  • Un editor de código como VSCode
  • Redis está instalado en su máquina.
  • Comprensión básica de HTML, DOM, VanillaJS y WebSocket.

Paso 1: Implementación del lado del servidor

Para comenzar a ejecutar el lado del servidor, inicializamos una aplicación Nodejs básica usando el comando inicial:

npm init -y

El comando anterior crea un archivo package.json predeterminado.

A continuación, instalamos el paquete de dependencia WebSocket (ws), que es necesario durante todo el transcurso de esta compilación:

npm install ws

La implementación del lado del servidor será una aplicación de chat sencilla. Seguiremos el siguiente flujo de trabajo:

  1. Configurar un servidor
  2. Leer el archivo HTML para renderizarlo en el navegador
  3. Configurar una conexión WebSocket.
Configuración del servidor

Crea un archivo llamado app.js en tu directorio y coloca el siguiente código dentro de él:

const http = require("http");
const server = http.createServer((req, res) => {
res.end("Hello Chat App");
});
const PORT = 3459;
server.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
});

Método crearServidor En el módulo http Doméstico Node.js Se utilizará para iniciar el servidor. Se establece el puerto en el que el servidor debe escuchar las solicitudes y se llama al método de escucha de la instancia del servidor creada para escuchar las solicitudes entrantes en el puerto especificado.

Orden nodo app.js Ejecútalo en tu terminal y deberías obtener una respuesta como esta:

OutputServer is up and running on port 3459

Si solicita este puerto en su navegador, debería obtener algo como esto como respuesta:


Leer el archivo HTML para renderizarlo en el navegador

Crea un archivo llamado index.html en la carpeta principal y copia el siguiente código:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>Serving HTML file</p>
</body>
</html>

Este es un archivo HTML básico que muestra "Hola". Ahora, necesitamos leerlo y mostrarlo como respuesta cada vez que se envíe una solicitud HTTP a nuestro servidor.

// app.js
const server = http.createServer((req, res) => {
const htmlFilePath = path.join(__dirname, "index.html");
fs.readFile(htmlFilePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Error occured while reading file");
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data);
});
});

Aquí, utilizamos el módulo de ruta interna y la función unirse Lo usamos para unir segmentos de ruta. Luego usamos la función leerArchivo Para leer el archivo índice.html Se usa de forma asíncrona. Requiere dos argumentos: la ruta del archivo a leer y una lectura. Código de estado. 500 Se envía un encabezado de respuesta y un mensaje de error al cliente. Si los datos se leen correctamente, se devuelve un código de estado de éxito. 200 Enviamos el encabezado de respuesta y los datos de respuesta al cliente, que en este caso son el contenido del archivo. Si no se especifica ninguna codificación, como UTF-8, se devuelve el búfer sin procesar. De lo contrario, el archivo... HTML Se devuelve.

Realice una solicitud al servidor en su navegador y debería obtener esto:


Configurar una conexión WebSocket
// app.js
const webSocketServer = new WebSocket.Server({ server });
webSocketServer.on("connection", (client) => {
console.log("successfully connected to the client");
client.on("message", (streamMessage) => {
console.log("message", streamMessage);
distributeClientMessages(streamMessage);
});
});
const distributeClientMessages = (message) => {
for (const client of webSocketServer.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
}
};

En la línea de código anterior, creamos un nuevo servidor WebSocket llamado Servidor webSocket Lo creamos y lo enviamos al servidor. HTTP Nos conectamos a nuestro servidor existente. Esto nos permite gestionar tanto solicitudes HTTP estándar como conexiones WebSocket en un único puerto, el 3459.

El evento de conexión on() se activa cuando se establece una conexión WebSocket exitosa. El cliente en la función llamar de vuelta Un objeto de conexión WebSocket representa una conexión con un cliente. Se utiliza para enviar y recibir mensajes y escuchar eventos como los mensajes del cliente.

La función distributeClientMessages se utiliza aquí para enviar mensajes entrantes a todos los clientes conectados. Toma un argumento de mensaje y recorre los clientes conectados a nuestro servidor. A continuación, comprueba el estado de conexión de cada cliente (readyState === WebSocket.OPEN). Esto garantiza que el servidor solo envíe mensajes a los clientes con conexiones abiertas. Si la conexión del cliente está abierta, el servidor envía el mensaje a ese cliente mediante el método client.send(message).

Paso 2: Implementación del lado del cliente

Para ejecutar el lado del cliente, el archivo índice.html Nos cambiamos un poco.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>Pub/Sub Pattern with Chat Messaging</p>
<div id="messageContainer"></div>
<form id="messageForm">
<form id="messageForm">
<input
type="text"
id="messageText"
placeholder="Send a message"
style="
padding: 10px;
margin: 5px;
border-radius: 5px;
border: 1px solid #ccc;
outline: none;
"
onfocus="this.style.borderColor='#007bff';"
onblur="this.style.borderColor='#ccc';"
/>
<input
type="button"
value="Send Message"
style="
padding: 10px;
margin: 5px;
border-radius: 5px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
"
onmouseover="this.style.backgroundColor='#0056b3';"
onmouseout="this.style.backgroundColor='#007bff';"
/>
</form>
</form>
<script>
const url = window.location.host;
const socket = new WebSocket(`ws://${url}`);
</script>
</body>
</html>

En este fragmento de código, hemos añadido un elemento de formulario con una entrada y un botón para enviar un mensaje. Las conexiones WebSocket las inician los clientes, y para comunicarnos con un servidor WebSocket configurado inicialmente, necesitamos crear una instancia del objeto WebSocket que especifique ws://url, que especifica el servidor que queremos usar. Al iniciar sesión, la variable URL contendrá la URL que conecta al puerto 3459, donde nuestro servidor está escuchando.

// app.js
console.log("url", url); // localhost:3459
console.log("socket", socket); // { url: "ws://localhost:3459/", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, onclose: null, extensions: "", protocol: "", onmessage: null, binaryType: "blob" }

Entonces, cuando realizas una solicitud al servidor en tu navegador, deberías ver esto:


Actualicemos nuestro script para que podamos enviar mensajes del cliente al servidor y recibir mensajes del servidor.

// index.html
<script>
const url = window.location.host;
const socket = new WebSocket(`ws://${url}`);
const messageContainer = document.getElementById("messageContainer");
socket.onmessage = function (eventMessage) {
eventMessage.data.text().then((text) => {
const messageContent = document.createElement("p");
messageContent.innerHTML = text;
document
.getElementById("messageContainer")
.appendChild(messageContent);
});
};
const form = document.getElementById("messageForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
const message = document.getElementById("messageText").value;
socket.send(message);
document.getElementById("messageText").value = "";
});
</script>

Como se mencionó anteriormente, recuperamos la URL que envía una solicitud a nuestro servidor desde el cliente (navegador) y creamos una nueva instancia de objeto WebSocket con la URL. Luego, al hacer clic en el botón Enviar mensaje Creamos un evento en el elemento de formulario. El texto introducido por el usuario en la interfaz de usuario se extrae del elemento de entrada y se llama al método de envío de la instancia del socket para enviar el mensaje al servidor.

Evento en mensaje Se llama en el objeto socket y se activa al recibir un mensaje del servidor. Se utiliza para actualizar la interfaz de usuario de un mensaje recibido. Parámetro mensaje de evento En función llamar de vuelta Contiene los datos (mensaje) enviados desde el servidor, pero devueltos como un blob. El método text() se utiliza entonces en los datos del blob, lo que devuelve una promesa y se resuelve mediante then() para obtener el texto real del servidor.

Probemos lo que tenemos. Inicie el servidor ejecutando

node app.js

Luego, abra dos pestañas diferentes del navegador, http://localhost:3459/ Abre e intenta enviar mensajes entre pestañas para probar:


Paso 3 – Escalar la aplicación

Supongamos que nuestra aplicación empieza a crecer e intentamos escalarla con varias instancias de nuestro servidor de chat. Lo que queremos lograr es que dos usuarios conectados a dos servidores distintos puedan enviarse mensajes de texto correctamente. Actualmente solo tenemos un servidor, y si solicitamos otro, por ejemplo http://localhost:3460/, mensajes del servidor en el puerto 3459 No tendremos. Es decir, solo usuarios conectados a 3460 Pueden chatear consigo mismos. La implementación actual funciona de tal manera que, cuando se envía un mensaje de chat en nuestra instancia de servidor en ejecución, el mensaje se distribuye localmente solo a los clientes conectados a ese servidor en particular, como se muestra cuando http://localhost:3459/ Lo abrimos en dos navegadores diferentes. Ahora, veamos cómo podemos integrar dos servidores diferentes para que puedan comunicarse entre sí.

Paso 4: Redis como agente de mensajes

Redis es un almacén de estructuras de datos en memoria rápido y flexible. Se utiliza a menudo como base de datos o servidor de caché para almacenar datos. Además, puede utilizarse para implementar un patrón centralizado de intercambio de mensajes Pub/Sub. Su velocidad y flexibilidad lo han convertido en una opción muy popular para compartir datos en un sistema distribuido.

El objetivo es integrar nuestros servidores de chat utilizando Redis como intermediario de mensajes. Cada instancia del servidor publica simultáneamente todos los mensajes recibidos del cliente (navegador) en el intermediario de mensajes. El intermediario de mensajes se suscribe a todos los mensajes enviados desde las instancias del servidor.

Vamos a archivar aplicación.js Cambiemos nosotros mismos:

//app.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const WebSocket = require("ws");
const Redis = require("ioredis");
const redisPublisher = new Redis();
const redisSubscriber = new Redis();
const server = http.createServer((req, res) => {
const htmlFilePath = path.join(__dirname, "index.html");
fs.readFile(htmlFilePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Error occured while reading file");
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data);
});
});
const webSocketServer = new WebSocket.Server({ server });
webSocketServer.on("connection", (client) => {
console.log("succesfully connected to the client");
client.on("message", (streamMessage) => {
redisPublisher.publish("chat_messages", streamMessage);
});
});
redisSubscriber.subscribe("chat_messages");
console.log("sub", redisSubscriber.subscribe("messages"));
redisSubscriber.on("message", (channel, message) => {
console.log("redis", channel, message);
for (const client of webSocketServer.clients) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
}
});
const PORT = process.argv[2] || 3459;
server.listen(PORT, () => {
console.log(`Server up and running on port ${PORT}`);
});

Aquí utilizamos capacidades de publicación y compartición. Redis Utilizamos dos instancias de conexión diferentes: una para publicar mensajes y otra para suscribirse a un canal. Cuando se envía un mensaje desde el cliente, se publica en un canal de Redis llamado redisPublisher mediante el método "Publisher" de la instancia redisPublisher. ""mensajes de chat"" Publicamos. Se llama al método subscribe de la instancia redisSubscribe para suscribirse al mismo canal que chat_message. Al publicar un mensaje en este canal, se activa el detector de eventos redisSubscriber.on. Este detector de eventos se repite en todos los clientes WebSocket conectados y envía el mensaje recibido a cada cliente. Esto garantiza que, cuando un usuario envía un mensaje, todos los demás usuarios conectados a cada instancia del servidor lo reciban en tiempo real.

Si está configurando dos servidores diferentes, digamos:

node app.js 3459
node app.js 3460

Cuando se envía el texto del chat en una instancia, ahora podemos transmitir los mensajes a través de nuestros servidores conectados en lugar de solo a un servidor específico. Puedes hacerlo ejecutando http://localhost:3459/ y http://localhost:3460/ Pruebe y luego envíe chats entre ellos y vea los mensajes transmitidos en tiempo real a través de los dos servidores.

Puedes ver los mensajes publicados en un canal desde redis-cli Monitorea y suscríbete también al canal para recibir mensajes periódicos:

Orden redis-cli Corre. Luego entra MONITOR Vuelve a tu navegador y empieza a chatear. En tu terminal, deberías ver algo como esto, suponiendo que envías un mensaje de texto de Wow:


Para ver los mensajes publicados compartidos, utilice el mismo comando redis-cli Corre y SUSCRÍBETE nombreCanal Ingrese el nombre del canal acerca de nosotros. mensajes de chat Si envías un texto deberías tener algo como esto en tu terminal: Genial desde el navegador:


Ahora, podemos ejecutar múltiples instancias de nuestro servidor en diferentes puertos o incluso en diferentes máquinas, y siempre que se suscriban al mismo canal Redis, pueden recibir y transmitir mensajes a todos los clientes conectados, lo que garantiza que los usuarios puedan chatear sin problemas en todas las instancias.

¿Recuerdas que en la introducción hablamos sobre la implementación del patrón Pub/Sub mediante un agente de mensajes? Este ejemplo lo resume a la perfección.


En la figura anterior, dos clientes (navegadores) diferentes están conectados a los servidores de chat. Estos no están conectados directamente, sino a través de una instancia de Redis. Esto significa que, si bien gestionan las conexiones de los clientes de forma independiente, comparten información (mensajes de chat) a través de un medio común (Redis). Cada servidor de chat mencionado se conecta a Redis. Esta conexión se utiliza para publicar mensajes en Redis y suscribirse a sus canales para recibirlos. Cuando un usuario envía un mensaje, el servidor de chat lo publica en el canal especificado en Redis.

Cuando Redis recibe un mensaje publicado, lo transmite a todos los servidores de chat participantes. Cada servidor de chat envía el mensaje a todos los clientes conectados, garantizando así que todos los usuarios reciban los mensajes enviados por todos, independientemente del servidor al que estén conectados.

Esta arquitectura nos permite escalar nuestra aplicación de chat horizontalmente añadiendo más instancias de servidor según sea necesario. Gracias a las capacidades del sistema de publicación/suscripción de Redis, que garantiza una distribución uniforme de mensajes en todas las instancias, cada una puede gestionar su propio conjunto de clientes conectados. Esta configuración es eficiente para gestionar un gran número de usuarios simultáneos y garantiza la disponibilidad de la aplicación.

Resultado

En este tutorial, aprendimos sobre el patrón Publicar/Suscribirse al crear una aplicación de chat sencilla para demostrarlo, utilizando Redis como intermediario de mensajes. El siguiente paso es aprender a implementar un sistema de mensajería punto a punto en casos donde un intermediario de mensajes no sea la mejor solución, por ejemplo, en sistemas distribuidos complejos donde un único punto de fallo (intermediario) no es una opción.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

También te puede gustar