36. Anwendungsübung: Version 16

36.1. Einleitung
Die URL unserer Anwendung haben derzeit die Form [/action/param1/param2/…]. Wir möchten diesen URL ein Präfix voranstellen. Mit dem Präfix [/do] hätten wir beispielsweise URL in der Form [/do/action/param1/param2/…].
36.2. Die neue Konfiguration der Routen

- in [2] geändert wird, ändert sich die Routenberechnung;
- bei [1] wird die Datei [config] angepasst, um diese Änderung widerzuspiegeln;
Die Konfiguration [config] sieht 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)
# Datenbankkonfiguration
import database
config["database"] = database.configure(config)
# Instanziierung der Anwendungsschichten
import layers
config['layers'] = layers.configure(config)
# Konfiguration der Schicht MVC
config['mvc'] = {}
# Konfiguration der Controller der Schicht [web]
import controllers
config['mvc'].update(controllers.configure(config))
# Aktionen ASV (Action Show View)
import asv_actions
config['mvc'].update(asv_actions.configure(config))
# Aktionen ADS (Aktion „Do Something“)
import ads_actions
config['mvc'].update(ads_actions.configure(config))
# Konfiguration der Antworten HTTP
import responses
config['mvc'].update(responses.configure(config))
# Konfiguration der Routen
import routes
routes.configure(config)
# Konfiguration wird übernommen
return config
- Zeilen 37–39: Das Modul [routes] (Zeile 38) ist für die Konfiguration der Routen zuständig (Zeile 39);
Die Datei mit den Routen ohne Token CSRF [configs/routes_without_csrftoken] ändert sich wie folgt:
# Abhängigkeiten
from flask import redirect, request, session, url_for
from flask_api import status
# 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)
# Anwendungsstammverzeichnis
def index() -> tuple:
# Weiterleitung zu /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
def init_session(type_response: str) -> tuple:
# der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Benutzer authentifizieren
def authentifier_utilisateur() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Steuerberechnung
def calculer_impot() -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
…
Die Datei wurde von ihren Routen befreit. Es bleiben nur noch die damit verbundenen Funktionen übrig.
Die Routendateien mit Token CSRF und [configs/routes_with_csrftoken] erleiden das gleiche Schicksal:
# Abhängigkeiten
from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf
# Konfiguration
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)
# Anwendungsstammverzeichnis
def index() -> tuple:
# Weiterleitung zu /init-session/html
return redirect(url_for("init_session", type_response="html", csrf_token=generate_csrf()), status.HTTP_302_FOUND)
# init-session
def init_session(type_response: str, csrf_token: str) -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
# Benutzer authentifizieren
def authentifier_utilisateur(csrf_token: str) -> tuple:
# Der zur Aktion gehörende Controller wird ausgeführt
return front_controller()
…
# init-session-without-csrftoken für JSON- und XML-Clients
def init_session_without_csrftoken(type_response: str) -> tuple:
# Weiterleitung zu /init-session/type_response
return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
status.HTTP_302_FOUND)
Die Routen werden vom folgenden Modul [configs/routes] berechnet:
from flask import Flask
def configure(config: dict):
# Konfiguration der Routen
# Flask-Anwendung
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
config['app'] = app
# Import der Routen der Webanwendung
if config['parameters']['with_csrftoken']:
import routes_with_csrftoken as routes
else:
import routes_without_csrftoken as routes
# Die Konfiguration wird in die Routen eingebunden
routes.config = config
# Das Präfix der URL der Anwendung
prefix_url = config["parameters"]["prefix_url"]
# Token CSRF
with_csrftoken = config["parameters"]['with_csrftoken']
if with_csrftoken:
csrftoken_param = f"/<string:csrf_token>"
else:
csrftoken_param = ""
# die Routen der Flask-Anwendung
# Stammverzeichnis der Anwendung
app.add_url_rule(f'{prefix_url}/', methods=['GET'],
view_func=routes.index)
# Sitzung initialisieren
app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
view_func=routes.init_session)
# Sitzung-initiieren-ohne-CSRF-Token
if with_csrftoken:
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken/<string:type_response>',
methods=['GET'],
view_func=routes.init_session_without_csrftoken)
# Benutzer-Authentifizierung
app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
view_func=routes.authentifier_utilisateur)
# Steuerberechnung
app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impot)
# Steuerberechnung im Stapelverfahren
app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
view_func=routes.calculer_impots)
# Simulationen auflisten
app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.lister_simulations)
# Simulation löschen
app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
view_func=routes.supprimer_simulation)
# Sitzung beenden
app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
view_func=routes.fin_session)
# Steuerberechnung anzeigen
app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_calcul_impot)
# Admin-Daten abrufen
app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
view_func=routes.get_admindata)
# Steuerberechnung-Ansicht-anzeigen
app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_calcul_impot)
# Authentifizierungsansicht anzeigen
app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_authentification)
# Ansicht-Simulationsliste-anzeigen
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_simulations)
# Ansicht-Fehlerliste-anzeigen
app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
view_func=routes.afficher_vue_liste_erreurs)
# Sonderfall
if with_csrftoken:
# Sitzung ohne CSRF-Token initialisieren für JSON- und XML-Clients
app.add_url_rule(f'{prefix_url}/init-session-without-csrftoken', methods=['GET'],
view_func=routes.init_session_without_csrftoken)
- Zeilen 6–8: Die Flask-Anwendung wird erstellt und in die Konfiguration aufgenommen;
- Zeilen 10–14: Die für den jeweiligen Fall geeignete Routendatei wird importiert. Man beachte, dass die importierte Datei eigentlich keine Routen enthält. Sie enthält lediglich die damit verbundenen Funktionen;
- Zeile 17: Die mit den Routen verbundenen Funktionen benötigen die Konfiguration der Anwendung;
- Zeile 20: Man beachte das Präfix „URL“. Dieses kann leer sein;
- Zeilen 22–27: Routen mit dem Token „CSRF“ haben einen zusätzlichen Parameter im Vergleich zu denen ohne dieses Token. Um diesen Unterschied zu handhaben, wird die Variable „[csrftoken_param]“ verwendet:
- Sie enthält die leere Zeichenkette, wenn in den Routen kein Token „CSRF“ vorhanden ist;
- sie enthält die Zeichenkette [/<string:csrf_token>], wenn ein Token CSRF vorhanden ist;
- Zeilen 29–96: Jede Route der Anwendung ist einer Funktion aus der in den Zeilen 10–14 importierten Routendatei zugeordnet;
36.3. Die neuen Controller

In der vorherigen Version erhielten alle Controller die aktuell bearbeitete Aktion wie folgt:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
params = request.path.split('/')
action = params[1]
Dieser Code funktioniert nicht mehr, wenn ein Präfix vorhanden ist, zum Beispiel [/do/lister-simulations]. In diesem Fall wäre die Aktion in Zeile 3 [do] und somit falsch.
Dieser Code wird wie folgt umgewandelt:
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# Die Elemente des Pfads werden abgerufen
prefix_url = config["parameters"]["prefix_url"]
params = request.path[len(prefix_url):].split('/')
action = params[1]
Dies wird in allen Controllern durchgeführt.
36.4. Die neuen Vorlagen

In der vorherigen Version generierte jede Vorlagenklasse eine Vorlage wie folgt:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# Die Daten der Seite werden in eine Vorlage eingebettet
modèle = {}
# Anwendungsstatus
état = résultat["état"]
# Die Vorlage hängt vom Status ab
…
# CSRF-Token
modèle['csrf_token'] = super().get_csrftoken(config)
# Mögliche Aktionen aus der Ansicht
modèle['actions_possibles'] = […]
# Die Vorlage wird gerendert
return modèle
Von nun an wird die Vorlage einen zusätzlichen Schlüssel haben, nämlich das Präfix „URL“:
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
# Die Daten der Seite werden in das Modell gekapselt
modèle = {}
# Anwendungsstatus
état = résultat["état"]
# Das Modell hängt vom Status ab
…
# CSRF-Token
modèle['csrf_token'] = super().get_csrftoken(config)
# Mögliche Aktionen aus der Ansicht
modèle['actions_possibles'] = […]
# Präfix der URL
modèle["prefix_url"] = config["parameters"]["prefix_url"]
# Das Modell wird zurückgegeben
return modèle
36.5. Die neuen Fragmente

Alle Fragmente, die URL enthalten, müssen geändert werden.
Das Fragment „[v-authentification]“
<!-- Formular HTML – die Werte werden mit der Aktion [authentifier-utilisateur] übermittelt -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- Titel -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
Das Fragment [v-calcul-impot]
<!-- Formular HTML versendet -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- Meldung in 12 Spalten auf blauem Hintergrund -->
…
</form>
Das Fragment [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- Simulationstabelle -->
<table class="table table-sm table-hover table-striped">
…
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
Das Fragment [v-menu]
<!-- Bootstrap-Menü -->
<nav class="nav flex-column">
<!-- Anzeige einer Liste von Links HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
36.6. Die neue Antwort HTML

In der vorherigen Version endete der Code der Antwort HTML mit einer Weiterleitung:
class HtmlResponse(InterfaceResponse):
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
# Die Antwort HTML hängt vom vom Controller zurückgegebenen Statuscode ab
état = résultat["état"]
…
# Nun muss die Weiterleitungs-ID URL generiert werden, wobei das Token CSRF nicht vergessen werden darf, falls es angefordert wird
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# Weiterleitungsantwort
return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
Dieser Code lautet nun wie folgt:
def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
résultat: dict) -> (Response, int):
…
# Nun muss die Weiterleitungs-ID „URL“ generiert werden, wobei das Token „CSRF“ nicht vergessen werden darf, falls es angefordert wird
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# Weiterleitungsantwort
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
36.7. Tests

Fügen wir das folgende Präfix in die Konfigurationsdatei [parameters] ein:
…
# CSRF-Token
"with_csrftoken": True,
# verwaltete Datenbanken: MySQL (MySQL), PostgreSQL (PostgreSQL)
"databases": ["mysql", "pgres"],
# Präfix der URL der Anwendung
# Leere Zeichenfolge eingeben, wenn kein Präfix gewünscht ist, andernfalls /Präfix
"prefix_url": "/do",
…
Starten wir die Anwendung und fragen wir dann nach dem URL [http://localhost:5000/do]. Die Antwort lautet wie folgt:

- in [1], das Präfix von URL;
- in [2], das Token CSRF;
Die Anwendung kann auch mit den Konsolentests von [http-clients/09] getestet werden:

In den Konfigurationsdateien [1] und [2] muss das Präfix von URL in das URL des Servers aufgenommen werden:
"server": {
# „urlServer“: „http://127.0.0.1:5000“,
"urlServer": "http://127.0.0.1:5000/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
- Zeile 3: Das URL des Servers enthält nun das Präfix von URL;
Nach dieser Änderung sollten alle Konsolentests funktionieren.