WSGI and ASGI Request Handling Internals
In this project, handlers serve as the critical bridge between the web server (WSGI or ASGI) and Django's internal request/response logic. They are responsible for translating server-specific environment data into a standardized HttpRequest object and, conversely, converting Django's HttpResponse back into a format the server can transmit.
WSGI Request Handling
The WSGI implementation relies on WSGIHandler and WSGIRequest, which operate within the synchronous paradigm defined by the WSGI specification.
WSGIHandler and the Call Lifecycle
WSGIHandler inherits from BaseHandler and implements the standard WSGI application interface via its __call__ method. When a request arrives, the handler:
- Sets the script prefix using
set_script_prefix. - Signals that a request has started.
- Instantiates a
WSGIRequestusing theenvirondictionary provided by the server. - Dispatches the request through the middleware chain via
self.get_response(request).
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
# ... response processing ...
start_response(status, response_headers)
return response
WSGIRequest and LimitedStream
WSGIRequest adapts the environ dictionary into the HttpRequest interface. A key design choice here is the use of LimitedStream to wrap wsgi.input. This prevents the application from reading past the CONTENT_LENGTH specified in the headers, which is a common requirement for stability in WSGI environments.
# django/core/handlers/wsgi.py
class LimitedStream(IOBase):
def __init__(self, stream, limit):
self._read = stream.read
self._pos = 0
self.limit = limit
def read(self, size=-1, /):
_pos = self._pos
limit = self.limit
if _pos >= limit:
return b""
# ... logic to cap size at limit - _pos ...
data = self._read(size)
self._pos += len(data)
return data
ASGI Request Handling
The ASGI implementation, found in django/core/handlers/asgi.py, handles the asynchronous nature of modern web protocols. It manages a more complex lifecycle involving multiple messages over receive and send channels.
ASGIHandler and Async Lifecycle
ASGIHandler manages the request-response cycle asynchronously. Unlike WSGI, which receives the entire environment at once, ASGI handlers must explicitly read the request body from the receive channel.
The handler uses an asyncio.TaskGroup to concurrently run the response generation and a listener for client disconnects. If the client disconnects before the response is sent, a RequestAborted exception is raised to terminate processing early.
# django/core/handlers/asgi.py
async def handle(self, scope, receive, send):
# ... read body ...
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(self.listen_for_disconnect(receive))
response = await self.run_get_response(request)
await self.send_response(response, send)
raise RequestProcessed
except* (RequestProcessed, RequestAborted):
pass
ASGIRequest and Header Normalization
ASGIRequest translates the ASGI scope into the HttpRequest interface. Because ASGI headers are provided as a list of byte tuples, ASGIRequest performs normalization, such as converting content-length to CONTENT_LENGTH and prefixing other headers with HTTP_.
To prevent security issues like header spoofing, the implementation explicitly ignores headers containing underscores:
# django/core/handlers/asgi.py
for name, value in self.scope.get("headers", []):
name = name.decode("latin1")
if "_" in name:
continue
# ... normalization logic ...
Request Adaptation and Path Resolution
Both handlers must resolve the request path and script name (the prefix where the application is "mounted").
Path Resolution Logic
In WSGI, get_script_name and get_path_info handle complexities like Apache's mod_rewrite by checking SCRIPT_URL or REDIRECT_URL. In ASGI, get_script_prefix primarily looks at the root_path in the scope. Both implementations respect the FORCE_SCRIPT_NAME setting if provided.
# django/core/handlers/asgi.py
def get_script_prefix(scope):
if settings.FORCE_SCRIPT_NAME:
return settings.FORCE_SCRIPT_NAME
return scope.get("root_path", "") or ""
Body Handling
- WSGI: Uses
LimitedStreamto read directly from the server's input stream. - ASGI: Uses
read_bodyto consume chunks from thereceivechannel and write them into atempfile.SpooledTemporaryFile. This file stays in memory until it exceedssettings.FILE_UPLOAD_MAX_MEMORY_SIZE, at which point it rolls over to disk.
Response Transmission
The final stage of the handler's job is transmitting the HttpResponse back to the server.
WSGI Response
WSGIHandler calls the start_response callable provided by the server and returns the response object (or a file wrapper if the server supports wsgi.file_wrapper).
ASGI Response
ASGIHandler.send_response iterates over the response content and sends it in chunks (defaulting to 64KB via chunk_size = 2**16) using the http.response.body message type. For StreamingHttpResponse, it consumes the asynchronous iterator and sends each part as a separate message.
# django/core/handlers/asgi.py
async def send_response(self, response, send):
# ... send http.response.start ...
if response.streaming:
async with aclosing(aiter(response)) as content:
async for part in content:
for chunk, _ in self.chunk_bytes(part):
await send({"type": "http.response.body", "body": chunk, "more_body": True})
await send({"type": "http.response.body"})
else:
# ... chunked send for standard response ...
Shared Logic in BaseHandler
Both WSGIHandler and ASGIHandler inherit from django.core.handlers.base.BaseHandler. This base class is responsible for:
- Middleware Loading:
load_middlewarepopulates the middleware chain, adapting each piece of middleware to be sync or async capable based on the handler's mode. - Response Resolution:
get_responseandget_response_asynchandle the actual resolution of the URL to a view and the execution of that view within the middleware context. - Exception Handling: Converting unhandled exceptions into appropriate HTTP responses (e.g., 500 errors).
By centralizing this logic in BaseHandler, the project ensures that the core request-response flow remains consistent regardless of whether the application is deployed via WSGI or ASGI.