Skip to content

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:

Image

Image

  • 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

Image

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]

Image

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:

Image

  • 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:

Image

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

Image

  • 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:

Image

Es erscheint die folgende Oberfläche:

Image

  • 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:

Image


# 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(01)
                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:

Image

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:

Image

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