How to update your web content in real time with WebSockets
Tags: websockets, javascript, node
Published: 2022-05-04 03:19:12

We have seen an evolution of how websites were created, at the start, there were only static web pages. For a static web page, a server is continuously listening for requests from users. If a user wants to read a document, he sends a request to the server and the server delivers the document as a static response. This technology was convenient for publishing files on the internet. However, the limited degree of interactivity couldn’t account for the increasing desire for user-generated content. Simple static web pages were suddenly not enough to keep up with the ever-growing craze of social interactivity online.

People started to create custom scripts that could be used to build websites dynamically; on the server-side, scripts received input from users and were able to accordingly deliver a dynamic response. With this kind of technology, forums and message boards started to appear and users could post their content for others to read. At one point, even this kind of interactivity was too limited. 

Social networks like Facebook as we know them today can deliver content interactively, without even reloading the page. This extreme degree of interactivity can be considered today’s state of the art, and the required methodology should be incorporated in every developer’s skills set.

Before we dive into the technical details of WebSockets, let’s have a quick recap on the inner workings of the classical HTTP protocol. The HTTP protocol is the traditional approach of how browsers and web servers communicate. The huge advantage of HTTP is the stateless design. This means that servers are not required to keep any information on active clients except for the very moment in which a request is served. The traditional way of using an HTTP connection is to simply deliver the requested data and close the connection immediately after the request got served. This design was very efficient in the early days of the internet when simple static websites were delivered to a large number of users.

The severe limitation of the stateless design got apparent once the content of websites got much richer and many individual requests were needed before the website could finally be displayed. It was not long before the original protocol got extended to handle those cases more efficiently. The basic idea consists of keeping the underlying connection alive so that many individual requests can be pushed over the wire before the connection is closed.

HTTP connections always follow a strict request-response scheme that is initiated exclusively by the client. Once a connection gets closed, the server is unable to inform the client about events or state changes that happened after the last request took place. With the advent of social networks gaining popularity, developers came up with clever workarounds to allow for interactive behavior.

One of the first approaches was polling. Polling works by constantly initiating new requests inside an infinite loop on the client-side. That way, the server always gets the opportunity to notify clients about the most recent events. A disadvantage is the heavy load this method imposes on servers, especially when many users open a site simultaneously. To counteract these inefficiencies, developers came up with the idea of long polling. With long polling, servers aim to slow down clients by blocking a connection whenever they have no data to send back. By keeping the connection to the client idle, clients will not overload the server with requests at an unreasonably high rate. Once the server has new data to send to the client, the idle connection is used and immediately closed. The client will then follow the original polling strategy by immediately reopening a new connection that the server then again holds idle until new data is available to be sent to the client. This strategy allowed for interactive features like chats, live tickers, newsfeeds, etc. over HTTP, with only a moderately high load on servers. Facebook was among the first websites that employed this strategy for updating timelines without the need for reloading pages. Luckily, there are even better methods available in modern browsers, so that push messages and interactivity are now supported as a default feature.

WebSockets can be seen as an extension to the classical HTTP protocol. Instead of sending an ordinary HTTP request (i.e., GET, POST, PATCH), clients send a special CONNECT request that indicates that a WebSocket connection should be initiated. When a web-server supports WebSockets, this request leads to a protocol change: both the server and the client will then deviate from the default HTTP behavior and instead switch to a new protocol while reusing the underlying channel of the previous connection. 

WebSockets is a two-directional full-duplex protocol for communication between the client and server over the web. This protocol allows real-time applications, the likes as chatting, notifications, live feed, multi-player gaming, and other functionalities as well.

Let us now explore the use of WebSockets in a hands-on manner:

```
//Normal fetch
fetch("http://localhost:3000")
.then(resp => resp.json())
.then(data => console.log(data))

//WebSocket
//create a WebSocket
const socket = new WebSocket("ws://localhost:7000/ws")
//Callback that should run once the connection has been established
socket.onopen = () => {
console.log("Connection is Open")
}
socket.onmessage = (event) => {
const obj = JSON.parse(event.data)
}
socket.onclose = () => {
console.log("Connection is Closed")
}
```

There are libraries in different languages to handle incoming WebSockets on the server-side. One of the easiest ways to work with WebSockets on the server is probably NodeJS. An advantage of NodeJs is that both the client and the server code can be written entirely in JavaScript. This resolves many compatibility issues and allows developers to assume a unified perspective when reasoning about the data flow within their code. Another benefit is the availability of asynchronous functions and the event-oriented design of Node.js that is perfectly suited for working with callbacks and setting up event listeners. This design avoids the additional complexity of multi-threaded code that could be found in other languages. Simply speaking, developers in Node.js can write their backend code on the server almost the same way as the corresponding JavaScript code in the browser. There are many libraries available that can be easily imported into the npm package manager to get a WebSocket application running in a short amount of time.

WebSockets Server
```
//The http raw-server using the http library
const http = require("http");

//create the websocket server - step1 - Get the class to handle events - to initiate the exchange protocol
const WebSocketServer = require("websocket").server

//create the websocket server - step3 - Global var to overwrite
let connection = null

//Persisted web-server injected into another library to allow protocol change
const httpServer = http.createServer((req, res) => {
*    console.log("We have received a request")
})

//create the websocket server - step2 - Takes a json, which is http server and you pass it httpServer - to initiate the exchange protocol
const webSocket = new WebSocketServer({
    "httpServer": httpServer
})

//create the websocket - step3 - The event, when request is sent call this function, we get a connection
webSocket.on("request", request => {
*    connection = request.accept(null, request.origin)
    connection.on("onopen", () => console.log("Open!!!"))
    connection.on("onclose", () => console.log("Closed!!!"))
    connection.on("onmessage", message => {
*        console.log(`Received message ${message}`)
    })
})

httpServer.listen(8080, () => console.log("My server is listening on port 8080"))
```

The code above shows how to set up a WebSocket server to handle incoming connections from clients…(try it out with the debugger and your browser's console, (*)where to add breakpoints)

Server to Client
```
//Console
ws = new WebSocket("ws://localhost:8080")
ws.onmessage = message => console.log(`We received a message from server ${message.data}`)
//Debugger
connection.send("Hello client! From the server")
```

Client to Server
```
//Console
ws = new WebSocket("ws://localhost:8080")
ws.onmessage = message => console.log(`We received a message from server ${message.data}`)
ws.send("Hello server, It's the client")
```

Conclusion
It is now possible to send and receive data across multiple connections interactively. The server can react to incoming messages and, if necessary, messages can be resent to other connected clients, essentially linking together multiple users with the server acting as a middle man that dispatches the data flow between different connections.