Search code examples
websocketfastapiuvicornstarlettepython-socketio

FastAPI RuntimeError: Expected ASGI message 'websocket.accept', 'websocket.close', or 'websocket.http.response.start' but got 'http.response.start'


I have server like:

main.py


import socketio
from fastapi import FastAPI

app = FastAPI()
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
sio_app = socketio.ASGIApp(socketio_server=sio, socketio_path="socket.io")
app.mount("/ws", sio_app)


@sio.on("connect")
async def handle_connect(sid, *args, **kwargs):
    await sio.emit("msg", "Test msg from FastAPI")

Launch server with uvicorn main:app --reload --host 0.0.0.0

And I try to connect to it with postman with ws://127.0.0.1:8000/ws/socket.io/?EIO=4&transport=websocket

or curl -v "http://127.0.0.1:8000/ws/socket.io/?EIO=4"

On fastapi version 0.108 and lower - I receive 127.0.0.1:58484 - "GET /socket.io/?EIO=4 HTTP/1.1" 200 OK with both

But if I try to use fastapii ^0.109:

  1. For curl I receive "GET /ws/socket.io/?EIO=4 HTTP/1.1" 404 Not Found
  2. For Postman even worse:
Exception in ASGI application
Traceback (most recent call last):
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 242, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/middleware/errors.py", line 151, in __call__
    await self.app(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 762, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 782, in app
    await route.handle(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/routing.py", line 485, in handle
    await self.app(scope, receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/engineio/async_drivers/asgi.py", line 77, in __call__
    await self.not_found(receive, send)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/engineio/async_drivers/asgi.py", line 125, in not_found
    await send({'type': 'http.response.start',
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/starlette/_exception_handler.py", line 50, in sender
    await send(message)
  File "/Users/eugene/Library/Caches/pypoetry/virtualenvs/websocket-test-j4LebyL2-py3.12/lib/python3.12/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 319, in asgi_send
    raise RuntimeError(msg % message_type)
RuntimeError: Expected ASGI message 'websocket.accept', 'websocket.close', or 'websocket.http.response.start' but got 'http.response.start'.
  1. I also tried to create html file and connect to socket
<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>
<body>
    
    <script>
        const socket = io("http://127.0.0.1:8000", { path: "/ws/socket.io" , transports: ["websocket"]});

        socket.on("connect", () => {
            console.log("Connected to WebSocket server!");
        });
        socket.on("disconnect", () => {
            console.log("Disconnected from WebSocket server");
        });
    </script>
</body>
</html>

On fastapi 0.108 I receive hello message from Fastapi enter image description here On 0.109+ - server error like with Postman

As far as I can see there something wrong with routes, but I can't get what exactly. Any ideas?

I want to find a problem and understand - should I downgrade fastapi to 0.108 to work with websockets or I can fix it somehow.


Solution

  • You are using FastAPI's mount mechanism to combine the two applications into one. I'm not very familiar with it, but one thing you can try is to set the socketio_path to /ws/socket.io, which is in fact what you are using.

    Alternatively, you can use the ASGIApp class itself to create the combined app instead of mount, which is a cleaner solution that keeps the two apps separate. That would be:

    fastapi_app = FastAPI()
    sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
    app = socketio.ASGIApp(socketio_server=sio, other_asgi_app=fastapi_app, socketio_path="/ws/socket.io")