Load balancing

WebSocket

There is almost nothing needed to proxy WebSocket connections, other than to set timeouts and enable connection closing. The load balancer knows how to upgrade an HTTP connection to a WebSocket connection and once that happens, messages will travel back and forth through a WebSocket tunnel.

However, you must design your system for scale if you plan to load balance multiple WebSocket servers. Each client connects to one of your servers, where it then opens a persistent WebSocket connection. Because each server has only its own list of connected clients, messages passed to one server must be shared with the other servers somehow. Similarly, when you want to broadcast a message to all clients, all servers must receive and relay it. A typical way to solve this is to store messages in a shared database like Redis or pass messages between servers using a Publish/Subscribe framework like Kafka or RabbitMQ.

Configure WebSockets Jump to heading

  1. List your WebSocket servers in a backend section:

    haproxy
    backend websocket_servers
    option http-server-close
    timeout tunnel 1h
    server s1 192.168.0.10:3000 check
    server s2 192.168.0.11:3000 check
    haproxy
    backend websocket_servers
    option http-server-close
    timeout tunnel 1h
    server s1 192.168.0.10:3000 check
    server s2 192.168.0.11:3000 check

    In this example:

    • option http-server-close closes connections to the server immediately after the client finishes their session rather than using Keep-Alive. This promotes faster reuse of connection slots.
    • timeout tunnel sets how long to keep an idle WebSocket connection open.
  2. Optional: Route WebSocket clients to the backend by using a use_backend directive with a conditional statement. In the following example, the frontend section sends requests that have a URL beginning with /ws to the websocket_servers backend:

    haproxy
    frontend fe_main
    bind :80
    use_backend websocket_servers if { path_beg /ws }
    default_backend http_servers
    haproxy
    frontend fe_main
    bind :80
    use_backend websocket_servers if { path_beg /ws }
    default_backend http_servers

    You may also want to add option logasap to the frontend so that connections are logged immediately, rather than logging them only after they close.

Example WebSocket client and server Jump to heading

  1. Use the WebSocket Javascript API to create a client application. Below is an example web page named index.html. Change the WebSocket URL ws://192.168.50.25/ws/echo to use your load balancer’s IP address:

    index.html
    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>WebSocket Example</title>
    </head>
    <body>
    <h1>WebSocket Example</h1>
    <form>
    <label for="message">Message:</label><br />
    <input type="text" id="message" name="message" /><br />
    <input type="button" id="sendBtn" value="Send" />
    </form>
    <div id="output"></div>
    <script type="text/javascript">
    window.onload = function() {
    // connect to the server
    let socket = new WebSocket("ws://192.168.50.25/ws/echo");
    socket.onopen = () => socket.send("Client connected!");
    // send a message to the server
    var sendButton = document.getElementById("sendBtn");
    var message = document.getElementById("message");
    sendButton.onclick = () => {
    socket.send(message.value);
    }
    // print a message from the server
    socket.onmessage = (evt) => {
    var output = document.getElementById("output");
    output.innerHTML += `<div>${evt.data}</div>`;
    }
    }
    </script>
    </body>
    </html>
    index.html
    html
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>WebSocket Example</title>
    </head>
    <body>
    <h1>WebSocket Example</h1>
    <form>
    <label for="message">Message:</label><br />
    <input type="text" id="message" name="message" /><br />
    <input type="button" id="sendBtn" value="Send" />
    </form>
    <div id="output"></div>
    <script type="text/javascript">
    window.onload = function() {
    // connect to the server
    let socket = new WebSocket("ws://192.168.50.25/ws/echo");
    socket.onopen = () => socket.send("Client connected!");
    // send a message to the server
    var sendButton = document.getElementById("sendBtn");
    var message = document.getElementById("message");
    sendButton.onclick = () => {
    socket.send(message.value);
    }
    // print a message from the server
    socket.onmessage = (evt) => {
    var output = document.getElementById("output");
    output.innerHTML += `<div>${evt.data}</div>`;
    }
    }
    </script>
    </body>
    </html>
  2. Create the WebSocket server. The following node.js application file is named index.js. It serves the web page from the previous step and hosts the /ws/echo WebSocket function:

    index.js
    javascript
    const express = require('express');
    const app = express();
    const path = require('path');
    const expressWs = require('express-ws')(app);
    // Serve web page HTML
    app.get('/ws', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html'));
    });
    // WebSocket function
    app.ws('/ws/echo', (ws, req) => {
    // receive a message from a client
    ws.on('message', msg => {
    console.log(msg);
    // broadcast message to all clients
    var wss = expressWs.getWss();
    wss.clients.forEach(client => client.send("Received: " + msg));
    })
    });
    app.listen(3000);
    index.js
    javascript
    const express = require('express');
    const app = express();
    const path = require('path');
    const expressWs = require('express-ws')(app);
    // Serve web page HTML
    app.get('/ws', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html'));
    });
    // WebSocket function
    app.ws('/ws/echo', (ws, req) => {
    // receive a message from a client
    ws.on('message', msg => {
    console.log(msg);
    // broadcast message to all clients
    var wss = expressWs.getWss();
    wss.clients.forEach(client => client.send("Received: " + msg));
    })
    });
    app.listen(3000);
  3. Add the following node.js package.json file to your project and start the application with the npm start command:

    package.json
    javascript
    {
    "name": "websocket-server",
    version": "1.0.0",
    "description": "Example WebSockets application",
    "main": "index.js",
    "scripts": {
    "start": "node index.js"
    },
    "author": "Your Name",
    "license": "ISC",
    "dependencies": {
    "express": "^4.17.1",
    "express-ws": "4.0.0"
    }
    }
    package.json
    javascript
    {
    "name": "websocket-server",
    version": "1.0.0",
    "description": "Example WebSockets application",
    "main": "index.js",
    "scripts": {
    "start": "node index.js"
    },
    "author": "Your Name",
    "license": "ISC",
    "dependencies": {
    "express": "^4.17.1",
    "express-ws": "4.0.0"
    }
    }
  4. Now that the node.js application is listening, load balance it by adding a frontend and backend to your load balancer configuration that routes traffic to port 3000:

    haproxy
    frontend fe_main
    bind :80
    default_backend websocket_servers
    backend websocket_servers
    option http-server-close
    option logasap
    timeout tunnel 1h
    server s1 127.0.0.1:3000 check
    haproxy
    frontend fe_main
    bind :80
    default_backend websocket_servers
    backend websocket_servers
    option http-server-close
    option logasap
    timeout tunnel 1h
    server s1 127.0.0.1:3000 check

The web page displays a text box that the user types a message into to send to the WebSocket server. The server then echoes that message back to all connected clients.

Example WebSocket application

If this page was useful, please, Leave the feedback.

© 2023 HAProxy Technologies, LLC. All Rights Reserved
Manage Cookie Preferences