3

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.

1 Answer 1

2

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")
Sign up to request clarification or add additional context in comments.

3 Comments

Both of your ideas are working! But, to be honest, I don't understand why does it work. In previous versions of fastapi part of mounting path ("/ws") was added to socketio_path? And in newer versions they removed it and we have to mount fastapi route to full WebSocket path? Great thanks for your help, but I don't understand the reason why it works like that.
I'm not familiar with the implementation of the mount() function in FastAPI, but my guess is that they've made a breaking change in its implementation if the code in your question used to work with older versions. I honestly don't like the idea of having FastAPI at the top level and routing to Socket.IO. The ASGIApp class is a simpler and much more lightweight solution that routes anything that does not start with the Socket.IO path to the other_asgi_app, transparently and without changing any paths or anything else in the requests.
Thank you very much! Yeah, the way with ASGIApp look much more logical and understandable. Now it`s pretty clear what happened and how to deal with it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.