Chapitre 15: Middleware
À l’occasion, vous aurez besoin de lancer un bout de code sur chaque requête que Django gère. Ce code peut avoir besoin de modifier la requête avant que la vue la prenne en charge, il peut avoir besoin de loguer des information au sujet de la requête afin de pouvoir débuguer, et ainsi de suite.
Vous pouvez réaliser cela grâce au framework middleware de Django, qui est un jeu d’accroches sur le traitement des requêtes/réponses de Django. Il s’agit d’un système de «plug-in» bas niveau, léger, capable d’altérer globalement les entrées et sorties de Django.
Chaque composant middleware est responsable d’une fonctionnalité spécifique. Si vous lisez ce livre linéairement (désolé pour les postmodernistes), vous avez déjà vu le middleware de nombreuses fois:
- toutes les sessions et les outils utilisateurs que nous avons vu au chapitre 12 sont rendus possibles par quelques petits morceaux de middleware (plus précisément, le middleware rend disponible request.session et request.user au sein de vos vues).
- le cache portant sur tout le site dont nous avons discuté au chapitre 13 est en fait une simple pièce du middleware qui dévie l’appel à votre vue si la réponse pour cette vue à déjà été mise en cache.
- les applications contributives flatpages, redirects, et csrf du chapitre 14 propose leur magie grâce aux composants middleware.
Ce chapitre plonge plus profondemment dans ce qu’est exactement un middleware ainsi que dans ses principes de fonctionnement, et explique comment vous pouvez écrire votre propre middleware.
Qu’est-ce qu’un middleware
Un composant middleware est simplement une classe Python qui se conforme à une certaine API. Avant de plonger dans les aspects formels de ce qu’est une API, jettons un oeil sur un exemple très simple.
Les sites à fort trafic ont souvent besoin de déployer Django derrière un proxy répartiteur de charge (voir le chapitre 20). Ceci peut causer quelques petites complications, l’une d’elle étant que toutes les requêtes (request.META["REMOTE_IP"]) aurons l’IP du répartiteur de charge, et non pas l’IP d’originie de la requête en cours. Les répartiteurs de charge gèrent cela en générant une en-tête spéciale, X-Forwarded-For, vers l’adresse IP à l’origine de la requête.
Voici donc une petit bout de middleware qui permet aux sites fonctionnant derrière un proxy de continuer à voir l’adresse IP correcte dans request.META["REMOTE_ADDR"]:
class SetRemoteAddrFromForwardedFor(object):
def process_request(self, request):
try:
real_ip =
request.META['HTTP_X_FORWARDED_FOR']
except KeyError:
pass
else:
# HTTP_X_FORWARDED_FOR can be a comma-
separated list of IPs.
# Take just the first one.
real_ip = real_ip.split(",")[0]
request.META['REMOTE_ADDR'] = real_ip
Si ceci est installé (voir la section suivante), chaque valeur X-Forwarded-For de la requête sera automatiquement insérée dans request.META['REMOTE_ADDR']. Cela signifie que vos applications Django n’ont pas besoin de savoir si elles sont derrière un proxy répartissant la charge ou pas; elles peuvent simplement accéder à request.META['REMOTE_ADDR'], et cela fonctionnera qu’un proxy soit utilisé ou non.
En fait, c’est un besoin si courant que la partie du middleware est une primitive de Django. Elle se trouve dans django.middleware.http, et vous pouvez en lire un peu plus à son sujet dans la section suivante.
Installation du middleware
Si vous avez lu ce livre d’un seul trait, vous avez déjà vu de nombreux exemples d’installation de middleware; beaucoup d’exemples dans les chapitres ont eu besoin de certain middleware. Par soucis d’éxhaustivité, voici comment installer un middleware.
Pour activer un composant de middleware, ajouter le au tuple MIDDLEWARE_CLASSES dans votre module de paramètres. Dans MIDDLEWARE_CLASSES, chaque composant de middleware est représenté par une chaîne: le chemin Python complet vers le nom de la classe du middleware. Par exemple, voici le middleware MIDDLEWARE_CLASSES par défaut crée par django-admin.py startproject:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.doc.XViewMiddleware'
)
Une installation Django ne nécessite aucun middleware -MIDDLEWARE_CLASSES peut être vide, si vous le voulez - mais nous vous recommandons d’activer CommonMiddleware, ce que nous expliquons brièvement.
L’ordre est significatif. Dans les phases de requête et de vue, Django applique le middleware selon l’ordre indiqué par MIDDLEWARE_CLASSES, puis lors des phases de réponse et d’exception, Django applique le middleware dans l’ordre inverse. Ceci étant, Django traite MIDDLEWARE_CLASSES comme une sorte «d’emballage» autour de la fonction vue: lors de la requête il parcours la liste puis les vues, et lors de la réponse il fait le chemin inverse. Lisez la section «Comment Django traite une requête» au chapitre 3 pour une revue des différentes phases.
Méthodes du middleware
À présent que vous savez ce qu’est un middleware et que vous savez comment l’installer, regardons toutes les méthodes à disposition que les classes de middleware peuvent définir.
Constructeur: __init__(self)
Utilisez __init__() pour paramétrer le système selon une classe de middleware donnée.
Pour des raisons de performances, chaque classe de middleware activée est instanciée une seule fois par processus serveur. Cela signifie que __init__() est appelé seulement une fois - au démarrage du serveur - et non pas à chaque requêtes individuelles.
Une raison courante d’implémenter une méthode __init__() est de vérifier si le middleware est réellement nécessaire. Si __init__() lève une exception django.core.exceptions.MiddlewareNotUsed, alors Django retirera le middleware de la pile de middleware. Vous pourriez utiliser cette fonctionnalité pour vérifier quelques composants logiciels nécessaire à la classe middleware, ou vérifier si le serveur tourne en mode débug, ou tout autre situation liée à l’environnement.
Si une classe de middleware définie une méthode __init__(), la méthode ne doit pas prendre d’argument autre que le standard self.
Le Préprocesseur de requête: process_request(self, request)
Cette méthode est appellée dès que la requête a été crée - avant que Django ait analysé l’URL pour déterminer quelle vue lancer. L’objet HttpRequest lui est transmis, ce que vous pouvez modifer à la volonté.
process_request() retourne soit None soit un objet HttpResponse.
- si elle retourne None, Django continuera à traiter cette requête, executant tout autre middleware puis ensuite la vue appropriée.
- si elle retourne un objet HttpResponse, Django ne s’embêtera pas à appeler tout autre middleware (de nimporte quel type) ou à appeler la vue appropriée. Django retournera immédiatement cette HttpResponse.
Préprocesseur de vue: process_view(self, request, view, args, kwargs)
Cette méthode est appelée après l’appel au préprocesseur et lorsque Django a déterminé la vue à exécuter, mais avant que cette vue soit réellement exécutée.
Les arguments transmis à cette vue sont détaillés dans le Tableau 15-1.
Table 15-1. Arguments transmis à process_view()
| Argument | Explanation |
| request | L’objet HttpRequest. |
| view | La fonction Python que Django appellera pour prendre en charge cette requête. Il s’agit de la fonction objet en cours, et non pas le nom de la fonction sous forme de chaîne. |
| args | La liste des arguments positionnels qui seront transmis à la vue, à l’exclusion de l’argument request (qui est toujours le premier argument d’une vue). |
| kwargs | Le dictionnaire d’arguments mot-clef qui sera transmis à la vue. Tout comme pour process_request(), process_view() doit retourner soit None soit un objet HttpResponse.
|
Préprocesseur de réponse: process_response(self, request, response)
Cette méthode est appellée après que la fonctino vue ait été appelée et que la réponse ait été générée. Ici, le préprocesseur peut modifier le contenu d’une réponse; un cas évident d’utilisation de ceci est la compression de contenu, tel que l’utilisation de gzip sur le HTML de la requête.
Le paramètre doit être assez explicite: request est l’objet requête, et response est l’objet réponse retourné par la vue.
Contrairement aux préprocesseurs de requête et de vue, qui peuvent retournés None, process_response() doit retourner un objet HttpResponse. Cette réponse peut être celle d’origine transmise à la fonction (éventuellement modifiée) ou bien une toute nouvelle réponse.
Postprocesseur d’exception: process_exception(self, request, exception)
Cette méthode est appellée seulement si quelque chose se déroule mal et qu’une vue lève un exception non gérée. Vous pouvez utiliser cette accroche pour envoyer des notifications d’erreur, sauvegarder les informations postmortem dans un fichier de log, ou même tenter de réparer cette erreur automatiquement.
Les paramètres de cette fonction sont les mêmes que pour l’objet request dont nous avons parlé jusqu’à présent, ainsi qu’exception, qui en fait l’actuel objet Exception levé par cette fonction vue.
process_exception() doit renvoyer soit None soit un objet HttpResponse.
- si elle rnvoie None, Django continuera à traiter cette requête avec la gestion des exceptions native du framework.
- si elle renvoie un objet HttpResponse, Django utilisera cette réponse à la place de la gestion des exceptions native du framework.
Note
Django est livré avec de nombreuses classes de middleware (abordées dans les sections suivantes) qui sont de bons exemples. Lire leur code doit vous donner une bonne idée de la puissance de middleware.
Vous pouvez aussi trouver de nombreux exemples de contributions de la communauté sur le wiki de Django: http://code.djangoproject.com/wiki/ContributedMiddleware
Middleware natifs
Django fournit nativement quelques middlewares pour pouvoir gérer les problèmes courants, ce dont nous discutons dans les sections qui viennent.
Middleware support d’identification
classe du middleware: django.contrib.auth.middleware.AuthenticationMiddleware.
ce middleware active le support d’identification. Il ajoute l’attribut request.user, représentant l’utilisateur actuellement connecté, et ce pour chaque objet HttpRequest.
Consultez le chapitre 12 pour des détails complets.
Middleware «commun»
Classe de middleware: django.middleware.common.CommonMiddleware.
Ce middleware ajoute quelques commodités pour les perfectionnistes:
interdire l’acces à l’agent utilisateur précisé dans le paramètre ``DISALLOWED_USER_AGENTS``: s’il est fournit, ce paramètre doit être une liste d’objets «expression rationnelles compilées» qui sont mis en correspondance avec l’en-tête de l’agent utilisateur pour chaque requête entrante. Voici un échantillon extrait d’un fichier de paramètres:
import re DISALLOWED_USER_AGENTS = ( re.compile(r'^OmniExplorer_Bot'), re.compile(r'^Googlebot') )Notez la présence de import re, puisque DISALLOWED_USER_AGENTS impose que ses valeurs soient des regexes compilées (autrement dit, la sortie de re.compile()). Le fichier de paramètres correspondant à du Python standard, il est donc tout à fait possible d’y inclure l’instruction Python import.
fait une réécriture d’URL sur la base des paramètres ``APPEND_SLASH`` et ``PREPEND_WWW``: si APPEND_SLASH est à True, les URLs auxquels il manque une barre oblique finale seront redirigés sur le même URL avec une barre oblique finale, à moins que le dernier composant du chemin contienne un point. Ainsi foo.com/bar est redirigé vers foo.com/bar/, alors que foo.com/bar/file.txt est transmis tel quel.
Si PREPEND_WWW est à True, les URLs auxquels il manque un «www.» initial seront redirigés vers le même URL avec un «www.» devant.
Ces deux options ont pour but de normaliser les URLs. La philosophie est que chaque URL doit exister dans un - et seulement un - endroit. Techniquement l’URL example.com/bar est distinct de example.com/bar/, qui à sont tour est différent de www.example.com/bar/. Un indexeur de moteur de recherche traitera ceux-ci comme des URLs séparés, au détriment du classement de votre site par le moteur. La normalisation des URLs est donc une bonne pratique.
gère les ETags sur la base du paramètre ``USE_ETAGS``: les ETags sont une optimisation au niveau de HTTP afin de mettre en cache conditionnellement les pages. Si USE_ETAGS est fixé à True, Django calculera un ETag pour chaque requête grâce à un hachage MD5 sur le contenu de la page, et prendra soin d’envoyer des réponses Not Modified (ndt: inchangé), si approprié.
Notez qu’il existe aussi le middleware conditionnel GET, abordé sous peu, et qui gère les ETags et fait même un peu plus.
Middleware de compression
Classe du middleware: django.middleware.gzip.GZipMiddleware.
Ce middleware compresse automatiquement le contenu à destination des navigateurs qui supportent la compression gzip (tous les navigateurs modernes). Ceci peut grandement réduire le niveau de consommation en bande passante d’un serveur web. La contre-partie est que cela nécessite un peu de délai de traitement pour compresser les pages.
Nous préférons habituellement la vitesse à la bande passante, mais si vous préférez l’inverse, il suffit d’activer ce middleware.
Middleware GET conditionnel
Classe du middleware: django.middleware.http.ConditionalGetMiddleware.
Ce middleawre fournit un support pour les opérations GET conditionnelles. Si la réponse contient un Last-Modified ou un ETag ou une en-tête, et que la requête contient un If-None-Match ou un If-Modified-Since, la réponse est remplacée par une réponse 304 («inchangé»). Le support ETag dépends du paramètre USE_ETAGS et s’attends à ce que l’en-tête ETag de la réponse soit déjà fixé. Comme annoncé auparavant, l’en-tête ETag est fixé par le middleware Commun.
Il supprime aussi le contenu de toute réponse à une requête HEAD et fixe les en-têtes de la réponse Date et de la réponse Content-Length pour toutes les requêtes.
Support du proxy inverse (middleware X-Forwarded-For)
Classe du middleware: django.middleware.http.SetRemoteAddrFromForwardedFor.
Il s’agit de l’exemple que nous avons abordé plus avant dans la section «Qu’est-ce qu’un middleware ?». Il fixe la request.META['REMOTE_ADDR'] basée sur request.META['HTTP_X_FORWARDED_FOR'], si ce dernier est paramétré. C’est utile si vous êtes derrière un proxy inverse qui fait en sorte que la REMOTE_ADDR de chaque requête soit placée à 127.0.0.1.
Danger !
Ce middleware ne valide pas HTTP_X_FORWARDED_FOR.
Si vous n’êtes pas derrière un proxy inverse qui fixe automatiquement HTTP_X_FORWARDED_FOR, n’utilisez pas ce middleware. N’importe qui peut usurper la valeur de HTTP_X_FORWARDED_FOR, et puisque ceci fixe la REMOTE_ADDR basée sur HTTP_X_FORWARDED_FOR, cela signifie que n’importe qui peut simuler cette adresse IP.
Utilisez ce middleware uniquement lorsque vous pouvez être absolument certain de la valeur de HTTP_X_FORWARDED_FOR.
Middleware support de session
Classe du middleware: django.contrib.sessions.middleware.SessionMiddleware.
Ce middleware active le support de session. Lisez le chapitre 12 pour les détails.
Middleware de cache pour tout le site
Classe du middleware: django.middleware.cache.CacheMiddleware.
Ce middleware met en cache chaque page propulsée par Django. Ceci a été discuté en détail au chapitre 13.
Middleware de transaction
Classe du middleware: django.middleware.transaction.TransactionMiddleware.
Ce middleware met en correspondance un COMMIT ou ROLLBACK de base de données avec la phase de requpete/réponse. Si une vue est lancée avec succès, un COMMIT est effectué. Si la vue lève une exception, un ROLLBACK est effectué.
L’ordre de ce middleware dans la pile est important. Les modules de middleware fonctionnant en dehors le font avec un commit au moment de la sauvegarde - une fonctionnalité de Django par défaut. Les modules de middleware fonctionnant au dedans (arrivant plus tard dans la pile) le feront sous le même contrôle de transaction que les vues.
Voir l’annexe C pour plus d’information au sujet des transactions en base de données.
Middleware «X-View»
Classe de middleware: django.middleware.doc.XViewMiddleware.
Ce middleware envoie des en-têtes HTTP X-View personnalisés aux requêtes HEAD provenant des adresses IP définies dans le paramètre INTERNAL_IPS. Ceci est utilisé par le système automatique de documentation de Django.
Et ensuite ?
Les développeurs web et les concepteurs de schéma de base de données nous pas toujours le luxe de partir de rien. Dans le chapitre suivant, nous couvrirons la façon d’intégrer les bases de données existantes, tels que les schémaa de base de données que vous avez hérités des années 80.
Dernière modification: 2008-08-05 15:15:59.784752