Skip to content

36. Anwendungsübung: Version 16

Image

36.1. Einführung

Die URLs in unserer Anwendung haben derzeit die Form [/action/param1/param2/…]. Wir möchten diesen URLs ein Präfix voranstellen können. Mit dem Präfix [/do] hätten wir beispielsweise URLs der Form [/do/action/param1/param2/…].

36.2. Die neue Routenkonfiguration

Image

  • In [2] wird die Routenberechnung geändert;
  • in [1] wird die [config]-Datei entsprechend dieser Änderung angepasst;

Die [config]-Konfiguration sieht nun wie folgt aus:

def configure(config: dict) -> dict:
    #  syspath configuration
    import syspath
    config['syspath'] = syspath.configure(config)

    #  application setup
    import parameters
    config['parameters'] = parameters.configure(config)

    #  database configuration
    import database
    config["database"] = database.configure(config)

    #  instantiation of application layers
    import layers
    config['layers'] = layers.configure(config)

    #  configuration MVC of the [web] layer
    config['mvc'] = {}

    #  web] layer controller configuration
    import controllers
    config['mvc'].update(controllers.configure(config))

    #  actions ASV (Action Show View)
    import asv_actions
    config['mvc'].update(asv_actions.configure(config))

    #  actions ADS (Action Do Something)
    import ads_actions
    config['mvc'].update(ads_actions.configure(config))

    #  response configuration HTTP
    import responses
    config['mvc'].update(responses.configure(config))

    #  route configuration
    import routes
    routes.configure(config)

    #  we return the configuration
    return config
  • Zeilen 37–39: Das Modul [routes] (Zeile 38) übernimmt die Konfiguration der Routen (Zeile 39);

Die Routen-Datei ohne CSRF-Token [configs/routes_without_csrftoken] entwickelt sich wie folgt:

#  dependencies

from flask import redirect, request, session, url_for
from flask_api import status

#  application configuration
config = {}

#  the front controller
def front_controller() -> tuple:
    #  forward the request to the main controller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#  application root
def index() -> tuple:
    #  redirect to /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:
    #  execute the controller associated with the action
    return front_controller()

#  authenticate-user
def authentifier_utilisateur() -> tuple:
    #  execute the controller associated with the action
    return front_controller()

#  calculate-tax
def calculer_impot() -> tuple:
    #  execute the controller associated with the action
    return front_controller()


Die Routen wurden aus der Datei entfernt. Es sind nur noch die damit verbundenen Funktionen übrig.

Die Routen-Datei mit dem CSRF-Token [configs/routes_with_csrftoken] erleidet das gleiche Schicksal:

#  dependencies

from flask import redirect, request, session, url_for
from flask_api import status
from flask_wtf.csrf import generate_csrf

#  configuration
config = {}

#  the front controller
def front_controller() -> tuple:
    #  forward the request to the main controller
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

#  application root
def index() -> tuple:
    #  redirect to /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:
    #  execute the controller associated with the action
    return front_controller()

#  authenticate-user
def authentifier_utilisateur(csrf_token: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()



#  init-session-without-csrftoken for json and xml clients
def init_session_without_csrftoken(type_response: str) -> tuple:
    #  redirect to /init-session/type_response
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()),
                    status.HTTP_302_FOUND)

Routen werden vom folgenden Modul [configs/routes] berechnet:

from flask import Flask

def configure(config: dict):
    #  route settings

    #  flask application
    app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
    config['app'] = app

    #  import routes from web application
    if config['parameters']['with_csrftoken']:
        import routes_with_csrftoken as routes
    else:
        import routes_without_csrftoken as routes

    #  inject configuration into routes
    routes.config = config

    #  the application's URL prefix
    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 = ""

    #  flask application routes
    #  application root
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

    #  init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

    #  init-session-without-csrftoken
    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)

    #  authenticate-user
    app.add_url_rule(f'{prefix_url}/authentifier-utilisateur{csrftoken_param}', methods=['POST'],
                     view_func=routes.authentifier_utilisateur)

    #  calculate-tax
    app.add_url_rule(f'{prefix_url}/calculer-impot{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impot)

    #  batch tax calculation
    app.add_url_rule(f'{prefix_url}/calculer-impots{csrftoken_param}', methods=['POST'],
                     view_func=routes.calculer_impots)

    #  lister-simulations
    app.add_url_rule(f'{prefix_url}/lister-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.lister_simulations)

    #  delete-simulation
    app.add_url_rule(f'{prefix_url}/supprimer-simulation/<int:numero>{csrftoken_param}', methods=['GET'],
                     view_func=routes.supprimer_simulation)

    #  end of session
    app.add_url_rule(f'{prefix_url}/fin-session{csrftoken_param}', methods=['GET'],
                     view_func=routes.fin_session)

    #  display-calculation-tax
    app.add_url_rule(f'{prefix_url}/afficher-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_calcul_impot)

    #  get-admindata
    app.add_url_rule(f'{prefix_url}/get-admindata{csrftoken_param}', methods=['GET'],
                     view_func=routes.get_admindata)

    #  afficher-vue-calcul-impot
    app.add_url_rule(f'{prefix_url}/afficher-vue-calcul-impot{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_calcul_impot)

    #  display-view-authentication
    app.add_url_rule(f'{prefix_url}/afficher-vue-authentification{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_authentification)

    #  display-view-list-simulations
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-simulations{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_simulations)

    #  display-view-error_list
    app.add_url_rule(f'{prefix_url}/afficher-vue-liste-erreurs{csrftoken_param}', methods=['GET'],
                     view_func=routes.afficher_vue_liste_erreurs)

    #  special case
    if with_csrftoken:
        #  init-session-without-csrftoken for json and 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 zur Konfiguration hinzugefügt;
  • Zeilen 10–14: Die für die Situation geeignete Routendatei wird importiert. Beachten Sie, dass die importierte Datei eigentlich keine Routen enthält; sie enthält nur die mit ihnen verbundenen Funktionen;
  • Zeile 17: Die mit den Routen verbundenen Funktionen müssen die Konfiguration der Anwendung kennen;
  • Zeile 20: Wir geben das URL-Präfix an. Dieses kann leer sein;
  • Zeilen 22–27: Routen mit einem CSRF-Token haben im Vergleich zu denen ohne einen zusätzlichen Parameter. Um diesen Unterschied zu behandeln, verwenden wir die Variable [csrftoken_param]:
    • Sie enthält eine leere Zeichenkette, wenn in den Routen kein CSRF-Token vorhanden ist;
    • sie enthält die Zeichenkette [/<string:csrf_token>], wenn ein CSRF-Token vorhanden ist;
  • Zeilen 29–96: Jede Route in der Anwendung ist mit einer Funktion aus der in den Zeilen 10–14 importierten Routendatei verknüpft;

36.3. Die neuen Controller

Image

In der vorherigen Version erhielten alle Controller die aktuell verarbeitete Aktion wie folgt:

1
2
3
4
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  path elements are retrieved
        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.

Wir ändern diesen Code wie folgt:

1
2
3
4
5
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        #  path elements are retrieved
        prefix_url = config["parameters"]["prefix_url"]
        params = request.path[len(prefix_url):].split('/')
        action = params[1]

Das machen wir in allen Controllern.

36.4. Die neuen Modelle

Image

In der vorherigen Version generierte jede Modellklasse ein Modell wie folgt:

def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = []

        #  we render the model
        return modèle

Das Modell verfügt nun über einen zusätzlichen Schlüssel, das URL-Präfix:

def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        #  we encapsulate the paged data in the model
        modèle = {}
        #  application status
        état = résultat["état"]
        #  the model depends on the state
        

        #  csrf token
        modèle['csrf_token'] = super().get_csrftoken(config)

        #  possible actions from the view
        modèle['actions_possibles'] = []

        #  URL prefix
        modèle["prefix_url"] = config["parameters"]["prefix_url"]

        #  we render the model
        return modèle

36.5. Die neuen Fragmente

Image

Alle Fragmente, die URLs enthalten, müssen geändert werden.

Das [v-authentication]-Fragment


<!-- form HTML - post its values with the [authenticate-user] action -->
<form method="post" action="{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}">
 
    <!-- title -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>

 
</form>

Das [v-calcul-impot]-Fragment


<!-- form HTML posted -->
<form method="post" action="{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
    <!-- 12-column message on blue background -->
    
 
</form>

Das [v-liste-simulations]-Fragment



 
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}

 
<!-- simulation table -->
<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 [v-menu]-Fragment


<!-- bootstrap menu -->
<nav class="nav flex-column">
    <!-- display a list of 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 HTML-Antwort

Image

In der vorherigen Version endete der HTML-Antwortcode 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):
        #  the HTML response depends on the status code returned by the controller
        état = résultat["état"]

        

        #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        return redirect(f"{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

Dieser Code sieht nun wie folgt aus:

def build_http_response(self, request: LocalProxy, session: LocalProxy, config: dict, status_code: int,
                            résultat: dict) -> (Response, int):
        

        #  now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        #  redirect response
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

36.7. Tests

Image

Fügen wir der Konfigurationsdatei [parameters] das folgende Präfix hinzu:



        # token csrf
        "with_csrftoken"True,
        # bases gérées MySQL (mysql), PostgreSQL (pgres)
        "databases"["mysql""pgres"],
        # préfixe des URL de l'application
        # mettre la chaîne vide si on ne veut pas de préfixe ou /préfixe sinon
        "prefix_url""/do",

Starten wir die Anwendung und rufen wir dann die URL [http://localhost:5000/do] auf; die Antwort lautet wie folgt:

Image

  • in [1] das URL-Präfix;
  • in [2] das CSRF-Token;

Die Anwendung kann auch mit den Konsolentests aus [http-clients/09] getestet werden:

Image

In den Konfigurationsdateien [1] und [2] muss das URL-Präfix in der Server-URL enthalten sein:


        "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: Die Server-URL enthält nun das URL-Präfix;

Mit dieser Änderung sollten alle Konsolentests funktionieren.