Skip to main content

Handling HTTP Requests

In this codebase, HTTP requests are encapsulated by the HttpRequest class, which serves as the central interface for accessing metadata, parameters, and content negotiation details. This implementation relies on specialized data structures like QueryDict for parameter handling and HttpHeaders for normalized header access.

The Request Object

The HttpRequest class (found in django/http/request.py) is the primary object representing an incoming HTTP request. It is initialized with several key attributes:

  • GET and POST: Instances of QueryDict containing URL parameters and form data.
  • COOKIES: A standard dictionary of cookies.
  • META: A dictionary containing all available HTTP headers and server environment variables (e.g., REMOTE_ADDR, SERVER_NAME).
  • FILES: A MultiValueDict containing uploaded files.

Lazy Loading and Encoding

The GET and POST attributes are populated lazily. If you change the encoding property of the HttpRequest after these dictionaries have been accessed, the existing dictionaries are deleted and recreated on the next access to ensure they are decoded correctly:

# From django/http/request.py
@encoding.setter
def encoding(self, val):
self._encoding = val
if hasattr(self, "GET"):
del self.GET
if hasattr(self, "_post"):
del self._post

Handling Multi-Value Parameters with QueryDict

Standard Python dictionaries do not support multiple values for a single key, which is common in HTTP requests (e.g., ?tags=python&tags=django). The QueryDict class solves this by subclassing MultiValueDict.

Immutability

By default, QueryDict instances created from request data are immutable. Attempting to modify them directly will raise an AttributeError. To modify parameters, you must create a mutable copy:

# Example based on tests/httpwrappers/tests.py
q = QueryDict("a=1&a=2&c=3")
try:
q["d"] = "4"
except AttributeError:
# This is expected as QueryDict is immutable by default
q_mutable = q.copy()
q_mutable["d"] = "4"

Accessing Values

Because a key can have multiple values, QueryDict provides two ways to retrieve data:

  1. __getitem__: Returns the last value for a key.
  2. getlist(key): Returns a list of all values for the key.
q = QueryDict("a=1&a=2")
q["a"] # Returns "2"
q.getlist("a") # Returns ["1", "2"]

Normalized Header Access

The HttpHeaders class provides a case-insensitive mapping for accessing request headers. It is accessible via the request.headers property.

In the underlying META dictionary, headers are typically prefixed with HTTP_ and use underscores (e.g., HTTP_USER_AGENT). HttpHeaders normalizes these so they can be accessed using standard HTTP header names or underscore-based names:

# Both of these work via request.headers
user_agent = request.headers["User-Agent"]
user_agent = request.headers["user_agent"]

# The underlying META still uses the WSGI/ASGI format
raw_ua = request.META["HTTP_USER_AGENT"]

The HttpHeaders.parse_header_name method handles this conversion by stripping the HTTP_ prefix and converting underscores to hyphens.

Content Negotiation

The HttpRequest class includes built-in support for content negotiation using the MediaType class. This allows you to determine what formats the client prefers based on the Accept header.

Checking Preferences

You can check if a client accepts a specific media type or find the best match from a list of options:

# Example based on tests/requests_tests/test_accept_header.py
request.META["HTTP_ACCEPT"] = "text/html,application/xhtml+xml,application/xml;q=0.9"

# Check specific type
if request.accepts("text/html"):
# Client accepts HTML
pass

# Get preferred type from options
preferred = request.get_preferred_type(["application/json", "text/html"])
# preferred will be "text/html"

The MediaType class handles the complexity of parsing quality values (q=0.9) and specificity (e.g., text/html is more specific than text/*).

Request Body and Streams

Accessing the raw request body is handled via the body property or by reading from the request as a stream.

Body Access Constraints

The body property reads the entire request into memory. This is protected by the DATA_UPLOAD_MAX_MEMORY_SIZE setting. If the request exceeds this size, a RequestDataTooBig exception is raised.

Warning: You cannot access request.body after you have started reading from the request's data stream (via read() or readline()), as this would require seeking the stream back to the beginning, which is not always possible.

Error Handling

If the client disconnects or the network connection is interrupted while reading the POST data, the system raises an UnreadablePostError. This is a subclass of OSError used specifically for stream failures during request processing.

try:
data = request.read()
except UnreadablePostError:
# Handle client disconnect
pass

Host and URI Construction

The HttpRequest provides methods to safely reconstruct the full URL or host of the request, respecting security settings like ALLOWED_HOSTS.

  • get_host(): Returns the host, checking HTTP_X_FORWARDED_HOST (if enabled) and validating against ALLOWED_HOSTS.
  • build_absolute_uri(location): Converts a relative path into an absolute URI using the current request's scheme and host.
# If request is to http://example.com/path/
uri = request.build_absolute_uri("/new-path/")
# uri is "http://example.com/new-path/"