Open aaronn opened 4 years ago
Hi @aaronn, it's been one of my long term goals get whitenoise working natively wth asgi, but I just haven't had time to make any progress on this. It should certainly be easier now that I've dropped support for older Python versions and there's no issue with using the async
keywords. But right now I don't have any specific advice on getting this working I'm afraid.
@evansd @andrewgodwin
Does Whitenoise work with Django's Daphne server? I see various reference to using Whitenoise but am confused because Daphne is ASGI yet seems to recommend Whitenoise. However, Whitenoise indicates it doesn't work with ASGI.
It would be nice to move to Daphne instead of Gunicorn to make use of websockets but compatibility is contradictory.
It's possible I was mistaken, but I used to use Whitenoise in Django under ASGI since it interacted with the Django request interface, which we provide a synchronous emulation of. Not sure if it still works, but unless it somehow pierces though Django to get a WSGI handle, it should behave fine behind the synchronous emulation layer.
Of course, that requires Django; you can't use it directly with ASGI since it's a different interface. There's asgiref.wsgi
which provides a basic WSGI-to-ASGI adapter that it might work with, though.
@andrewgodwin Makes sense!
@evansd Is it correct that Whitenoise is compatible with Django served by ASGI if using middleware integration? Just barebones ASGI integration is unsupported?
Hi, yes DjangoWhitenoise acts as standard Django middleware so should run fine via the compatibility layer. I'd love to get native ASGI integration into Whitenoise but super busy with the medical day job at the moment and ENOTIME :)
Hi, I've had success in using WhiteNoiseMiddleware in an async context in Django, but I noticed that WhiteNoiseMiddleware does not inherit from MiddlewareMixin.
The consequence is that WhiteNoiseMiddleware does not get the full async compatibility layer from Django 3.1.
It would not be a full async support but maybe implementing sync_capable=True
and being able to call an async get_response
(and be called as async) could be a
useful improvement of WhiteNoiseMiddleware.
It would reduce the time spent in the sync emulation layer (which is a separate
thread with essentially zero concurrency because Django calls sync_to_async
with the thread_sensitive=True
option) and allow async views to pass through
directly when not serving a static file request.
Copying the behaviour of MiddlewareMixin, it could roughly look like this:
class AsyncWhitenoiseMiddleware(WhiteNoiseMiddleware):
sync_capable = True
async_capable = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._async_check()
def _async_check(self):
if asyncio.iscoroutinefunction(self.get_response):
self._is_coroutine = asyncio.coroutines._is_coroutine
def __call__(self, request):
if asyncio.iscoroutinefunction(self.get_response):
return self.__acall__(request)
else:
return super().__call__(request)
async def __acall__(self, request):
response = self.process_request(request)
if response is None:
response = await self.get_response(request)
return response
It's probably better to inherit from MiddlewareMixin instead of duplicating code.
In the previous code, process_request
is called directly, which means doing
disk IO in the main async thread. This is not ideal but probably not awful, I'd
guess static files are very likely to be quickly available from the system cache.
To add to this, what I have in my projects is the following:
@sync_and_async_middleware
def whitenoise_middleware(
get_response: Callable[[HttpRequest], HttpResponse | Coroutine[Any, Any, HttpResponse]],
):
mid = WhiteNoiseMiddleware(get_response)
def get_static_file(request: HttpRequest):
# This is copied from WhiteNoiseMiddleware.__call__
if mid.autorefresh:
static_file = mid.find_file(request.path_info)
else:
static_file = mid.files.get(request.path_info)
return mid.serve(static_file, request) if static_file is not None else None
if inspect.iscoroutinefunction(get_response):
aget_static_file = sync_to_async(get_static_file, thread_sensitive=False)
async def middleware(request): # type: ignore
response = await aget_static_file(request)
if response is not None:
return response
return await cast(Awaitable, get_response(request))
else:
def middleware(request):
response = get_static_file(request)
if response is not None:
return response
return get_response(request)
return middleware
It is using the new function middleware approach.
whitenoise can probably provide something similar and deprecate the use of WhiteNoiseMiddleware
I'm about to deploy a django app. I was originally going to use
uvicorn
with Django 3.0, but after seeingwhitenoise
doesn't work with asgi servers yet was thinking of just reverting back to wsgi.I just came across @tomchristie's pull request and he mentions he's able to (non-ideally) get it working with a wsgi-to-asgi adapter. I was wondering if there's any additional information on how to get that working.
Ideally I'd like to just swap that out whenever whitenoise goes asgi, instead of reverting from
uvicorn
togunicorn
.Thanks for the continued work on a great project!