The Wayback Machine - https://web.archive.org/web/20200718064712/https://github.com/tiangolo/fastapi/issues/129
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] Use of socket.io #129

Open
siedi opened this issue Mar 31, 2019 · 23 comments
Open

[QUESTION] Use of socket.io #129

siedi opened this issue Mar 31, 2019 · 23 comments

Comments

@siedi
Copy link

@siedi siedi commented Mar 31, 2019

Description

How can I use socket.io instead of the plain websocket integration? I guess this is more a question belonging to starlette.

Currently migrating from a flask application using flask-socketio / python-socketio

Any hint is appreciated. Thx.

@siedi siedi added the question label Mar 31, 2019
@tiangolo
Copy link
Owner

@tiangolo tiangolo commented Mar 31, 2019

@marcus-oscarsson
Copy link

@marcus-oscarsson marcus-oscarsson commented Apr 20, 2019

Hi,

Also have a question regarding python-socketio, Ive tried:

sio = socketio.AsyncServer(async_mode='asgi')
sio_asgi_app = socketio.ASGIApp(sio, app)
app.mount("/api/socket.io", sio_asgi_app)

Which does indeed seem to work, but only partially. Only GET requests seems to get through
and POST's are somehow blocked, I get:

INFO:uvicorn:('127.0.0.1', 51888) - "GET /api/socket.io/ HTTP/1.1" 200
INFO:uvicorn:('127.0.0.1', 51890) - "POST /api/socket.io/ HTTP/1.1" 405

Is there a way to also allow POST on /api/socket.io/ ?

Thanks

@BlackHoleFox
Copy link

@BlackHoleFox BlackHoleFox commented Apr 21, 2019

I am unable to get this to work at all, even with the snippet above.

@marcus-oscarsson
Copy link

@marcus-oscarsson marcus-oscarsson commented Apr 22, 2019

I see, here is my complete example,

    app = FastAPI(debug=True)
    sio = socketio.AsyncServer(async_mode='asgi')
    sio_asgi_app = socketio.ASGIApp(sio, app, socketio_path="/api/socket.io")

    app.mount("/api/socket.io", sio_asgi_app)
    sio.register_namespace(ws.ConnectNS('/'))

Where the ws module contains:


class ConnectNS(socketio.AsyncNamespace):
    def on_connect(self, sid, environ):
        logging.debug("Websocket connected %s", sid)

    def on_disconnect(self, sid):
        logging.debug("Websocket disconnected %s" % sid)

I then run uvicorn with the app object, running it with sio_asgi_app works fine but the FastAPI app does not receive any traffic.

It seems that the routing mechanism in either FastAPI ot Starlette somehow only allows GET requests, but I might be wrong and I'm maybe doing something else wrong.

@BlackHoleFox: it did not work at all for me either until I noticed that second parameter of ASGIApp, which gets me to the currently partially working state ...

@marcus-oscarsson
Copy link

@marcus-oscarsson marcus-oscarsson commented Apr 22, 2019

Finally,

    sio = socketio.AsyncServer(async_mode='asgi')
    sio_asgi_app = socketio.ASGIApp(sio, app, socketio_path="/api/socket.io")
    sio.register_namespace(ws.ConnectNS('/'))

Works just fine if you use the ASGIApp object, It might maybe have other consequences but it seems
to work fine for me so far. I had some other issues with the routing in my example application making it fail the first time i tried. I never got app.mount("/api/socket.io", sio_asgi_app) to work properly though.

@BlackHoleFox
Copy link

@BlackHoleFox BlackHoleFox commented Apr 22, 2019

@marcus-oscarsson I ended up with the same results. I can directly use the ASGIApp with Uvicorn but the mounting method never passes through the requests properly and my client gets a Unexpected response from server whenever it attempts to connect. Seems the solution for me at the moment will be to run the socket backend and REST backend separately.

@marcus-oscarsson
Copy link

@marcus-oscarsson marcus-oscarsson commented Apr 23, 2019

@BlackHoleFox I see, I simply removed the app.mount(...) and I let the ASGIApp handle the rest. It works fine for me all running in the same process. So finally using the ASGIApp is a working solution for me.

@kientt86
Copy link

@kientt86 kientt86 commented May 3, 2019

So are we giving up on app.mount(...)? ASGIApp is kind of work but it ends up with maintaining 2 applications
Any idea to go further on this?

@marcus-oscarsson
Copy link

@marcus-oscarsson marcus-oscarsson commented May 6, 2019

@kientt86 I would say that its up to the maintainer, @tiangolo, to decide exactly what to do with app.mount. I'm not sure if I used it correctly in my example so it could also simply be that I'm missing some option. It would be nice if it worked with app.mount since it would be more consistent.

Using the ASGIApp otherwise seems to work fine and one would still need to create and somehow handle it so I don't really see an issue with it

@tiangolo
Copy link
Owner

@tiangolo tiangolo commented May 10, 2019

Thanks for all the reports guys.

I want to check how to integrate Socket.IO and integrate it directly in the docs, I haven't had the time though, but I'll do it hopefully soon.

@rudmac
Copy link

@rudmac rudmac commented Jun 1, 2019

I´m looking forward for this integrate. It would be great for the project.

cheers!

@databasedav
Copy link

@databasedav databasedav commented Jul 23, 2019

This is more of a feature request but related to using python-socketio. flask-socketio comes with a test_client that makes testing very convenient (no need to manually spin up a server in a separate process, can emit events with callbacks, stores messages received from the server, etc.). Comparatively, starlette.testclient.TestClient is very basic.

Hoping this can be implemented on top of the general socket.io support. I wouldn't mind working on a PR for this, but would require some direction. Thanks!

@szelenka
Copy link

@szelenka szelenka commented Aug 5, 2019

I'm struggling with getting this to work with FastAPI. I've tried this code:

fast_app = FastAPI(
    openapi_url='/api/notifications/openapi.json',
    docs_url='/api/notifications/docs',
    redoc_url='/api/notifications/redoc'
)
sio = socketio.AsyncServer(
    async_mode='asgi',
    cors_allowed_origins='*'
)
app = socketio.ASGIApp(
    socketio_server=sio,
    other_asgi_app=fast_app,
    socketio_path='/api/notifications/socket.io/'
)

Which allows me to connect to the socket.io endpoint, but when I attempt to connect to a FastAPI defined endpoint, it'll raise a traceback from python-engineio:

INFO: ('127.0.0.1', 64336) - "GET /api/notifications/openapi.json HTTP/1.1" 500
ERROR: Exception in ASGI application
Traceback (most recent call last):
  File "/Users/szelenka/anaconda3/envs/python37/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 368, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/szelenka/anaconda3/envs/python37/lib/python3.7/site-packages/uvicorn/middleware/debug.py", line 96, in __call__
    raise exc from None
  File "/Users/szelenka/anaconda3/envs/python37/lib/python3.7/site-packages/uvicorn/middleware/debug.py", line 78, in __call__
    await self.app(scope, receive, inner_send)
  File "/Users/szelenka/anaconda3/envs/python37/lib/python3.7/site-packages/engineio/async_drivers/asgi.py", line 53, in __call__
    await self.other_asgi_app(scope, receive, send)
TypeError: __call__() takes 2 positional arguments but 4 were given

As best I can tell, engineio is attempting to call FastAPI with 4 parameters ('self', 'scope', 'receive', and 'send') .. while 0.33 version of FastAPI is simply using the Starlette call method that only accepts ('self' and 'scope').

I'm interested to learn what version other people (@marcus-oscarsson ) are using where this works inside FastAPI app.

I've managed to hack around it with this helper class:

from fastapi import FastAPI
from starlette.types import ASGIInstance, Scope, Receive, Send


class FastAPISocketIO(FastAPI):
    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> ASGIInstance:
        fn = super(FastAPISocketIO, self).__call__(scope=scope)
        return await fn(receive=receive, send=send)

But it seems the problem is an incompatibility with Starlette and python-socketio?

@szelenka
Copy link

@szelenka szelenka commented Aug 6, 2019

Turns out my problem was with Starlette.

Starlette version 0.11.1 needed the above hack, while version 0.12.0 doesn't have the problem and works without the above hack.

@mjmare
Copy link

@mjmare mjmare commented Oct 30, 2019

A bit more complete example, based on @szelenka's work that might help other people.
I don't like that it uses two apps but it seems to work.

'''
Run with:
uvicorn app_uvicorn_fastapi:app --reload --port 5000

'''
from starlette.staticfiles import StaticFiles
from fastapi import FastAPI
import uvicorn
import socketio

# There is some complexity in setting up Fastapi combined with socketio
# https://github.com/tiangolo/fastapi/issues/129

fast_app = FastAPI(
    openapi_url='/docs/openapi.json',
    docs_url='/docs/docs',
    redoc_url='/docs/redoc'
)
sio = socketio.AsyncServer(
    async_mode='asgi',
    cors_allowed_origins='*'
)
app = socketio.ASGIApp(
    socketio_server=sio,
    other_asgi_app=fast_app,
    socketio_path='/socket.io/'
)

@sio.event
def connect(sid, environ):
    print("connect ", sid, environ)

@sio.event
async def chat_message(sid, data):
    print("message ", data)
    await sio.emit('reply', room=sid)

@sio.event
def disconnect(sid):
    print('disconnect ', sid)


#  File serving, api serving by Fastapi etc

fast_app.mount("/static", StaticFiles(directory="static"), name="static")

@fast_app.get("/hello")
async def root():
    return {"message": "Hello World"}
@GustavoCarvalho
Copy link

@GustavoCarvalho GustavoCarvalho commented Oct 30, 2019

Any news? I look forward to it working.

@rudmac
Copy link

@rudmac rudmac commented Nov 19, 2019

@mjmare Very good... that's works perfectly using uvicorn.

But with gunicorn WSGI got some errors:

[2019-11-19 14:50:51 +0000] [23] [ERROR] Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 146, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
  File "/usr/local/lib/python3.7/site-packages/uvicorn/middleware/message_logger.py", line 58, in __call__
    raise exc from None
  File "/usr/local/lib/python3.7/site-packages/uvicorn/middleware/message_logger.py", line 54, in __call__
    await self.app(scope, inner_receive, inner_send)
  File "/usr/local/lib/python3.7/site-packages/engineio/async_drivers/asgi.py", line 46, in __call__
    await self.engineio_server.handle_request(scope, receive, send)
  File "/usr/local/lib/python3.7/site-packages/socketio/asyncio_server.py", line 326, in handle_request
    return await self.eio.handle_request(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/engineio/asyncio_server.py", line 297, in handle_request
    r['response'], environ)
  File "/usr/local/lib/python3.7/site-packages/engineio/async_drivers/asgi.py", line 168, in make_response
    'headers': headers})
  File "/usr/local/lib/python3.7/site-packages/uvicorn/middleware/message_logger.py", line 49, in inner_send
    await send(message)
  File "/usr/local/lib/python3.7/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 213, in asgi_send
    raise RuntimeError(msg % message_type)
RuntimeError: Expected ASGI message 'websocket.send' or 'websocket.close', but got 'http.response.start'.

After some testing, I'm using this code:

app = FastAPI(
    title=config.PROJECT_NAME,
    description=config.PROJECT_NAME,
    version=config.PROJECT_VERSION,
    debug=True
)

sio = socketio.AsyncServer(
    async_mode='asgi',
    cors_allowed_origins=','.join(config.ALLOW_ORIGIN)
)

sio_asgi_app = socketio.ASGIApp(
    socketio_server=sio,
    other_asgi_app=app
)

app.add_route("/socket.io/", route=sio_asgi_app, methods=['GET', 'POST'])
app.add_websocket_route("/socket.io/", sio_asgi_app)

kind off working with gunicorn...

sometimes, got the error above...

I will keep trying and let you guys know...

Cheers

@acnebs
Copy link

@acnebs acnebs commented Nov 26, 2019

Can anyone provide some insight into whether they've gotten this to work well with a synchronous database connection (I'm using SQLAlchemy ORM) and what it takes to ensure thread-safety?

@shawnd
Copy link

@shawnd shawnd commented Dec 11, 2019

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

kind off working with gunicorn...
sometimes, got the error above...

I'm receiving the same error with running using Gunicorn. Running with Uvicorn doesn't get me the same response.

@antnieszka
Copy link

@antnieszka antnieszka commented Jan 9, 2020

I'm struggling with the same problem :( If I have 4 workers (gunicorn -w 4 ...) this error almost always happens. For 1 worker it rarely (or maybe never) does.

@jackdh
Copy link

@jackdh jackdh commented Jan 19, 2020

Has there been any update on this? Still struggling to find a complete working example. Thanks.

@dmontagu
Copy link
Collaborator

@dmontagu dmontagu commented Jan 21, 2020

There are a lot of different pieces in play here (gunicorn, uvicorn, starlette, fastapi, socket.io, python, ...), each of which is potentially relevant to figuring out if there is a problem.

My strong suspicion is that this issue is not specific to FastAPI, but rather is a problem with uvicorn or starlette. It's probably worth trying to replicate the problem using just starlette and/or creating an issue in the starlette repo asking for guidance.

At any rate, if you are having an issue and want help, please post the versions of each of the various dependencies you are using. That may not be enough to help resolve the issue, but as things stand it's basically impossible to help without knowing more.

@hillairet
Copy link

@hillairet hillairet commented Apr 29, 2020

I had the same problem using the given example so I started from the python-socketio asgi example and added fastapi and it works 🎉

I upgraded a few packages compared to the example and here is my requirements.txt:

Click==7.0
fastapi==0.54.1
h11==0.8.1
httptools==0.1.1
pydantic==1.5.1
python-engineio==3.12.1
python-socketio==4.5.1
six==1.11.0
starlette==0.13.2
uvicorn==0.11.5
uvloop==0.14.0
websockets==8.1

And then the code is the example app.py mounted in a fastapi app (partial copy):

#!/usr/bin/env python 
import socketio    
from fastapi import FastAPI    
    
    
app = FastAPI()    
    
sio = socketio.AsyncServer(async_mode='asgi')    
socket_app = socketio.ASGIApp(sio, static_files={'/': 'app.html'})    
background_task_started = False    
    
    
async def background_task():    
<... EXACTLY LIKE app.py IN python-socketio/examples/server/asgi ...>

@sio.on('disconnect')
def test_disconnect(sid):
    print('Client disconnected')


@app.get("/hello")
async def root():
    return {"message": "Hello World"}


app.mount('/', socket_app)

Then used uvicorn fast_app:app --reload --host 0.0.0.0 and all good. All features seem to work properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.