Qu’est ce qu’un middleware Django ? 11


Avec Django 1.10, les middlewares ont changé. Allez à la fin de l’article pour en savoir plus.

Vous savez qu’il y a un settings qui ressemble à ça:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
)

Vous savez qu’il est important. Vous ne savez pas vraiment pourquoi.

WaT iZ a MideulWaire ?

Un middleware est un moyen d’exécuter du code à toutes requêtes et/ou à toutes les réponses reçues et envoyées par Django. Si vous ne voyez pas comment fonctionne le cycle de requêtes/réponses, faites un petit saut sur notre schéma de fonctionnement général de Django.

Un middleware, c’est une classe Python ordinaire, avec deux méthodes: une appelée pour les requêtes, une appelée pour les réponses.

L’exemple bidon habituel

class MideulWaireForEverReturnsTheRevenge3:
 
    # cette méthode sera appelée automatiquement pour chaque requête
    # et Django lui passera la requête en cours
    def process_request(self, request):
        print("Hey, une requête est arrivée !")
        # on est pas obligé de retourner quoi que ce soit
 
    # cette méthode sera appelée automatiquement pour chaque réponse
    # et Django lui passera la réponse en cours et la requête
    # à laquelle on répond
    def process_response(self, request, response):
        print("Hey, on a répondu a une requête !")
        # on DOIT retourner une réponse
        return response

On met tout ça dans un fichier mon_projet/mon_app/middlewares.py, et on active le middleware en le rajoutant dans les settings:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'mon_projet.mon_app.middlewares.MideulWaireForEverReturnsTheRevenge3'
)

Et à chaque reload de page, dans l’affichage du terminal de dev, on a ça:

Capture d'écran de l'output terminal de ./mange.py runserver avec un middleware qui print sur stdout

./manage.py runserver

Et voici ce qui se passe dans le fonctionnement de Django: il trouve la liste des classes de middlewares, et pour chaque requête et réponse, il appelle les méthodes process_response et process_request de chaque middleware.

Schéma de fonctionnement des middlewares en Django

C'est l'histoire de la viiiiiiiiiiiiiie. C'est le cycle éterneeeeeleuuuuuuuuuuuu

Les points les plus importants

Aucune méthode n’est obligatoire, vous pouvez créer un middleware avec uniquement l’une ou l’autre.

Si process_request renvoie une réponse, tout s’arrête. Les process_request des autres middlewares ne sont pas appelés, vos vues ne sont pas appelées, et c’est le cycle de réponse qui commence directement. Très utile si par exemple vous voulez faire une redirection brutale pour toutes les requêtes qui correspondent à un certain critère (par exemple rediriger vers un site mobile).

Les middlewares sont appelés dans leur ordre de déclaration dans MIDDLEWARE_CLASSES pour chaque requête, et dans l’ordre inverse pour chaque réponse. Mettez donc votre middleware au bon endroit dans la file: inutile de mettre un middleware qui vérifie si un user a des droits avant le middleware d’authentification, puisque l’utilisateur n’est pas encore authentifié.

Un middleware peut posséder 3 autres méthodes également facultatives: process_view (appelée juste avant l’appel de chaque vue, et permettant de modifier l’instance de la vue elle-même), process_template_response (appelée sur chaque instance implémentant une méthode render, et surtout utilisée pour injecter des données dans TemplateResponse) et process_exception (appelée quand une vue lève une exception).

Pour quoi utiliser les middleware ?

Exemples de quelques middlewares qui viennent d’office avec Django:

  • UpdateCacheMiddleware, FetchFromCacheMiddleware: ils cachent l’intégralité des pages.
  • GZipMiddleware: retourne une réponse compressée si le browser le gère.
  • LocaleMiddleware: set la langue que Djando doit utiliser, en fonction du navigateur.
  • MessageMiddleware: gestion des “flash-messages”.
  • SessionMiddleware: gestion des sessions.
  • AuthenticationMiddleware: authentifier un utilisateur.

Il y en a beaucoup d’autres, et il y en a plein qui trainent sur internet. Mais bien entendu le plus chouette, c’est d’en faire un soi-même.

Par exemple, quand je développe (pas en prod, hien !), j’utilise souvent ce petit middleware que j’ai fait avec amour:

class ForceSuperUserMiddleWare(object):
    def process_request(self, request):
        request.user = User.objects.filter(is_superuser=True)[0]

Comme ça je suis toujours connecté en tant que super user, même sur les projets avec des timeout courts pour la session, des doubles authentifications super relous et tout le toutim des clients paranos (traduction: des américains).

Les middlewares en Django 1.10 et plus

À partir de Django 1.10 (publié en 2016), les middlewares ont changé de tête et deviennent des fonctions qui doivent retourner… une fonction.

Cela ressemble à ça (si vous avez suivi le tuto sur les décorateurs, vous ne serez pas trop perdu):

# la première fonction est une factory, c'est-à-dire une fonction
# dont le but est de définir et retourner une autre fonction
def middleware_factory(get_response):
 
    # On met ici le code d'initialisation du middleware
    # qui ne sera appelée qu'une fois au démarrage de 
    # django
 
    def middleware(request):
        # On met ici le code a exécuter à chaque requête, 
        # avant la vue
 
        # récupération de la réponse. Ouai, le paramètre 
        # get_reponse est une fonction. Bienvenu dans la
        # programmation fonctionnelle.
        response = get_response(request)
 
        # On met ici le code a exécuter pour chaque requête,
        # après que la réponse soit créée.
 
        # n'oubliez pas de retourner la réponse
        return response
 
    # n'oubliez pas de retourner votre middleware
    return middleware

Alors je ne sais pas trop ce qu’ils ont fumé chez Django, car d’un côté ils ont soûlé tout le monde avec les class based views, et de l’autre ils passent en FP pour les middlewares. Mais bon, perso j’aime bien ce nouveau look, je trouve ça plus facile, mais je comprends qu’un débutant regarde ça avec des yeux ronds.

Voici notre premier exemple, avec le nouveau style:

def mideulwaire_for_ever_returns_the_revenge_3(get_response):
 
    def middleware(request):
 
        print("Hey, une requête est arrivée !")
        response = get_response(request)
        print("Hey, on a répondu a une requête !")
 
        return response
 
    return middleware

Pour éviter de tout mélanger, le settings pour les nouveaux middlewares est appelé MIDDLEWARES, et non plus MIDDLEWARE_CLASSES. Vous pouvez déclarer l’un ou l’autre, mais pas les deux.

La bonne nouvelle, c’est qu’il est possible de coder des middlewares qui soient compatibles avec les deux versions. En fait, tous ceux fournis avec Django par défaut le sont, donc si vous utilisez seulement ceux par défaut vous pouvez juste renommer le settings et vous êtes bons.

Si par contre vous devez transformer un de vos anciens middlewares avec un nouveau,vous pouvez hériter django.utils.deprecation.MiddlewareMixin qui 90% du temps suffit à transformer votre vieux middleware en version compatible avec le nouveau style. Si vous préférez le faire à la main parce que vous avez un comportement très spécial, vous pouvez toujours overrider __call__ puisque ça marche avec n’importe quel callable.

11 thoughts on “Qu’est ce qu’un middleware Django ?

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.