summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2016-06-14 00:41:58 -0700
committerCarl Meyer <carl@oddbird.net>2016-06-17 10:00:39 -0700
commit7d1b69dbe7f72ac04d2513f0468fe2146231b286 (patch)
tree31478358078d603fb54c02f22792d47707b9aed5 /docs
parent104ee2fdae7fcd402396c709c91f5c84de6e4165 (diff)
Refs #26601 -- Improved backwards-compatibility of DEP 5 middleware exception handling.
Diffstat (limited to 'docs')
-rw-r--r--docs/topics/http/middleware.txt210
1 files changed, 122 insertions, 88 deletions
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index 1f5414e95b..1f79256027 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -40,14 +40,9 @@ A middleware can be written as a function that looks like this::
def middleware(request):
# Code to be executed for each request before
- # the view is called.
+ # the view (and later middleware) are called.
- try:
- response = get_response(request)
- except Exception as e:
- # Code to handle an exception that wasn't caught
- # further up the chain, if desired.
- ...
+ response = get_response(request)
# Code to be executed for each request/response after
# the view is called.
@@ -56,7 +51,7 @@ A middleware can be written as a function that looks like this::
return middleware
-Or it can be written as a class with a ``__call__()`` method, like this::
+Or it can be written as a class whose instances are callable, like this::
class SimpleMiddleware(object):
def __init__(self, get_response):
@@ -65,24 +60,15 @@ Or it can be written as a class with a ``__call__()`` method, like this::
def __call__(self, request):
# Code to be executed for each request before
- # the view is called.
+ # the view (and later middleware) are called.
- try:
- response = self.get_response(request)
- except Exception as e:
- # Code to handle an exception that wasn't caught
- # further up the chain, if desired.
- ...
+ response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
-In both examples, the ``try``/``except`` isn't required if the middleware
-doesn't need to handle any exceptions. If it is included, it should probably
-catch something more specific than ``Exception``.
-
The ``get_response`` callable provided by Django might be the actual view (if
this is the last listed middleware) or it might be the next middleware in the
chain. The current middleware doesn't need to know or care what exactly it is,
@@ -92,30 +78,32 @@ The above is a slight simplification -- the ``get_response`` callable for the
last middleware in the chain won't be the actual view but rather a wrapper
method from the handler which takes care of applying :ref:`view middleware
<view-middleware>`, calling the view with appropriate URL arguments, and
-applying :ref:`template-response <template-response-middleware>` middleware.
+applying :ref:`template-response <template-response-middleware>` and
+:ref:`exception <exception-middleware>` middleware.
Middleware can live anywhere on your Python path.
``__init__(get_response)``
--------------------------
-Middleware classes must accept a ``get_response`` argument. You can also
+Middleware factories must accept a ``get_response`` argument. You can also
initialize some global state for the middleware. Keep in mind a couple of
caveats:
* Django initializes your middleware with only the ``get_response`` argument,
so you can't define ``__init__()`` as requiring any other arguments.
-* Unlike the ``__call__()`` method which get called once per request,
+* Unlike the ``__call__()`` method which is called once per request,
``__init__()`` is called only *once*, when the Web server starts.
.. versionchanged:: 1.10
- In older versions, ``__init__`` was not called until the Web server
+ In older versions, ``__init__()`` wasn't called until the Web server
responded to its first request.
- If you want to allow your middleware to be used in Django 1.9 and earlier,
- make ``get_response`` an optional argument (``get_response=None``).
+ In older versions, ``__init__()`` didn't accept any arguments. To allow
+ your middleware to be used in Django 1.9 and earlier, make ``get_response``
+ an optional argument (``get_response=None``).
Marking middleware as unused
----------------------------
@@ -133,9 +121,9 @@ To activate a middleware component, add it to the :setting:`MIDDLEWARE` list in
your Django settings.
In :setting:`MIDDLEWARE`, each middleware component is represented by a string:
-the full Python path to the middleware's class or function name. For example,
-here's the default value created by :djadmin:`django-admin startproject
-<startproject>`::
+the full Python path to the middleware factory's class or function name. For
+example, here's the default value created by :djadmin:`django-admin
+startproject <startproject>`::
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
@@ -159,25 +147,29 @@ authenticated user in the session; therefore, it must run after
:ref:`middleware-ordering` for some common hints about ordering of Django
middleware classes.
-Hooks and application order
-===========================
+Middleware order and layering
+=============================
+
+During the request phase, before calling the view, Django applies middleware in
+the order it's defined in :setting:`MIDDLEWARE`, top-down.
-During the request phase, before calling the view, Django applies middleware
-in the order it's defined in :setting:`MIDDLEWARE`, top-down. You can think of
-it like an onion: each middleware class is a "layer" that wraps the view.
+You can think of it like an onion: each middleware class is a "layer" that
+wraps the view, which is in the core of the onion. If the request passes
+through all the layers of the onion (each one calls ``get_response`` to pass
+the request in to the next layer), all the way to the view at the core, the
+response will then pass through every layer (in reverse order) on the way back
+out.
-Middleware see only the changes made by middleware that run before it. A
-middleware (and the view) is skipped entirely if a preceding middleware
-short-circuits by returning a response without ever calling ``get_response``.
-That response will only pass through the middleware that have already run.
+If one of the layers decides to short-circuit and return a response without
+ever calling its ``get_response``, none of the layers of the onion inside that
+layer (including the view) will see the request or the response. The response
+will only return through the same layers that the request passed in through.
-Similarly, a middleware that sees the request on the way in and doesn't return
-a response is guaranteed that it will always see the response on the way back
-out. If the middleware also wants to see any uncaught exception on the way out,
-it can wrap its call to ``get_response()`` in a ``try``/``except``.
+Other middleware hooks
+======================
-Besides the middleware pattern described earlier, you can add two other methods
-to class-based middleware:
+Besides the basic request/response middleware pattern described earlier, you
+can add three other special methods to class-based middleware:
.. _view-middleware:
@@ -217,6 +209,28 @@ bother calling the appropriate view; it'll apply response middleware to that
:func:`~django.views.decorators.csrf.csrf_protect` decorators which allow
views to explicitly control at what point the CSRF validation should occur.
+.. _exception-middleware:
+
+``process_exception()``
+-----------------------
+
+.. method:: process_exception(request, exception)
+
+``request`` is an :class:`~django.http.HttpRequest` object. ``exception`` is an
+``Exception`` object raised by the view function.
+
+Django calls ``process_exception()`` when a view raises an exception.
+``process_exception()`` should return either ``None`` or an
+:class:`~django.http.HttpResponse` object. If it returns an
+:class:`~django.http.HttpResponse` object, the template response and response
+middleware will be applied and the resulting response returned to the
+browser. Otherwise, :ref:`default exception handling <error-views>` kicks in.
+
+Again, middleware are run in reverse order during the response phase, which
+includes ``process_exception``. If an exception middleware returns a response,
+the ``process_exception`` methods of the middleware classes above that
+middleware won't be called at all.
+
.. _template-response-middleware:
``process_template_response()``
@@ -268,31 +282,24 @@ must test for streaming responses and adjust their behavior accordingly::
for chunk in content:
yield alter_content(chunk)
-.. _exception-middleware:
-
-Exception middleware
-====================
-
-A middleware that does some custom exception handling might looks like this::
-
- class ExceptionMiddleware(object):
- def __init__(self, get_response):
- self.get_response = get_response
+Exception handling
+==================
- def __call__(self, request):
- try:
- response = self.get_response(request)
- except Exception as e:
- # Do something with the exception and possibly reraise it
- # unless you wish to silence it.
- ...
- return response
+Django automatically converts exceptions raised by the view or by middleware
+into an appropriate HTTP response with an error status code. :ref:`Certain
+exceptions <error-views>` are converted to 4xx status codes, while an unknown
+exception is converted to a 500 status code.
-Middleware that wants to do something for all exception responses, an HTTP 404
-for example, need to both catch the appropriate exception (e.g. ``Http404``)
-and look for regular responses with the status code of interest. You can
-subclass :class:`~django.middleware.exception.ExceptionMiddleware` if you want
-to transform exceptions into the appropriate response.
+This conversion takes place before and after each middleware (you can think of
+it as the thin film in between each layer of the onion), so that every
+middleware can always rely on getting some kind of HTTP response back from
+calling its ``get_response`` callable. Middleware don't need to worry about
+wrapping their call to ``get_response`` in a ``try/except`` and handling an
+exception that might have been raised by a later middleware or the view. Even
+if the very next middleware in the chain raises an
+:class:`~django.http.Http404` exception, for example, your middleware won't see
+that exception; instead it will get an :class:`~django.http.HttpResponse`
+object with a :attr:`~django.http.HttpResponse.status_code` of 404.
.. _upgrading-middleware:
@@ -302,30 +309,57 @@ Upgrading pre-Django 1.10-style middleware
.. class:: django.utils.deprecation.MiddlewareMixin
:module:
-Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease providing
-the existing built-in middleware in both new-style and old-style forms and to
-ease similar conversions of third-party middleware.
+Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease creating
+middleware classes that are compatible with both :setting:`MIDDLEWARE` and the
+old :setting:`MIDDLEWARE_CLASSES`.
+
+The mixin provides an ``__init__()`` method that accepts an optional
+``get_response`` argument and stores it in ``self.get_response``.
+
+The ``__call__()`` method:
+
+#. Calls ``self.process_request(request)`` (if defined).
+#. Calls ``self.get_response(request)`` to get the response from later
+ middleware and the view.
+#. Calls ``self.process_response(request, response)`` (if defined).
+#. Returns the response.
+
+If used with :setting:`MIDDLEWARE_CLASSES`, the ``__call__()`` method will
+never be used; Django calls ``process_request()`` and ``process_response()``
+directly.
-In most cases, this mixin will be sufficient to convert a middleware with
-sufficient backwards-compatibility; the new short-circuiting semantics will be
-harmless or even beneficial to the existing middleware.
+In most cases, inheriting from this mixin will be sufficient to make an
+old-style middleware compatible with the new system with sufficient
+backwards-compatibility. The new short-circuiting semantics will be harmless or
+even beneficial to the existing middleware. In a few cases, a middleware class
+may need some changes to adjust to the new semantics.
-In a few cases, a middleware class may need more invasive changes to adjust to
-the new semantics.
+These are the behavioral differences between using :setting:`MIDDLEWARE` and
+:setting:`MIDDLEWARE_CLASSES`:
-For example, in the current request-handling logic, the handler transforms any
-exception that passes through all ``process_exception`` middleware uncaught
-into a response with appropriate status code (e.g. 404, 403, 400, or 500), and
-then passes that response through the full chain of ``process_response``
-middleware.
+1. Under :setting:`MIDDLEWARE_CLASSES`, every middleware will always have its
+ ``process_response`` method called, even if an earlier middleware
+ short-circuited by returning a response from its ``process_request``
+ method. Under :setting:`MIDDLEWARE`, middleware behaves more like an onion:
+ the layers that a response goes through on the way out are the same layers
+ that saw the request on the way in. If a middleware short-circuits, only
+ that middleware and the ones before it in :setting:`MIDDLEWARE` will see the
+ response.
-In new-style middleware, a given middleware only gets one shot at a given
-response or uncaught exception "on the way out," and will see either a returned
-response or an uncaught exception, but not both.
+2. Under :setting:`MIDDLEWARE_CLASSES`, ``process_exception`` is applied to
+ exceptions raised from a middleware ``process_request`` method. Under
+ :setting:`MIDDLEWARE`, ``process_exception`` applies only to exceptions
+ raised from the view (or from the ``render`` method of a
+ :class:`~django.template.response.TemplateResponse`). Exceptions raised from
+ a middleware are converted to the appropriate HTTP response and then passed
+ to the next middleware.
-This means that certain middleware which want to do something with all 404
-responses (for example, the ``RedirectFallbackMiddleware`` and
-``FlatpageFallbackMiddleware`` in ``django.contrib.redirects`` and
-``django.contrib.flatpages``) now need to watch out for both a 404 response
-and an uncaught ``Http404`` exception. They do this by subclassing
-:class:`~django.middleware.exception.ExceptionMiddleware`.
+3. Under :setting:`MIDDLEWARE_CLASSES`, if a ``process_response`` method raises
+ an exception, the ``process_response`` methods of all earlier middleware are
+ skipped and a ``500 Internal Server Error`` HTTP response is always
+ returned (even if the exception raised was e.g. an
+ :class:`~django.http.Http404`). Under :setting:`MIDDLEWARE`, an exception
+ raised from a middleware will immediately be converted to the appropriate
+ HTTP response, and then the next middleware in line will see that
+ response. Middleware are never skipped due to a middleware raising an
+ exception.