In Django's MIDDLEWARE, where do GZipMiddleware, LocaleMiddleware and CommonMiddleware belong relative to the other built-ins, and why?
answer
- outer layers touch the response last
- who sets Content-Length
- compress after everything else
- language before URL resolution
basics
~20 sGZipMiddleware 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 sBecause 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 linesMIDDLEWARE = [
"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
Recall that GZipMiddleware goes high in the list, LocaleMiddleware between SessionMiddleware and CommonMiddleware, and CommonMiddleware near the top.
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.
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.
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.