#33738: ASGI http.disconnect not handled on requests with body.
------------------------------------------+------------------------
Reporter: Carlton Gibson | Owner: nobody
Type: New feature | Status: new
Component: HTTP handling | Version: 4.0
Severity: Normal | Keywords: ASGI
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
------------------------------------------+------------------------
Noticed whilst reviewing [https://github.com/django/django/pull/15704 PR
15704] for #33699, we're not handling the ASGI `http.disconnect` message
correctly. Since it's only dealt with whilst reading the request body,
`http.disconnect` is not processed on a request that includes a body.
https://github.com/django/django/blob/241fe59b74bb6031fa644f3ad55e6ad6a9187510/django/core/handlers/asgi.py#L189
{{{
async def read_body(self, receive):
"""Reads an HTTP body from an ASGI connection."""
# Use the tempfile that auto rolls-over to a disk file as it fills
up.
body_file = tempfile.SpooledTemporaryFile(
max_size=settings.FILE_UPLOAD_MAX_MEMORY_SIZE, mode="w+b"
)
while True:
message = await receive()
if message["type"] == "http.disconnect": # This is the only
place `http.disconnect` is handled.
body_file.close()
# Early client disconnect.
raise RequestAborted()
# Add a body chunk from the message, if provided.
if "body" in message:
body_file.write(message["body"])
# Quit out if that's the end.
if not message.get("more_body", False):
break
body_file.seek(0)
return body_file
}}}
`http.disconnect` is designed for long-polling — so we imagine a client
opening a request, with a request body, and then disconnecting before the
response is generated.
The protocol server (Daphne/uvicorn/...) will send the `http.diconnect`
message, but it's not handled.
This test fails on main (at 9f5548952906c6ea97200c016734b4f519520a64 — 4.2
pre-alpha)
{{{
diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py
index ef7b55724e..a68ca8a473 100644
--- a/tests/asgi/tests.py
+++ b/tests/asgi/tests.py
@@ -188,6 +188,18 @@ class ASGITest(SimpleTestCase):
with self.assertRaises(asyncio.TimeoutError):
await communicator.receive_output()
+ async def test_disconnect_with_body(self):
+ application = get_asgi_application()
+ scope = self.async_request_factory._base_scope(path="/")
+ communicator = ApplicationCommunicator(application, scope)
+ await communicator.send_input({
+ "type": "http.request",
+ "body": b"some body",
+ })
+ await communicator.send_input({"type": "http.disconnect"})
+ with self.assertRaises(asyncio.TimeoutError):
+ await communicator.receive_output()
+
async def test_wrong_connection_type(self):
application = get_asgi_application()
scope = self.async_request_factory._base_scope(path="/",
type="other")
}}}
To handle this correctly it looks like we'd need something like Channel's
[https://github.com/django/channels/blob/ae60a7d86f3655a1cc35cd9198e61fe5dcc5d1a1/channels/utils.py#L32
`await_many_dispatch()`] to keep receiving from the input queue whilst
dispatching the request. 🤔
--
Ticket URL: <https://code.djangoproject.com/ticket/33738>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/01070180f50c3d9f-321e8423-08c6-4b6b-8a53-ea07aa1871e1-000000%40eu-central-1.amazonses.com.