Skip to content

27. Anwendungsübung: Version 9

Wir kehren zur Version 7 der Anwendungsübung zurück, und anstatt dass der Client und der Webserver Zeichenfolgen vom Typ jSON austauschen, tauschen sie hier XML aus.

Die Architektur bleibt unverändert:

Image

27.1. Der Webserver

Image

Der Ordner „[http-servers/04]“ wird durch Kopieren des Ordners „[http-servers/02]“ erstellt, mit Ausnahme des Unterordners „[utilities]“. Anschließend werden die folgenden Elemente geändert:

Image

Die Datei [config] wird wie folgt geändert:


    # absolute 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",
        # index_controller
        f"{root_dir}/impots/http-servers/01/controllers",
        # Skripte [config_database, config_layers]
        script_dir,
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
    ]
  • Zeile 21: Geben Sie den Ordner der Dienstprogramme an, die in [http-servers/02] verblieben sind;

Das Hauptskript [main] ändert sich wie folgt:


# Startseite URL
@app.route('/', methods=['GET'])
@auth.login_required
def index():
    logger = None
    try:
        # Logger
        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"[index] requête : {request}\n")
        
        # Die Anfrage wird von einem Controller ausgeführt
        résultat, status_code = index_controller.execute(request, config)
        # Ist ein schwerwiegender Fehler aufgetreten?
        
        # Die Antwort wird protokolliert
        logger.write(f"[index] {résultat}\n")
        # Die Antwort wird gesendet
        return xml_response(résultat, status_code)
    except BaseException as erreur:
        # Der Fehler wird protokolliert, sofern dies möglich ist
        if logger:
            logger.write(f"[index] {erreur}")
        # Die Antwort an den Kunden wird vorbereitet
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        # die Antwort wird gesendet
        return xml_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # Die Protokolldatei wird geschlossen, falls sie geöffnet war
        if logger:
            logger.close()
  • Die einzige Änderung betrifft Zeile 23: Es wird nun eine Antwort XML gesendet;

Die Funktion [xml_response] ist im Modul [myutils] definiert:


import xmltodict

def xml_response(résultat: dict, status_code: int) -> tuple:
    # Ergebnis: Das Wörterbuch, das in eine Zeichenkette umgewandelt werden soll: XML
    xmlString = xmltodict.unparse(résultat)
    # die Antwort wird zurückgegeben HTTP
    response = make_response(xmlString)
    response.headers['Content-Type'] = 'application/xml; charset=utf-8'
    return response, status_code
  • Zeile 3: Die Funktion [xml_response] erhält als Parameter:
    • das Wörterbuch [résultat], das in XML umgewandelt werden soll;
    • den Statuscode [status_code], der an den Web-Client zurückgegeben werden soll;
  • Zeile 5: Die Bibliothek [xmltodict] wird verwendet, um die Zeichenkette XML zu erzeugen;
  • Zeile 8: Der Header [Content-Type] wird verwendet, um dem Client mitzuteilen, dass ihm XML gesendet wird;

Die Funktion [xml_response] muss in das Skript [__init__.py] importiert werden:


from .myutils import set_syspath, json_response, decode_flask_session, xml_response

Anschließend muss das Modul [myutils] in die maschinenbezogenen Module eingebunden werden. Dies erfolgt in einem Terminal PyCharm mit dem Befehl [pip install .] (im Ordner „packages“).

27.2. Der Webclient

27.2.1. Der Code

Image

Die Datei [http-clients/04] wird durch Kopieren des Clients [http-clients/02] erstellt. Anschließend ändern wir die Klasse [ImpôtsDaoWithHttpClient] wie folgt:


# Importe

import requests
import xmltodict
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsMétier import InterfaceImpôtsMétier
from TaxPayer import TaxPayer


class ImpôtsDaoWithHttpClient(AbstractImpôtsDao, InterfaceImpôtsMétier):

    # Konstruktor
    def __init__(self, config: dict):
        

    # nicht verwendete Methode
    def get_admindata(self) -> AdminData:
        pass

    # Steuerberechnung
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        ….
        # Statuscode der Antwort HTTP
        status_code = response.status_code
        # Die Antwort XML wird in ein Dictionary geschrieben
        résultat = xmltodict.parse(response.text[39:])
        # Fehler, wenn der Statuscode nicht 200 ist OK
        ….
  • Zeile 30: Die Antwort HTTP [response] des Webservers ist nun eine Zeichenkette XML. Die Protokolle zeigen deren Art:

2020-07-27 15:53:47.886283, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<réponse><result><marié>non</marié><enfants>2</enfants><salaire>100000</salaire><impôt>19884</impôt><surcôte>4480</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction></result></réponse>

Die Zeichenkette [<?xml version="1.0" encoding="utf-8"?>] umfasst 38 Zeichen. Betrachtet man die Protokolldatei zudem mit einem Hexadezimal-Editor, so sieht man, dass hinter dieser Zeichenkette ein Zeilenumbruchzeichen \n steht. Darauf folgt die Antwort <Antwort>…</Antwort>. Die Zeichenfolge XML, die wir konvertieren müssen, beginnt also nach den ersten 39 Zeichen der Zeichenfolge XML. Sie beginnt ab Zeichen Nr. 39, wobei das erste Zeichen mit 0 nummeriert ist. Diese Zeichenfolge ergibt sich aus dem Ausdruck [response.text[39:]].

Wenn wir den Client ausführen (befolgen Sie dabei die Vorgehensweise aus den vorherigen Beispielen), erhalten wir in der Datei „[résultats.json]“ dieselben Ergebnisse wie in den vorherigen Versionen. Die Protokolle lauten wie folgt:


2020-07-27 16:21:14.015941, Thread-1 : début du thread [Thread-1] avec 2 contribuable(s)
2020-07-27 16:21:14.016940, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}
2020-07-27 16:21:14.016940, Thread-2 : début du thread [Thread-2] avec 3 contribuable(s)
2020-07-27 16:21:14.018939, Thread-2 : début du calcul de l'impôt de {"id": 3, "marié": "oui", "enfants": 3, "salaire": 50000}
2020-07-27 16:21:14.019979, Thread-3 : début du thread [Thread-3] avec 3 contribuable(s)
2020-07-27 16:21:14.019979, Thread-3 : début du calcul de l'impôt de {"id": 6, "marié": "oui", "enfants": 3, "salaire": 100000}
2020-07-27 16:21:14.021938, Thread-4 : début du thread [Thread-4] avec 2 contribuable(s)
2020-07-27 16:21:14.021938, Thread-4 : début du calcul de l'impôt de {"id": 9, "marié": "oui", "enfants": 2, "salaire": 30000}
2020-07-27 16:21:14.021938, Thread-5 : début du thread [Thread-5] avec 1 contribuable(s)
2020-07-27 16:21:14.022939, Thread-5 : début du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000}
2020-07-27 16:21:14.031942, Thread-1 : <?xml version="1.0" encoding="utf-8"?>
<réponse><result><marié>oui</marié><enfants>2</enfants><salaire>55555</salaire><impôt>2814</impôt><surcôte>0</surcôte><taux>0.14</taux><décôte>0</décôte><réduction>0</réduction></result></réponse>
2020-07-27 16:21:14.031942, Thread-1 : fin du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}
2020-07-27 16:21:14.031942, Thread-1 : début du calcul de l'impôt de {"id": 2, "marié": "oui", "enfants": 2, "salaire": 50000}
2020-07-27 16:21:14.034941, Thread-4 : <?xml version="1.0" encoding="utf-8"?>
<réponse><result><marié>oui</marié><enfants>2</enfants><salaire>30000</salaire><impôt>0</impôt><surcôte>0</surcôte><taux>0.0</taux><décôte>0</décôte><réduction>0</réduction></result></réponse>

2020-07-27 16:21:17.055931, Thread-3 : fin du thread [Thread-3]
2020-07-27 16:21:17.059930, Thread-2 : <?xml version="1.0" encoding="utf-8"?>
<réponse><result><marié>non</marié><enfants>3</enfants><salaire>100000</salaire><impôt>16782</impôt><surcôte>7176</surcôte><taux>0.41</taux><décôte>0</décôte><réduction>0</réduction></result></réponse>
2020-07-27 16:21:17.060971, Thread-2 : fin du calcul de l'impôt de {"id": 5, "marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-27 16:21:17.060971, Thread-2 : fin du thread [Thread-2]

Auf der Serverseite lauten die Protokolle wie folgt:


2020-07-27 16:32:04.983020, Thread-46 : [index] requête : <Request 'http://127.0.0.1:5000/?verheiratet=ja&Kinder=2&Gehalt=50000' [GET]>
2020-07-27 16:32:04.983020, Thread-46 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-27 16:32:04.984021, Thread-47 : [index] requête : <Request 'http://127.0.0.1:5000/?verheiratet=ja&kinder=2&gehalt=55555' [GET]>
2020-07-27 16:32:04.984021, Thread-47 : [index] mis en pause du thread pendant 1 seconde(s)

2020-07-27 16:32:07.001271, Thread-56 : [index] mis en pause du thread pendant 1 seconde(s)
2020-07-27 16:32:07.003078, Thread-54 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}}}
2020-07-27 16:32:07.006078, Thread-55 : [index] {'réponse': {'result': {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
2020-07-27 16:32:08.002824, Thread-56 : [index] {'réponse': {'result': {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}}}
  • Der Logger schreibt weiterhin das Antwortwörterbuch und nicht die Zeichenfolge XML, die an den Client gesendet wird. Dies ist kein Fehler und beabsichtigt;

27.2.2. Test der [dao]-Schicht auf der Client-Seite

Image

Die Testklasse [TestHttpClientDao] ist dieselbe wie in |Version 7| und liefert dieselben Ergebnisse.