Skip to main content

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 the status argument.
  • Headers: Managed by the ResponseHeaders class, which provides a case-insensitive dictionary-like interface.
  • Cookies: Handled via the SimpleCookie object and helper methods like set_cookie.

The two primary branches of the hierarchy are:

  1. HttpResponse: For responses where the entire content is loaded into memory as a bytestring.
  2. 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() and seek() if the file is seekable.
  • Content-Type: Guessed from the filename using mimetypes.
  • Content-Disposition: Set if as_attachment=True is 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']).