Search code examples
pythonwebsocketfastapi

Pass data to websocket on POST request | FastAPI


I have an application that allows multiple bots to connect to it via /ws/{bot_id} endpoint. I'm storing all the connections inside websockets dict. When someone makes a POST request I want to pass some data to the websocket connection, but it's not letting me because the connection is already closed somehow:

RuntimeError: Unexpected ASGI message 'websocket.send', after sending 'websocket.close' or response already completed

import asyncio
from fastapi import FastAPI, WebSocket

app = FastAPI()
websockets: dict[int, WebSocket] = {}


@app.websocket("/ws/{bot_id}")
async def websocket_endpoint(websocket: WebSocket, bot_id: int):
    websockets[bot_id] = websocket
    await websocket.accept()


@app.post("/bot/{bot_id}/start_lobby")
async def start_lobby(bot_id: int):
    websocket = websockets.get(bot_id)
    await websocket.send_text("START")


@app.post("/bot/{bot_id}/quit_lobby")
async def destroy_lobby(bot_id: int):
    websocket = websockets.get(bot_id)
    await websocket.send_text("DELETE")

Here's the minimal code on a bot client that listens to everything (stdout is always empty):

def websocket_connection():
    with connect("ws://localhost:8000/ws/1") as websocket:
        while True:
            message = websocket.recv()
            if message == "START":
                start_lobby()
            elif message == "DELETE":
                destroy_lobby()
            websocket.send("BOT 1: RECEIVED")
            print(f"Received: {message}")

Solution

  • You need to keep open connection with client.

    Example to fix ws handler

    @app.websocket("/ws/{bot_id}")
    async def websocket_endpoint(websocket: WebSocket, bot_id: int):
        websockets[bot_id] = websocket
        await websocket.accept()
        while True:
            data = await websocket.receive()
            print(data)
    

    Logs:

    Server
    INFO:     127.0.0.1:49200 - "POST /bot/1/start_lobby HTTP/1.1" 200 OK
    {'type': 'websocket.receive', 'text': 'BOT 1: RECEIVED'}
    
    Client
    starting lobby
    Received: START