Middleware

El middleware es un marco de ganchos en el procesamiento de solicitudes y respuestas de Django. Es un sistema ligero y de bajo nivel de «plugin» global para alterar globalemente la entrada o salida de Django.

Cada componente de middleware es responsable de realizar alguna función específica. Por ejemplo, Django incluye un componente de middleware, AuthenticationMiddleware, que asocia usuarios con solicitudes utilizando sesiones.

Este documento explica cómo funciona el middleware, cómo activarlo y cómo escribir su propio middleware. Django viene con algunos middleware integrados que puedes utilizar directamente desde la caja. Están documentados en la referencia de middleware integrada.

Escribiendo tu propio middleware

Una instancia de middleware es un callable que toma una solicitud y devuelve una respuesta, exactamente como una vista.

Un middleware puede escribirse como una función que se parece a esto:

def simple_middleware(get_response):
    # One-time configuration and initialization.

    def middleware(request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

    return middleware

O también puede escribirse como una clase cuyas instancias son llamables, como esto:

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

El callable get_response proporcionado por Django podría ser la vista real (si este es el último middleware listado) o podría ser el siguiente middleware en la cadena. El middleware actual no necesita saber ni preocuparse de qué exactamente es, solo que representa lo que sigue.

Lo anterior es una simplificación ligeramente exagerada – el callable get_response para el último middleware en la cadena no será la vista real sino más bien un método de envoltura del manipulador que se encarga de aplicar los middleware de vistas, llamar a la vista con argumentos URL adecuados y aplicar los middleware de respuesta de plantilla y middleware de excepción.

Un middleware puede apoyar solo Python sincrónico (por defecto), solo Python asíncrono o ambos. Consulte middleware asíncrono para detalles sobre cómo anunciar qué soportas y qué tipo de solicitud estás recibiendo.

Un middleware puede vivir en cualquier lugar de tu ruta de Python.

__init__(get_response)

Las fábricas de middleware deben aceptar un argumento get_response. También puedes inicializar algún estado global para el middleware. Ten en cuenta una o dos precauciones:

  • Django inicia tu middleware con solo el argumento get_response, por lo que no puedes definir __init__() como requiriendo otros argumentos.

  • A diferencia del método __call__() que se llama una vez por solicitud, __init__() se llama solo una vez, cuando arranca el servidor web.

Marcar middleware como no utilizados

En ocasiones puede ser útil determinar en tiempo de inicio si un pedazo de middleware debe usarse. En estos casos, el método __init__() de tu middleware puede levantar la excepción MiddlewareNotUsed. Django entonces eliminará ese middleware del proceso de middleware y registrará un mensaje de depuración en el logger django.request cuando DEBUG esté configurado como True.

Activar middleware

Para activar un componente de middleware, agrega su nombre a la lista MIDDLEWARE en tus ajustes Django.

En MIDDLEWARE, cada componente de middleware se representa por una cadena: el camino completo del módulo Python al clase o función del factoría de middleware. Por ejemplo, aquí está el valor predeterminado creado por django-admin startproject:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

Una instalación de Django no requiere ningún middleware — MIDDLEWARE puede estar vacío si lo deseas — pero se sugiere fuertemente que al menos uses CommonMiddleware.

El orden en MIDDLEWARE importa porque un middleware puede depender de otros. Por ejemplo, AuthenticationMiddleware almacena el usuario autenticado en la sesión; por lo tanto, debe ejecutarse después que SessionMiddleware. Consulta Orden de middleware para algunas pautas comunes sobre el orden de las clases de middleware Django.

Orden y capas de middleware

Durante la fase de solicitud, antes de llamar a la vista, Django aplica los middleware en el orden definido en MIDDLEWARE, de arriba hacia abajo.

Puedes pensar en ello como una cebolla: cada clase de middleware es una «capa» que envuelve la vista, que se encuentra en el núcleo de la cebolla. Si la solicitud pasa por todas las capas de la cebolla (cada una llama a get_response para pasar la solicitud al siguiente capa), hasta llegar a la vista en el núcleo, la respuesta luego pasará por cada capa (en orden inverso) en su camino hacia afuera.

Si una de las capas decide cortar la circulación y devuelve una respuesta sin llamar nunca a su get_response, ninguna de las capas de la cebolla dentro de esa capa (incluyendo la vista) verán la solicitud o la respuesta. La respuesta solo regresará por las mismas capas que la solicitud pasó en su camino hacia adentro.

Otros hooks de middleware

Además del patrón básico de middleware de solicitud/respuesta descrito anteriormente, puedes agregar tres otros métodos especiales a middleware basados en clases:

process_view()

process_view(request, view_func, view_args, view_kwargs)

request es un objeto HttpRequest. view_func es la función Python que Django está a punto de usar. (Es el objeto de función real, no el nombre de la función como una cadena.) view_args es una lista de argumentos posicionales que se pasarán a la vista, y view_kwargs es un diccionario de argumentos clave que se pasarán a la vista. Ninguno de view_args ni view_kwargs incluye el primer argumento de la vista (request).

process_view() se llama justo antes de que Django llame a la vista.

Debería devolver None o un objeto HttpResponse. Si devuelve None, Django continuará procesando esta solicitud, ejecutando cualquier otro process_view() middleware y luego la vista adecuada. Si devuelve un objeto HttpResponse, Django no se molestará en llamar a la vista adecuada; aplicará middleware de respuesta a ese HttpResponse y regresará el resultado.

Nota

Acceder a request.POST dentro del middleware antes de que la vista se ejecute o en process_view() impedirá que cualquier vista que se ejecute después del middleware pueda modificar los manipuladores de carga para la solicitud, y normalmente debería evitarse.

La clase CsrfViewMiddleware puede considerarse una excepción, ya que proporciona los decoradores csrf_exempt() y csrf_protect() que permiten a las vistas controlar explícitamente en qué punto debe ocurrir la validación CSRF.

procesar_excepción()

process_exception(request, exception)

solicitud es un objeto HttpRequest. excepción es un objeto Exception lanzado por la función de vista.

Django llama a procesar_excepción() cuando una vista lanza una excepción. procesar_excepción() debe devolver None o un objeto HttpResponse. Si devuelve un objeto HttpResponse, se aplicará la respuesta de plantilla y el middleware de respuesta, y la respuesta resultante se devolverá al navegador. De lo contrario, se activa el manejo por defecto de excepciones:ref:manejo de excepciones <error-views> .

Nuevamente, los middleware se ejecutan en orden inverso durante la fase de respuesta, que incluye procesar_excepción. Si un middleware de excepción devuelve una respuesta, no se llamarán los métodos process_exception de las clases de middleware por encima del middleware.

procesar_respuesta_de_plantilla()

process_template_response(request, response)

solicitud es un objeto HttpRequest. respuesta es el objeto TemplateResponse (o equivalente) devuelto por una vista de Django o por un middleware.

Se llama a procesar_respuesta_de_plantilla() justo después de que la vista haya terminado de ejecutarse, si la instancia de respuesta tiene un método render(), lo que indica que es un TemplateResponse o equivalente.

Debe devolver un objeto de respuesta que implemente el método render. Podría alterar la respuesta dada cambiando response.template_name y response.context_data, o podría crear y devolver una nueva TemplateResponse o equivalente.

No necesitas renderizar explícitamente respuestas – las respuestas se renderizarán automáticamente una vez que se hayan llamado todos los middleware de respuesta de plantilla.

Los middleware se ejecutan en orden inverso durante la fase de respuesta, lo que incluye procesar_respuesta_de_plantilla().

Dealing with streaming responses

A diferencia de HttpResponse, StreamingHttpResponse no tiene un atributo content. Como resultado, el middleware ya no puede asumir que todas las respuestas tendrán un atributo content. Si necesitan acceso al contenido, deben probar si se trata de respuestas en streaming y ajustar su comportamiento según sea necesario:

if response.streaming:
    response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
    response.content = alter_content(response.content)

Nota

Se debe suponer que streaming_content es demasiado grande para almacenarlo en memoria. El middleware de respuesta puede envolverlo en un nuevo generador, pero no debe consumirlo. La envoltura se implementa típicamente de la siguiente manera:

def wrap_streaming_content(content):
    for chunk in content:
        yield alter_content(chunk)

StreamingHttpResponse permite tanto iteradores síncronos como asíncronos. La función de envoltura debe coincidir con el tipo de iterador. Verifique si su middleware necesita admitir ambos tipos de iterador StreamingHttpResponse.is_async.

Manejo de excepciones

Django convierte automáticamente las excepciones levantadas por la vista o por el middleware en una respuesta HTTP adecuada con un código de estado de error. Certain exceptions se convierten a códigos de estado 4xx, mientras que una excepción desconocida se convierte a un código de estado 500.

Esta conversión tiene lugar antes y después de cada middleware (puedes pensar en ella como la delgada capa entre cada capa de la cebolla), por lo que cada middleware puede confiar en obtener alguna clase de respuesta HTTP de llamar a su callable get_response. El middleware no necesita preocuparse por envolver su llamada a get_response con un try/except y manejar una excepción que podría haber sido levantada por un middleware posterior o la vista. Incluso si el muy próximo middleware en la cadena levanta una Http404 excepción, por ejemplo, tu middleware no verá esa excepción; en su lugar obtendrá un objeto de respuesta HTTP con un código de estado status_code de 404.

Puedes establecer DEBUG_PROPAGATE_EXCEPTIONS en True para saltarte esta conversión y propagar excepciones hacia arriba.

Soporte asíncrono

El middleware puede admitir cualquier combinación de solicitudes síncronas y asíncronas. Django adaptará las solicitudes para que se ajusten a los requisitos del middleware si no puede admitir ambos, pero con una penalización en rendimiento.

Por defecto, Django asume que tu middleware es capaz de manejar solo solicitudes síncronas. Para cambiar estas suposiciones, establece las siguientes atributos en la función o clase de fabricación de middleware:

  • sync_capable es un booleano que indica si el middleware puede manejar solicitudes síncronas. Por defecto, True.

  • async_capable es una booleana que indica si el middleware puede manejar solicitudes asíncronas. Por defecto es False.

Si tu middleware tiene tanto sync_capable = True como async_capable = True, entonces Django pasará la solicitud al middleware sin convertirla. En este caso, puedes determinar si tu middleware recibirá solicitudes asíncronas comprobando si el objeto get_response que se te pasa es una función coroutine utilizando asgiref.sync.iscoroutinefunction.

El módulo django.utils.decorators contiene los decoradores sync_only_middleware(), async_only_middleware() y sync_and_async_middleware() que te permiten aplicar estas banderas a funciones de fábrica de middleware.

El callable devuelto debe coincidir con la naturaleza sincrónica o asíncrona del método get_response. Si tienes un get_response asíncrono, debes devolver una función coroutine (async def).

Los métodos process_view, process_template_response y process_exception, si se proporcionan, también deben adaptarse para coincidir con el modo sincrónico/ásincrono. Sin embargo, Django los adaptará individualmente según sea necesario si no lo haces, con una penalización de rendimiento adicional.

Aquí tienes un ejemplo de cómo crear una función de middleware que soporta ambos modos:

from asgiref.sync import iscoroutinefunction
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 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

Nota

Si declaras un middleware híbrido que admite tanto llamadas sincrónicas como asíncronas, el tipo de llamada que obtienes puede no coincidir con la vista subyacente. Django optimizará la pila de llamadas del middleware para tener tantos cambios sincrónico/ásincrono como sea posible.

Por lo tanto, incluso si estás rodeando una vista asíncrona, puedes ser llamado en modo sincrónico si hay otros middleware sincrónicos entre tú y la vista.

Cuando se utiliza un middleware de clase asíncrono, debes asegurarte de que las instancias estén correctamente marcadas como funciones coroutine:

from asgiref.sync import iscoroutinefunction, markcoroutinefunction


class AsyncMiddleware:
    async_capable = True
    sync_capable = False

    def __init__(self, get_response):
        self.get_response = get_response
        if iscoroutinefunction(self.get_response):
            markcoroutinefunction(self)

    async def __call__(self, request):
        response = await self.get_response(request)
        # Some logic ...
        return response

Actualización de middleware pre-Django 1.10

class django.utils.deprecation.MiddlewareMixin

Django proporciona django.utils.deprecation.MiddlewareMixin para facilitar la creación de clases de middleware compatibles con tanto MIDDLEWARE como la antigua MIDDLEWARE_CLASSES, y que admitan solicitudes síncronas y asíncronas. Todas las clases de middleware incluidas en Django son compatibles con ambos ajustes.

El mixin proporciona un método __init__() que requiere un argumento get_response y lo almacena en self.get_response.

El método __call__():

  1. Llama a self.process_request(request) (si está definido).

  2. Llama a self.get_response(request) para obtener la respuesta de middleware y vistas posteriores.

  3. Llama a self.process_response(request, response) (si está definido).

  4. Devuelve la respuesta.

Si se utiliza con MIDDLEWARE_CLASSES, el método __call__() nunca será utilizado; Django llama directamente a process_request() y process_response().

En la mayoría de los casos, heredar de este mixin será suficiente para hacer que una middleware antigua sea compatible con el nuevo sistema con suficiente compatibilidad hacia atrás. Los nuevos semánticas cortocircuitos serán inocuos o incluso beneficiosas para las middleware existentes. En unos pocos casos, una clase de middleware puede necesitar algunos cambios para ajustarse a las nuevas semánticas.

Estas son las diferencias comportamentales entre utilizar MIDDLEWARE y MIDDLEWARE_CLASSES:

  1. Under CLASES DE MEDIOS, cada medio siempre tendrá su método process_response llamado, incluso si un medio anterior cortocircuitó devolviendo una respuesta desde su método process_request. Bajo MIDDLEWARE, los medios se comportan más como una cebolla: las capas que una respuesta pasa por en el camino de salida son las mismas capas que vieron la solicitud en el camino de entrada. Si un medio cortocircuita, solo ese medio y los anteriores en MIDDLEWARE verán la respuesta.

  2. Bajo CLASES DE MEDIOS, se aplica process_exception a las excepciones lanzadas desde un método process_request de un medio. Bajo MIDDLEWARE, process_exception solo se aplica a excepciones lanzadas desde la vista (o desde el método render de una TemplateResponse). Las excepciones lanzadas desde un medio se convierten en la respuesta HTTP adecuada y luego se pasa al siguiente medio.

  3. Bajo CLASES DE MEDIOS, si un método process_response lanza una excepción, los métodos process_response de todos los medios anteriores se saltan y siempre se devuelve una respuesta HTTP 500 Internal Server Error (incluso si la excepción lanzada fue e.g. un Http404). Bajo MIDDLEWARE, una excepción lanzada desde un medio se convierte inmediatamente en la respuesta HTTP adecuada, y luego el siguiente medio en línea verá esa respuesta. Los medios nunca se saltan debido a que un medio lanza una excepción.