Messaging with Node.js



Resources
Principle

Messages (beyond an e-mail, a SMS…) are streamed data whose processing (filtering, aggregating…) comes from real-time needs.

Basically, 3 strategies exist with Node.js:

  1. HTTP-HTTPS Messaging relies on Server-sent Events. Client subscribes to server data from polling principle managed by server.
  2. WebSockets
  3. Message brokers are middleware in which messages are published and consumed by subscribers. Publishers and subscribers are broker “clients”. The key advantage is load balancing facilities provided by middleware.
Server-sent Events here

Upon HTTP-HTTPS, clients that have to timely get data may generate too many requests with a risk of overloading servers. Server-sent Events allow clients to subscribe to these data (onmessage handler function below), which are then pushed by servers. Clients no longer have to generate HTTP-HTTPS requests.

Server-side

const server = http.createServer((request, response) => {
    if (request.url === "/") { // 'index.html' served...
        response.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
        const __dirname = path.dirname(fileURLToPath(import.meta.url));
        fs.readFile(path.resolve(__dirname, '..') + '/index.html', null, (error, data) => {
            if (error) {
                response.writeHead(404);
                response.write("Whoops! 'index.html' not found...");
            } else
                response.write(data); // Content of 'index.html'...
            response.end();
        });
    } else
    if (request.url === '/Server_sent_data') { // Client asks for dealing with 'Server-sent' data...
        response.statusCode = 200;
        response.setHeader("Access-Control-Allow-Origin", "*"); // CORS
        response.setHeader("Cache-Control", "no-cache");
        // Client and server agree to keep the connection for subsequent requests or responses open:
        response.setHeader("connection", "keep-alive");
        response.setHeader("Content-Type", "text/event-stream");

        const data = JSON.stringify({integer: Math.floor(Math.random() * 10)}); // [0 - 10[
        // The event-stream format must be respected (https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format):
        setTimeout(() => response.write(`id: ${(new Date()).toLocaleTimeString()}\ndata: ${data}\n\n`), 2500);
    }
});
server.listen(process.env['PORT'] || 8080);

Client-side

window.onload = () => {
    const event_source = new EventSource("http://127.0.0.1:8080/Server_sent_data");
    window.console.assert(event_source.readyState === event_source.CONNECTING);
    const message_data = window.document.getElementById("message_data");
    // Stop listening server:
    message_data.addEventListener('click', () => {
        event_source.close();
        message_data.textContent = "Server-sent data stopped...";
    });
    event_source.onopen = (event) => window.console.assert(event_source.readyState === event_source.OPEN);
    event_source.onmessage = (event) => {
        const {integer} = window.JSON.parse(event.data);
        message_data.textContent = integer + " just received... 'click' to stop or 'reload' for new data)";
    };
    // Other stuff here...
};

Exercise

WebSockets

The native WebSockets API in the browser (example) allows the connection and exchange with a WebSockets server. To have a WebSockets server on the top of Node.js calls for appropriate libraries like ws or socket.io (socket.io uses WebSockets as transportation facility; however, it is incompatible with the WebSockets API in the browser).

Server

import * as WebSocket from "ws";

const Launch_WebSockets_server = (configuration: Object = {host: 'localhost', path: '/FranckBarbier', port: 1963}) => {
    const server = new WebSocket.Server(configuration);
    server.on('connection', (ws: WebSocket) => {
        console.log('OnOpen... status: ', ws.readyState === 1 ? "OPEN" : "Not OPEN");
        ws.on('message', (event: WebSocket.MessageEvent) => {
            console.log('Message from client: %s', event);
        });
        ws.send("{\"Connected\": \"Yes\"}");
    });
    server.on('close', (event: WebSocket.CloseEvent) => {
        console.log('OnClose: ', event ? event.reason : "Unknown reason");
    });
    server.on('error', (event: WebSocket.ErrorEvent) => {
        console.log('onError: ', event ? event.error : "Unknown error");
    });
    setTimeout((information) => {
        console.log(`${information}`);
        server.close();
    }, 10000, 'WebSockets server is stopping...');
};

export default Launch_WebSockets_server;

Launch server (and client in browser)

// import Launch_WebSockets_server from "./Server"; // Compilation...
import Launch_WebSockets_server from "./Server.js"; // Execution...

Launch_WebSockets_server({host: 'localhost', path: '/FranckBarbier', port: 1963});

// Just create a Web server to have the WebSockets client in the browser:
const port = process.env['PORT'] || 8080;
const server = http.createServer((request, response) => {
    response.writeHead(200, {"Content-Type": "text/html; charset=utf-8"});
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    // console.info("Location of 'index.html': " + path.resolve(__dirname, '..'));
    fs.readFile(path.resolve(__dirname, '..') + '/index.html', null, function (error, data) {
        if (error) {
            response.writeHead(404);
            response.write("Whoops! 'index.html' not found...");
        } else 
            response.write(data);
        response.end();
    });
}).listen(port);
console.log("Web server ready to accept requests on port %d", port);

setTimeout((information) => {
    // 'open' is a utility to launch the client in the 'index.html' file:
    open("http://localhost:" + port).then(() => {
        console.log(`${information}` + " Browser just opened...");
    });
}, 1000, 'WebSockets server started 1 sec. ago...');
Broker-based Messaging

Apache Kafka, Mosquitto, or RabbitMQ are popular message brokers. Messaging protocols are varied, but MQTT is an OASIS worldwide standard and, as such, widely supported.

Messaging broker clients require appropriate libraries like KafkaJS (Apache Kafka), MQTT.js (Mosquitto), or amqp.js (RabbitMQ) to allow clients' connection and exchange with messaging brokers.

Intuitive messaging versus broker-based messaging (right-hand side)