diff options
author | th3nn3ss <chuksmcdennis@yahoo.com> | 2022-12-21 14:25:24 -0500 |
---|---|---|
committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-04-03 14:01:48 +0200 |
commit | 1d1ddffc27cd55c011298cd09bfa4de3fa73cf7a (patch) | |
tree | c77c385a04bb16b84bab217258356cc480a85abc /django | |
parent | 4e4eda6d6c8a5867dafd2ba9167ad8c064bb644a (diff) | |
download | django-1d1ddffc27cd55c011298cd09bfa4de3fa73cf7a.tar.gz |
Fixed #33738 -- Allowed handling ASGI http.disconnect in long-lived requests.
Diffstat (limited to 'django')
-rw-r--r-- | django/core/handlers/asgi.py | 41 |
1 files changed, 38 insertions, 3 deletions
diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py index 569157b277..846bece39b 100644 --- a/django/core/handlers/asgi.py +++ b/django/core/handlers/asgi.py @@ -1,3 +1,4 @@ +import asyncio import logging import sys import tempfile @@ -177,15 +178,49 @@ class ASGIHandler(base.BaseHandler): body_file.close() await self.send_response(error_response, send) return - # Get the response, using the async mode of BaseHandler. + # Try to catch a disconnect while getting response. + tasks = [ + asyncio.create_task(self.run_get_response(request)), + asyncio.create_task(self.listen_for_disconnect(receive)), + ] + done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + done, pending = done.pop(), pending.pop() + # Allow views to handle cancellation. + pending.cancel() + try: + await pending + except asyncio.CancelledError: + # Task re-raised the CancelledError as expected. + pass + try: + response = done.result() + except RequestAborted: + body_file.close() + return + except AssertionError: + body_file.close() + raise + # Send the response. + await self.send_response(response, send) + + async def listen_for_disconnect(self, receive): + """Listen for disconnect from the client.""" + message = await receive() + if message["type"] == "http.disconnect": + raise RequestAborted() + # This should never happen. + assert False, "Invalid ASGI message after request body: %s" % message["type"] + + async def run_get_response(self, request): + """Get async response.""" + # Use the async mode of BaseHandler. response = await self.get_response_async(request) response._handler_class = self.__class__ # Increase chunk size on file responses (ASGI servers handles low-level # chunking). if isinstance(response, FileResponse): response.block_size = self.chunk_size - # Send the response. - await self.send_response(response, send) + return response async def read_body(self, receive): """Reads an HTTP body from an ASGI connection.""" |