Deploy behind HAProxy

This guide demonstrates a way to load balance connections across multiple websockets server processes running on the same machine with HAProxy.

We’ll run server processes with Supervisor as described in this guide.

Run server processes

Save this app to app.py:

#!/usr/bin/env python

import asyncio
import os
import signal

import websockets


async def echo(websocket):
    async for message in websocket:
        await websocket.send(message)


async def main():
    # Set the stop condition when receiving SIGTERM.
    loop = asyncio.get_running_loop()
    stop = loop.create_future()
    loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)

    async with websockets.serve(
        echo,
        host="localhost",
        port=8000 + int(os.environ["SUPERVISOR_PROCESS_NAME"][-2:]),
    ):
        await stop


if __name__ == "__main__":
    asyncio.run(main())

Each server process listens on a different port by extracting an incremental index from an environment variable set by Supervisor.

Save this configuration to supervisord.conf:

[supervisord]

[program:websockets-test]
command = python app.py
process_name = %(program_name)s_%(process_num)02d
numprocs = 4
autorestart = true

This configuration runs four instances of the app.

Install Supervisor and run it:

$ supervisord -c supervisord.conf -n

Configure and run HAProxy

Here’s a simple HAProxy configuration to load balance connections across four processes:

defaults
    mode http
    timeout connect 10s
    timeout client 30s
    timeout server 30s

frontend websocket
    bind localhost:8080
    default_backend websocket

backend websocket
    balance leastconn
    server websockets-test_00 localhost:8000
    server websockets-test_01 localhost:8001
    server websockets-test_02 localhost:8002
    server websockets-test_03 localhost:8003

In the backend configuration, we set the load balancing method to leastconn in order to balance the number of active connections across servers. This is best for long running connections.

Save the configuration to haproxy.cfg, install HAProxy, and run it:

$ haproxy -f haproxy.cfg

You can confirm that HAProxy proxies connections properly:

$ PYTHONPATH=src python -m websockets ws://localhost:8080/
Connected to ws://localhost:8080/.
> Hello!
< Hello!
Connection closed: 1000 (OK).