Middleware
Présentation du middleware
La définition officielle : le middleware est un hook de niveau cadre pour traiter les requêtes et réponses de Django. C'est un système de plugins léger et de bas niveau utilisé pour modifier globalement les entrées et sorties de Django. Chaque composant de middleware est responsable d'une fonctionnalité spécifique.
Cependant, puisque son impact est global, il faut l'utiliser avec prudence car une mauvaise utilisation peut affecter les performances.
En termes simples, le middleware nous permet d'effectuer des opérations supplémentaires avant et après l'exécution de la fonction de vue. Il s'agit essentiellement d'une classe personnalisée avec plusieurs méthodes que le framework Django exécutera à des moments spécifiques de la requête.
Nous utilisons déjà le middleware sans nous en rendre compte. Ouvrons le fichier Settings.py d'un projet Django et examinons la configuration MIDDLEWARE.
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',
]
La configuration MIDDLEWARE est une liste de chaînes de caractères représentant des classes, c'est-à-dire des middlewares. Nous avons déjà été en contact avec un middleware lié à la protection CSRF. Au début, nous devions le commenter pour pouvoir soumettre des requêtes POST sans être bloqués. Ensuite, après avoir appris à utiliser le jeton csrf_token, nous ne commentons plus ce middleware.
Intéressons-nous maintenant aux méthodes du middleware et aux moments où elles sont exécutées.
Création d'un middleware personnalisé
Le middleware peut définir cinq méthodes, dont les principales sont process_request et process_response :
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
La valeur de retour de ces méthodes peut être None ou un objet HttpResponse. Si c'est None, l'exécution continue selon les règles définies par Django. Si c'est un objet HttpResponse, cet objet est directement retourné à l'utilisateur.
Exemple de création d'un middleware personnalisé
from django.utils.deprecation import MiddlewareMixin
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
process_request
process_request possède un paramètre, request, qui est identique à celui de la fonction de vue.
Sa valeur de retour peut être None ou un HttpResponse. Si la valeur de retour est None, le processus normal continue et le middleware suivant traite la requête. Si c'est un objet HttpResponse, Django n'exécutera pas la fonction de vue mais retournera cet objet au navigateur.
Voyons comment Django exécute la méthode process_request lorsqu'il y a plusieurs middlewares.
from django.utils.deprecation import MiddlewareMixin
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
class Inter2(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter2")
pass
Enregistrons ces deux midddlewares personnalisés dans la configuration MIDDLEWARE de settings.py :
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',
'middlewares.Inter1',
'middlewares.Inter2'
]
Maintenant, si nous accédons à une vue, le terminal affichera :
process_request dans Inter1
process_request dans Inter2
vue index de app01
Changeons les positions de Inter1 et Inter2, puis accédons à une vue. Le terminal affichera alors :
process_request dans Inter2
process_request dans Inter1<br></br>vue index de app01
D'après les résultats, nous voyons que la fonction de vue est toujours exécutée en dernier. Inter2 exécute sa méthode process_request avant Inter1.
Si nous affichons le paramètre request des méthodes process_request des deux middlewares, nous voyons qu'il s'agit du même objet.
Conclusions :
- La méthode process_request du middleware est exécutée avant la fonction de vue.
- Lorsque plusieurs middlewares sont configurés, ils sont exécutés selon l'ordre d'enregistrement dans MIDDLEWARE, c'est-à-dire de l'indice 0 vers la fin de la liste.
- Le même objet request est transmis entre les différents middlewares.
La méthode process_response des plusieurs middlewares est exécutée dans l'ordre inverse de l'enregistrement dans MIDDLEWARE. Cela signifie que la méthode proces_request du premier middleware s'exécute en premier, mais sa méthode process_response s'exécute en dernier. La méthode process_request du dernier middleware s'exécute en dernier, et sa méthode process_response s'exécute en premier.
process_response
Elle possède deux paramètres : request et response. request est le même objet que dans les exemples précédents, et response est l'objet HttpResponse retourné par la fonction de vue. Cette méthode doit également retourner un objet HttpResponse.
Ajoutons la méthode process_response à Inter1 et Inter2 :
from django.utils.deprecation import MiddlewareMixin
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
class Inter2(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter2")
pass
def process_response(self, request, response):
print("process_response dans Inter2")
return response
Accédons à une vue et observons la sortie du terminal :
process_request dans Inter2
process_request dans Inter1
vue index de app01
process_response dans Inter1
process_response dans Inter2
D'après les résultats :
La méthode process_response est exécutée après la fonction de vue, et l'ordre est Inter1 avant Inter2 (avec Inter2 enregistré avant Inter1 dans settings.py).
La méthode process_response des plusieurs middlewares est exécutée dans l'ordre inverse de l'enregistrement dans MIDDLEWARE. Cela signifie que la méthode process_request du premier middleware s'exécute en premier, mais sa méthode process_response s'exécute en dernier. La méthode process_request du dernier middleware s'exécute en dernier, et sa méthode process_response s'exécute en premier.
process_view
process_view(self, request, view_func, view_args, view_kwargs)
Cette méthode possède quatre paramètres :
request est l'objet HttpRequest.
view_func est la fonction de vue que Django va utiliser. (C'est l'objet fonction réel, pas le nom de la fonction en chaîne.)
view_args est la liste des arguments positionnels à transmettre à la vue.
view_kwargs est le dictionnaire des arguments nommés à transmettre à la vue. view_args et view_kwargs ne contiennent pas le premier paramètre de la vue (request).
Django appelle la méthode process_view avant d'exécuter la fonction de vue.
Elle doit retourner None ou un objet HttpResponse. Si elle retourne None, Django continuera à traiter cette requête en exécutant les méthodes process_view des autres middlewares, puis la vue correspondante. Si elle retourne un objet HttpResponse, Django n'appelera pas la fonction de vue appropriée. Il exécutera la méthode process_response du middleware et appliquera cet HttpResponse au résultat.
Ajoutons la méthode process_view à Inter1 et Inter2 :
from django.utils.deprecation import MiddlewareMixin
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter1")
print(view_func, view_func.__name__)
class Inter2(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter2")
pass
def process_response(self, request, response):
print("process_response dans Inter2")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter2")
print(view_func, view_func.__name__)
Accédons à la fonction de vue index et observons les résultats :
process_request dans Inter2
process_request dans Inter1
--------------------------------------------------------------------------------
process_view dans Inter2
<function index at 0x000001DE68317488> index
--------------------------------------------------------------------------------
process_view dans Inter1
<function index at 0x000001DE68317488> index
vue index de app01
process_response dans Inter1
process_response dans Inter2
La méthode process_view est exécutée après process_request et avant la fonction de vue, dans l'ordre d'enregistrement dans MIDDLEWARE (de l'avant vers l'arrière).
process_exception
process_exception(self, request, exception)
Cette méthode possède deux paramètres :
request est un objet HttpRequest.
exception est l'objet Exception généré par la fonction de vue.
Cette méthode ne s'exécute que lorsqu'une exception se produit dans la fonction de vue. Sa valeur de retour peut être None ou un objet HttpResponse. Si c'est un objet HttpResponse, Django appellera les méthodes process_response du template et du middleware, et retournera au navigateur. Sinon, il traitera l'exception par défaut. Si elle retourne None, la méthode process_exception du middleware suivant sera appelée pour traiter l'exception. L'ordre d'exécution est également l'ordre inverse de l'enregistrement du middleware.
Ajoutons cette méthode à Inter1 et Inter2 :
from django.utils.deprecation import MiddlewareMixin
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter1")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("process_exception dans Inter1")
class Inter2(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter2")
pass
def process_response(self, request, response):
print("process_response dans Inter2")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter2")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("process_exception dans Inter2")
Si la fonction de vue ne lève pas d'exception, la méthode process_exception n'est pas exécutée.
Lançons une exception dans la fonction de vue :
def index(request):
print("vue index de app01")
raise ValueError("Oups")
return HttpResponse("OK")
Retournons un objet réponse dans process_exception de Inter1 :
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter1")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("process_exception dans Inter1")
return HttpResponse(str(exception))
Observons les résultats :
process_request dans Inter2
process_request dans Inter1
--------------------------------------------------------------------------------
process_view dans Inter2
<function index at 0x0000022C09727488> index
--------------------------------------------------------------------------------
process_view dans Inter1
<function index at 0x0000022C09727488> index
vue index de app01
Oups
process_exception dans Inter1
process_response dans Inter1
process_response dans Inter2
Remarque : la méthode process_exception de Inter2 n'a pas été exécutée car la méthode process_exception de Inter1 a retourné directement un objet réponse.
process_template_response (utilisé moins fréquemment)
process_template_response(self, request, response)
Ses paramètres : un objet HttpRequest, response est un objet TemplateResponse (généré par la fonction de vue ou le middleware).
process_template_response s'exécute immédiatement après l'exécution de la fonction de vue, mais sous une condition préalable : l'objet retourné par la fonction de vue doit avoir une méthode render() (ou être un objet TemplateResponse ou équivalent).
class Inter1(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter1")
def process_response(self, request, response):
print("process_response dans Inter1")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter1")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("process_exception dans Inter1")
return HttpResponse(str(exception))
def process_template_response(self, request, response):
print("process_template_response dans Inter1")
return response
class Inter2(MiddlewareMixin):
def process_request(self, request):
print("process_request dans Inter2")
pass
def process_response(self, request, response):
print("process_response dans Inter2")
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print("-" * 80)
print("process_view dans Inter2")
print(view_func, view_func.__name__)
def process_exception(self, request, exception):
print(exception)
print("process_exception dans Inter2")
def process_template_response(self, request, response):
print("process_template_response dans Inter2")
return response
Dans views.py :
def index(request):
print("vue index de app01")
def render():
print("dans index/render")
return HttpResponse("OK")
rep = HttpResponse("OK")
rep.render = render
return rep
Accédons à la vue index, les résultats affichés dans le terminal :
process_request dans Inter2
process_request dans Inter1
--------------------------------------------------------------------------------
process_view dans Inter2
<function index at 0x000001C111B97488> index
--------------------------------------------------------------------------------
process_view dans Inter1
<function index at 0x000001C111B97488> index
vue index de app01
process_template_response dans Inter1
process_template_response dans Inter2
dans index/render
process_response dans Inter1
process_response dans Inter2
D'après les résultats :
Immédiatement après l'exécution de la fonction de vue, la méthode process_template_response du middleware est exécutée, dans l'ordre inverse. D'abord celle de Inter1, puis celle de Inter2. Ensuite, la méthode render de l'objet HttpResponse retourné par la vue est exécutée, retournant un nouvel objet HttpResponse. Ensuite, les méthodes process_response du middleware sont exécutées.
Flux d'exécution du middleware
Dans la partie précédente, nous avons découvert les 5 méthodes du middleware, leurs paramètres, valeurs de retour et moments d'exécution. Résumons maintenant le flux d'exécution du middleware.
Quando una richiesta raggiunge il middleware, questi esegue nell'ordine diretto ogni metodo process_request registrato. Se process_request restituisce None, continua l'esecuzionesequenziale; se restituisce un HttpResponse, non esegue i metodi process_request successivi, ma esegue il metodo process_response del middleware corrente per restituire l'HttpResponse al browser. Quindi, se su 6 middleware registrati il terzo restituisce un HttpResponse, i metodi process_request e process_response dei middleware 4, 5 e 6 non vengono eseguiti; vengono eseguiti nell'ordine inverso i metodi process_response dei middleware 3, 2 e 1.
Dopo che tutti i metodi process_request sono stati eseguiti, viene eseguito il routing per trovare la funzione di vista da eseguire. Prima di eseguire la vista, vengono eseguiti i metodi process_view dei middleware. Se process_view restituisce None, continua nell'ordine; se restituisce un HttpResponse, i metodi process_view dei middleware successivi e la funzione di vista non vengono eseguiti, ma parte l'esecuzione in ordine inverso del metodo process_response dell'ultimo middleware.
I metodi process_template_response e process_exception vengono attivati solo in condizioni specifiche e vengono eseguiti in ordine inverso. Il flusso completo è :
Middleware di verifica de connexion
Le middleware de vérification de connexion nécessite l'utilisation des sessions, donc la table django_session doit exister dans la base de données.
urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/$', views.index),
url(r'^login/$', views.login, name='login'),
]
urls.pyviews.py
from django.shortcuts import render, HttpResponse, redirect
def index(request):
return HttpResponse('Ceci est la page index')
def home(request):
return HttpResponse('Ceci est la page home')
def login(request):
if request.method == "POST":
utilisateur = request.POST.get("utilisateur")
motdepasse = request.POST.get("motdepasse")
if utilisateur == "Admin" et motdepasse == "123456":
# Définir la session
request.session["utilisateur"] = utilisateur
# Obtenir l'URL précédente avant la connexion
next_url = request.GET.get("next")
# Si elle existe, rediriger vers l'URL précédente
if next_url:
return redirect(next_url)
# Sinon, rediriger vers la page index par défaut
else:
return redirect("/index/")
return render(request, "login.html")
views.pylogin.html
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page de connexion</title>
</head>
<body>
<form action="{% url 'login' %}">
<p>
<label for="utilisateur">Nom d'utilisateur :</label>
<input type="text" name="utilisateur" id="utilisateur">
</p>
<p>
<label for="motdepasse">Mot de passe :</label>
<input type="text" name="motdepasse" id="motdepasse">
</p>
<input type="submit" value="Se connecter">
</form>
</body>
</html>
login.htmlmiddlewares.py
class AuthentificationMW(MiddlewareMixin):
liste_blanche = ['/login/', ]
liste_noire = ['/interdit/', ]
def process_request(self, request):
from django.shortcuts import redirect, HttpResponse
url_suivante = request.path_info
print(request.path_info, request.get_full_path())
if url_suivante in self.liste_blanche or request.session.get("utilisateur"):
return
elif url_suivante in self.liste_noire:
return HttpResponse('URL illégale')
else:
return redirect("/login/?next={}".format(url_suivante))
Middleware de vérification de connexionL'enregistrer dans settings.py
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',
'middlewares.AuthentificationMW',
]
Enregistrement du middleware dans settings.pyAprès l'enregistrement du middleware AuthentificationMW, toutes les requêtes passent par la méthode process_request de AuthentificationMW.
Si l'URL visitée est dans la liste blanche ou si le nom d'utilisateur existe dans la session, aucune restriction n'est appliquée et le processus normal continue ;
Si l'URL est dans la liste noire, retourne la chaîne "URL illégale" ;
Pour les URL normales nécessitant une connexion, redirige le navigateur vers la page de connexion.
Note : Le middleware AuthentificationMW a besoin de la session, donc sa position d'enregistrement doit être inférieure à celle du middleware de session.
Schéma du flux de requête Django