Plug-ins
BunkerWeb est livré avec un système de plugins permettant d'ajouter facilement de nouvelles fonctionnalités. Une fois qu'un plugin est installé, vous pouvez le gérer à l'aide de paramètres supplémentaires définis par le plugin.
Plugins officiels
Voici la liste des plugins "officiels" que nous maintenons (voir le dépôt bunkerweb-plugins pour plus d'informations) :
Nom | Version | Description | Lien |
---|---|---|---|
ClamAV | 1.9 | Analyse automatiquement les fichiers téléchargés avec le moteur antivirus ClamAV et rejette la demande lorsqu'un fichier est détecté comme malveillant. | bunkerweb-plugins/clamav |
Coraza | 1.9 | Inspectez les requĂŞtes Ă l'aide du WAF Coraza (alternative Ă ModSecurity). | bunkerweb-plugins/coraza |
Discorde | 1.9 | Envoyez des notifications de sécurité à un canal Discord à l'aide d'un Webhook. | bunkerweb-plugins/discord |
Lâche | 1.9 | Envoyez des notifications de sécurité à un canal Slack à l'aide d'un Webhook. | bunkerweb-plugins/slack |
VirusTotal | 1.9 | Analyse automatiquement les fichiers téléchargés à l'aide de l'API VirusTotal et rejette la demande lorsqu'un fichier est détecté comme malveillant. | bunkerweb-plugins/virustotal |
Crochet Web | 1.9 | Envoyez des notifications de sécurité à un point de terminaison HTTP personnalisé à l'aide d'un Webhook. | bunkerweb-plugins/webhook |
Comment utiliser un plugin
Automatique
Si vous souhaitez installer rapidement des plugins externes, vous pouvez utiliser ce EXTERNAL_PLUGIN_URLS
paramètre. Il prend une liste d'URL séparées par des espaces, chacune pointant vers une archive compressée (format zip) contenant un ou plusieurs plugins.
Vous pouvez utiliser la valeur suivante si vous souhaitez installer automatiquement les plugins officiels : EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/tags/v1.9.zip
Manuelle
La première étape consiste à installer le plugin en plaçant ses fichiers dans le dossier de données correspondant plugins
. La procédure dépend de votre intégration :
Lors de l'utilisation de l'intégration Docker, les plugins doivent être placés dans le volume monté sur /data/plugins
dans le conteneur du planificateur.
La première chose à faire est de créer le dossier des plugins :
mkdir -p ./bw-data/plugins
Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :
git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins
Utilisation d'un dossier local pour les données persistantes
Le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID 101 et GID 101 à l'intérieur du conteneur. La raison en est la sécurité : en cas d'exploitation d'une vulnérabilité, l'attaquant n'aura pas de privilèges root complets (UID/GID 0). Mais il y a un inconvénient : si vous utilisez un dossier local pour les données persistantes, vous devrez définir les permissions correctes afin que l'utilisateur non privilégié puisse écrire des données dedans. Quelque chose comme ça devrait faire l'affaire :
mkdir bw-data && \
chown root:101 bw-data && \
chmod 770 bw-data
Alternativement, si le dossier existe déjà :
chown -R root:101 bw-data && \
chmod -R 770 bw-data
Si vous utilisez Docker en mode rootless ou podman, les UID et GID dans le conteneur seront mappés à des valeurs différentes sur l'hôte. Vous devrez d'abord vérifier vos subuid et subgid initiaux :
grep ^$(whoami): /etc/subuid && \
grep ^$(whoami): /etc/subgid
Par exemple, si vous avez une valeur de 100000, l'UID/GID mappé sera 100100 (100000 + 100) :
mkdir bw-data && \
sudo chgrp 100100 bw-data && \
chmod 770 bw-data
Ou si le dossier existe déjà :
sudo chgrp -R 100100 bw-data && \
chmod -R 770 bw-data
Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Docker :
services:
...
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
volumes:
- ./bw-data:/data
...
Lors de l'utilisation de l'intégration Docker autoconf, les plugins doivent être placés dans le volume monté sur /data/plugins
dans le conteneur du planificateur.
La première chose à faire est de créer le dossier des plugins :
mkdir -p ./bw-data/plugins
Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :
git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* ./bw-data/plugins
Étant donné que le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID et GID 101, vous devrez modifier les permissions :
chown -R 101:101 ./bw-data
Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Docker :
services:
...
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
volumes:
- ./bw-data:/data
...
Obsolète
L'intégration Swarm est obsolète et sera supprimée dans une future version. Veuillez envisager d'utiliser l'intégration Kubernetes à la place.
Plus d'informations sont disponibles dans la documentation de l'intégration Swarm.
Lors de l'utilisation de l'intégration Swarm, les plugins doivent être placés dans le volume monté sur /data/plugins
dans le conteneur du planificateur.
Volume Swarm
La configuration d'un volume Swarm qui persistera lorsque le service du planificateur s'exécute sur différents nœuds n'est pas couverte dans cette documentation. Nous supposons que vous avez un dossier partagé monté sur /shared
sur tous les nœuds.
La première chose à faire est de créer le dossier des plugins :
mkdir -p /shared/bw-plugins
Ensuite, vous pouvez déposer les plugins de votre choix dans ce dossier :
git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /shared/bw-plugins
Étant donné que le planificateur s'exécute en tant qu'utilisateur non privilégié avec UID et GID 101, vous devrez modifier les permissions :
chown -R 101:101 /shared/bw-plugins
Ensuite, vous pouvez monter le volume lors du démarrage de votre stack Swarm :
services:
...
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
volumes:
- /shared/bw-plugins:/data/plugins
...
Lors de l'utilisation de l'intégration Kubernetes, les plugins doivent être placés dans le volume monté sur /data/plugins
dans le conteneur du planificateur.
La première chose à faire est de déclarer un PersistentVolumeClaim qui contiendra nos données de plugins :
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-bunkerweb-plugins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Vous pouvez maintenant ajouter le montage de volume et un conteneur d'initialisation pour provisionner automatiquement le volume :
apiVersion: apps/v1
kind: Deployment
metadata:
name: bunkerweb-scheduler
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: bunkerweb-scheduler
template:
metadata:
labels:
app: bunkerweb-scheduler
spec:
serviceAccountName: sa-bunkerweb
containers:
- name: bunkerweb-scheduler
image: bunkerity/bunkerweb-scheduler:1.6.5-rc1
imagePullPolicy: Always
env:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
volumeMounts:
- mountPath: "/data/plugins"
name: vol-plugins
initContainers:
- name: bunkerweb-scheduler-init
image: alpine/git
command: ["/bin/sh", "-c"]
args: ["git clone https://github.com/bunkerity/bunkerweb-plugins /data/plugins && chown -R 101:101 /data/plugins"]
volumeMounts:
- mountPath: "/data/plugins"
name: vol-plugins
volumes:
- name: vol-plugins
persistentVolumeClaim:
claimName: pvc-bunkerweb-plugins
Lors de l'utilisation de l'intégration Linux, les plugins doivent être écrits dans le dossier /etc/bunkerweb/plugins
:
git clone https://github.com/bunkerity/bunkerweb-plugins && \
cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins && \
chown -R nginx:nginx /etc/bunkerweb/plugins
Écrire un plugin
Structure
Plugins existants
Si la documentation n'est pas suffisante, vous pouvez consulter le code source existant des plugins officiels et des plugins core (déjà inclus dans BunkerWeb mais ce sont des plugins, techniquement parlant).
Ă€ quoi ressemble la structure d'un plugin :
plugin /
confs / conf_type / conf_name.conf
ui / actions.py
hooks.py
template.html
blueprints / <blueprint_file(s)>
templates / <blueprint_template(s)>
jobs / my-job.py
templates / my-template.json
my-template / configs / conf_type / conf_name.conf
plugin.lua
plugin.json
-
conf_name.conf : Ajout de configurations NGINX personnalisées (en tant que modèles Jinja2).
-
actions.py : Script à exécuter sur le serveur Flask. Ce script s'exécute dans un contexte Flask, ce qui vous donne accès à des bibliothèques et des utilitaires tels que
jinja2
etrequests
. -
hooks.py : Fichier Python personnalisé qui contient les hooks de flask et qui sera exécuté lors du chargement du plugin.
-
template.html : Page de plugin personnalisée accessible via l'interface utilisateur.
-
Dossier Blueprints (dans l'interface utilisateur) : Ce dossier est utilisé pour remplacer les blueprints Flask existants ou en créer de nouveaux. À l'intérieur, vous pouvez inclure des fichiers de blueprint et un **** sous-dossier de modèles facultatif pour les modèles spécifiques aux blueprints.
-
jobs py file : Fichiers Python personnalisés exécutés en tant que jobs par le planificateur.
-
my-template.json : Ajoutez des modèles personnalisés pour remplacer les valeurs par défaut des paramètres et appliquer facilement des configurations personnalisées.
-
plugin.lua : Code exécuté sur NGINX à l'aide du module NGINX LUA.
-
plugin.json : Métadonnées, paramètres et définitions de tâches pour votre plugin.
Commencer
La première étape consiste à créer un dossier qui contiendra le plugin :
mkdir myplugin && \
cd myplugin
Métadonnées
Un fichier nommé plugin.json et écrit à la racine du dossier du plugin doit contenir des métadonnées sur le plugin. En voici un exemple :
{
"id": "myplugin",
"name": "My Plugin",
"description": "Just an example plugin.",
"version": "1.0",
"stream": "partial",
"settings": {
"DUMMY_SETTING": {
"context": "multisite",
"default": "1234",
"help": "Here is the help of the setting.",
"id": "dummy-id",
"label": "Dummy setting",
"regex": "^.*$",
"type": "text"
}
},
"jobs": [
{
"name": "my-job",
"file": "my-job.py",
"every": "hour"
}
]
}
Voici le détail des champs :
Champ | Obligatoire | Type | Description |
---|---|---|---|
id |
oui | corde | ID interne du plugin : doit être unique parmi les autres plugins (y compris les "core") et ne contenir que des caractères minuscules. |
name |
oui | corde | Nom de votre plugin. |
description |
oui | corde | Description de votre plugin. |
version |
oui | corde | Version de votre plugin. |
stream |
oui | corde | Informations sur la prise en charge des flux : no , yes ou partial . |
settings |
oui | Dict | Liste des paramètres de votre plugin. |
jobs |
Non | liste | Liste des jobs de votre plugin. |
Chaque paramètre comporte les champs suivants (la clé est l'ID des paramètres utilisés dans une configuration) :
Champ | Obligatoire | Type | Description |
---|---|---|---|
context |
oui | corde | Contexte du cadre : multisite ou global . |
default |
oui | corde | Valeur par défaut du paramètre. |
help |
oui | corde | Texte d'aide sur le plugin (affiché dans l'interface utilisateur Web). |
id |
oui | corde | ID interne utilisé par l'interface utilisateur web pour les éléments HTML. |
label |
oui | corde | Étiquette affichée par l'interface utilisateur Web. |
regex |
oui | corde | L'expression régulière utilisée pour valider la valeur fournie par l'utilisateur. |
type |
oui | corde | Le type du champ : text , check , select ou password . |
multiple |
Non | corde | ID unique pour regrouper plusieurs paramètres avec des chiffres comme suffixe. |
select |
Non | liste | Liste des valeurs de chaîne possibles lorsque est type . select |
Chaque emploi comporte les champs suivants :
Champ | Obligatoire | Type | Description |
---|---|---|---|
name |
oui | corde | Nom de l'emploi. |
file |
oui | corde | Nom du fichier à l'intérieur du dossier jobs. |
every |
oui | corde | Fréquence de planification des tâches : minute , hour , day week , ou once (pas de fréquence, une seule fois avant de (ré)générer la configuration). |
Configurations
Vous pouvez ajouter des configurations NGINX personnalisées en ajoutant un dossier nommé confs avec un contenu similaire aux configurations personnalisées. Chaque sous-dossier à l'intérieur des confs contiendra des modèles jinja2 qui seront générés et chargés dans le contexte correspondant (http
server-http
default-server-http
stream
server-stream
, , modsec
modsec-crs
, crs-plugins-before
et crs-plugins-after
).
Voici un exemple de fichier de modèle de configuration à l'intérieur du dossier confs/server-http nommé example.conf :
location /setting {
default_type 'text/plain';
content_by_lua_block {
ngx.say('{{ DUMMY_SETTING }}')
}
}
{{ DUMMY_SETTING }}
sera remplacée par la valeur de la DUMMY_SETTING
choisie par l'utilisateur du plugin.
Modèles
Pour plus d'informations, consultez la documentation des modèles.
LUA
Scénario principal
Sous le capot, BunkerWeb utilise le module LUA de NGINX pour exécuter du code dans NGINX. Les plugins qui ont besoin d'exécuter du code doivent fournir un fichier lua dans le répertoire racine du dossier du plugin en utilisant la id
valeur de plugin.json comme nom. Voici un exemple nommé myplugin.lua :
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local myplugin = class("myplugin", plugin)
function myplugin:initialize(ctx)
plugin.initialize(self, "myplugin", ctx)
self.dummy = "dummy"
end
function myplugin:init()
self.logger:log(ngx.NOTICE, "init called")
return self:ret(true, "success")
end
function myplugin:set()
self.logger:log(ngx.NOTICE, "set called")
return self:ret(true, "success")
end
function myplugin:access()
self.logger:log(ngx.NOTICE, "access called")
return self:ret(true, "success")
end
function myplugin:log()
self.logger:log(ngx.NOTICE, "log called")
return self:ret(true, "success")
end
function myplugin:log_default()
self.logger:log(ngx.NOTICE, "log_default called")
return self:ret(true, "success")
end
function myplugin:preread()
self.logger:log(ngx.NOTICE, "preread called")
return self:ret(true, "success")
end
function myplugin:log_stream()
self.logger:log(ngx.NOTICE, "log_stream called")
return self:ret(true, "success")
end
return myplugin
Les fonctions déclarées sont automatiquement appelées dans des contextes spécifiques. Voici le détail de chaque fonction :
Fonction | Contexte | Description | Valeur de retour |
---|---|---|---|
init |
init_by_lua | Appelé lorsque NGINX vient de démarrer ou a reçu un ordre de rechargement. Le cas d'utilisation typique consiste à préparer toutes les données qui seront utilisées par votre plugin. | ret , msg
|
set |
set_by_lua | Appelé avant chaque requête reçue par le serveur. Le cas d'utilisation typique est celui de l'informatique avant la phase d'accès. | ret , msg
|
access |
access_by_lua | Appelé à chaque requête reçue par le serveur. Le cas d'utilisation typique consiste à effectuer les vérifications de sécurité ici et à refuser la demande si nécessaire. | ret , msg ,status ,redirect
|
log |
log_by_lua | Appelé lorsqu'une requête est terminée (et avant qu'elle ne soit consignée dans les journaux d'accès). Le cas d'utilisation typique est de créer des statistiques ou de calculer des compteurs par exemple. | ret , msg
|
log_default |
log_by_lua | Identique mais log uniquement appelé sur le serveur par défaut. |
ret , msg
|
preread |
preread_by_lua | Similaire Ă la access fonction mais pour le mode flux. |
ret , msg ,status
|
log_stream |
log_by_lua | Similaire Ă la log fonction mais pour le mode flux. |
ret , msg
|
Bibliothèques
Toutes les directives du module LUA NGINX sont disponibles et le module LUA de flux NGINX. En plus de cela, vous pouvez utiliser les bibliothèques LUA incluses dans BunkerWeb : voir ce script pour la liste complète.
Si vous avez besoin de bibliothèques supplémentaires, vous pouvez les mettre dans le dossier racine du plugin et y accéder en les préfixant avec votre ID de plugin. Voici un exemple de fichier nommé mylibrary.lua :
local _M = {}
_M.dummy = function ()
return "dummy"
end
return _M
Et voici comment vous pouvez l'utiliser Ă partir du fichier myplugin.lua :
local mylibrary = require "myplugin.mylibrary"
...
mylibrary.dummy()
...
Aides
Certains modules d'aide fournissent des aides utiles courantes :
self.variables
: permet d'accéder et de stocker les attributs des pluginsself.logger
: journaux d'impressionbunkerweb.utils
: diverses fonctions utilesbunkerweb.datastore
: accéder aux données globales partagées sur une instance (magasin de clés/valeurs)bunkerweb.clusterstore
: accéder à un magasin de données Redis partagé entre les instances de BunkerWeb (magasin clé/valeur)
Pour accéder aux fonctions, il faut d'abord demander les modules :
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local clustestore = require "bunkerweb.clustertore"
Récupérer une valeur de réglage :
local myvar = self.variables["DUMMY_SETTING"]
if not myvar then
self.logger:log(ngx.ERR, "can't retrieve setting DUMMY_SETTING")
else
self.logger:log(ngx.NOTICE, "DUMMY_SETTING = " .. value)
end
Stockez quelque chose dans le cache local :
local ok, err = self.datastore:set("plugin_myplugin_something", "somevalue")
if not ok then
self.logger:log(ngx.ERR, "can't save plugin_myplugin_something into datastore : " .. err)
else
self.logger:log(ngx.NOTICE, "successfully saved plugin_myplugin_something into datastore")
end
Vérifiez si une adresse IP est globale :
local ret, err = utils.ip_is_global(ngx.ctx.bw.remote_addr)
if ret == nil then
self.logger:log(ngx.ERR, "error while checking if IP " .. ngx.ctx.bw.remote_addr .. " is global or not : " .. err)
elseif not ret then
self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " is not global")
else
self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " is global")
end
Plus d'exemples
Si vous souhaitez voir la liste complète des fonctions disponibles, vous pouvez consulter les fichiers présents dans le répertoire lua du dépôt.
Emplois
BunkerWeb utilise un planificateur de tâches interne pour des tâches périodiques telles que le renouvellement des certificats avec certbot, le téléchargement de listes noires, le téléchargement de fichiers MMDB, ... Vous pouvez ajouter des tâches de votre choix en les plaçant dans un sous-dossier nommé tâches et en les répertoriant dans le fichier de métadonnées plugin.json. N'oubliez pas d'ajouter les permissions d'exécution pour tout le monde afin d'éviter tout problème lorsqu'un utilisateur clone et installe votre plugin.
Page du plugin
Tout ce qui concerne l'interface utilisateur web se trouve dans le sous-dossier ui comme nous l'avons vu dans la section précédente sur la structure..
Conditions préalables
Lorsque vous souhaitez créer une page de plugin, vous avez besoin de deux fichiers :
-
template.html qui seront accessibles avec un GET /plugins/<plugin_id>.
-
actions.py où vous pouvez ajouter des scripts et de la logique avec un POST /plugins/<plugin_id>. Notez que ce fichier a besoin d'une fonction portant le même nom que le plugin pour fonctionner. Ce fichier est nécessaire même si la fonction est vide.
Exemple de base
Modèle Jinja 2
Le fichier template.html est un modèle Jinja2, veuillez vous référer à la documentation Jinja2 si nécessaire.
Nous pouvons mettre de côté le ** fichier actions.py et commencer à utiliser uniquement le modèle dans une situation GET**. Le modèle peut accéder au contexte de l'application et aux libs, ce qui vous permet d'utiliser les utilitaires Jinja, request ou flask.
Par exemple, vous pouvez obtenir les arguments de requête dans votre modèle comme ceci :
<p>request args : {{ request.args.get() }}.</p>
Actions.py
Jeton CSRF
Please note that every form submission is protected via a CSRF token, you will need to include the following snippet into your forms :
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
Vous pouvez alimenter votre page de plugin avec des scripts supplémentaires avec le ** fichier actions.py lors de l 'envoi d'un POST /plugins/<plugin_id>**.
Vous avez deux fonctions par défaut dans actions.py :
pre_render fonction
Cela vous permet de récupérer les données lorsque vous OBTENEZ le modèle, et d'utiliser les données avec la variable pre_render disponible dans Jinja pour afficher le contenu de manière plus dynamique.
def pre_render(**kwargs)
return <pre_render_data>
BunkerWeb vous enverra ce type de réponse :
return jsonify({"status": "ok|ko", "code" : XXX, "data": <pre_render_data>}), 200
Fonction <plugin_id>
Cela vous permet de récupérer des données lorsque vous effectuez un POST à partir du point de terminaison du modèle, qui doit être utilisé dans AJAX.
def myplugin(**kwargs)
return <plugin_id_data>
BunkerWeb vous enverra ce type de réponse :
return jsonify({"message": "ok", "data": <plugin_id_data>}), 200
Ce à quoi vous pouvez accéder à partir de action.py
Voici les arguments qui sont passés et auxquels on accède sur action.py fonctions :
function(app=app, args=request.args.to_dict() or request.json or None)
Bibliothèques Python Disponibles
L'interface utilisateur Web de BunkerWeb inclut un ensemble de bibliothèques Python préinstallées que vous pouvez utiliser dans le fichier actions.py
de votre plugin ou d'autres scripts liés à l'interface utilisateur. Elles sont disponibles prêtes à l'emploi sans nécessiter d'installations supplémentaires.
Voici la liste complète des bibliothèques incluses :
- bcrypt - Bibliothèque de hachage de mots de passe
- biscuit-python - Jetons d'authentification Biscuit
- certbot - Client ACME pour Let's Encrypt
- Flask - Framework web
- Flask-Login - Gestion des sessions utilisateur
- Flask-Session[cachelib] - Stockage des sessions côté serveur
- Flask-WTF - Gestion des formulaires et protection CSRF
- gunicorn[gthread] - Serveur HTTP WSGI
- pillow - Traitement d'images
- psutil - Utilitaires système et processus
- python_dateutil - Utilitaires de date et heure
- qrcode - Génération de codes QR
- regex - Expressions régulières avancées
- urllib3 - Client HTTP
- user_agents - Analyse des agents utilisateur
Utilisation des Bibliothèques dans Votre Plugin
Pour importer et utiliser ces bibliothèques dans votre fichier actions.py
, utilisez simplement l'instruction import
standard de Python. Par exemple :
from flask import request
import bcrypt
Bibliothèques Externes
Si vous avez besoin de bibliothèques non listées ci-dessus, installez-les dans le dossier ui
de votre plugin et importez-les en utilisant la directive import
classique. Assurez-vous de la compatibilité avec l'environnement existant pour éviter les conflits.
Quelques exemples
- Récupérer les données soumises par le formulaire
from flask import request
def myplugin(**kwargs) :
my_form_value = request.form["my_form_input"]
return my_form_value
- Accéder à la configuration de l'application
action.py
from flask import request
def pre_render(**kwargs) :
config = kwargs['app'].config["CONFIG"].get_config(methods=False)
return config
modèle
<!-- metadata + config -->
<div>{{ pre_render }}</div>
Hooks.py
Cette documentation décrit les hooks de cycle de vie utilisés pour gérer les différentes étapes d'une demande au sein de l'application. Chaque crochet est associé à une phase spécifique.
Ces hooks sont exécutés avant le traitement d'une requête entrante. Ils sont généralement utilisés pour des tâches de prétraitement telles que l'authentification, la validation ou la journalisation.
Si le hook retourne un objet de réponse, Flask ignorera le traitement de la requête et retournera la réponse directement. Cela peut être utile pour court-circuiter le pipeline de traitement des requêtes.
Exemple :
from flask import request, Response
def before_request():
print("Before-request: Validating request...", flush=True)
# Perform authentication, validation, or logging here
if not is_valid_request(request): # We are in the app context
return Response("Invalid request!", status=400)
def is_valid_request(request):
# Dummy validation logic
return "user" in request
Ces hooks qui s'exécutent après le traitement de la requête. Ils sont idéaux pour les tâches de post-traitement telles que le nettoyage, la journalisation supplémentaire ou la modification de la réponse avant qu'elle ne soit renvoyée.
Ils reçoivent l'objet de réponse en tant qu'argument et peuvent le modifier avant de le retourner. Le premier crochet after_request qui retourne une réponse sera utilisé comme réponse finale.
Exemple :
from flask import request
def after_request(response):
print("After-request: Logging response...", flush=True)
# Perform logging, cleanup, or response modifications here
log_response(response)
return response
def log_response(response):
# Dummy logging logic
print("Response logged:", response, flush=True)
Ces hooks sont invoqués lorsque le contexte de la requête est en cours de destruction. Ces hooks sont utilisés pour libérer des ressources ou gérer les erreurs qui se sont produites au cours du cycle de vie de la demande.
Exemple:
def teardown_request(error=None):
print("Teardown-request: Cleaning up resources...", flush=True)
# Perform cleanup, release resources, or handle errors here
if error:
handle_error(error)
cleanup_resources()
def handle_error(error):
# Dummy error handling logic
print("Error encountered:", error, flush=True)
def cleanup_resources():
# Dummy resource cleanup logic
print("Resources have been cleaned up.", flush=True)
Ces points d'entrée sont utilisés pour injecter du contexte supplémentaire dans des modèles ou des vues. Ils enrichissent le contexte d'exécution en transmettant des données courantes (telles que des informations sur l'utilisateur ou des paramètres de configuration) aux modèles.
Si un processeur de contexte retourne un dictionnaire, les clés et les valeurs seront ajoutées au contexte pour tous les modèles. Cela permet de partager des données entre plusieurs vues ou modèles.
Exemple :
def context_processor() -> dict:
print("Context-processor: Injecting context data...", flush=True)
# Return a dictionary containing context data for templates/views
return {
"current_user": "John Doe",
"app_version": "1.0.0",
"feature_flags": {"new_ui": True}
}
Cette conception de crochet de cycle de vie offre une approche modulaire et systématique de la gestion de divers aspects du cycle de vie d'une demande :
- Modularité : Chaque crochet est responsable d'une phase distincte, ce qui permet de séparer les préoccupations.
- Maintenabilité : les développeurs peuvent facilement ajouter, modifier ou supprimer des implémentations de hooks sans affecter les autres parties du cycle de vie de la requête.
- Extensibilité : le cadre est flexible, ce qui permet d'ajouter des crochets ou d'apporter des améliorations à mesure que les exigences de l'application évoluent.
En définissant clairement les responsabilités de chaque hook et leurs préfixes de journalisation associés, le système garantit que chaque étape du traitement des requêtes est transparente et gérable.
Plans
Dans Flask, les blueprints servent de moyen modulaire pour organiser les composants associés, tels que les vues, les modèles et les fichiers statiques, au sein de votre application. Ils vous permettent de regrouper logiquement les fonctionnalités et peuvent être utilisés pour créer de nouvelles sections de votre application ou remplacer celles existantes.
Création d'un Blueprint
Pour définir un blueprint, vous devez créer une instance de la Blueprint
classe, en spécifiant son nom et son chemin d'importation. Vous définissez ensuite les itinéraires et les vues associés à ce blueprint.
Exemple : Définition d'un nouveau blueprint
from os.path import dirname
from flask import Blueprint, render_template
# Define the blueprint
my_blueprint = Blueprint('my_blueprint', __name__, template_folder=dirname(__file__) + '/templates') # The template_folder is set to avoid conflicts with the original blueprint
# Define a route within the blueprint
@my_blueprint.route('/my_blueprint')
def my_blueprint_page():
return render_template('my_blueprint.html')
Dans cet exemple, un blueprint nommé my_blueprint
est créé et un itinéraire /my_blueprint
est défini à l'intérieur de celui-ci.
Remplacement d'un blueprint existant
Les Blueprints peuvent également remplacer les Blueprints existants pour modifier ou étendre les fonctionnalités. Pour ce faire, assurez-vous que le nouveau blueprint porte le même nom que celui que vous remplacez et enregistrez-le après l'original.
Exemple : Remplacement d'un blueprint existant
from os.path import dirname
from flask import Flask, Blueprint
# Original blueprint
instances = Blueprint('instances', __name__, template_folder=dirname(__file__) + '/templates') # The template_folder is set to avoid conflicts with the original blueprint
@instances.route('/instances')
def override_instances():
return "My new instances page"
Dans ce scénario, l'accès à l'URL /instances
affiche "Ma nouvelle page d'instances", car le instances
blueprint, enregistré en dernier, remplace le blueprint d'origine instances
.
À propos de la dérogation
Soyez prudent lorsque vous remplacez des blueprints existants, car cela peut avoir un impact sur le comportement de l'application. Assurez-vous que les modifications sont conformes aux exigences de l'application et n'introduisent pas d'effets secondaires inattendus.
Toutes les routes existantes seront supprimées du blueprint original, vous devrez donc les réimplémenter si nécessaire.
Conventions de nommage
Important
Assurez-vous que le nom du blueprint correspond au nom de la variable de blueprint, sinon il ne sera pas considéré comme un blueprint valide et ne sera pas enregistré.
Pour plus de cohérence et de clarté, il est conseillé de suivre les conventions de nommage suivantes :
-
Noms de blueprint: utilisez des noms courts et tout en minuscules. Des traits de soulignement peuvent être utilisés pour la lisibilité, par exemple,
user_auth
. -
Noms de fichiers: faites correspondre le nom de fichier au nom du blueprint, en vous assurant qu'il est entièrement en minuscules avec des traits de soulignement si nécessaire, par exemple,
user_auth.py
.
Cette pratique s'aligne sur les conventions de nommage des modules de Python et permet de maintenir une structure de projet claire.
Exemple : Blueprint et nommage de fichier
plugin /
ui / blueprints / user_auth.py
templates / user_auth.html
Dans cette structure, user_auth.py
contient le user_auth
blueprint et user_auth.html
est le modèle associé, en respectant les conventions de nommage recommandées.