33. Anwendungsübung: Version 13
Version 13 unterscheidet sich von Version 12 in folgenden Punkten:
- Bestimmte Teile des Codes werden umstrukturiert (Refactoring);
- die Sitzung wird mithilfe des Moduls [flask_session] anders verwaltet;
- in der Konfigurationsdatei werden verschlüsselte Passwörter verwendet;
Version 13 wird zunächst durch Kopieren von Version 12 erstellt:


- in [1] wird die Konfiguration refaktorisiert. Sie wird insbesondere aus dem Ordner [flask] entfernt;
- in [2] wird das Hauptskript überarbeitet. Es wird ebenfalls aus dem Ordner [flask] herausgelöst;
- In [3] wird die Konfiguration auf mehrere Dateien aufgeteilt;
- in [4]: Das Hauptskript [main] wird vereinfacht, indem Code in andere Dateien ausgelagert wird;
- In [5] wird der Authentifizierungscontroller geändert, da die Passwörter der Benutzer nun verschlüsselt werden;
- In [6] wird der Haupt-Controller Code integrieren, der zuvor im Hauptskript [main] enthalten war;
33.1. Überarbeitung der Anwendungskonfiguration

Es werden drei neue Konfigurationsdateien angezeigt:
- [mvc]: zur Konfiguration der Architektur MVC der Anwendung;
- [parameters]: zum Zusammenführen aller Konstanten der Anwendung;
- [syspath]: zur Konfiguration des Python-Pfads der Anwendung;
Die Datei [syspath.py] lautet wie folgt:
def configure(config: dict) -> dict:
import os
# Verzeichnis dieser Datei
script_dir = os.path.dirname(os.path.abspath(__file__))
# Stammverzeichnis
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# Abhängigkeiten
absolute_dependencies = [
# Projektordner
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# Konstanten, Tranchen
f"{root_dir}/impots/v05/entities",
# Logger, SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# Ordner des Hauptskripts
script_dir,
# Konfigurationen [database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# Controller
f"{script_dir}/../controllers",
# Antworten HTTP
f"{script_dir}/../responses",
# Ansichtsvorlagen
f"{script_dir}/../models_for_views",
]
# Der Syspath wird festgelegt
from myutils import set_syspath
set_syspath(absolute_dependencies)
# Konfiguration bereitstellen
return {
"root_dir": root_dir,
"script_dir": script_dir
}
- Das Skript [syspath] dient dazu, den Python-Pfad der Anwendung zu konfigurieren (Zeilen 40–41);
- Es stellt zwei Informationen bereit, die für die anderen Konfigurationsskripte nützlich sind (Zeilen 45–46);
Das Skript [mvc] konfiguriert die Architektur MVC der Webanwendung jSON / XML / HTML:
def configure(config: dict) -> dict:
# Konfiguration der Anwendung MVC
# die Controller
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
from InitSessionController import InitSessionController
from ListerSimulationsController import ListerSimulationsController
from MainController import MainController
from SupprimerSimulationController import SupprimerSimulationController
# die Antworten HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# die Ansichtsvorlagen
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# Zulässige Aktionen und ihre Steuerelemente
controllers = {
# Initialisierung einer Rechensitzung
"init-session": InitSessionController(),
# Authentifizierung eines Benutzers
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# Steuerberechnung im Einzelmodus
"calculer-impot": CalculerImpotController(),
# Steuerberechnung im Stapelmodus
"calculer-impots": CalculerImpotsController(),
# Liste der Simulationen
"lister-simulations": ListerSimulationsController(),
# Löschen einer Simulation
"supprimer-simulation": SupprimerSimulationController(),
# Beenden der Berechnungssitzung
"fin-session": FinSessionController(),
# Anzeige der Steuerberechnungsansicht
"afficher-calcul-impot": AfficherCalculImpotController(),
# Abruf der Daten von der Steuerbehörde
"get-admindata": GetAdminDataController(),
# Hauptcontroller
"main-controller": MainController()
}
# die verschiedenen Antworttypen (JSON, XML, HTML)
responses = {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
}
# Die Ansichten HTML und ihre Vorlagen hängen vom vom Controller zurückgegebenen Status ab
views = [
{
# Authentifizierungsansicht
"états": [
700, # /init-session – erfolgreich
201, # /Benutzer-Authentifizierung – fehlgeschlagen
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# Ansicht der Steuerberechnung
"états": [
200, # /Benutzer-Authentifizierung – erfolgreich
300, # /Steuerberechnung erfolgreich
301, # /Steuerberechnung fehlgeschlagen
800, # /Steuerberechnung-anzeigen erfolgreich
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# Ansicht der Simulationsliste
"états": [
500, # /Simulationen-auflisten erfolgreich
600, # /Simulation-löschen erfolgreich
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
},
]
# Ansicht der unerwarteten Fehler
view_erreurs = {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
}
# Weiterleitungen
redirections = [
{
"états": [
400, # /Sitzung erfolgreich beenden
],
# Weiterleitung zu URL
"to": "/init-session/html",
}
]
# Konfiguration wird zurückgesetzt MVC
return {
# Controller
"controllers": controllers,
# Antworten HTTP
"responses": responses,
# Ansichten und Vorlagen
"views": views,
# Liste der Weiterleitungen
"redirections": redirections,
# Ansicht unerwarteter Fehler
"view_erreurs": view_erreurs
}
- Zeilen 1–101: Dieser Code ist bekannt;
- Zeilen 105–116: Die Konfiguration MVC der Anwendung wird zurückgegeben;
Das Skript [parameters] sammelt die Konstanten der Anwendung:
def configure(config: dict) -> dict:
# Anwendungseinstellungen
# script_dir
script_dir = config['syspath']['script_dir']
# Konfiguration der Anwendung
parameters = {
# Benutzer, die zur Nutzung der Anwendung berechtigt sind
"users": [
{
"login": "admin",
"password": "$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
}
],
# Protokolldatei
"logsFilename": f"{script_dir}/../data/logs/logs.txt",
# Serverkonfiguration SMTP
"adminMail": {
# Server SMTP
"smtp-server": "localhost",
# Port des Servers SMTP
"smtp-port": "25",
# Administrator
"from": "guest@localhost.com",
"to": "guest@localhost.com",
# Betreff der E-Mail
"subject": "plantage du serveur de calcul d'impôts",
# TLS auf „True“, wenn der Server SMTP eine Autorisierung erfordert, andernfalls auf „False“
"tls": False
},
# Pausenzeit des Threads in Sekunden
"sleep_time": 0,
# Redis-Server
"redis": {
"host": "127.0.0.1",
"port": 6379
},
}
# Die Anwendungseinstellungen werden übernommen
return parameters
- Zeile 13: Von nun an werden die Passwörter der Benutzer verschlüsselt;
- Zeilen 34–38: Die Konfiguration eines Servers [Redis], auf den wir noch zurückkommen werden;
Mit diesen neuen Konfigurationsdateien sieht das Skript [config] nun wie folgt aus:
def configure(config: dict) -> dict:
# Konfiguration des Syspath
import syspath
config['syspath'] = syspath.configure(config)
# Anwendungskonfiguration
import parameters
config['parameters'] = parameters.configure(config)
# Konfiguration der Datenbank
import database
config["database"] = database.configure(config)
# Instanziierung der Anwendungsschichten
import layers
config['layers'] = layers.configure(config)
# Konfiguration der Schicht MVC
import mvc
config['mvc'] = mvc.configure(config)
# Die Konfiguration wird zurückgegeben
return config
33.2. Umgestaltung des Hauptskripts [main]

Das Hauptskript [main.py] dient lediglich dazu, den Server zu starten:
# Es wird ein MySQL- oder PostgreSQL-Parameter erwartet
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# die Anwendung wird konfiguriert
import config
config = config.configure({'sgbd': sgbd})
# Abhängigkeiten
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis
# Versenden einer E-Mail an den Administrator
def send_adminmail(config: dict, message: str):
# Es wird eine E-Mail an den Administrator der Anwendung gesendet
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# Überprüfung der Protokolldatei
logger = None
erreur = False
message_erreur = None
try:
# Protokollierung
logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
# Konsolenprotokoll
print(f"L'erreur suivante s'est produite : {exception}")
# Der Fehler wird notiert
erreur = True
message_erreur = f"{exception}"
# Der Logger wird in der Konfiguration gespeichert
config['logger'] = logger
# Fehlerbehandlung
if erreur:
# E-Mail an den Administrator
send_adminmail(config, message_erreur)
# Beenden der Anwendung
sys.exit(1)
# Startprotokoll
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")
# Die Verfügbarkeit des Redis-Servers wird überprüft
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
port=config["parameters"]["redis"]["port"])
# Der Redis-Server wird angepingt
try:
redis_client.ping()
except BaseException as exception:
# Redis nicht verfügbar
log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
# Konsole
print(log)
# Protokoll
logger.write(f"{log}\n")
# Ende
sys.exit(1)
# Der Client [redis] wird in der Konfiguration gespeichert
config['redis_client'] = redis_client
# Abruf der Daten der Steuerbehörde
erreur = False
try:
# „admindata“ wird zu einer schreibgeschützten Dateninstanz auf Anwendungsebene
config["admindata"] = config["layers"]["dao"].get_admindata()
# Erfolgsprotokoll
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# Der Fehler wird protokolliert
erreur = True
# Fehlerprotokoll
log = f"L'erreur suivante s'est produite : {ex}"
# Konsole
print(log)
# Protokolldatei
logger.write(f"{log}\n")
# E-Mail an den Administrator
send_adminmail(config, log)
# Der Hauptthread benötigt den Logger nicht mehr
logger.close()
# Bei einem Fehler wird der Vorgang beendet
if erreur:
sys.exit(2)
# Import der Routen der Webanwendung
import routes
routes.config=config
routes.execute(__name__)
- Zeilen 56–73: Ein Redis-Server und dessen Client werden eingebunden;
- Zeilen 102–104: Die Routen der Anwendung wurden in das Skript [routes] ausgelagert;
33.2.1. Die Module [flask_session] und [redis]
Der Server [Redis] wird zum Speichern der Benutzersitzungen verwendet. Zur Verwaltung dieser Sitzungen nutzen wir das Modul [flask_session]. Dieses Modul kann Benutzersitzungen an verschiedenen Orten speichern. Redis ist einer dieser Orte, und wir werden ihn verwenden.
Das Modul [flask_session] muss in einem PyCharm-Terminal installiert werden:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask-session
Collecting flask-session
…
Um mit dem Redis-Server zu kommunizieren, benötigen wir einen Redis-Client. Dieser wird uns vom Modul [redis] bereitgestellt, das wir ebenfalls installieren:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install redis
Collecting redis
…
33.2.2. Der Redis-Server
Der Redis-Server dient dazu, die Sitzungen der Benutzer zu speichern. Das Modul [flask_session] funktioniert wie folgt:
- Jeder Benutzer verfügt über eine Sitzungs-ID, und genau diese wird an den Client gesendet – und nur diese. Der Client erhält nur einmal, nach seiner ersten Anfrage, ein Sitzungs-Cookie. Dieses Cookie enthält die Sitzungs-ID des Benutzers, die sich im Laufe der Anfragen des Clients nicht mehr ändert. Aus diesem Grund muss der Server kein neues Sitzungs-Cookie zurücksenden;
- Bisher wurde der Inhalt der Sitzung an den Client gesendet. Dies ist nun nicht mehr der Fall. Der Inhalt der Benutzersitzung wird auf dem Redis-Server gespeichert;
Laragon wird standardmäßig mit einem deaktivierten Redis-Server ausgeliefert. Dieser muss daher zunächst aktiviert werden:

- In [3] den Server [Redis] aktivieren;
- In [4] den Port [6379] beibehalten, den Redis-Clients standardmäßig verwenden;
Die Laragon-Dienste werden nach der Aktivierung von Redis automatisch neu gestartet:

Der Redis-Server kann im Befehlsmodus abgefragt werden. Öffnen Sie ein Laragon-Terminal [6]:

- Unter [1] startet der Befehl [redis-cli] den Redis-Client im Befehlsmodus;
Im Juli 2019 stehen dem Redis-Client 172 Befehle zur Kommunikation mit dem Server [https://redis.io/commands#list] zur Verfügung. Einer davon, [command count] [2], zeigt diese Zahl an: [3].
Das Schreiben in [Redis] erfolgt mit dem Redis-Befehl [set attribut valeur] [4]. Der Wert kann anschließend mit dem Befehl [get attribut] [5] abgerufen werden.
Möglicherweise muss der Redis-Speicher geleert werden. Dies geschieht mit dem Befehl [flushdb] [6]. Wenn man anschließend den Wert des Attributs [titre] [7] abfragt, erhält man eine Referenz [nil] [8], die angibt, dass das Attribut nicht gefunden wurde. Man kann auch den Befehl [exists] [9-10] verwenden, um zu überprüfen, ob ein Attribut vorhanden ist.
Um den Redis-Client zu beenden, geben Sie den Befehl [quit] [11] ein.
Man kann auch eine Weboberfläche verwenden, um die auf dem Redis-Server vorhandenen Schlüssel zu verwalten. Dazu muss der Apache-Server von Laragon gestartet sein:

Es erscheint die folgende Oberfläche:

- in [1-4], eine der auf dem Redis-Server gespeicherten Sitzungen;
33.2.3. Verwaltung des Redis-Servers im Hauptskript [main]
Das Skript [main] überprüft das Vorhandensein des Redis-Servers wie folgt:
import redis
…
# Die Verfügbarkeit des Redis-Servers wird überprüft
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
port=config["parameters"]["redis"]["port"])
# Der Redis-Server wird angepingt
try:
redis_client.ping()
except BaseException as exception:
# Redis nicht verfügbar
log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
# Konsole
print(log)
# Protokoll
logger.write(f"{log}\n")
# Ende
sys.exit(1)
# Der Client [redis] wird in der Konfiguration gespeichert
config['redis_client'] = redis_client
- Zeile 4: Der Konstruktor der Klasse [redis.Redis] erstellt einen Redis-Server-Client. Dessen Eigenschaften (Adresse, Port) werden im Skript [parameters] abgerufen;
- Zeile 8: Mit der Methode [ping] wird überprüft, ob der Redis-Server vorhanden ist;
- Zeilen 9–17: Wenn der Ping fehlschlägt, wird der Fehler protokolliert und der Server beendet;
- Zeile 20: Die Referenz des Redis-Clients wird in die Konfiguration aufgenommen;
33.2.4. Routenverwaltung im Hauptskript [main]
Die Routenverwaltung in [main] beschränkt sich auf die folgenden Zeilen:
# Import der Routen der Webanwendung
import routes
routes.config=config
routes.execute(__name__)
- Zeile 1: Die Routen wurden in das Modul [routes] ausgelagert;
- Zeile 3: Die Routen müssen die Ausführungskonfiguration kennen;
- Zeile 4: Die Flask-Anwendung wird gestartet, wobei ihr der Name des ausgeführten Skripts (__main__) übergeben wird;
Das Skript für die Routen lautet wie folgt:
# Abhängigkeiten
import os
from flask import Flask, redirect, request, session, url_for
from flask_api import status
from flask_session import Session
# Flask-Anwendung
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
# Anwendungskonfiguration
config = {}
# Der Front-Controller
def front_controller() -> tuple:
# Die Anfrage wird an den Haupt-Controller weitergeleitet
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
@app.route('/', methods=['GET'])
def index() -> tuple:
# Weiterleitung zu /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Benutzer authentifizieren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Steuerberechnung
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Steuerberechnung im Stapelverfahren
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Simulationen auflisten
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
# Simulation löschen
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
# Sitzung beenden
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
# Steuerberechnung anzeigen
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Admin-Daten abrufen
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
def execute(name: str):
# geheimer Sitzungsschlüssel
app.secret_key = os.urandom(12).hex()
# Flask-Session
app.config.update(SESSION_TYPE='redis',
SESSION_REDIS=config['redis_client'])
Session(app)
# Fall, in dem die Flask-Anwendung über ein Konsolenskript gestartet wird
if name == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- Zeile 9: Die Flask-Anwendung wird instanziiert;
- Zeile 12: Die Konfiguration der Anwendung ist zum Zeitpunkt der Erstellung des Skripts noch nicht bekannt. Sie wird erst bei der Ausführung bekannt;
- Zeilen 20–77: Die Routen der Anwendung, wie sie in der vorherigen Version definiert waren. Daran ändert sich nichts;
- Zeilen 14–18: Alle Routen rufen lediglich die Funktion [front_controller] auf. Wir haben diese Funktion von ihrem ursprünglichen Code befreit. Sie ruft nun lediglich den Hauptcontroller der Webanwendung auf;
- Zeilen 79–89: [execute] ist die Funktion, die vom Skript [main] aufgerufen wird, um die Webanwendung zu starten;
- Zeile 81: Das Modul [flask_session] verwendet den geheimen Schlüssel von Flask;
- Zeilen 82–84: Konfiguration des Moduls [flask_session]. Dabei werden die Schlüssel [SESSION_TYPE, SESSION_REDIS] zur Konfiguration [app.config] der Flask-Anwendung [app] hinzugefügt:
- [SESSION_TYPE]: Der Typ der Sitzung. Es gibt mehrere davon. Der Typ [redis] gibt an, dass [flask_session] einen Server vom Typ [redis] verwendet, um die Sitzungen der Benutzer zu speichern. Aus diesem Grund muss der Schlüssel [SESSION_REDIS] definiert werden, der als Referenz für einen Redis-Client dienen soll;
- Zeile 85: Die Sitzung [Flask-Session] ist der Flask-Anwendung zugeordnet;
- Zeilen 86–89: Wenn der Parameter [name] aus Zeile 79 die Zeichenfolge [__main__] ist, wird die Flask-Anwendung gestartet;
33.3. Umgestaltung des Hauptcontrollers
Der Code, der sich zuvor in der Funktion [front_controller] des Skripts [main] befand, wurde in den Haupt-Controller verschoben:

# Import der Abhängigkeiten
import threading
import time
from random import randint
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail
def send_adminmail(config: dict, message: str):
# Senden einer E-Mail an den Administrator der Anwendung
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# Hauptcontroller der Anwendung
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Anfrage wird bearbeitet
logger = None
action = None
type_response1 = None
try:
# Die Elemente des Pfads werden abgerufen
params = request.path.split('/')
# Die Aktion ist das erste Element
action = params[1]
# zunächst keine Fehler
erreur = False
# Der Sitzungstyp muss vor bestimmten Aktionen bekannt sein
type_response1 = session.get('typeResponse')
if type_response1 is None and action != "init-session":
# Der Fehler wird protokolliert
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# protokollieren
logger = Logger(config['parameters']['logsFilename'])
# wird in einer dem Thread zugeordneten Konfiguration gespeichert
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# die Anfrage wird protokolliert
logger.write(f"[MainController] requête : {request}\n")
# Der Thread wird unterbrochen, falls dies angefordert wurde
sleep_time = config['parameters']['sleep_time']
if sleep_time != 0:
# Die Pause erfolgt zufällig, sodass einige Threads unterbrochen werden und andere nicht
aléa = randint(0, 1)
if aléa == 1:
# Protokollierung vor der Pause
logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# Pause
time.sleep(sleep_time)
# Für bestimmte Aktionen muss man authentifiziert sein
user = session.get('user')
if not erreur and user is None and action not in ["init-session", "authentifier-utilisateur"]:
# Der Fehler wird vermerkt
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# Gibt es Fehler?
if erreur:
# Die Anfrage ist ungültig
status_code = status.HTTP_400_BAD_REQUEST
else:
# Der zur Aktion gehörende Controller wird ausgeführt
controller = config['mvc']['controllers'][action]
résultat, status_code = controller.execute(request, session, config)
except BaseException as exception:
# Weitere (unerwartete) Ausnahmen
résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
status_code = status.HTTP_400_BAD_REQUEST
finally:
pass
# Das an den Client gesendete Ergebnis wird protokolliert
log = f"[MainController] {résultat}\n"
logger.write(log)
# Ist ein schwerwiegender Fehler aufgetreten?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# Es wird eine E-Mail an den Administrator der Anwendung gesendet
send_adminmail(config, log)
# Der gewünschte Typ für die Antwort wird festgelegt
type_response2 = session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# Der Sitzungstyp wurde noch nicht festgelegt – es wird jSON sein
type_response = 'json'
elif type_response2 is not None:
# Der Antworttyp ist bekannt und in der Sitzung enthalten
type_response = type_response2
else:
# Andernfalls wird weiterhin type_response1 verwendet
type_response = type_response1
# Die zu sendende Antwort wird erstellt
response_builder = config['mvc']['responses'][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# die Protokolldatei wird geschlossen, falls sie geöffnet war
if logger:
logger.close()
# Die Antwort wird gesendet HTTP
return response, status_code
Dieser gesamte Code ist bereits an der einen oder anderen Stelle aufgetaucht.
33.4. Verwaltung verschlüsselter Passwörter
Zur Verwaltung verschlüsselter Passwörter verwenden wir das Modul [passlib], das wir über ein PyCharm-Terminal installieren:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install passlib
Collecting passlib
…
Hier ist ein Beispiel für ein Skript, das das als Parameter übergebene Passwort verschlüsselt:

Das Skript [create_hashed_password] lautet wie folgt (https://passlib.readthedocs.io/en/stable/):
import sys
# Verschlüsselungsfunktion
from passlib.hash import pbkdf2_sha256
# Es wird auf das zu verschlüsselnde Passwort gewartet
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
else:
password = sys.argv[1]
# Das Passwort wird verschlüsselt
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")
# Überprüfung
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
- Zeile 16: Das als Parameter übergebene Passwort wird verschlüsselt;
- Zeile 20: Das als Parameter übergebene Passwort [password] wird mit seiner verschlüsselten Version [hash] verglichen. Die Funktion [verify] verschlüsselt das Passwort [password] und vergleicht die erhaltene verschlüsselte Zeichenfolge mit [hash]. Gibt „True“ zurück, wenn beide Zeichenfolgen identisch sind;
Mit dem obigen Skript erhalten wir die verschlüsselte Version des Passworts [admin]:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/08/passlib/create_hashed_password.py admin
version cryptée de [admin] = $pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek
True
Zeile 2, der Wert, den wir in das Skript [parameters] eingeben:
"users": [
{
"login": "admin",
"password": "$pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek"
}
],
Der Authentifizierungs-Controller [AuthentifierUtilisateurController] entwickelt sich wie folgt:
from passlib.handlers.pbkdf2 import pbkdf2_sha256
…
# Die Gültigkeit des Paares (Benutzername, Passwort) wird überprüft
users = config['parameters']['users']
i = 0
nbusers = len(users)
trouvé = False
while not trouvé and i < nbusers:
trouvé = user == users[i]["login"] and pbkdf2_sha256.verify(password, users[i]["password"])
i += 1
# Gefunden?
if not trouvé:
…
33.5. Tests
Neben den Tests mit einem Browser können auch die für Version 12 geschriebenen Clients aus dem Ordner [http-servers/07] verwendet werden. Diese sollten auch für Version 13 funktionieren:

- In [1] sollten alle drei Clients funktionieren;
- in [2] sollten die beiden Tests funktionieren;