I thought I’d keep a topic here that lists the current pieces of work needing to be done on the async views work, both for transparency and also if anyone wants to volunteer to help.
Merge async_views branch: Done
Make MiddlewareMixin async-aware: Done!
Make Signal async-aware: This will allow us to avoid forced sync_to_async calls in the async path for the request_started and request_finished signals
Add an async runserver option: The current runserver command uses a WSGI server to work; we should provide an option to run it in ASGI mode (optional so we don’t force Daphne or uvicorn as dependencies of Django)
Trying to add websockets to a Django 3.0 app I figure out the ASGIHandler don’t allow for this, just http (noticed the fix me:p).
We could modify this handler to use either an ASGIHttpHandler (built-in) or an ASGIWebSocketHandler (users could hook it throw settings) base on the scope. @andrewgodwi what are your thoughts in this?
@ordanis I don’t want to include websocket support in base Django - I think we should keep the implementation in Channels - so what we need to do is ensure we can somehow hook Channels into the request stack easily. Not sure if we should do that above ASGIHandler (maybe in asgi.py even) or not.
How would you go about adding an async runserver option? I started to look at the code for runserver, and I see that it’s mostly wrapping stdlib code for a WSGI server. I thought about looking at asyncio.start_server, but then I think that would require custom protocol class. Am I missing an easy solution here or is the answer a trimmed down server like uvicorn inside the django code base?
The only solution is to rely on an ASGI server (daphne or uvicorn), which is why we didn’t do it yet; there’s no ASGI server in the standard library, unfortunately, and the asyncio server protocol is way too low level.
For clarification, I’m able to run through uvicorn just fine to do some local dev. I was asking in relation to helping with:
Add an async runserver option : The current runserver command uses a WSGI server to work; we should provide an option to run it in ASGI mode (optional so we don’t force Daphne or uvicorn as dependencies of Django)
Is the suggestion here to have an option like ./manage.py runserver --asgi uvicorn?
I more mean that we would have to have Django depend on, or ship with, one of those servers, to make it fully integrated!
I do like your suggestion, though - allow people to install the server separately if they want it, and then have built-in code to configure it along with auto-reloading if you pass --asgi. We could definitely do that.
It adds support to the runserver command for daphne. It’s mostly a mashup of the current django runserver + what’s in channels’ runserver. I ignored uvicorn for the time being.
It also uses a get_internal_asgi_application, which is similar to get_internal_wsgi_application:
Additionally, it wraps the ASGIHandler in the ASGI3Middleware to make the asgi handler compatible with daphne:
I think that’s roughly the right pattern to follow, though I’d hope we can refactor and share most of that logic across the ASGI and WSGI paths.
My main concern would be stability under autoreload; async tends to be unhappy when its threads are mangled around. Does it seem relatively fine under high reload stress?
I think that’s roughly the right pattern to follow, though I’d hope we can refactor and share most of that logic across the ASGI and WSGI paths.
I agree. I wanted to err on the side of caution and just get it out. With some small method additions on the built in runserver command, it could be improved. I’ll try and make a pr and share it with you soon.
My main concern would be stability under autoreload; async tends to be unhappy when its threads are mangled around. Does it seem relatively fine under high reload stress?
As far as the stability goes, it’s somewhere between it works on my machine, and i presume it’s good enough in the channels codebase. I’ll keep an eye on this though. Thanks for pointing it out.
It looks like the StaticFilesHandlerMixin is missing the the async response function.
Without this, this is the traceback
Exception inside application: 'NoneType' object is not callable
Traceback (most recent call last):
File ".../lib/python3.7/site-packages/daphne/cli.py", line 30, in asgi
await self.app(scope, receive, send)
File ".../src/django/django/contrib/staticfiles/handlers.py", line 86, in __call__
return await super().__call__(scope, receive, send)
File ".../src/django/django/core/handlers/asgi.py", line 161, in __call__
response = await self.get_response_async(request)
File ".../src/django/django/core/handlers/base.py", line 148, in get_response_async
response = await self._middleware_chain(request)
TypeError: 'NoneType' object is not callable
I forgot to make it a pr to ask for a review on github. I’ll try and remember do that next time. Let me know if that seems right to you and it was just missing.
Yes, we haven’t needed it until now, so I suspect I never re-added it. Would be good to get that in there before we ship 3.1, if you want to cook up a PR and submit it for review.
Also, if you want to keep talking about this, might be best to start a new thread for it here so other people see the thread title if they want to jump in!
@andrewgodwin I completely agree with you on WebSockets being handled by channels or any other package. After analyzing it for quite a bit I came up with adding a custom ASGI app that defaults http requests to the ASGIHandler.
Nothing, really. You can write an ASGI application (not sure I want to call it middleware) that dispatches different protocols to different sub-applications - django or a custom one - if you want.
I asked around in some other threads and there was some issue in the request/response (or scope, endscope) situation where I’m not clear where to put the websocket connection so that it would:
Self-initiate
Be persistent over request ends and connection closes
But maybe that’s best left elsewhere.
I guess the relevant question for ASGI in general is: Can there be ASGI apps bundled together with Django that are long running async processes and don’t share request or context? And if so how.
That’s pretty much what Channels provides. It works by creating long-running apps that work under the Daphne ASGI server, but can communicate with Django through the channels infrastructure.
If you’re looking at initiating a websocket connection that shares information with your Django app, you can create a Django management command doing that. As a long-running process, those management commands can also use the channels layer to communicate with other parts of your system.