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)
@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.
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.
@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.
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.