Skip to main content

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:

  1. Sets the script prefix using set_script_prefix.
  2. Signals that a request has started.
  3. Instantiates a WSGIRequest using the environ dictionary provided by the server.
  4. 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 LimitedStream to read directly from the server's input stream.
  • ASGI: Uses read_body to consume chunks from the receive channel and write them into a tempfile.SpooledTemporaryFile. This file stays in memory until it exceeds settings.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_middleware populates the middleware chain, adapting each piece of middleware to be sync or async capable based on the handler's mode.
  • Response Resolution: get_response and get_response_async handle 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.