Skip to content

36. Anwendungsübung: Version 16

Image

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

Image

  • 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

Image

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

Image

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

Image

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

Image

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

Image

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:

Image

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

Image

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.