Websockets

From bibbleWiki
Jump to navigation Jump to search

Pros and Cons

Pros

  • Full Duplex (no polling)
  • Http compatiable
  • Firewall friendly

Cons

  • Proxy is tricky
  • L7 Load Balancing challenging (timeouts)
  • Stateful, difficult to horizontally scale

Simple JS example

Client

<html>
    <body>
        <script>
            const ws = new WebSocket("ws://localhost:3100")

            ws.addEventListener("open", () => {
                console.log("We are connected")
                ws.send("hello everyone")
            })

            ws.addEventListener("message", (e) => {
                console.log("We have data", e)
            })

        </script>
    </body>
</html>

Server

We need to npm install

  "dependencies": {
    "ws": "^8.8.1"
  }

And run this.

const Websocket  = require("ws")

const wss = new Websocket.Server({port: 3100})

wss.on("connection", ws => {

    console.log("New Client Connected")

    ws.on("message", (data) => {
        console.log("Client Sent", data)
        ws.send(data.toUpperCase())
    }) 

    ws.on("close", () => {
        console.log("Client Disconnected")
    })
})

Another example

This one uses websocket library - npm i websockets

const http = require("http");
const WebSocketServer = require("websocket").server

const port  = 3100
let connection = null;

//create a raw http server (this will help us create the TCP which will then pass to the websocket to do the job)
const httpserver = http.createServer((req, res) =>
    console.log("we have received a request"))

//pass the httpserver object to the WebSocketServer library to do all the job, this class will override the req/res 
const websocket = new WebSocketServer({
    "httpServer": httpserver
})

httpserver.listen(port, () => console.log(`My server is listening on port ${port}`))

//when a legit websocket request comes listen to it and get the connection .. once you get a connection thats it! 
websocket.on("request", request => {

    connection = request.accept(null, request.origin)
    connection.on("open", () => console.log("Opened!!!"))
    connection.on("close", () => console.log("CLOSED!!!"))
    connection.on("message", message => {

        console.log(`Received message ${message.utf8Data}`)
        connection.send(`got your message: ${message.utf8Data}`)
    })

    //use connection.send to send stuff to the client 
    sendevery5seconds();
})

function sendevery5seconds() {
    connection.send(`Message ${Math.random()}`);
    setTimeout(sendevery5seconds, 5000);
}

//client code 
//let ws = new WebSocket("ws://localhost:8080");
//ws.onmessage = message => console.log(`Received: ${message.data}`);
//ws.send("Hello! I'm client")

Bills Server

This is an attempt to manage connections in a server process

let failedConnections = []

class BillsServer {

    constructor() {
        this.index = 0
        this.connections = []
        this.loop()
    }

    addConnection(connection) {

        connection.on("close", () => {
            failedConnections.push(connection.id)
            console.log(`closing socket for ${connection.id}`)
        })

        connection.on("message", message => {
            console.log(`Received message ${message.utf8Data}`)
        })

        this.connections.push(connection)
    }

    loop = () => {
        setInterval(this.process, 5000);
    }

    process = () => {

        console.log("Current connections: ", this.connections.length)
        if(this.connections.length > 0) {

            this.processAll()

            if(failedConnections.length > 0) {
                console.log(`Removing ${failedConnections.length} failed connections`)
                this.connections = this.connections.filter((connection) => !failedConnections.includes(connection.id))
                console.log(`${this.connections.length} Remaining Connections`)
                failedConnections = []
            }
        }
    }

    processAll = () => {
        for(let i = 0; i < this.connections.length; i++) {
            try {
                console.log(`${i.toString()}: Processing connection`,this.connections[i].id)
                this.handleConnection(this.connections[i]) 
            }
            catch(e) {
                console.log(`${i.toString()}: Got an error handling connection`, e ? e : "Unknown error")
            }
        }
    }

    handleConnection = (connection) => {
        console.log("Sending to client", connection.id)
        connection.send(`Sending you greetings`)
    }
}

module.exports  =  BillsServer

Bills Server Typescript

Javascript is dead - long live the king

Setting up Typescript

Found this a bit of challenge first time through but it seems to work. Here is the project setup. The dist is generated via the build
TS Setup Project Structure.png
We need to install our types where available

npm i -D typescript
npm i -D @types/node
npm i -D ts-node

Amend our scripts in the package script

  "scripts": {
    "start": "ts-node ./src/index.ts",
...
  },

Create a tsconfig.json file.

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
  },
  "include": ["./src"],
  "exclude": ["./node_modules"]
}

Now we should be able to compile our solution

npx tsc

BillServer Typescript Code

Here we go with the easy bit

index.ts

import * as http from "http"
import {server as WebSocketServer} from "websocket"
import {v4 as uuidv4} from 'uuid';

import { BillsConnection } from "./billsConnection";
import  { BillsServer } from "./billsServer"

const port  = 3100

const httpserver = http.createServer((req, res) =>
     console.log("we have received a request"))
     
const websocket = new WebSocketServer({
     "httpServer": httpserver
})

const server = new BillsServer()

websocket.on("request", request => {
     console.log("Adding a new client")
     const billsConnection =  new BillsConnection(request.accept(null, request.origin), uuidv4())
     server.addConnection(billsConnection)
})


httpserver.listen(port, () => console.log(`My server is listening on port ${port}`))

BillsConnection.ts

import { connection } from "websocket";

export class BillsConnection extends connection {

    id: string

    constructor(connection: connection, id: string) {

        super(
            connection.socket, 
            connection.extensions, 
            connection.protocol, 
            connection.maskOutgoingPackets, 
            connection.config)
            
        this.id = id
    }
}

BillsSever.ts

import { Message } from "websocket"
import { BillsConnection } from "./billsConnection"

let failedConnections: string[] = []

export class BillsServer {

    connections: BillsConnection[]

    constructor() {
        this.connections = []
        this.loop()
    }

    public addConnection(connection: BillsConnection): void {

        connection.on("close", () => {
            failedConnections.push(connection.id)
            console.log(`closing socket for ${connection.id}`)
        })

        connection.on("message", (data: Message) => {
            console.log(`Received message ${data}`)
        })

        this.connections.push(connection)
    }

    loop = () => {
        setInterval(this.process, 5000);
    }

    process = () => {

        console.log("Current connections: ", this.connections.length)
        if(this.connections.length > 0) {

            this.processAll()

            if(failedConnections.length > 0) {
                console.log(`Removing ${failedConnections.length} failed connections`)
                this.connections = this.connections.filter((connection) => !failedConnections.includes(connection.id))
                console.log(`${this.connections.length} Remaining Connections`)
                failedConnections = []
            }
        }
    }

    processAll = () => {
        for(let i = 0; i < this.connections.length; i++) {
            try {
                console.log(`${i.toString()}: Processing connection`,this.connections[i].id)
                this.handleConnection(this.connections[i]) 
            }
            catch(e) {
                console.log(`${i.toString()}: Got an error handling connection`, e ? e : "Unknown error")
            }
        }
    }

    handleConnection = (connection: BillsConnection) => {
        console.log("Sending to client", connection.id)
        connection.send(`Sending you greetings`)
    }
}

Running the example

Don't forget to complile and then run

npx tsc
npm run start