skip to content

In Django's MIDDLEWARE, where do GZipMiddleware, LocaleMiddleware and CommonMiddleware belong relative to the other built-ins, and why?

level: middleimportance: should knowfreq 40%

answer

  1. outer layers touch the response last
  2. who sets Content-Length
  3. compress after everything else
  4. language before URL resolution

basics

~20 s

GZipMiddleware goes above anything that reads or changes the response body, so it compresses the final bytes. LocaleMiddleware goes after SessionMiddleware and before CommonMiddleware. CommonMiddleware stays near the top; middleware above it that changes the body must reset Content-Length.

solid answer

~40 s

Because responses travel back up the list, a middleware higher in `MIDDLEWARE` handles the response **later**. `GZipMiddleware` should therefore sit above every middleware that reads or rewrites the body, so it compresses the final content; it skips bodies under 200 bytes, resets `Content-Length` after compressing and weakens strong ETags. `CommonMiddleware` sets `Content-Length` on non-streaming responses on the way out and may redirect for `PREPEND_WWW` or, after a 404, `APPEND_SLASH`, so it goes near the top, and any middleware above it that changes the body must reset `Content-Length`. `LocaleMiddleware` goes after `SessionMiddleware` and before `CommonMiddleware`, because `CommonMiddleware` needs the activated language to resolve the requested URL. A typical result is Security, Session, Locale, GZip, Common, then Csrf, Authentication and Messages.

code

python · 11 lines
python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.gzip.GZipMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

go deeper

for a junior

Recall that GZipMiddleware goes high in the list, LocaleMiddleware between SessionMiddleware and CommonMiddleware, and CommonMiddleware near the top.

for a middle

Explain that higher entries handle the response later, and use that to justify GZip above body-changing middleware and the Content-Length rule around CommonMiddleware.

for a senior

Reason about each placement as a before/after constraint, spot header bugs such as a stale Content-Length or ETag, and fit third-party middleware into the list safely.

for a principal

Weigh doing compression and redirects in Django against doing them at the edge, and keep the in-app stack small and explained.

## The principle: outer layers see the response last In Django's `MIDDLEWARE` list, the first entry is the outermost layer. On the way **in**, entries run top to bottom; on the way **out**, bottom to top. Two practical consequences follow for the built-ins: - a middleware that **prepares the request** for others (sessions, language, user) must be **above** the ones that use it; - a middleware that must act on the **final response body**, such as compression, must be **above** every middleware that still changes the body, because it gets the response after them. ## GZipMiddleware `django.middleware.gzip.GZipMiddleware` compresses responses for clients that send `Accept-Encoding: gzip`. - It belongs **above** any middleware that needs to read or write the response body, so compression happens after they are done. - It leaves alone bodies under **200 bytes**, responses that already have `Content-Encoding`, and clients that do not accept gzip. - After compressing it **resets `Content-Length`** for normal responses (and removes it for streaming ones), and turns a strong `ETag` into a weak one. - `ConditionalGetMiddleware`, if you use it, goes **below** it so ETags are not computed on compressed content. - The docs also place it after `UpdateCacheMiddleware`, since it modifies the `Vary` header. ## CommonMiddleware `django.middleware.common.CommonMiddleware` does several jobs, and each one affects its position: 1. On the way in it refuses user agents matching `DISALLOWED_USER_AGENTS` and, with `PREPEND_WWW=True` (default `False`), redirects to the `www.` host. 2. On the way out, if the response is a 404 and `APPEND_SLASH` is `True` (the default) and the slash-appended URL resolves, it replaces the response with a redirect. 3. On the way out it sets `Content-Length` on non-streaming responses that do not have one. Because of the redirects it belongs near the top. Because of step 3, a middleware **above** it that changes the body after `Content-Length` was set must correct the header itself, as `GZipMiddleware` does. ## LocaleMiddleware `django.middleware.locale.LocaleMiddleware` activates a language per request and adds `Accept-Language` to `Vary`. - Django's translation docs ask for it to be **one of the first** middleware, **after** `SessionMiddleware`. - It must come **before** `CommonMiddleware`, because `CommonMiddleware` needs an activated language to resolve the requested URL, for example when checking whether a slash-appended path exists under language-prefixed or translated URL patterns. - With the whole-site cache middleware it goes after `UpdateCacheMiddleware`. ## A worked order | Position | Middleware | Placement rule | |---|---|---| | 1 | `SecurityMiddleware` | redirect to HTTPS before other work | | 2 | `SessionMiddleware` | before anything that uses `request.session` | | 3 | `LocaleMiddleware` | after sessions, before `CommonMiddleware` | | 4 | `GZipMiddleware` | above every middleware that touches the body | | 5 | `CommonMiddleware` | near the top; sets `Content-Length` | | 6 | `CsrfViewMiddleware` | before view middleware that assumes CSRF was checked | | 7 | `AuthenticationMiddleware` | after sessions | | 8 | `MessageMiddleware` | after sessions | | 9 | `XFrameOptionsMiddleware` | header only | This is one valid arrangement, not the only one: the constraints are pairwise, and several positions can move as long as each pair is respected. ## Common mistakes - Putting `GZipMiddleware` at the bottom, where it compresses the view's output before other middleware inspect or rewrite it. - Adding a body-rewriting middleware above `CommonMiddleware` without fixing `Content-Length`, which leaves a header that no longer matches the body. - Placing `LocaleMiddleware` after `CommonMiddleware`, so slash-appending redirects are decided without the request's language. - Assuming the order in the docs is a single required sequence rather than a set of before/after constraints.

  • Why does GZipMiddleware set Content-Length itself?
    Compression changes the body length. `CommonMiddleware`, lower in the list, has already set `Content-Length` for the uncompressed body on its way out, so `GZipMiddleware`, which handles the response later, must replace the header with the compressed length. For streaming responses it deletes the header instead, because the final length is unknown.
  • Where does ConditionalGetMiddleware go relative to GZipMiddleware, and why?
    Below it. `ConditionalGetMiddleware` computes and compares `ETag` headers; placed below `GZipMiddleware`, it works on the uncompressed response and runs before compression on the way out, so it does not calculate an `ETag` from gzipped bytes. `GZipMiddleware` then weakens any strong `ETag` it finds.

saying these in an interview costs you the question

  • GZipMiddleware should be the last entry so it compresses the view's output directly.
  • Django recomputes Content-Length after all middleware have run.
  • LocaleMiddleware can go anywhere because it only reads a header.
  • CommonMiddleware's APPEND_SLASH redirect happens before the URL is resolved.
  • The documented middleware order is one fixed sequence with no flexibility.