Websockets
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
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