summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorth3nn3ss <chuksmcdennis@yahoo.com>2022-12-21 14:25:24 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-04-03 14:01:48 +0200
commit1d1ddffc27cd55c011298cd09bfa4de3fa73cf7a (patch)
treec77c385a04bb16b84bab217258356cc480a85abc /django
parent4e4eda6d6c8a5867dafd2ba9167ad8c064bb644a (diff)
downloaddjango-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.py41
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."""