30. Anwendungsübung: Version 12
In diesem Kapitel werden wir eine Webanwendung erstellen, die der Architektur MVC (Model-View-Controller) entspricht. Die Anwendung kann ihre Antworten in drei Formaten ausgeben: jSON, XML, HTML. Es gibt einen deutlichen Sprung in der Komplexität zwischen dem, was wir nun tun werden, und dem, was zuvor gemacht wurde. Wir werden die meisten der bisher behandelten Konzepte wiederverwenden und alle Schritte bis hin zur fertigen Anwendung detailliert beschreiben.
30.1. Architektur MVC
Wir werden das sogenannte MVC-Architekturmodell (Model-View-Controller) wie folgt implementieren:
Die Bearbeitung einer Anfrage eines Kunden läuft wie folgt ab:
- 1 – Anfrage
Die angeforderten URL haben die Form http://machine:port/action/param1/param2/…. Das [Contrôleur principal] verwendet eine Konfigurationsdatei, um die Anfrage an den richtigen Controller weiterzuleiten. Dazu verwendet er das Feld [action] des URL. Der Rest des URL [param1/param2/…] besteht aus optionalen Parametern, die an die Aktion übergeben werden. Das C in MVC ist hier die Zeichenkette [Contrôleur principal, Contrôleur / Action]. Wenn kein Controller die angeforderte Aktion verarbeiten kann, antwortet der Webserver, dass die angeforderte URL nicht gefunden wurde.
- 2 – Verarbeitung
- Die ausgewählte Aktion [2a] kann die Parameter parami nutzen, die ihr von [Contrôleur principal] übergeben wurden. Diese können aus zwei Quellen stammen:
- aus dem Pfad [/param1/param2/…] von URL,
- aus Parametern, die im Hauptteil der Client-Anfrage übermittelt wurden;
- Bei der Bearbeitung der Benutzeranfrage benötigt die Aktion möglicherweise die Schichten [métier] und [2b]. Sobald die Client-Anfrage bearbeitet wurde, kann sie verschiedene Antworten auslösen. Ein klassisches Beispiel ist:
- eine Fehlerantwort, wenn die Anfrage nicht korrekt verarbeitet werden konnte;
- ansonsten eine Bestätigungsantwort;
- [Contrôleur / Action] sendet seine Antwort [2c] zusammen mit einem Statuscode an den Hauptcontroller zurück. Diese Statuscodes geben den aktuellen Status der Anwendung eindeutig wieder. Es handelt sich dabei entweder um einen Erfolgscode oder einen Fehlercode;
- 3 – Antwort
- je nachdem, ob der Client eine Antwort jSON, XML oder HTML angefordert hat, wird [Contrôleur principal] den entsprechenden Antworttyp [3a] instanziieren und diesen auffordern, die Antwort an den Client zu senden. Das [Contrôleur principal] übermittelt ihm sowohl die Antwort als auch den Statuscode, die vom ausgeführten [Contrôleur / Action] bereitgestellt wurden;
- Wenn die gewünschte Antwort vom Typ jSON oder XML ist, formatiert die ausgewählte Antwort die von [Contrôleur / Action] gelieferte Antwort entsprechend und sendet sie an [3c]. Der Client, der diese Antwort verarbeiten kann, kann ein Python-Konsolenskript oder ein JavaScript-Skript sein, das auf einer Seite mit dem Namen HTML gehostet wird;
- Wenn die gewünschte Antwort vom Typ HTML ist, wählt die ausgewählte Antwort anhand des ihr übermittelten Statuscodes eine der Ansichten HTML oder [Vuei] aus. Dies ist das V von MVC. Einem Statuscode entspricht eine einzige Ansicht. Diese Ansicht V zeigt die Antwort des ausgeführten [Contrôleur / Action] an. Sie gestaltet die Daten dieser Antwort mithilfe von HTML, CSS und JavaScript. Diese Daten werden als Modell der Ansicht bezeichnet. Das ist das „M“ in MVC. Der Client ist dabei meist ein Browser;
Lassen Sie uns nun den Zusammenhang zwischen der Webarchitektur MVC und der Schichtenarchitektur näher erläutern. Je nachdem, wie man das Modell definiert, stehen diese beiden Konzepte in Zusammenhang oder auch nicht. Nehmen wir eine einschichtige Webanwendung MVC:

Im obigen Beispiel umfasst jede der [Contrôleur / Action]-Schichten einen Teil der Schichten [métier] und [dao]. In der Schicht [web] gibt es zwar eine Architektur vom Typ MVC, aber die gesamte Anwendung weist keine mehrschichtige Architektur auf. Hier gibt es nur eine Schicht, die Webschicht, die alle Aufgaben übernimmt.
Betrachten wir nun eine mehrschichtige Webarchitektur:

Die Schicht [web] kann implementiert werden, ohne dem Modell MVC zu folgen. Man hat dann zwar eine mehrschichtige Architektur, aber die Webschicht implementiert das Modell MVC nicht.
Beispielsweise kann in der Welt .NET die oben genannte Schicht [web]oben mit ASP.NET und MVC implementiert werden, und man erhält somit eine Schichtenarchitektur mit einer Schicht [web] vom Typ MVC. Ist dies geschehen, kann man diese Schicht ASP.NET MVC durch eine klassische Schicht ASP.NET (WebForms) ersetzen, während der Rest (Geschäftsbereich, DAO, Treiber) unverändert beibehalten. Man erhält somit eine Schichtenarchitektur mit einer Schicht [web], die nicht mehr vom Typ MVC ist.
In MVC haben wir festgelegt, dass das Modell M dem der Ansicht V, c.a.d, entspricht – also der Gesamtheit der von der Ansicht V angezeigten Daten. Eine weitere Definition des Modells M von MVC lautet:

Viele Autoren sind der Ansicht, dass das, was sich rechts von der Ebene [web] befindet, das Modell M von MVC bildet. Um Mehrdeutigkeiten zu vermeiden, kann man sprechen von:
- vom Domänenmodell, wenn man alles bezeichnet, was rechts von der Schicht [web] liegt;
- vom Modell der Ansicht, wenn man die von einer Ansicht V angezeigten Daten bezeichnet;
Wenn wir im Folgenden vom Modell sprechen, ist immer das Ansichtsmodell gemeint.
30.2. Architektur der Client-Server-Anwendung
Die Webanwendung wird folgende Architektur aufweisen:
- In [1] wird der Webserver zwei Arten von Clients haben:
- in [2] einen Konsolen-Client, der jSON und XML mit dem Server austauscht;
- in [3] einen Browser, der HTML vom Server empfängt und anzeigt;
- der Webserver [1] behält die Schichten [métier] und [dao] aus früheren Versionen bei;
- Der Webclient [2] wird weiterentwickelt, um die neuen URL-Dienstfunktionen der Webanwendung zu berücksichtigen;
- die vom Browser angezeigte Anwendung HTML muss komplett neu geschrieben werden;
Wir werden die Anwendung in mehreren Schritten entwickeln:
- Wir werden die Serverversion jSON entwickeln. Wir werden die Server-Endpunkte nacheinander mit einem Postman-Client testen. Diese Methode ermöglicht es uns, das Grundgerüst des Webservers aufzubauen, ohne uns um die Ansichten (=HTML) der Anwendung kümmern zu müssen;
- Nachdem wir den Server jSON mit Postman getestet haben, werden wir ihn mit einem Konsolen-Client testen;
- Anschließend wechseln wir zur Serverversion XML. Wir haben gesehen, dass der Wechsel von jSON zu XML trivial war;
- schließlich werden wir zur Serverversion HTML wechseln. Wir werden eine Architektur MVC aufbauen und die anzuzeigenden Ansichten definieren. Die Anwendung HTML wird sowohl mit dem Postman-Client als auch mit einem herkömmlichen Browser getestet;
30.3. Die Verzeichnisstruktur des Servercodes

- in [1: den Webserver in seiner Gesamtheit;
- in [2]: Die Ordner [static, templates, tests_views], die die Serverversion HTML betreffen, lassen wir vorerst außer Acht. Außerhalb dieses Ordners finden wir das Hauptskript [main] und dessen Konfiguration;
- in [3] die Controller des Webservers. Dabei handelt es sich um Instanzen von Klassen;
![]() | ![]() |
- in [4] wird die Antwort HTTP des Servers durch Klassen verwaltet;
- in [5] behalten wir die Protokolldatei der vorherigen Server bei;
Wenn wir die Serverversion HTML erstellen, kommen weitere Ordner hinzu:
![]() | ![]() |
- in [6] die statischen Elemente der Anwendung HTML;
- in [7] die Vorlagen der Anwendung HTML, aufgeschlüsselt in Ansichten [9] und Ansichtsfragmente [8];
- in [9] die Klassen, die die Modelle der Ansichten implementieren;
30.4. Die URL der Anwendungsdienste
Um den Webserver zu erstellen, gehen wir wie folgt vor:
- Ausgehend von den Ansichten der Anwendung HTML definieren wir die Aktionen, die die Webanwendung implementieren muss. Wir verwenden hier die tatsächlichen Ansichten, es könnten aber auch einfach Skizzen auf Papier sein;
- Ausgehend von diesen Aktionen definieren wir die URL-Dienstkomponenten der Anwendung HTML;
- Wir werden diese Service-URL mit einem Server implementieren, der jSON bereitstellt. Auf diese Weise lässt sich das Grundgerüst des Webservers definieren, ohne sich um die bereitzustellenden HTML-Seiten kümmern zu müssen. Wir werden diese URL-Dienste mit Postman testen;
- anschließend testen wir unseren jSON-Server mit einem Konsolen-Client;
- Sobald der Server jSON validiert wurde, werden wir mit der Entwicklung der Anwendung HTML fortfahren;
Die erste Ansicht wird die Authentifizierungsansicht sein:

- Die Aktion, die zu dieser ersten Ansicht führt, heißt [init-session] [1];
- Ein Klick auf die Schaltfläche [Valider] löst die Aktion [authentifier-utilisateur] mit zwei übermittelten Parametern [2-3] aus;
Die Ansicht der Steuerberechnung:

- in [1] die Aktion [authentifier-utilisateur], die zu dieser Ansicht geführt hat;
- in [2] löst der Klick auf die Schaltfläche [Valider] die Ausführung der Aktion [calculer-impot] mit drei übergebenen Parametern [2-5] aus;
- Ein Klick auf den Link [6] löst die Aktion [lister-simulations] ohne Parameter aus;
- Ein Klick auf den Link [7] löst die Aktion [fin-session] ohne Parameter aus;
Die dritte Ansicht zeigt die vom authentifizierten Benutzer durchgeführten Simulationen:

- in [3] die Aktion [lister-simulations], die zu dieser Ansicht geführt hat;
- in [2] löst ein Klick auf den Link [Supprimer] die Aktion [supprimer-simulation] mit einem Parameter aus, nämlich der Nummer der aus der Liste zu löschenden Simulation;
- Ein Klick auf den Link [3] löst die Aktion [afficher-calcul-impot] ohne Parameter aus, wodurch die Ansicht der Steuerberechnung erneut angezeigt wird;
- Ein Klick auf den Link [4] löst die Aktion [fin-session] ohne Parameter aus;
Anhand dieser ersten Informationen können wir die verschiedenen URL-Dienste des Servers definieren:
Aktion | Rolle | Ausführungskontext |
/init-session | Dient zur Festlegung des Typs (json, xml, html) der gewünschten Antworten | Abfrage GET Kann jederzeit gesendet werden |
/authentifier-utilisateur | Erteilt einem Benutzer die Berechtigung zur Anmeldung oder verweigert sie | Anfrage POST. Die Anfrage muss zwei über POST übermittelte Parameter enthalten [user, password] Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist |
/steuerberechnung | Führt eine Simulation der Steuerberechnung durch | Anfrage POST. Die Anfrage muss drei über POST übermittelte Parameter enthalten: [marié, enfants, salaire] Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist |
/Simulationen-auflisten | Fordert die Liste der seit Beginn der Sitzung durchgeführten Simulationen an | Anfrage GET. Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist |
/simulation-löschen/nummer | Löscht eine Simulation aus der Liste der Simulationen | Anfrage GET. Kann nur ausgegeben werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist |
/Steuerberechnung-anzeigen | Zeigt die Seite HTML zur Steuerberechnung an | Anfrage GET. Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist |
/end-session | Beendet die Simulationssitzung. | Technisch gesehen wird die alte Websitzung gelöscht und eine neue Sitzung erstellt Kann nur gesendet werden, wenn der Sitzungstyp (json, xml, html) bekannt ist und der Benutzer authentifiziert ist |
Diese verschiedenen Service-URL werden sowohl für den Server HTML als auch für die Server jSON oder XML verwendet. Zwei URL werden ausschließlich für die beiden letztgenannten Server verwendet: Es handelt sich dabei um die URL aus der vorherigen Version des Web-Clients/Servers, die wir hier übernehmen:
Aktion | Rolle | Ausführungskontext |
/get-admindata | Liefert die Steuerdaten zur Berechnung der Steuer | Abfrage GET. Wird nur verwendet, wenn der Sitzungstyp „json“ oder „xml“ ist. Der Benutzer muss authentifiziert sein |
/calculer-impots | Berechnet die Steuer für eine Liste von Steuerpflichtigen, die in jSON übermittelt wurden | Abfrage GET. Wird nur verwendet, wenn der Sitzungstyp „json“ oder „xml“ ist. Der Benutzer muss authentifiziert sein |
Alle mit diesen Aktionen verbundenen Controller verfahren auf dieselbe Weise:
- Sie überprüfen ihre Parameter. Diese befinden sich im Objekt:
- [request.path] für die Parameter, die in URL in der Form [/action/param1/param2/…] vorhanden sind;
- im Objekt [request.form] für diejenigen, die in [x-www-form-urlencoded] im Hauptteil der Anfrage übermittelt werden;
- im Objekt [request.data] für diejenigen, die in jSON im Hauptteil der Anfrage übermittelt werden;
- Ein Controller ähnelt einer Funktion oder Methode, die die Gültigkeit ihrer Parameter überprüft. Beim Controller ist dies jedoch etwas komplizierter:
- Die erwarteten Parameter können fehlen;
- die vom Controller abgerufenen Parameter sind Zeichenfolgen. Ist der erwartete Parameter eine Zahl, muss der Controller überprüfen, ob die Zeichenfolge des Parameters tatsächlich die einer Zahl ist;
- Sobald überprüft wurde, dass die erwarteten Parameter vorhanden und syntaktisch korrekt sind, muss geprüft werden, ob sie im aktuellen Ausführungskontext gültig sind. Dieser Kontext ist in der Sitzung vorhanden. Das Beispiel der Authentifizierung ist ein Beispiel für einen Ausführungskontext. Bestimmte Aktionen dürfen erst verarbeitet werden, wenn der Client authentifiziert ist. In der Regel gibt ein Schlüssel in der Sitzung an, ob diese Authentifizierung stattgefunden hat oder nicht;
- sobald die vorangegangenen Überprüfungen abgeschlossen sind, kann der sekundäre Controller seine Arbeit aufnehmen. Diese Überprüfung der Parameter ist sehr wichtig. Es ist nicht akzeptabel, dass ein Client uns zu einem beliebigen Zeitpunkt während der Laufzeit der Anwendung beliebige Daten sendet. Wir müssen die gesamte Laufzeit der Anwendung vollständig kontrollieren;
- Sobald seine Arbeit erledigt ist, gibt der sekundäre Controller ein Dictionary mit den Schlüsseln [action, état, réponse] an den primären Controller zurück, der ihn aufgerufen hat:
- [action] ist die soeben ausgeführte Aktion;
- [état] ist eine dreistellige Zahl, die das Ergebnis der Aktion angibt:
- [x00] signalisiert eine erfolgreiche Verarbeitung;
- [x01] signalisiert einen Fehlschlag der Verarbeitung;
- [réponse] ist das Ergebniswörterbuch in der Form {‘Antwort’:Objekt}. Das Objekt weist je nach verarbeiteter Aktion unterschiedliche Strukturen auf;
Wir werden nun die verschiedenen Controller – oder, was auf dasselbe hinausläuft, die verschiedenen Aktionen, die diese Controller verarbeiten und die den Ablauf der Webanwendung bestimmen – im Einzelnen betrachten.
30.5. Serverkonfiguration

Die Konfiguration der Datenbank [config_database] sowie die der Server-Schichten [config_layers] sind identisch mit denen der Vorgängerversionen. In der Datei [config] tauchen neue Informationen auf:
def configure(config: dict) -> dict:
import os
# Schritt 1 ------
# Ordner 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",
# Skripte [config_database, config_layers]
script_dir,
# Controller
f"{script_dir}/../controllers",
# Antworten HTTP
f"{script_dir}/../responses",
# Ansichtsvorlagen
f"{script_dir}/../models_for_views",
]
# Festlegen des Syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# Abhängigkeiten des Webservers
# 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 View-Vorlagen
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# Schritt 2 ------
# Konfiguration der Anwendung
config.update({
# Benutzer, die zur Nutzung der Anwendung berechtigt sind
"users": [
{
"login": "admin",
"password": "admin"
}
],
# 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,
# Zulässige Aktionen und ihre Controller
"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": [
# /init-session erfolgreich
700,
# /Benutzer-Authentifizierung fehlgeschlagen
201
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# Ansicht der Steuerberechnung
"états": [
# /Benutzer-Authentifizierung erfolgreich
200,
# /Steuerberechnung erfolgreich
300,
# /Steuerberechnung fehlgeschlagen
301,
# /Steuerberechnung-anzeigen
800
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# Ansicht der Simulationsliste
"états": [
# /Simulationen-auflisten
500,
# /Simulation-löschen
600
],
"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 beendet
],
# Weiterleitung zu
"to": "/init-session/html",
}
],
}
)
# Schritt 3 ------
# Datenbankkonfiguration
import config_database
config["database"] = config_database.configure(config)
# Schritt 4 ------
# Instanziierung der Anwendungsschichten
import config_layers
config['layers'] = config_layers.configure(config)
# Die Konfiguration wird zurückgegeben
return config
- Bis Zeile 41 finden sich die üblichen Angaben;
- Zeilen 43–66: Ab Zeile 43 wird der Python-Pfad des Servers definiert. Anschließend können die Abhängigkeiten des Projekts importiert werden:
- Zeilen 45–55: die Liste der Controller;
- Zeilen 57–60: die Liste der Antworten HTTP;
- Zeilen 62–66: die Liste der View-Vorlagen;
- Zeilen 68–189: die Konfiguration der Anwendung mit einer Reihe von Konstanten;
- Zeilen 71–98: Diese Zeilen sind uns bereits aus früheren Versionen bekannt;
- Zeilen 101–122: das Controller-Wörterbuch:
- Die Schlüssel sind die Namen der Aktionen;
- die Werte sind eine Instanz des Controllers, der diese Aktion verarbeiten soll. Jeder Controller wird nur einmal instanziiert (Singleton). Dieselbe Instanz wird von verschiedenen Threads des Servers ausgeführt. Daher muss auf gemeinsam genutzte Daten geachtet werden, die jeder Controller möglicherweise ändern möchte;
- Zeilen 125–129: das Wörterbuch der drei möglichen Antworten HTTP:
- Die Schlüssel sind der vom Kunden gewünschte Antworttyp (jSON, xml, html);
- die Werte sind eine Instanz der Antwort HTTP. Jeder Antwortgenerator wird nur einmal instanziiert (Singleton). Derselbe Generator wird von verschiedenen Threads des Servers ausgeführt. Daher muss auf gemeinsam genutzte Daten geachtet werden, die jeder Generator möglicherweise ändern möchte;
- Zeilen 132–186: Konfiguration der Ansichten HTML. Diese Zeilen werden vorerst ignoriert;
- Zeilen 191–202: Diese Zeilen sind uns bereits aus früheren Versionen bekannt;
30.6. Verlauf einer Client-Anfrage innerhalb des Servers

Wir werden den Weg einer auf dem Server eingehenden Client-Anfrage bis zur zurückgesendeten Antwort HTTP verfolgen. Sie folgt dem Pfad des Servers MVC.
30.6.1. Das Skript [main]

Das Skript [main] ist in vielen Punkten identisch mit dem der vorherigen Versionen. Wir geben es dennoch vollständig wieder, um auf einer soliden Grundlage zu beginnen:
# 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 flask import request, Flask, session, url_for, redirect
from flask_api import status
from SendAdminMail import SendAdminMail
from myutils import json_response
from Logger import Logger
import threading
import time
from random import randint
from ImpôtsError import ImpôtsError
import os
# 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["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["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}"
# Logger in der Konfiguration speichern
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")
print(log)
# Abruf der Daten der Steuerbehörde
erreur = False
try:
# „admindata“ wird zu einer schreibgeschützten, anwendungsweiten Variable
config["admindata"] = config["layers"]["dao"].get_admindata().asdict()
# Erfolgsprotokoll
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# 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 das Programm beendet
if erreur:
sys.exit(2)
# Flask-Anwendung
app = Flask(__name__, template_folder="templates", static_folder="static")
# Geheimer Sitzungsschlüssel
app.secret_key = os.urandom(12).hex()
# Der Front-Controller
def front_controller() -> tuple:
# Die Anfrage wird bearbeitet
logger = None
…
@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()
# 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/<int:numero>', methods=['GET'])
def get_admindata() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
# nur „main“
if __name__ == '__main__':
# Der Server wird gestartet
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- Zeilen 1–92: Alle diese Zeilen wurden bereits behandelt und erläutert;
- Zeile 92: Der Server verwaltet eine Sitzung. Daher benötigen wir einen geheimen Schlüssel. Für jeden Benutzer speichern wir zwei Informationen in der Sitzung:
- ob sich der Benutzer erfolgreich authentifiziert hat;
- Jedes Mal, wenn er eine Steuerberechnung durchführt, werden die Ergebnisse dieser Berechnung in eine Liste aufgenommen, die wir als „Simulationsliste des Benutzers“ bezeichnen. Diese Liste wird in der Sitzung gespeichert;
- Zeilen 100–151: die Liste der URL-Dienste des Servers. Die zugehörigen Funktionen dienen als Filter: Alle URL, die nicht in dieser Liste enthalten sind, werden vom Flask-Server mit dem Fehler [404 NOT FOUND] abgelehnt. Nach Bestehen dieser Filterung wird die Anfrage systematisch an einen „Front Controller“ weitergeleitet, der durch die Funktion [front_controller] in den Zeilen 94–98 implementiert ist, die wir gleich vorstellen werden;
- Zeilen 100–103: Verwaltung der Route [/]. Der Einstiegspunkt der Webanwendung ist die Funktion URL in Zeile 107. Daher leiten wir in Zeile 103 den Client an diese Funktion URL weiter:
- Die Funktion [url_for] wird in Zeile 18 importiert. Sie hat hier zwei Parameter:
- Der erste Parameter ist der Name einer der Routing-Funktionen, hier die aus Zeile 107. Man sieht, dass diese Funktion einen Parameter [type_response] erwartet, der den vom Kunden gewünschten Antworttyp (json, xml, html) angibt;
- der zweite Parameter übernimmt den Namen des Parameters aus Zeile 107, [type_response], und weist ihm einen Wert zu. Gäbe es weitere Parameter, würde man den Vorgang für jeden einzelnen wiederholen;
- sie liefert den Wert URL, der der Funktion zugeordnet ist, die durch die beiden ihr übergebenen Parameter bezeichnet wird. In diesem Fall ergibt sich der Wert URL aus Zeile 106, wobei der Parameter durch seinen Wert [/init-session/html] ersetzt wird;
- Die Funktion [redirect] wurde in Zeile 18 importiert. Ihre Aufgabe ist es, einen Umleitungsheader HTTP an den Client zu senden:
- Der erste Parameter ist der Wert URL, zu dem der Client umgeleitet werden soll;
- der zweite Parameter ist der Statuscode der Antwort an den Kunden. Der Code entspricht einer Weiterleitung;
Die Funktion [front_controller] in den Zeilen 94–98 führt die ersten Verarbeitungsschritte der Client-Anfrage durch:
# der Front-Controller
def front_controller() -> tuple:
# Die Anfrage wird verarbeitet
logger = None
try:
# Protokollierung
logger = Logger(config["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"[ front_controller] requête : {request}\n")
# Der Thread wird unterbrochen, falls dies angefordert wurde
sleep_time = config["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)
# Die Anfrage wird an den Hauptcontroller weitergeleitet
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# Das an den Client gesendete Ergebnis wird protokolliert
log = f"[front_controller] {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 Anwendungsadministrator gesendet
send_adminmail(config, log)
# Der gewünschte Antworttyp wird ermittelt
if session.get('typeResponse') is None:
# Der Sitzungstyp wurde noch nicht festgelegt – es wird jSON sein
type_response = 'json'
else:
type_response = session['typeResponse']
# Die zu sendende Antwort wird erstellt
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# Die Antwort wird gesendet
return response, status_code
except BaseException as erreur:
# Es handelt sich um einen unerwarteten Fehler – der Fehler wird protokolliert, sofern möglich
if logger:
logger.write(f"[ front_controller] {erreur}")
# Die Antwort an den Kunden wird vorbereitet
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# Eine Antwort wird gesendet: jSON
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# Die Protokolldatei wird geschlossen, falls sie geöffnet war
if logger:
logger.close()
- Zeilen 1–57: Dieser Code ist uns bekannt. Es handelte sich beispielsweise um den Code der Funktion [main] im Skript [main] der vorherigen Version. Nur eine Sache ist zu beachten, nämlich der in den Zeilen 25–26 verwendete Controller:
- Zeile 25: Aus der Konfiguration wird die Controller-Instanz abgerufen, die dem Namen [main-controller] zugeordnet ist. Es handelt sich um die folgenden Zeilen:
# Abhängigkeiten des Webservers
# die Controller
…
from MainController import MainController
# Zulässige Aktionen und ihre Controller
"controllers": {
…,
# Haupt-Controller
"main-controller": MainController()
},
- (Fortsetzung)
- In Zeile 10 oben ist zu beachten, dass eine Klasseninstanz abgerufen wird;
- Zeile 26: Der Controller [MainController] wird aufgefordert, die Anfrage zu bearbeiten;
- Zeilen 30–45: Die vom Controller [MainController] zurückgegebene Antwort wird an den Client gesendet. Wir werden später noch einmal auf diese Zeilen zurückkommen;
Die Aufgabe der Funktion [front_controller] und anschließend der Klasse [MainController] besteht darin, die für alle Anfragen gemeinsamen Aufgaben zu erledigen:
Im obigen Schema befinden wir uns noch immer in Phase 1 der Anfragebearbeitung. Der Hauptcontroller [MainController] wird Schritt 1 fortsetzen.
30.6.2. Der Hauptcontroller [MainController]
Der Hauptcontroller [MainController] setzt die von der Funktion [front_controller] begonnene Arbeit fort:
Alle Controller implementieren die folgende Schnittstelle [InterfaceController] [2]:

from abc import ABC, abstractmethod
from werkzeug.local import LocalProxy
class InterfaceController(ABC):
@abstractmethod
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
pass
- Die Schnittstelle [InterfaceController] definiert lediglich die einzige Methode [execute] in Zeile 8. Diese Methode erhält drei Parameter:
- [request]: die Anfrage des Kunden;
- [session]: die Sitzung des Kunden;
- [config]: die Anwendungskonfiguration;
Die Methode [execute] gibt ein Tupel mit zwei Elementen zurück:
- Das erste Element ist das Ergebniswörterbuch in der Form {‘action’: action, ‘état’: état, ‘réponse’: résultats};
- das zweite Element ist der Statuscode HTTP, der an den Client zurückgegeben werden soll;
Der Hauptcontroller [MainController] [1] implementiert die Schnittstelle [InterfaceController] wie folgt:
# Import der Abhängigkeiten
from flask_api import status
from werkzeug.local import LocalProxy
# Controller der Webanwendung
from InterfaceController import InterfaceController
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Abfrage der Elemente des Pfads
params = request.path.split('/')
action = params[1]
# Fehler
erreur = False
# Der Sitzungstyp muss vor bestimmten Aktionen bekannt sein
type_response = session.get('typeResponse')
if type_response 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
# 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:
# Es wird eine Fehlermeldung zurückgegeben
return résultat, status.HTTP_400_BAD_REQUEST
else:
# Der zur Aktion gehörende Controller wird ausgeführt
controller = config["controllers"][action]
résultat, status_code = controller.execute(request, session, config)
return résultat, status_code
Der Controller [MainController] führt die ersten Überprüfungen der Gültigkeit der Anfrage durch.
- Zeilen 11–13: Der Controller ruft zunächst die vom Client angeforderte Aktion ab. Zur Erinnerung: Die Service-URL haben die Form [/action/param1/param2/…], und diese URL befindet sich in [request.path];
- Zeilen 17–23: Die Aktion [init-session] dient dazu, den vom Client gewünschten Antworttyp (json, xml, html) zu initialisieren. Diese Information wird in der Sitzung unter dem Schlüssel [typeRéponse] gespeichert. Wenn die Aktion also nicht [init-session] lautet, muss die Sitzung den Schlüssel [typeRéponse] enthalten, andernfalls ist die Anfrage fehlerhaft;
- Zeilen 21–22: Die Struktur des von jedem Controller zurückgegebenen Ergebnisses, hier ein Fehlerergebnis:
- [action]: ist der Name der aktuellen Aktion. Dadurch steht der Name zur Verfügung, wenn das Ergebnis der Anfrage protokolliert wird;
- [état]: ist ein dreistelliger Statuscode:
- [x00] für einen Erfolg;
- [x01] bei einem Fehler;
- [réponse]: ist die Antwort auf die Anfrage. Ihre Art ist für jede Anfrage spezifisch;
- Zeilen 24–30: Die Aktion [authentifier-utilisateur] dient der Authentifizierung des Benutzers. Bei Erfolg wird ein Schlüssel [user=True] in die Sitzung des Benutzers gesetzt. Bestimmte Service-URL sind nur für einen authentifizierten Benutzer zugänglich. Dies wird hier überprüft;
- Zeile 26: Nur die Aktionen [init-session] und [authentifier-utilisateur] können von einem noch nicht authentifizierten Benutzer ausgeführt werden;
- Zeilen 28–29: das im Fehlerfall zu sendende Ergebnis;
- Zeilen 32–34: Wenn einer der beiden vorgenannten Fehler aufgetreten ist, wird die Fehlerantwort mit dem Status HTTP 400 BAD REQUEST an den Client gesendet;
- Zeilen 35–39: Wenn kein Fehler aufgetreten ist, wird die Kontrolle an den Controller übergeben, der für die Bearbeitung der laufenden Aktion zuständig ist. Seine Instanz wird in der Anwendungskonfiguration gefunden;
Die Klasse [MainController] setzt die Arbeit der Funktion [front_controller] fort: Zusammen fassen sie alles zusammen, was bei der Bearbeitung von Anfragen faktorisierbar ist, und warten bis zum letzten Moment, um die Anfrage an einen spezifischen Controller weiterzuleiten. Die Aufteilung des Codes zwischen der Funktion [front_controller] und der Klasse [MainController] ist rein subjektiv. Hier wollte ich die Errungenschaften der Vorgängerversion beibehalten: Die Funktion [front_controller] existierte bereits unter dem Namen [main]. In der Praxis könnte man:
- alles in die Funktion [front_controller] integrieren und die Klasse [MainController] entfernen;
- alles in die Klasse [MainController] integrieren und die Funktion [front_controller] entfernen. Ich würde eher diese Lösung wählen, da sie den Code des Hauptskripts [main] vereinfacht;
30.7. Aktionsspezifische Verarbeitung
Kehren wir zur Architektur MVC der Anwendung zurück:

Wir befinden uns immer noch in Schritt 1 oben. Wenn kein Fehler aufgetreten ist, beginnt Schritt 2. Die Anfrage wurde an den für die von der Anfrage angeforderte Aktion spezifischen Controller weitergeleitet. Nehmen wir an, diese Aktion sei [/init-session], definiert durch die Route:
# Sitzung initialisieren
@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()
Diese Aktion ist mit einem Controller in der Konfiguration [config] verknüpft:
# Zulässige Aktionen und die zugehörigen Controller
"controllers": {
# Initialisierung einer Berechnungssitzung
"init-session": InitSessionController(),
…
},
Der Controller [InitSessionController] (Zeile 4) übernimmt daher die Steuerung. Sein Code lautet wie folgt:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class InitSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
dummy, action, type_response = request.path.split('/')
# Zunächst kein Fehler
erreur = False
# Überprüfung des Antworttyps
if type_response not in config['responses'].keys():
erreur = True
résultat = {"action": action, "état": 701,
"réponse": [f"paramètre [type={type_response}] invalide"]}
# Wenn kein Fehler vorliegt
if not erreur:
# wird der Sitzungstyp in die Flask-Sitzung geschrieben
session['typeResponse'] = type_response
résultat = {"action": action, "état": 700,
"réponse": [f"session démarrée avec le type de réponse {type_response}"]}
return résultat, status.HTTP_200_OK
else:
return résultat, status.HTTP_400_BAD_REQUEST
- Zeile 6: Wie die anderen Controller implementiert auch der Controller [InitSessionController] die Schnittstelle [InterfaceController];
- Zeile 10: Der Controller URL ist vom Typ [/init-session/type_response]. Wir rufen die Aktion [init-session] und den gewünschten Antworttyp ab;
- Zeile 15: Der gewünschte Antworttyp kann nur einer der in der Antwortkonfiguration vorhandenen sein:
# die verschiedenen Antworttypen (JSON, XML, HTML)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
- Ist dies nicht der Fall, wird eine Fehlermeldung 701 vorbereitet (Zeile 17);
- Zeilen 20–25: Fall, in dem der gewünschte Antworttyp gültig ist;
- Zeile 22: Der gewünschte Antworttyp wird in der Sitzung gespeichert. Dieser muss nämlich für die folgenden Anfragen im Gedächtnis behalten werden;
- Zeilen 23–24: Es wird eine Erfolgsmeldung 700 vorbereitet;
- Zeile 25: Die Erfolgsmeldung wird an den aufrufenden Code zurückgegeben;
- Zeile 27: Wenn ein Fehler aufgetreten ist, wird die Fehlerantwort an den aufrufenden Code zurückgegeben;
30.8. Erstellung der Serverantwort HTTP
Kehren wir zur Architektur MVC der Anwendung zurück:

Wir haben gerade die Schritte 1 und 2 betrachtet. Dabei sind wir auf drei Statuscodes gestoßen:
- 700: /init-session war erfolgreich;
- 701: /init-session ist fehlgeschlagen;
- 101: Ungültige Anfrage, entweder weil die Sitzung nicht initialisiert wurde oder weil der Benutzer nicht authentifiziert ist;
Schauen wir uns an, wie die Antwort des Servers im obigen Schritt 3 an den Client gesendet wird. Dies geschieht in der Funktion [front_controller] des Skripts [main]:
# Der Front-Controller
def front_controller() -> tuple:
# Die Anfrage wird verarbeitet
logger = None
try:
# Protokollierung
logger = Logger(config["logsFilename"])
# Speicherung in einer dem Thread zugeordneten Konfiguration
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# Die Anfrage wird protokolliert
logger.write(f"[ front_controller] requête : {request}\n")
# Der Thread wird unterbrochen, falls dies angefordert wurde
sleep_time = config["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)
# Die Anfrage wird an den Hauptcontroller weitergeleitet
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# Das an den Client gesendete Ergebnis wird protokolliert
log = f"[front_controller] {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 Anwendungsadministrator gesendet
send_adminmail(config, log)
# Der gewünschte Antworttyp wird ermittelt
if session.get('typeResponse') is None:
# Der Sitzungstyp wurde noch nicht festgelegt – es wird jSON sein
type_response = 'json'
else:
type_response = session['typeResponse']
# Die zu sendende Antwort wird erstellt
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# Die Antwort wird gesendet
return response, status_code
except BaseException as erreur:
# Es handelt sich um einen unerwarteten Fehler – der Fehler wird protokolliert, sofern möglich
if logger:
logger.write(f"[ front_controller] {erreur}")
# Die Antwort an den Kunden wird vorbereitet
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# Eine Antwort wird im Format jSON gesendet
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# Die Protokolldatei wird geschlossen, falls sie geöffnet war
if logger:
logger.close()
- Wir befinden uns in Zeile 26: Der Hauptcontroller hat seine Fehlerantwort zurückgegeben;
- Zeilen 27–29: Unabhängig von der Antwort des Hauptcontrollers (erfolgreich oder fehlgeschlagen) wird diese Antwort in der Protokolldatei protokolliert;
- Zeilen 30–33: Wie in den vorherigen Versionen wird, wenn der Status HTTP den Wert [500 INTERNAL SERVER ERROR] hat, eine E-Mail mit dem Fehlerprotokoll an den Anwendungsadministrator gesendet;
- Zeilen 34–39: Es wird die Antwort „HTTP“ gesendet, und das vom Controller zurückgegebene Ergebnis wird in den Hauptteil dieser Antwort eingefügt. Wir müssen wissen, in welchem Format (JSON, XML, HTML) der Client diese Antwort wünscht. Wir suchen in der Sitzung nach dem gewünschten Antworttyp. Ist dieser nicht vorhanden, legen wir diesen Typ willkürlich auf jSON fest;
- Zeilen 40–43: Die Antwort „HTTP“ wird erstellt;
In der Konfigurationsdatei wurde jeder Antworttyp (JSON, XML, HTML) einer Klasseninstanz zugeordnet:
# die verschiedenen Antworttypen (JSON, XML, HTML)
"responses": {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
},
Die Antwortklassen befinden sich im Ordner „[responses]“ der Serververzeichnisstruktur:

Jede Antwortklasse implementiert die folgende Schnittstelle [InterfaceResponse]:
from abc import ABC, abstractmethod
from flask.wrappers import Response
from werkzeug.local import LocalProxy
class InterfaceResponse(ABC):
@abstractmethod
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
pass
- Zeilen 8–11: Die Schnittstelle [InterfaceResponse] definiert eine einzige Methode [build_http_response] mit den folgenden Parametern:
- [request, session, config]: Dies sind die Parameter, die vom Aktions-Controller empfangen werden;
- [résultat, status_code]: Dies sind die vom Aktionscontroller erzeugten Ergebnisse;
Wir stellen nun die Antwort jSON vor. Sie wird von der folgenden Klasse [JsonResponse] erzeugt:
import json
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
class JsonResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# Ergebnisse: das Ergebniswörterbuch
# status_code: Der Statuscode der Antwort HTTP
# Die Antwort wird zurückgegeben: HTTP
response = make_response(json.dumps(résultat, ensure_ascii=False))
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status_code
Diesen Code kennen wir bereits, da wir ihm schon oft begegnet sind. Es handelt sich um den Code der Funktion [json_response] aus dem Modul [myutils].
30.9. Erste Tests
Im untersuchten Code sind wir auf drei Statuscodes gestoßen:
- 700: /init-session erfolgreich;
- 701: /init-session ist fehlgeschlagen;
- 101: Ungültige Anfrage, entweder weil die Sitzung nicht initialisiert wurde oder weil der Benutzer nicht authentifiziert ist;
Wir werden versuchen, diese mit einer Sitzung namens jSON abzurufen.
- Wir starten den Webserver, den SGBD und den Mailserver;
- Wir starten einen Postman-Client;
Test 1
Zunächst zeigen wir eine ungültige Anfrage, da die Sitzung nicht initialisiert wurde:

- [1-2]: Die Anfrage [POST http://localhost:5000/authentifier-utilisateur] ist eine gültige Route:
# Benutzer authentifizieren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Sie wird jedoch nur akzeptiert, wenn die Sitzung zuvor mit der Aktion [/init-session] initialisiert wurde.
Führen wir die Anfrage aus und sehen wir uns das vom Server gesendete Ergebnis an:

- [1-2]: Wir haben eine Antwort mit dem Typ jSON erhalten. Wenn der Antworttyp vom Client noch nicht festgelegt wurde, verwendet der Server jSON als Antwort;
- [3-5]: das Wörterbuch jSON der Antwort;
- [action]: die ausgeführte Aktion;
- [état]: der Statuscode der Antwort. Ein Code [x01] weist auf einen Fehler hin;
- [réponse]: wird an jede Aktion angepasst. Hier enthält sie eine Fehlermeldung;
Nun initialisieren wir eine Sitzung mit einem falschen Antworttyp:

- [1-2] ist eine korrekte Route:
# Sitzung initialisieren
@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()
Sie gelangt somit in den Anforderungsverarbeitungstunnel des Servers MVC. Allerdings dürfte sie im Laufe dieser Verarbeitung abgelehnt werden, da der angeforderte Sitzungstyp falsch ist.
Die Antwort lautet wie folgt:

- in [4], ein Fehlercode [x01];
- in [5] die Fehlerbeschreibung;
Nun initialisieren wir eine Sitzung mit jSON:

Die Antwort lautet wie folgt:

Nun initialisieren wir eine Sitzung mit der ID XML. Die Antwort jSON wird durch eine Antwort XML ersetzt, die von der folgenden Klasse [XmlResponse] generiert wird:
import xmltodict
from flask import make_response
from flask.wrappers import Response
from werkzeug.local import LocalProxy
from InterfaceResponse import InterfaceResponse
from Logger import Logger
class XmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# Ergebnisse: das Ergebniswörterbuch
# status_code: Der Statuscode der Antwort HTTP
# Ergebnis: das in eine Zeichenkette umzuwandelnde Wörterbuch XML
xml_string = xmltodict.unparse({"root": résultat})
# Die Antwort lautet HTTP
response = make_response(xml_string)
response.headers['Content-Type'] = 'application/xml; charset=utf-8'
return response, status_code
Das ist uns bekannter Code, nämlich der der Funktion [xml_response] aus dem gemeinsam genutzten Modul [myutils].
Wir initialisieren eine Sitzung XML:

Das Ergebnis des Servers lautet dann wie folgt:

Wir erhalten dieselbe Antwort wie bei jSON, doch diesmal ist die Antwort als XML aufbereitet.
30.10. Die Aktion [authentifier-utilisateur]
Die Aktion [authentifier-utilisateur] ermöglicht die Authentifizierung eines Benutzers, der die Steuerberechnungsanwendung nutzen möchte. Ihr Ablauf ist im Skript [main] wie folgt definiert:
# Benutzer authentifizieren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Der Server erwartet zwei über POST übermittelte Parameter:
- [user]: die Benutzer-ID;
- [password]: sein Passwort;
Die Liste der autorisierten Benutzer ist in der Konfiguration [config] definiert:
# Benutzer, die zur Nutzung der Anwendung berechtigt sind
"users": [
{
"login": "admin",
"password": "admin"
}
],
Hier haben wir eine Liste mit einem Element.
Die Aktion [authentifier-utilisateur] wird vom folgenden Controller [AuthentifierUtilisateurController] verarbeitet:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from Logger import Logger
class AuthentifierUtilisateurController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# die Parameter von POST
post_params = request.form
# Statuscode der Antwort HTTP
status_code = None
# zunächst keine Fehler
erreur = False
erreurs = []
# Es wird ein POST mit zwei Parametern benötigt
if len(post_params) != 2:
erreur = True
status_code = status.HTTP_400_BAD_REQUEST
erreurs.append("méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user, password]")
if not erreur:
# Die Parameter werden aus dem POST übernommen
# Parameter [user]
user = post_params.get("user")
if user is None:
erreur = True
erreurs.append("paramètre [user] manquant")
# Parameter [password]
password = post_params.get("password")
if password is None:
erreur = True
erreurs.append("paramètre [password] manquant")
# Fehler?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
# Fehler?
if not erreur:
# Die Gültigkeit der Kombination (Benutzername, Passwort) wird überprüft
users = config['users']
i = 0
nbusers = len(users)
trouvé = False
while not trouvé and i < nbusers:
trouvé = user == users[i]["login"] and password == users[i]["password"]
i += 1
# gefunden?
if not trouvé:
# Der Fehler wird protokolliert
erreur = True
status_code = status.HTTP_401_UNAUTHORIZED
erreurs.append(f"Echec de l'authentification")
else:
# In der Sitzung wird vermerkt, dass der Benutzer gefunden wurde
session["user"] = True
# Fertig
if not erreur:
# Rückgabe ohne Fehler
résultat = {"action": action, "état": 200, "réponse": f"Authentification réussie"}
return résultat, status.HTTP_200_OK
else:
# Rückgabe mit Fehler
return {"action": action, "état": 201, "réponse": erreurs}, status_code
- Zeile 14: Die Parameter von POST werden abgerufen;
- Zeile 19: Die Liste der in der Anfrage gefundenen Fehler;
- Zeilen 20–24: Es wird überprüft, ob tatsächlich zwei Parameter übermittelt wurden;
- Zeilen 27–31: Es wird überprüft, ob ein Parameter [users] vorhanden ist;
- Zeilen 32–36: Es wird überprüft, ob der Parameter [password] vorhanden ist;
- Zeilen 38–39: Sind die übermittelten Parameter fehlerhaft, wird eine Antwort mit den Parametern HTTP, 400, BAD und REQUEST vorbereitet;
- Zeilen 40–58: Es wird überprüft, ob die Anmeldedaten [user, password] zu einem Benutzer gehören, der zur Nutzung der Anwendung berechtigt ist;
- Zeilen 51–55: Ist der Benutzer (user, password) nicht zur Nutzung der Anwendung berechtigt, wird eine Antwort mit den Codes HTTP 401 UNAUTHORIZED vorbereitet;
- Zeilen 56–58: Ist er berechtigt, wird mit dem Schlüssel [user] in der Sitzung vermerkt, dass er sich authentifiziert hat;
Es ist zu beachten: Wenn der Benutzer mit den Anmeldedaten [identifiants1] authentifiziert war und die Authentifizierung mit den Anmeldedaten [identifiants2] fehlschlägt, bleibt er dennoch mit den Anmeldedaten [identifiants1] authentifiziert.
Führen wir einige Postman-Tests durch:
- Wir starten den Webserver, den SGBD und den Mailserver;
- mit dem Postman-Client:
- starten wir eine Sitzung mit den Anmeldedaten jSON;
- dann authentifizieren wir uns;
Hier sind verschiedene Fälle.
Fall 1: POST ohne übermittelte Parameter

- In [3-5] hat POST keinen Hauptteil;
Das Ergebnis der Anfrage lautet wie folgt:

- Bei [2] erhielten wir eine Antwort HTTP 400 BAD REQUEST;
- bei [5] erhielten wir den Fehlercode [201];
Fall 2: POST mit falschen Anmeldedaten

- bei [6] sind die Anmeldedaten falsch;
Der Server sendet folgende Antwort:

- bei [2] die Antwort HTTP 401 UNAUTHORIZED;
- bei [5] die Fehlermeldung;
Fall 2: POST mit korrekten Anmeldedaten

- in [6], die Anmeldedaten sind korrekt;
Die Antwort des Servers lautet wie folgt:
- in [2], eine Antwort HTTP 200 OK;
- in [5], die erfolgreiche Antwort;
30.11. Die Aktion [calculer_impot]
Die Aktion [calculer_impot] dient zur Berechnung der Steuer eines Steuerpflichtigen. Ihr Ablauf ist im Skript [main] wie folgt definiert:
# Steuerberechnung
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Der Server erwartet drei über POST übermittelte Parameter:
- [marié]: ja / nein;
- [enfants]: Anzahl der Kinder des Steuerpflichtigen;
- [salaire]: Jahresgehalt des Steuerpflichtigen;
Der Controller [CalculerImpotController] verarbeitet die Aktion [calculer_impot]:
import re
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# zunächst kein Fehler
erreur = False
erreurs = []
# die Parameter von POST
post_params = request.form
# Es wird ein POST mit drei Parametern benötigt
if len(post_params) != 3:
erreur = True
erreurs.append(
"méthode POST requise avec les paramètres postés [marié, enfants, salaire]")
# Die übermittelten Parameter werden analysiert
if not erreur:
# Parameter ist verheiratet
marié = post_params.get("marié")
if marié is None:
erreurs.append("paramètre [marié] manquant")
else:
# Ist der Parameter gültig?
marié = marié.lower()
if marié != "oui" and marié != "non":
erreur = True
erreurs.append(f"valeur [{marié}] invalide pour le paramètre [marié (oui/non)]")
# Parameter [enfants]
enfants = post_params.get("enfants")
if enfants is None:
erreur = True
erreurs.append("paramètre [enfants] manquant")
else:
# Ist der Parameter gültig?
enfants = enfants.strip()
match = re.match(r"\d+", enfants)
if not match:
erreur = True
erreurs.append(f"valeur [{enfants}] invalide pour le paramètre [enfants (entier>=0)]")
# Parameter „Gehalt“
salaire = post_params.get("salaire")
if salaire is None:
erreur = True
erreurs.append("paramètre [salaire] manquant")
else:
# Ist der Parameter gültig?
salaire = salaire.strip()
match = re.match(r"\d+", salaire)
if not match:
erreur = True
erreurs.append(f"valeur [{salaire}] invalide pour le paramètre [salaire (entier>=0)]")
# Fehler?
if erreur:
status_code = status.HTTP_400_BAD_REQUEST
résultat = {"action": action, "état": 301, "réponse": erreurs}
# Das Ergebnis wird zurückgegeben
return résultat, status_code
# Steuerberechnung
# Die Ebene [métier] und das Wörterbuch [adminData] werden abgerufen
métier = config["layers"]["métier"]
admin_data = config["admindata"]
# Steuerberechnung
taxpayer = TaxPayer().fromdict({'marié': marié, 'enfants': enfants, 'salaire': salaire})
métier.calculate_tax(taxpayer, admin_data)
# Simulationsnummer
id_simulation = session.get('id_simulation', 0)
id_simulation += 1
session['id_simulation'] = id_simulation
# Das Ergebnis wird in die Sitzung in Form des Wörterbuchs eines TaxPayer übernommen
simulation = taxpayer.fromdict({'id': id_simulation}).asdict()
# Das Ergebnis wird zur Liste der bereits durchgeführten Simulationen hinzugefügt und diese wird in die Sitzung übernommen
simulations = session.get("simulations", [])
simulations.append(simulation)
session["simulations"] = simulations
# Ergebnis
résultat = {"action": action, "état": 300, "réponse": simulation}
status_code = status.HTTP_200_OK
# Das Ergebnis wird ausgegeben
return résultat, status_code
- Zeile 13: Der Name der aktuellen Aktion wird abgerufen;
- Zeile 17: Die Fehler werden in einer Liste gesammelt;
- Zeile 19: Die übermittelten Parameter werden abgerufen. Diese werden in der Form [x-www-form-urlencoded] übermittelt, weshalb sie in [request.form] abgerufen werden. Wären sie als „jSON“ übermittelt worden, hätten wir sie als „[request.data]“ abgerufen;
- Zeilen 21–24: Es wird überprüft, ob tatsächlich drei Parameter übermittelt wurden;
- Zeilen 27–36: Überprüfung des Vorhandenseins und der Gültigkeit des übermittelten Parameters [marié];
- Zeilen 37–48: Überprüfung des Vorhandenseins und der Gültigkeit des übermittelten Parameters [enfants];
- Zeilen 49–60: Überprüfung des Vorhandenseins und der Gültigkeit des übermittelten Parameters [salaire];
- Zeilen 62–66: Wenn ein Fehler aufgetreten ist, wird eine Fehlermeldung 400 BAD REQUEST mit dem Statuscode [301] gesendet;
- Zeilen 69–71: Wenn kein Fehler aufgetreten ist, werden Vorbereitungen für die Berechnung der Steuer getroffen. Dazu
- Zeile 70: wird eine Referenz auf die Ebene [métier] abgerufen;
- Zeile 71: Die Daten der Steuerbehörde werden aus der Serverkonfiguration abgerufen;
- Zeilen 72–74: Die Steuer des Steuerpflichtigen wird berechnet;
- Zeilen 75–77: Die Anzahl der vom Benutzer durchgeführten Steuerberechnungen wird gezählt;
- Zeile 76: Die Nummer der zuletzt durchgeführten Berechnung wird aus der Sitzung abgerufen. Das Ergebnis einer Berechnung wird hier als [simulation] bezeichnet;
- Zeile 77: Die Nummer der letzten Simulation wird erhöht;
- Zeile 78: Diese Nummer wird wieder in die Sitzung geschrieben;
- Zeilen 79–84: Um die vom Benutzer durchgeführten Berechnungen nachzuverfolgen, wird die Liste der von ihm durchgeführten Simulationen in seine Sitzung geschrieben;
- Zeile 80: Eine Simulation ist das Wörterbuch eines Objekts TaxPayer, dessen Eigenschaft [id] den Wert der Simulationsnummer annimmt;
- Zeilen 82–84: Die aktuelle Simulation wird der Liste der in der Sitzung vorhandenen Simulationen hinzugefügt;
- Zeilen 86–87: Es wird eine erfolgreiche Antwort HTTP vorbereitet;
- Zeile 90: Das Ergebnis wird zurückgegeben;
Führen wir einige Tests durch: Der Webserver, der SGBD, der Mailserver und ein Postman-Client werden gestartet.
Fall 1: Eine Steuerberechnung durchführen, obwohl die Sitzung nicht initialisiert ist

Die Antwort lautet wie folgt:

Fall 2: Eine Steuerberechnung durchführen, ohne authentifiziert zu sein
Zunächst wird eine Sitzung jSON mit [/init-session/json] gestartet. Anschließend wird dieselbe Abfrage wie zuvor durchgeführt. Die Antwort lautet dann wie folgt:

Fall 3: Steuerberechnung mit fehlenden Parametern
Man initialisiert eine Sitzung jSON, authentifiziert sich und führt dann die folgende Abfrage durch:

- In [5] fehlt der Parameter [marié];
Die Antwort lautet wie folgt:
Fall 4: Steuerberechnung mit falschen Parametern


Die Antwort des Servers lautet wie folgt:

Fall 4: Steuerberechnung mit korrekten Parametern

Die Antwort des Servers lautet wie folgt:

30.12. Die Aktion [lister-simulations]
Die Aktion [lister-simulations] ermöglicht es einem Benutzer, die Liste der Simulationen abzurufen, die er seit Beginn der Sitzung durchgeführt hat. Ihr Pfad ist im Skript [main] wie folgt definiert:
# 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()
Der Server erwartet keine Parameter. Die Aktion [lister-simulations] wird vom folgenden Controller [ListerSimulationsController] verarbeitet:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class ListerSimulationsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# die Liste der Simulationen in der Sitzung wird abgerufen
simulations = session.get("simulations", [])
# Das Ergebnis wird ausgegeben
return {"action": action, "état": 500,
"réponse": simulations}, status.HTTP_200_OK
- Zeile 13: Die Liste der Simulationen wird aus der Sitzung übernommen;
- Zeilen 15–16: Es wird eine Erfolgsmeldung zurückgegeben;
Führen wir den folgenden Postman-Test durch:
- Wir starten eine Sitzung mit dem Namen jSON;
- man authentifiziert sich;
- wir führen zwei Steuerberechnungen durch;
- wir fordern die Liste der Simulationen an;
Die Anfrage lautet wie folgt:
- In [3] gibt es keine Parameter;
Die Antwort des Servers lautet wie folgt:

- in [4] die Liste der Simulationen des Benutzers;
30.13. Die Aktion [supprimer-simulation]
Die Aktion [supprimer-simulation] ermöglicht es einem Benutzer, eine der Simulationen aus seiner Simulationsliste zu löschen. Ihr Pfad ist im Skript [main] wie folgt definiert:
# 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()
Der Server erwartet einen einzigen Parameter, nämlich die Nummer der zu löschenden Simulation. Die Aktion [supprimer-simulation] wird vom folgenden Controller [SupprimerSimulationController] verarbeitet:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class SupprimerSimulationController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# die Elemente des Pfads werden abgerufen
dummy, action, numéro = request.path.split('/')
# Der Parameter [numéro] ist gemäß seiner Route eine positive ganze Zahl oder Null
numéro = int(numéro)
# Die Simulation mit der ID „numéro“ muss in der Liste der Simulationen vorhanden sein
simulations = session.get("simulations", [])
liste_simulations = list(filter(lambda simulation: simulation['id'] == numéro, simulations))
if not liste_simulations:
msg_erreur = f"la simulation n° [{numéro}] n'existe pas"
# Es wird ein Fehler zurückgegeben
return {"action": action, "état": 601, "réponse": [msg_erreur]}, status.HTTP_400_BAD_REQUEST
# Löschen der Simulation mit der ID „Nummer“
simulation = liste_simulations.pop(0)
simulations.remove(simulation)
# Die Simulationen werden wieder in die Sitzung aufgenommen
session["simulations"] = simulations
# Das Ergebnis wird ausgegeben
return {"action": action, "état": 600, "réponse": simulations}, status.HTTP_200_OK
- Zeile 10: Die beiden Elemente des Anfragepfads werden abgerufen. Sie werden als Zeichenkette abgerufen;
- Zeile 13: Der Parameter [numéro] wird in eine Ganzzahl umgewandelt. Dass dies möglich ist, wissen wir aufgrund der Signatur seiner Route,
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
Außerdem wissen wir, dass es sich um eine ganze Zahl >= 0 handelt. Tatsächlich kann es kein URL oder [/supprimer-simulation/-4] geben. Diese werden vom Flask-Server abgelehnt;
- Zeile 15: Wir rufen die Liste der Simulationen aus der Sitzung ab;
- Zeile 16: Mit der Funktion [filter] wird die Simulation mit id==Nummer gesucht. Man erhält ein Objekt [filter], das in den Typ [list] konvertiert wird;
- Zeilen 17–20: Wenn der Filter keine Ergebnisse zurückgibt, bedeutet dies, dass die zu löschende Simulation nicht existiert. Es wird eine Fehlermeldung zurückgegeben, die darauf hinweist;
- Zeilen 21–23: Die vom Filter zurückgegebene Simulation wird gelöscht;
- Zeile 25: Die neue Liste der Simulationen wird in die Sitzung zurückgesetzt;
- Zeile 27: Die neue Liste der Simulationen wird in der Antwort zurückgegeben;
Wir führen einen Erfolgstest und einen Fehlertest durch. Wir führen Simulationen durch und fordern anschließend die Liste der Simulationen an:

- Die Simulationen haben hier die Nummern 2 und 3;
Wir fordern an, die Simulation mit der Nummer 3 zu löschen.

Die Antwort lautet wie folgt:
Führen wir nun denselben Vorgang erneut durch (Löschen der Simulation mit der ID 3). Die Antwort lautet dann wie folgt:


30.14. Die Aktion [fin-session]
Die Aktion [fin-session] ermöglicht es einem Benutzer, seine Simulationssitzung zu beenden. Ihr Pfad ist im Skript [main] wie folgt definiert:
# Ende der Sitzung
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Der Server erwartet keine Parameter. Die Aktion wird vom folgenden Controller [FinSessionController] verarbeitet:
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class FinSessionController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# alle Schlüssel der aktuellen Sitzung werden gelöscht
session.clear()
# Das Ergebnis wird zurückgegeben
return {"action": action, "état": 400, "réponse": "session réinitialisée"}, status.HTTP_200_OK
- Zeile 13: Alle Schlüssel der Sitzung werden gelöscht. Dadurch werden entfernt:
- [typeResponse]: den Typ der Antworten HTTP (json, xml, html);
- [id_simulation]: Nummer der zuletzt durchgeführten Simulation;
- [simulations]: die Liste der Simulationen des Benutzers;
- [user]: Kennzeichen, dass der Benutzer authentifiziert wurde;
- Die Antwort wird zurückgegeben;
Man könnte sich fragen, wie die Antwort HTTP aus Zeile 15 zurückgegeben wird, da der Antworttyp nun nicht mehr in der Sitzung enthalten ist. Um dies herauszufinden, muss man zur Funktion |front_controller| des Hauptskripts [main] zurückkehren und sie wie folgt ändern:
…
# on not# Der gewünschte Antworttyp wird notiert, sofern diese Information in der Sitzung vorhanden ist
type_response1 = session.get('typeResponse', None)
# Die Anfrage wird an den Hauptcontroller weitergeleitet
main_controller = config['controllers']["main-controller"]
résultat, status_code = main_controller.execute(request, session, config)
# Das an den Client gesendete Ergebnis wird protokolliert
log = f"[front_controller] {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 Anwendungsadministrator gesendet
send_adminmail(config, log)
# Der gewünschte Antworttyp wird ermittelt
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:
type_response=type_response1
# Die zu sendende Antwort wird erstellt
response_builder = config["responses"][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# die Antwort wird gesendet
return response, status_code
- Zeile 3: Der Typ der aktuell in der Sitzung befindlichen Antwort wird gespeichert;
- Zeile 6: Die Aktion wird ausgeführt. Handelt es sich um:
- [fin-session], ist der Schlüssel [typeResponse] nicht mehr in der Sitzung vorhanden;
- [init-session], hat sich der Wert des Schlüssels [typeResponse] der Sitzung möglicherweise geändert;;
- Zeilen 14–20: Es muss die Antwort HTTP gesendet werden. Wir müssen wissen, in welcher Form:
- Zeilen 16–18: Wenn der Typ der Antwort weder durch [type_response1] in Zeile 3 noch durch [type_response2] in Zeile 15 definiert ist, dann war der Antworttyp weder vor noch nach der Aktion definiert. In diesem Fall wird jSON (Zeile 18) verwendet;
- Zeilen 19–21: Wenn [type_response2] existiert, also der Typ in der Sitzung nach der Aktion, dann muss dieser Typ verwendet werden;
- Zeilen 22–23: Andernfalls ist [type_response1], der Antworttyp vor der Aktion (dieser ist zwangsläufig [fin-session]), zu verwenden;
30.15. Die Aktion [get-admindata]
Wir befassen uns nun mit den beiden URL, die für die Dienste jSON und XML reserviert sind:
Aktion | Rolle | Ausführungskontext |
/get-admindata | Liefert die Steuerdaten zur Berechnung der Steuer | Abfrage GET. Wird nur verwendet, wenn der Sitzungstyp „json“ oder „xml“ ist. Der Benutzer muss authentifiziert sein |
/calculer-impots | Berechnet die Steuer für eine Liste von Steuerpflichtigen, die in jSON übermittelt wurden | Abfrage GET. Wird nur verwendet, wenn der Sitzungstyp „json“ oder „xml“ ist. Der Benutzer muss authentifiziert sein |
Die Route URL [/get-admindata] ist in den Routen des Hauptskripts [main] wie folgt definiert:
# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Die Route [/get-admindata] wird vom folgenden Controller [GetAdminDataController] verarbeitet:
# Import der Abhängigkeiten
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
class GetAdminDataController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# Es werden nur JSON- und XML-Sitzungen akzeptiert
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# Es wird eine Fehlermeldung zurückgegeben
return {
"action": action,
"état": 1001,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
else:
# Es wird eine Erfolgsmeldung zurückgegeben
return {"action": action, "état": 1000, "réponse": config["adminData"].asdict()}, status.HTTP_200_OK
- Zeilen 13–21: Es wird überprüft, ob es sich um eine JSON- oder XML-Sitzung handelt;
- Zeile 24: Das Datenwörterbuch der Steuerbehörde wird zurückgegeben, das bereits beim Start des Servers in der Konfiguration abgelegt wurde:
# „admindata“ ist ein schreibgeschützter Datensatz im Anwendungsbereich
config["admindata"] = config["layers"]["dao"].get_admindata()
Nehmen wir einen Postman-Client und fragen wir die URL [/get-admindata] ab, nachdem wir eine Sitzung jSON gestartet und uns authentifiziert haben:

Die Antwort des Servers lautet wie folgt:

30.16. Die Aktion [calculer-impots]
Die Aktion [calculer-impots] berechnet die Steuern für eine Liste von Steuerzahlern, die im Hauptteil der Anfrage in Form einer Zeichenkette jSON enthalten ist. Diese Aktion ist uns bereits bekannt: In der vorherigen Version hieß sie [calculate_tax_in_bulk_mode].
Ihr Pfad lautet wie folgt:
# Steuerberechnung im Batch-Verfahren
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
# Der mit der Aktion verknüpfte Controller wird ausgeführt
return front_controller()
Diese Aktion wird vom folgenden Controller [CalculerImpotsController] verarbeitet:
import json
from flask_api import status
from werkzeug.local import LocalProxy
from ImpôtsError import ImpôtsError
from InterfaceController import InterfaceController
from TaxPayer import TaxPayer
class CalculerImpotsController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
dummy, action = request.path.split('/')
# Es werden nur JSON- und XML-Sitzungen akzeptiert
type_response = session.get('typeResponse')
if type_response != 'json' and type_response != 'xml':
# Es wird eine Fehlermeldung zurückgegeben
return {
"action": action,
"état": 1501,
"réponse": ["cette action n'est possible que pour les sessions json ou xml"]
}, status.HTTP_400_BAD_REQUEST
# Der Post-Body wird abgerufen – es wird eine Liste von Dictionaries erwartet
msg_erreur = None
list_dict_taxpayers = None
# Der Body jSON von POST
request_text = request.data
try:
#, den man in eine Liste von Wörterbüchern umwandelt
list_dict_taxpayers = json.loads(request_text)
except BaseException as erreur:
# man stellt den Fehler fest
msg_erreur = f"le corps du POST n'est pas une chaîne jSON valide : {erreur}"
# Haben wir eine nicht leere Liste?
if not msg_erreur and (not isinstance(list_dict_taxpayers, list) or len(list_dict_taxpayers) == 0):
# man notiert den Fehler
msg_erreur = "le corps du POST n'est pas une liste ou alors cette liste est vide"
# Liegt eine Liste von Wörterbüchern vor?
if not msg_erreur:
erreur = False
i = 0
while not erreur and i < len(list_dict_taxpayers):
erreur = not isinstance(list_dict_taxpayers[i], dict)
i += 1
# Fehler?
if erreur:
msg_erreur = "le corps du POST doit être une liste de dictionnaires"
# Fehler?
if msg_erreur:
# Es wird eine Fehlermeldung an den Client gesendet
résultats = {"action": action, "état": 1501, "réponse": [msg_erreur]}
return résultats, status.HTTP_400_BAD_REQUEST
# die TaxPayers werden nacheinander überprüft
# zunächst keine Fehler
list_erreurs = []
for dict_taxpayer in list_dict_taxpayers:
# Es wird ein TaxPayer aus dict_taxpayer erstellt
msg_erreur = None
try:
# Der folgende Schritt filtert die Fälle heraus, bei denen die Parameter nicht
# Eigenschaften der Klasse TaxPayer sind sowie Fälle, in denen deren Werte
# falsch sind
TaxPayer().fromdict(dict_taxpayer)
except BaseException as erreur:
msg_erreur = f"{erreur}"
# bestimmte Schlüssel müssen im Dictionary vorhanden sein
if not msg_erreur:
# Die Schlüssel [marié, enfants, salaire] müssen im Wörterbuch vorhanden sein
keys = dict_taxpayer.keys()
if 'marié' not in keys or 'enfants' not in keys or 'salaire' not in keys:
msg_erreur = "le dictionnaire doit inclure les clés [marié, enfants, salaire]"
# Fehler?
if msg_erreur:
# Der Fehler ist im Schlüssel TaxPayer selbst zu finden
dict_taxpayer['erreur'] = msg_erreur
# TaxPayer wird zur Fehlerliste hinzugefügt
list_erreurs.append(dict_taxpayer)
# Alle Steuerzahler wurden verarbeitet – gibt es Fehler?
if list_erreurs:
# Es wird eine Fehlermeldung an den Kunden gesendet
résultats = {"action": action, "état": 1501, "réponse": list_erreurs}
return résultats, status.HTTP_400_BAD_REQUEST
# Keine Fehler, es kann weitergearbeitet werden
# Abruf der Daten von der Steuerbehörde
admindata = config["admindata"]
métier = config["layers"]["métier"]
try:
# Die TaxPayer-Dateien werden einzeln verarbeitet
list_taxpayers = []
for dict_taxpayer in list_dict_taxpayers:
# Steuerberechnung
taxpayer = TaxPayer().fromdict(
{'marié': dict_taxpayer['marié'], 'enfants': dict_taxpayer['enfants'],
'„Gehalt“: dict_taxpayer['salaire']})
métier.calculate_tax(taxpayer, admindata)
# Das Ergebnis wird als Dictionary gespeichert
list_taxpayers.append(taxpayer.asdict())
# list_taxpayers wird zu den aktuellen Simulationen hinzugefügt, wobei jeder Simulation eine Nummer zugewiesen wird
simulations = session.get("simulations", [])
id_simulation = session.get("id_simulation", 0)
for simulation in list_taxpayers:
# Jede Simulation erhält eine Nummer
id_simulation += 1
simulation['id'] = id_simulation
# man fügt sie zur aktuellen Liste der Simulationen hinzu
simulations.append(simulation)
# Das Ganze wird erneut in die Sitzung aufgenommen
session["simulations"] = simulations
session["id_simulation"] = id_simulation
# Die Antwort wird an den Client gesendet
return {"action": action, "état": 1500, "réponse": list_taxpayers}, status.HTTP_200_OK
except ImpôtsError as erreur:
# Es wird eine Fehlermeldung an den Kunden gesendet
return {"action": action, "état": 1501, "réponse": [f"{erreur}"]}, status.HTTP_500_INTERNAL_SERVER_ERROR
- Zeilen 16–24: Es wird überprüft, ob es sich tatsächlich um eine JSON- oder XML-Session handelt
- Zeilen 26–120: Dieser Code ist uns im Großen und Ganzen bekannt. Es handelt sich um den Code der Funktion |index_controller| aus Version 10 der Anwendung, der angepasst wurde, um den Spezifikationen der implementierten Schnittstelle [InterfaceController] zu entsprechen;
- Zeilen 104–115: Der Code, der hinzugefügt wurde, um der neuen Umgebung dieses Controllers Rechnung zu tragen. Wir haben gerade Steuerberechnungen durchgeführt. Wir müssen die Ergebnisse in der Liste der in der Sitzung verwalteten Simulationen speichern;
- Zeile 105: Wir rufen die Liste der in der Sitzung vorhandenen Simulationen ab;
- Zeile 106: Die Nummer der zuletzt durchgeführten Simulation wird abgerufen;
- Zeilen 107–112: Wir durchlaufen die Liste der Datensätze mit den Ergebnissen der Steuerberechnung, weisen jedem einzelnen eine Simulationsnummer [id] zu und fügen jeden Datensatz der Liste der Simulationen hinzu;
- Zeilen 113–115: Die neue Liste der Simulationen sowie die Nummer der zuletzt durchgeführten Simulation werden in die Sitzung zurückgeschrieben;
Wir führen den folgenden Postman-Test durch, nachdem wir eine Sitzung mit der Nummer jSON initialisiert und uns authentifiziert haben:


Die Antwort des Servers lautet wie folgt:

Wenn wir nun die Liste der Simulationen abfragen:
Es fällt auf, dass in der Ergebnisliste von [/calcul-impots] die Steuerzahler kein Attribut [id] aufweisen, während in der Liste der Simulationen jede Simulation eine Nummer hat, die sie identifiziert.




