skip to Main Content

Trying to implement websockets in my fastapi application, however, when I connect to the websocket from the javascript side, it opens 4 connections, I have implemented a workaround in the backend side that check if the certain customer is connected, however this would mean that a client couldn’t connect to the websocket on mobile while it’s open on the computer for example.

class ConnectionManager:
    def __init__(self):
        self.connections: dict[int, WebSocket] = {}

    async def connect(self, websocket, customer_id):
        existing_connection = self.connections.get(customer_id)
        if existing_connection:
            await websocket.close()
            raise ExistingConnectionError("Existing connection for customer_id")

        await websocket.accept()
        websocket.customer_id = customer_id
        self.connections[customer_id] = websocket
        print(list(self.connections.values()))

    async def disconnect(self, websocket):
        customer_id = getattr(websocket, 'customer_id', None)
        if customer_id in self.connections:
            del self.connections[customer_id]

    async def broadcast(self, message):
        for websocket in self.connections.values():
            await websocket.send_json(message)

    async def broadcast_to_customer(self, customer_id, message):
        matching_connection = self.connections.get(customer_id)
        if matching_connection:
            await matching_connection.send_json(message)


connection_manager = ConnectionManager()
@router.websocket("/sock")
async def websocket_endpoint(websocket: WebSocket, customer_id: int):
    try:
        await connection_manager.connect(websocket, customer_id)
        while True:
            data = await websocket.receive_json()
    except WebSocketDisconnect:
        await connection_manager.disconnect(websocket)
    except ExistingConnectionError:
        print("Existing connection detected, rejecting the new connection")

Javascript:

let socket;

    if (!socket || socket.readyState !== WebSocket.OPEN) {
        socket = new WebSocket(`ws://localhost:3002/sock?customer_id=${customer_id}`);

        socket.onopen = () => {
            console.log('Connected to WebSocket server');
        };

        let i = 0;

        socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            console.log('Incoming data:', data);
            console.log('i:', i);
            i = i + 1;
        };
    }

Backend logs:

INFO:     connection open
INFO:     ('127.0.0.1', 33366) - "WebSocket /sock?customer_id=185" 403
Existing connection detected, rejecting the new connection
INFO:     connection rejected (403 Forbidden)
INFO:     connection closed
INFO:     ('127.0.0.1', 33368) - "WebSocket /sock?customer_id=185" 403
Existing connection detected, rejecting the new connection
INFO:     connection rejected (403 Forbidden)
INFO:     connection closed
INFO:     ('127.0.0.1', 33384) - "WebSocket /sock?customer_id=185" 403
Existing connection detected, rejecting the new connection
INFO:     connection rejected (403 Forbidden)
INFO:     connection closed

Frontend logs:

Connected to WebSocket server
Firefox can’t establish a connection to the server at ws://localhost:3002/sock?customer_id=185.
Firefox can’t establish a connection to the server at ws://localhost:3002/sock?customer_id=185.
Firefox can’t establish a connection to the server at ws://localhost:3002/sock?customer_id=185.

Tried to implement websocket in FastAPI, opens multiple connections instead of one, for each client.

2

Answers


  1. The issue might be related to how you are checking for an existing WebSocket connection on the frontend.

    In your JavaScript code, you are creating a new WebSocket connection every time without checking if a connection already exists. To ensure that only one WebSocket connection is established, you should handle the WebSocket creation logic differently. One approach is to create a WebSocket only if it doesn’t exist or if it is not in the open state.
    example modification to your JavaScript code:

    let socket;
    
    // Check if the socket is undefined or not open
    if (!socket || socket.readyState !== WebSocket.OPEN) {
        // Create a new WebSocket connection
        socket = new WebSocket(`ws://localhost:3002/sock?customer_id=${customer_id}`);
    
        socket.onopen = () => {
            console.log('Connected to WebSocket server');
        };
    
        socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            console.log('Incoming data:', data);
        };
    
        socket.onclose = (event) => {
            console.log('WebSocket closed:', event);
        };
    }
    

    This modification ensures that a new WebSocket connection is created only if there isn’t an existing connection or if the existing connection is not in the open state.

    You may want to handle the onclose event to handle cases where the WebSocket connection is closed unexpectedly.

    Login or Signup to reply.
  2. I think you need something like this:

    On the backend store connections for every device of every client:

    class ConnectionManager:
        def __init__(self):
            self.clients: dict[int, dict[str, WebSocket]] = {}
    
        async def connect(self, websocket, customer_id, device_hash):
            client_connections = self.clients.get(customer_id)
            if client_connections:
                client_device_connection = client_connections.get(device_hash)
                if client_device_connection:
                    await websocket.close()
                    raise ExistingConnectionError("Existing connection for customer_id")
            else:
                self.clients[customer_id] = {}
    
            await websocket.accept()
            websocket.customer_id = customer_id
            websocket.device_hash = device_hash
    
            self.clients[customer_id][device_hash] = websocket
            print(list(self.clients.values()))
    
        async def disconnect(self, websocket):
            customer_id = getattr(websocket, 'customer_id', None)
            device_hash = getattr(websocket, 'device_hash', None)
            client_connections = self.clients.get(customer_id)
            if client_connections:
                if client_connections.get(device_hash):
                    client_connections.pop(device_hash)
    
        async def broadcast(self, message):
            for client_connections in self.clients.values():
                for websocket in client_connections.values():
                    await websocket.send_json(message)
    
        async def broadcast_to_customer(self, customer_id, message):
            client_connections = self.client.get(customer_id)
            if client_connections:
                for connection in client_connections.values():
                    await connection.send_json(message)
    
    
    connection_manager = ConnectionManager()
    
    @app.websocket("/sock")
    async def websocket_endpoint(websocket: WebSocket, customer_id: int, device_hash: str):
    
        try:
            await connection_manager.connect(websocket, customer_id, device_hash)
            while True:
                data = await websocket.receive_json()
        except WebSocketDisconnect:
            await connection_manager.disconnect(websocket)
        except ExistingConnectionError:
            print("Existing connection detected, rejecting the new connection")
    

    When you connect to websocket from frontend, add additional query parameter device_hash, which should be unique for every user device.
    You can also generate this device_hash on the server side from request headers (e.g. you can use user-agent)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search