bottlepy / bottle

bottle.py is a fast and simple micro-framework for python web-applications.
http://bottlepy.org/
MIT License
8.45k stars 1.46k forks source link

Update 'Asynchronous Applications' documentation #1464

Open ZenenTreadwell opened 1 month ago

ZenenTreadwell commented 1 month ago

Happy to see Bottle get a version bump. I read through the patch notes and noticed added support for aiohttp and uvloop server adapters. I'm hoping to add a basic websocket implementation to my bottle server (because this project is the best), and I would like to use the right tool for the job.

According to this page of the bottle docs, the suggested solution is to use the gevent-websocket package. The library hasn't been updated in a while and I'm wondering if the solution you'd suggest has changed since the documentation was written. In this Github Issue defnull suggested using an asyncio server.

Given developments to asynchronous python libraries over the last few years, is there a more elegant solution to implementing asynchronous programming than is currently suggested by the documentation?


Also, as a higher level question - how much work would be required to adapt the current Bottle implementation to meet the newly established ASGI standard? Basically, is it within the scope of this project to fulfill that feature set or would it be a new thing entirely?

defnull commented 1 month ago

Using an highly efficient async servers (e.g. gunicorn) is easy, as long as the server still speaks WSGI to applications. WSGI is synchronous by design, though. Bottle is a WSGI framework. Supporting ASGI and async/await request handlers in Bottle would be so much work, it may be easier to just build a new framework from scratch. So, that won't happen any time soon. There are lots of nice (and very Bottle-like) ASGI frameworks out there.

Most applications do not need ASGI. Websockets are a very important exception, though. If you are fine with the synchronous nature of WSGI/Bottle and only need ASGI for websockets, then there are multiple options:

defnull commented 1 month ago

The gevent example still works, great. The last example is missing a gevent.monkey.patch_all() line though, which must happen before the first bottle import.

I just found out that the gevent-websocket library also ships with a ready-to-use gunicorn worker! When starting the bottle app via gunicorn -k "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" my_wsgi:app all the gevent setup code is no longer necessary. It just works.

I'll keep this issue open until I (or some one else?) find the time to update the documentation.

ZenenTreadwell commented 1 month ago

Great, thanks for the speedy response.

I think the FastAPI approach is a non-starter for me, the number of dependencies it would introduce adds way too much complexity to the project. I also found the greenlet/gevent stack to be rather heavyweight; I'll keep it in my back pocket but I'm pretty sure Python's websockets library is what I'm looking for.

I think the suggested approach of running an asynchronous server separately and reverse proxying to it makes the most sense to me. I'll need to find a good way of sharing application state between the two projects, or find a way to run them in a shared context.

ZenenTreadwell commented 1 month ago

Regarding that last point, I think we might be back to my original question. Since libraries like aiohttp or gunicorn (with gevent's websocket lib) provide support for web sockets out of the box, does that mean that it would be able to serve the bottle application and also a web sockets endpoint with a shared context? Or is that still subject to the limits of synchronous wsgi?

defnull commented 1 month ago

Yes, with gunicorn+gevent you write your entire app as if you'd have an unlimited amount of threads in your thread pool and with the geventwebsocket worker or adapter you also have a magic wsgi.websocket variable in your environment dictionary if the client is actually a websocket client. You can fetch that and blocking-read from it or blocking-write to it in a while-loop for as long as you want. Without gevent this would be bad because it blocks a thread for as long as the client is connected, with gevent you have unlimited threads so blocking one is no big deal.

With aiohttp you would use something like aiohttp-wsgi to mount a synchronous WSGI app into an asynchronous aiohttp-app. The aiohttp runtime would then create a normal thread pool and run WSGI actions in that pool, while everything else runs on the event loop using async/await. Both worlds are strictly separated, although they run in the same process and on top of the same server engine.

abersheeran commented 1 month ago

the number of dependencies it would introduce adds way too much complexity to the project

If you want to try an asynchronous web framework with no dependencies and support for WebSockets, you might want to try baize.

ZenenTreadwell commented 1 month ago

So, something that I didn't really consider until I found it saved in my GitHub stars: nchan

It's a nginx library which seems to handle all of the complicated stuff in a very elegant way. You can POST data to the /pub endpoint and the data is relayed to a websocket at the /sub endpoint; it seems really well suited to my use case of sending notifications to the client. I'm already using nginx as my reverse proxy so this was a really small addition - the library is under 1MB and appears to be a very robust solution.