Crafting HTTP Responses
In this codebase, HTTP responses are structured around a hierarchy that separates header management from content delivery. While HttpResponseBase provides the foundation for status codes, headers, and cookies, concrete subclasses like HttpResponse, JsonResponse, and StreamingHttpResponse implement specific strategies for handling different types of payload data.
The Response Hierarchy
All response types inherit from HttpResponseBase in django/http/response.py. This base class does not handle content itself but manages the HTTP metadata:
- Status Codes: Defaults to
200, but can be set via thestatusargument. - Headers: Managed by the
ResponseHeadersclass, which provides a case-insensitive dictionary-like interface. - Cookies: Handled via the
SimpleCookieobject and helper methods likeset_cookie.
The two primary branches of the hierarchy are:
HttpResponse: For responses where the entire content is loaded into memory as a bytestring.StreamingHttpResponse: For responses where content is yielded piece-by-piece from an iterator, reducing memory overhead for large datasets.
Standard Content with HttpResponse
The HttpResponse class is the most common way to return data. It accepts a string or bytestring as its primary content.
Content Handling and Encoding
When you assign a value to the content property, HttpResponse immediately consumes it. If the input is an iterator, it is exhausted and joined into a single bytestring.
# Example from django/http/response.py
def __init__(self, content=b"", *args, **kwargs):
super().__init__(*args, **kwargs)
self.content = content
The class uses the make_bytes method to ensure all content is properly encoded using the response's charset (defaulting to settings.DEFAULT_CHARSET).
File-like Interface
HttpResponse implements a partial file-like interface, allowing you to "write" to the response body after instantiation:
response = HttpResponse("Initial content.")
response.write(" Additional data.")
# response.content now returns b"Initial content. Additional data."
API Responses with JsonResponse
JsonResponse is a specialized subclass of HttpResponse designed for returning JSON-encoded data. It automatically sets the Content-Type header to application/json.
Serialization and the Safe Parameter
By default, JsonResponse only allows dict objects to be passed to the data parameter. This is a security measure to prevent certain JSON hijacking vulnerabilities in older browsers. To serialize other types (like lists), you must set safe=False.
# Example of returning a list in JsonResponse
from django.http import JsonResponse
def my_api_view(request):
data = [1, 2, 3]
return JsonResponse(data, safe=False)
It uses django.core.serializers.json.DjangoJSONEncoder by default, which handles Django-specific types like UUID or Promise objects.
Streaming and File Responses
For large payloads or files, loading the entire content into memory is inefficient.
StreamingHttpResponse
StreamingHttpResponse uses an iterator instead of a single string. It is used in scenarios like generating large CSV files or proxying data.
# Example from tests/httpwrappers/tests.py
def test_streaming_response(self):
iterable = ["hello", "world"]
response = StreamingHttpResponse(iter(iterable))
# Content is not accessible via .content; use .streaming_content
chunks = list(response.streaming_content)
FileResponse
FileResponse is a subclass of StreamingHttpResponse optimized for binary files. It automatically handles several headers based on the file object provided:
- Content-Length: Calculated using
tell()andseek()if the file is seekable. - Content-Type: Guessed from the filename using
mimetypes. - Content-Disposition: Set if
as_attachment=Trueis passed to the constructor.
In django/views/static.py, FileResponse is used to serve static files efficiently:
# django/views/static.py
response = FileResponse(fullpath.open("rb"), content_type=content_type)
response.headers["Last-Modified"] = http_date(statobj.st_mtime)
return response
Managing Headers and Cookies
ResponseHeaders
The headers attribute on any response is an instance of ResponseHeaders. This class ensures that header keys are ASCII and values are Latin-1 or MIME-encoded. It also protects against header injection by raising a BadHeaderError if newlines are detected in values.
response = HttpResponse()
response.headers["X-Custom-Header"] = "Value"
# Case-insensitive access
print(response.headers["x-custom-header"]) # "Value"
Cookies
HttpResponseBase provides set_cookie and set_signed_cookie for managing client-side state.
# Example from django/views/i18n.py
response.set_cookie(
settings.LANGUAGE_COOKIE_NAME,
lang_code,
max_age=settings.LANGUAGE_COOKIE_AGE,
path=settings.LANGUAGE_COOKIE_PATH,
domain=settings.LANGUAGE_COOKIE_DOMAIN,
secure=settings.LANGUAGE_COOKIE_SECURE,
httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
samesite=settings.LANGUAGE_COOKIE_SAMESITE,
)
set_signed_cookie uses the system's cryptographic signing to ensure the cookie value has not been tampered with by the client.
Common Status Subclasses
The codebase provides several pre-defined subclasses for common HTTP status codes in django/http/response.py:
HttpResponseRedirect: Returns a 302 status.HttpResponsePermanentRedirect: Returns a 301 status.HttpResponseNotModified: Returns a 304 status and prevents setting a response body.HttpResponseBadRequest: Returns a 400 status.HttpResponseNotFound: Returns a 404 status.HttpResponseForbidden: Returns a 403 status.HttpResponseNotAllowed: Returns a 405 status and requires a list of permitted methods (e.g.,['GET', 'POST']).