Loading...

Express - WebSockets

Lesson plan

In this lesson we are going to learn how to combine web sockets in our express app. We will try to demonstrate the usage of sockets by creating a chat application. This lesson will show you how to deal with some real time data transfer with the end users and keeping the connection to the user open. Lets start...

What are web sockets

Web socket is a communication protocol, they provide full duplex communication between client and server. This means the client can send messages to the server and the server can send messages to the client on an already open connection. WebSocket is a different protocol then the http protocol but they both rely on TCP protocol which gurentees that the packets are recieved and in the same order they are sent (or error is sent if communication fails). The first phase for creating a WebSocket is a handshake phase where we use the http protocol to send a request that we want to create a socket connection.

WebSockets best practices

It's important to maintain security on a WebSocket connection as well. WebSockets have their own URL schema which starts with ws:// or using TLS with schema wss://. You should always TLS encrypt your web socket communication which will prevent from a man in the middle attack to intercept your communication. Always validate the messages you recieve from the client, and also validate the messages your client recieves from the server. Maintain a keep alive on the connection, make sure you ping the connection from now and again and close the connection if the keep alive fails. Authentication and authorization is different in WebSockets. In WebSockets we cannot send authentication data in the headers and in fact modify the headers during handshake phase or after the connection is created. One way to authenticate the user is force the user to send his authentication token with each message he is sending, otherwise reject the message and close the connection. Another way you can authenticate the user is before the connection is established, create with the authentication token a ticket describing the current connection and the user token. The data for the current connection can be the user ip, the timestamp of the connection. you can send this ticket from the server back to the client and force the client to send it every time.

Openning a web socket server

Ok lets start building our chat application. Create a new directory for the project called: websockets-tutorial. Init npm in that directory and install express and ws.

        
            > mkdir websockets-tutorial
            > cd websockets-tutorial
            > npm init --yes
            > npm install express ws --save
        
    

Create a file in that directory called app.js where we will create a web socket server, when a client send a message to our server, we will publish the message to all the clients (it's going to be a group text where everyone is getting everyones messages). We are also going to create an express application to serve the chat page and serve a js file in that page to handle the communication. Add the following code to app.js

        
            const express = require('express');
            const WebSocket = require('ws');
            const path = require('path');

            // openning a websocket server
            const wss = new WebSocket.Server({port: 3002});
            wss.on('connection', function(ws) {
                ws.on('message', function sendToAllClients(message) {
                    wss.clients.forEach(function(client) {
                        client.send(message);
                    })
                })
            });

            const app = express();
            app.set('view engine', 'pug');
            app.set('views', path.resolve(__dirname, 'views'));

            app.use(express.static(path.resolve(__dirname, 'assets')));

            app.get('*', function(req, res) {
                res.render('chat');
            });

            app.listen(3001, function() {
                console.log('listening on port 3001');
            });
        
    

Lets go over the code above using the ws package, we are creating an instance of WebSocket server listening on port 3002. The web socket server class is extending EventEmitter and there are a few events you can subscribe to which the server emits: - listening will be invoked once after the server is starting to listen and can accept connections - headers event will be invoked on every connection at the handshake phase, with the headers passed in the handshake and the request. - connection event will be invoked on every new connection, the callback will be called with an instance of WebSocket. - error event will pop if there is an error. We are not subscribing to this event but it's important to do so if connection fails, otherwise the failed launching of the server will crash our process. On the connection event we are getting in the callback an instance of a WebSocket describing the connection. That instance is also extending EventEmitter and there are a few events we can subscribe to lets cover the common events: - message will be called when the client sends a message - error will be called on connection error - open/close will be called when the connection is openning or closing, close will also be called when the client will exit our application by closing the tab or moving to a different site. After setting the connection we start dealing with our express application. We will use Pug as our templaing engine so we set the view engine to pug. This will require us to install pug as well so in your terminal type:

        
            > npm install pug --save
        
    

we are also settings the views template directory to be views directory. This means you will have to create that directory in your project, so in the root dir of the project create a directory called: views Since the WebSocket connection on the client side is done via JavaScript, we will have to load on the browser a js file, which means we will have to load the static middleware in express to load static files. We then open a catch all route that will serve a template called chat. Time to create the pug template that will be rendered to the html page that the user will recieve from our express app. In the views directory create a file called: chat.pug with the following code:

        
            doctype html
            html
                head
                    title Chat Application
                body
                    h1 Welcome to the chat application
                    div(class="messages-wrapper")
                        ul(id="message-container")
                    div(class="input-wrapper")
                        form(id="chat")
                            div
                                label write message
                                input(type="text" placeholder="Enter message" id="chat-message" name="chat-message")
                    script(src="/js/ws.js")
        
    

The main thing to note in this template is that we are creating two sections in our page - one section is wrapped in a div with a class messages-wrapper will hold under an unordered list, the list of chat messages. - one section containing a form for the user to type a chat message. At the bottom of the file we are loading a js script for the browser to load to open the WebSocket connection. Lets create the ws.js file for the browser. Create a folder in the root directory called assets and in that folder, create a folder called js to hold our static js files, in the js folder create the file ws.js with the following code:

        
            const ws = new WebSocket('ws://localhost:3002');

            ws.onerror = function(err) {
                console.error('failed to make websocket connection');
                throw err;
            };
            
            ws.onopen = function() {
                console.log('connection established');
            };
            
            ws.onmessage = function(event) {
                const li = document.createElement('li');
                li.textContent = event.data;
                const ul = document.getElementById('message-container');
                ul.appendChild(li);
            }
            
            const form = document.getElementById('chat');
            form.addEventListener('submit', function(event) {
                const textInput = document.getElementById('chat-message');
                const chatText = textInput.value;
                textInput.value = '';
                ws.send(chatText);
                event.preventDefault();
            });
        
    

The first line of the code above is creating a new instance of WebSocket connection where we have to supply a string of the url we are connecting to. We can then attach events on the connection instance, and one of those events onmessage will jump when we recieve a message through our connection. We also attach an event for the form submit, where we take the text message in the text input, and send the message via to connection object. After sending the message, our server will get the message and pop a message event where we will send that message to all of our connections. When our browser gets a message from the server the onmessage event is called and it will create a new list item with the message and append it to the ul in our dom. Try and launch our application and connect from different tabs, send messages and see how our application is working.

KeepAlive

When launching our application and openning a WebSocket connection to our server, after leaving the site via closing the tab or going to a different site, the connection will be closed. Although this fact will cover most of the use cases and will automatically close the connections for us, sometimes there are case where the link between the client and server is interupted but the server and the client are unaware of the broken state of the connection. In this case a ping message can be used to verify the connection. On our server, we will keep an isAlive boolean property. The job of this property is to say if the connection is alive so every few seconds we will check to see if its true. There is a function in the WebSocket class that is called ping which will send optional data to the client, and the client will message back and we will get an event called pong on the WebSocket connection. We can use these to keep our isAlive property up to date. Modify the app.js add the following code to replace the creation of the websocket server. Add this code:

        
            // openning a websocket server
            const wss = new WebSocket.Server({port: 3002});
            wss.on('connection', function(ws) {
                ws.isAlive = true;
                ws.on('message', function sendToAllClients(message) {
                    wss.clients.forEach(function(client) {
                        client.send(message);
                    })
                });
            
                ws.on('pong', function heartbeat() {
                    this.isAlive = true;
                })
            });
            
            setInterval(function pingAllConnections() {
                wss.clients.forEach(function(ws) {
                    if (ws.isAlive === false) return ws.terminate();
                    ws.isAlive = false;
                    ws.ping();
                })
            }, 60000);
        
    

Note that every 60 seconds we are going over all the connections, setting the isAlive to false and pinging them. When they get the ping they will send data back and the pong event will be called were we set the isAlive back to true. This way a connection that didn't sent the pong will terminate.

Summmary

With WebSockets we can achieve some real time action between a server and a client and a connection that remains open. It's vital to know about web sockets and are really important when creating chats or games. Creating web sockets with Node is super easy, and since ES6 we no longer need to load additional libraries in the browser and can just use the built in WebSocket class. Try to expend our application and try to connect the chat application to a postgres database and save the chat messages in a table in the database. Happy Coding...