summaryrefslogtreecommitdiff
path: root/docs/topics/http
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2020-02-12 15:15:00 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-03-18 19:59:12 +0100
commitfc0fa72ff4cdbf5861a366e31cb8bbacd44da22d (patch)
treed419ce531586808b0a111664907b859cb6d22862 /docs/topics/http
parent3f7e4b16bf58f99c71570ba75dc97db8265071be (diff)
Fixed #31224 -- Added support for asynchronous views and middleware.
This implements support for asynchronous views, asynchronous tests, asynchronous middleware, and an asynchronous test client.
Diffstat (limited to 'docs/topics/http')
-rw-r--r--docs/topics/http/middleware.txt87
-rw-r--r--docs/topics/http/views.txt25
2 files changed, 110 insertions, 2 deletions
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index d72d39de5e..3fe00b947f 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -71,6 +71,10 @@ method from the handler which takes care of applying :ref:`view middleware
applying :ref:`template-response <template-response-middleware>` and
:ref:`exception <exception-middleware>` middleware.
+Middleware can either support only synchronous Python (the default), only
+asynchronous Python, or both. See :ref:`async-middleware` for details of how to
+advertise what you support, and know what kind of request you are getting.
+
Middleware can live anywhere on your Python path.
``__init__(get_response)``
@@ -282,6 +286,81 @@ if the very next middleware in the chain raises an
that exception; instead it will get an :class:`~django.http.HttpResponse`
object with a :attr:`~django.http.HttpResponse.status_code` of 404.
+.. _async-middleware:
+
+Asynchronous support
+====================
+
+.. versionadded:: 3.1
+
+Middleware can support any combination of synchronous and asynchronous
+requests. Django will adapt requests to fit the middleware's requirements if it
+cannot support both, but at a performance penalty.
+
+By default, Django assumes that your middleware is capable of handling only
+synchronous requests. To change these assumptions, set the following attributes
+on your middleware factory function or class:
+
+* ``sync_capable`` is a boolean indicating if the middleware can handle
+ synchronous requests. Defaults to ``True``.
+
+* ``async_capable`` is a boolean indicating if the middleware can handle
+ asynchronous requests. Defaults to ``False``.
+
+If your middleware has both ``sync_capable = True`` and
+``async_capable = True``, then Django will pass it the request in whatever form
+it is currently in. You can work out what type of request you have by seeing
+if the ``get_response`` object you are passed is a coroutine function or not
+(using :py:func:`asyncio.iscoroutinefunction`).
+
+The ``django.utils.decorators`` module contains
+:func:`~django.utils.decorators.sync_only_middleware`,
+:func:`~django.utils.decorators.async_only_middleware`, and
+:func:`~django.utils.decorators.sync_and_async_middleware` decorators that
+allow you to apply these flags to middleware factory functions.
+
+The returned callable must match the sync or async nature of the
+``get_response`` method. If you have an asynchronous ``get_response``, you must
+return a coroutine function (``async def``).
+
+``process_view``, ``process_template_response`` and ``process_exception``
+methods, if they are provided, should also be adapted to match the sync/async
+mode. However, Django will individually adapt them as required if you do not,
+at an additional performance penalty.
+
+Here's an example of how to detect and adapt your middleware if it supports
+both::
+
+ import asyncio
+ from django.utils.decorators import sync_and_async_middleware
+
+ @sync_and_async_middleware
+ def simple_middleware(get_response):
+ # One-time configuration and initialization goes here.
+ if asyncio.iscoroutinefunction(get_response):
+ async def middleware(request):
+ # Do something here!
+ response = await get_response(request)
+ return response
+
+ else:
+ def middleware(request):
+ # Do something here!
+ response = get_response(request)
+ return response
+
+ return middleware
+
+.. note::
+
+ If you declare a hybrid middleware that supports both synchronous and
+ asynchronous calls, the kind of call you get may not match the underlying
+ view. Django will optimize the middleware call stack to have as few
+ sync/async transitions as possible.
+
+ Thus, even if you are wrapping an async view, you may be called in sync
+ mode if there is other, synchronous middleware between you and the view.
+
.. _upgrading-middleware:
Upgrading pre-Django 1.10-style middleware
@@ -292,8 +371,8 @@ Upgrading pre-Django 1.10-style middleware
Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease creating
middleware classes that are compatible with both :setting:`MIDDLEWARE` and the
-old ``MIDDLEWARE_CLASSES``. All middleware classes included with Django
-are compatible with both settings.
+old ``MIDDLEWARE_CLASSES``, and support synchronous and asynchronous requests.
+All middleware classes included with Django are compatible with both settings.
The mixin provides an ``__init__()`` method that requires a ``get_response``
argument and stores it in ``self.get_response``.
@@ -345,3 +424,7 @@ These are the behavioral differences between using :setting:`MIDDLEWARE` and
HTTP response, and then the next middleware in line will see that
response. Middleware are never skipped due to a middleware raising an
exception.
+
+.. versionchanged:: 3.1
+
+ Support for asynchronous requests was added to the ``MiddlewareMixin``.
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index 3076192676..1d3f9b5a11 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -202,3 +202,28 @@ in a test view. For example::
response = self.client.get('/403/')
# Make assertions on the response here. For example:
self.assertContains(response, 'Error handler content', status_code=403)
+
+.. _async-views:
+
+Asynchronous views
+==================
+
+.. versionadded:: 3.1
+
+As well as being synchronous functions, views can also be asynchronous
+functions (``async def``). Django will automatically detect these and run them
+in an asynchronous context. You will need to be using an asynchronous (ASGI)
+server to get the full power of them, however.
+
+Here's an example of an asynchronous view::
+
+ from django.http import HttpResponse
+ import datetime
+
+ async def current_datetime(request):
+ now = datetime.datetime.now()
+ html = '<html><body>It is now %s.</body></html>' % now
+ return HttpResponse(html)
+
+You can read more about Django's asynchronous support, and how to best use
+asynchronous views, in :doc:`/topics/async`.