1
0
mirror of https://github.com/django/django.git synced 2025-10-24 06:06:09 +00:00

Fixed #31224 -- Added support for asynchronous views and middleware.

This implements support for asynchronous views, asynchronous tests,
asynchronous middleware, and an asynchronous test client.
This commit is contained in:
Andrew Godwin
2020-02-12 15:15:00 -07:00
committed by Mariusz Felisiak
parent 3f7e4b16bf
commit fc0fa72ff4
30 changed files with 1344 additions and 214 deletions

View File

@@ -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``.

View File

@@ -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`.