29. Anwendungsaufgabe: Version 11
29.1. Einleitung
In früheren Versionen der Client-Server-Anwendung zur Steuerberechnung befand sich die Schicht [métier], die die Geschäftsregeln dieser Berechnung implementiert, auf der Serverseite. Nun soll sie auf die Clientseite verlagert werden. Was ist der Vorteil? Ein Teil der Arbeit, die bisher vom Server ausgeführt wurde, wird auf die Client-Seite verlagert. Stellt man sich die Situation vor, dass ein Server von N Clients abgefragt wird, so werden N geschäftliche Steuerberechnungen von den Clients durchgeführt. In früheren Versionen führte der Server diese N geschäftlichen Berechnungen durch. Da der Server die geschäftlichen Berechnungen nun nicht mehr selbst durchführt, kann er schneller auf seine Clients reagieren und somit mehr Clients gleichzeitig bedienen.
Die Client-Server-Architektur sieht nun wie folgt aus:

- Die Schicht [métier] [10] wurde auf dem Client als [12] dupliziert;
- ein neues Skript [main2] [11] wurde auf dem Client hinzugefügt;
Der Web-Client hat zwei Möglichkeiten, die Steuer für die in [3] gefundene Steuerzahlerliste zu berechnen:
- Verwendung der Methode aus der vorherigen Version. Dabei wird die Serverschicht [métier] [10] genutzt. Das Skript [main] wird diese Methode verwenden;
- einfach die Daten der Steuerbehörde [2-4] vom Server anfordern und anschließend die clientseitige Ebene [métier] [12] verwenden;
Wir werden die Leistung der beiden Methoden vergleichen.
29.2. Der Webserver
Die Verzeichnisstruktur des Webservers sieht wie folgt aus:

- Der Ordner „[http-servers/06]“ wird zunächst durch Kopieren des Ordners „[http-servers/05]“ erstellt. Wir werden nämlich die Errungenschaften der vorherigen Version 10 beibehalten. Wir werden lediglich eine neue Funktion hinzufügen. Diese zeigt sich durch das Vorhandensein eines neuen Controllers [get_admindata_controller] [1]. Der andere Controller [calculate_tax_controller] ist nichts anderes als der alte Controller [index_controller], der umbenannt wurde;
29.3. Konfiguration
Der Server wird zwei URL-Dienste bereitstellen:
- [/calculate-tax] zur Berechnung der Steuer für eine Liste von Steuerpflichtigen, die im Hauptteil eines POST übergeben wird. Er entspricht somit dem URL [/] der vorherigen Version 10;
- [/get-admindata] liefert die Zeichenfolge jSON mit den Daten der Steuerbehörde;
Die Konfiguration [config] ordnet jede dieser URL dem Controller zu, der sie verarbeitet:
# Wörterbuch der Controller der Webanwendung
import calculate_tax_controller, get_admindata_controller
controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
config['controllers'] = controllers
29.4. Das Hauptskript [main]
Das Hauptskript [main] strukturiert das Skript [main] aus der vorherigen Version neu:
# Es wird ein Parameter „mysql“ oder „pgres“ 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
…
# Abruf der Daten von der Steuerbehörde
erreur = False
try:
# „admindata“ wird ein schreibgeschützter Datensatz auf Anwendungsebene sein
config["admindata"] = config["layers"]["dao"].get_admindata()
# Erfolgsprotokoll
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# Der Fehler wird protokolliert
…
…
# Bei einem Fehler wird der Vorgang abgebrochen
if erreur:
sys.exit(2)
# Die Flask-Anwendung kann gestartet werden
app = Flask(__name__)
# Hauptcontroller
def main_controller() -> tuple:
# Die angeforderte Aktion wird abgerufen
dummy, action=request.path.split('/')
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")
# 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"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# Pause
time.sleep(sleep_time)
# Die Anfrage wird vom Anfragecontroller ausgeführt
controller = config['controllers'][action]
résultat, status_code = controller.execute(request, config)
# Ist ein schwerwiegender Fehler aufgetreten?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# Es wird eine E-Mail an den Anwendungsadministrator gesendet
config_mail = config["adminMail"]
config_mail["logger"] = logger
SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
# Die Antwort wird protokolliert
logger.write(f"[index] {résultat}\n")
# Die Antwort wird gesendet
print(résultat)
return json_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 json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# Die Protokolldatei wird geschlossen, falls sie geöffnet war
if logger:
logger.close()
# Steuerberechnung
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
# die Kontrolle wird an den Hauptcontroller übergeben
return main_controller()
# Daten von der Steuerbehörde abrufen
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
# Übergabe an den Hauptprüfer
return main_controller()
# nur Übergabe
if __name__ == '__main__':
# Server wird gestartet
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- Zeilen 88–93: Die Funktion [calculate_tax] verarbeitet die Skripte URL und [/calculate-tax];
- Zeilen 95–100: Die Funktion [get_admindata] verarbeitet die Funktionen URL und [/get-admindata];
- Diese beiden Funktionen führen selbst keine Aktionen aus. Sie übergeben die Kontrolle sofort an den Hauptcontroller [main_controller] in den Zeilen 37–86;
- Zeilen 37–86: Der Hauptcontroller [main_controller] ist nichts anderes als die Funktion [index] aus der vorherigen Version, bis auf ein Detail: Während die Funktion [index] nur eine einzige URL verarbeitete, verarbeitet [main_controller] hier zwei URL. Daher muss sie diese von einem der beiden Controller [calculate_tax_controller, get_admin,data_controller] verarbeiten lassen;
- Zeilen 39–40: Die angeforderte Aktion [calculate_tax] oder [get_admindata] wird abgerufen. Diese Information befindet sich im Pfad von URL [request.path]. Je nach Fall entspricht [request.path] entweder [/get-admindata] oder [/calculate_tax]. Die Aufteilung der Zeile 40 ergibt zwei Elemente:
- die leere Zeichenkette für den Teil vor dem /;
- den Namen der angeforderten Aktion für den Teil, der auf das „/“ folgt;
- Zeilen 62–63: Sobald die Aktion von URL ermittelt wurde, ist bekannt, welcher Controller zur Verarbeitung von URL verwendet werden muss. Diese Informationen befinden sich in der Konfiguration von [config];
29.5. Die Controller
Der Controller [calculate_tax_controller] ist nichts anderes als der Controller [index_controller] aus der vorherigen Version.
Der Controller [get_admindata_controller] ist hingegen der folgende:
from flask_api import status
from werkzeug.local import LocalProxy
def execute(request: LocalProxy, config: dict) -> tuple:
# die Antwort wird zurückgegeben
return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
- Der URL [/get-admindata] muss die Zeichenfolge jSON aus den Daten der Steuerbehörde zurückgeben;
- Zeile 6: Diese wurden vom Hauptskript [main] abgerufen und in das Wörterbuch [config] in Form eines Objekts [AdminData] eingefügt. Das Wörterbuch dieses Objekts wird zurückgegeben;
29.6. Postman-Tests
Wir starten den Webserver, den SGBD und den Mailserver [hMailServer]. Anschließend berechnen wir mit einem Postman-Client die Steuer für mehrere Steuerzahler:

In der Postman-Konsole sieht der Client-Server-Dialog wie folgt aus:
POST /calculate-tax HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 5e71461a-fec8-4315-85e8-41721de939e5
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
…
{
"marié": "oui",
"enfants": 3,
"salaire": 200000
}
]
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1461
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:02:07 GMT
{"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347…]}}
Nun wollen wir das URL [/get-admindata] mit einem GET abfragen:

Der Client-Server-Dialog in der Postman-Konsole sieht wie folgt aus:
GET /get-admindata HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 4af342c4-7ecb-4ab2-9e12-d653f81da424
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 596
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:07:24 GMT
{"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 93749.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}
29.7. Der Webclient


Der Ordner „[http-clients/06]“ wird zunächst durch Kopieren des Ordners „[http-clients/05]“ erstellt. Die Anpassungsarbeiten bestehen im Wesentlichen darin:
- die Konfiguration [config_layers] so anzupassen, dass sie nun eine Ebene [métier] enthält. Zuvor hatte sie nur eine Ebene [dao];
- Hinzufügen einer neuen Methode zur Schicht [dao];
- ein Skript [main2] zu schreiben, das auf der Client-Schicht [métier] basiert, um die Steuern der Steuerzahler zu berechnen;
29.7.1. Konfiguration der Client-Schichten
Die Konfiguration der Schichten erfolgt an zwei Stellen:
- in der Konfiguration [config], die in den Abhängigkeiten des Clients den Ordner enthalten muss, der die Implementierung der Schicht [métier] enthält. Dieser Ordner war bereits in den Abhängigkeiten enthalten:
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",
# ImpôtsDaoWithHttpClient
f"{script_dir}/../services",
# Konfigurationsskripte
script_dir,
# Logger
f"{root_dir}/impots/http-servers/02/utilities",
]
Anschließend muss die Datei [config_layers] geändert werden:
def configure(config: dict) -> dict:
# Instanziierung der Anwendungsschichten
# Schicht [métier]
from ImpôtsMétier import ImpôtsMétier
métier = ImpôtsMétier()
# DAO-Schicht
from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
dao = ImpôtsDaoWithHttpClient(config)
# Die Konfiguration der Schichten wird zurückgegeben
return {
"dao": dao,
"métier": métier
}
- Zeilen 4–6: Instanziierung der Schicht [métier];
- Zeilen 13–16: Die Schicht [métier] wird im Schichtwörterbuch zurückgegeben;
29.7.2. Implementierung der Schicht [dao]

Die Schicht [dao] wird die folgende Schnittstelle [InterfaceImpôtsDaoWithHttpClient] aufweisen:
from abc import abstractmethod
from AbstractImpôtsDao import AbstractImpôtsDao
class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):
# Steuerberechnung
@abstractmethod
def calculate_tax_in_bulk_mode(self, taxpayers: list):
pass
- Zeile 5: Die Schnittstelle [InterfaceImpôtsDaoWithHttpClient] erbt von der abstrakten Klasse [AbstractImpôtsDao], die den Zugriff auf das Dateisystem des Clients verwaltet. Zur Erinnerung: Sie verfügt über eine abstrakte Methode [get_admindata];
- Zeilen 7–10: Die Methode [calculate_tax_in_bulk_mode], die wir in der vorherigen Version definiert haben, ermöglicht die Berechnung der Steuer für eine Liste von Steuerpflichtigen;
Diese Schnittstelle wird durch die folgende Klasse [ImpôtsDaoWithHttpClient] implementiert:
# Importe
import json
import requests
from flask_api import status
from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpClient import InterfaceImpôtsDaoWithHttpClient
class ImpôtsDaoWithHttpClient(InterfaceImpôtsDaoWithHttpClient):
# Hersteller
def __init__(self, config: dict):
# Initialisierung der übergeordneten Ebene
AbstractImpôtsDao.__init__(self, config)
# Speicherung der Konfigurationselemente
# Allgemeine Konfiguration
self.__config = config
# Server
self.__config_server = config["server"]
# Debug-Modus
self.__debug = config["debug"]
# Logger
self.__logger = None
def get_admindata(self) -> AdminData:
# Ausnahmen werden weitergeleitet
# Dienst-URL
config_server = self.__config_server
config_services = config_server['url_services']
url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"
# Verbindung
if config_server['authBasic']:
response = requests.get(url_service,
auth=(
config_server["user"]["login"],
config_server["user"]["password"]))
else:
response = requests.get(url_service)
# Debug-Modus?
if self.__debug:
# Logger
if not self.__logger:
self.__logger = self.__config['logger']
# Protokollierung
self.__logger.write(f"{response.text}\n")
# Statuscode
status_code = response.status_code
# Ergebnis in Form eines Dictionaries
résultat = json.loads(response.text)
# wenn Statuscode ungleich 200 OK
if status_code != status.HTTP_200_OK:
raise ImpôtsError(58, résultat['réponse']['erreurs'])
# Das Ergebnis (ein Dictionary) wird zurückgegeben
return AdminData().fromdict(résultat["réponse"]["result"])
# Steuerberechnung im Bulk-Modus
def calculate_tax_in_bulk_mode(self, taxpayers: list):
# Ausnahmen werden weitergeleitet
…
- Zeile 13: Die Klasse [ImpôtsDaoWithHttpClient] implementiert die Schnittstelle [InterfaceImpôtsDaoWithHttpClient]. Sie leitet sich somit von der Klasse [AbstractImpôtsDao] ab;
- Zeilen 65–66: die Methode [calculate_tax_in_bulk_mode], die in der vorherigen Version behandelt wurde;
- Zeilen 29–62: die Methode [get_admindata], die von der übergeordneten Klasse [AbstractImpôtsDao] als abstrakt deklariert wurde. Sie wird daher in der untergeordneten Klasse implementiert;
- Zeilen 33–35: Hier wird die URL des Webdienstes festgelegt, den die Methode [get-admindata] abfragen soll. Diese Service-URL sind in der Client-Konfiguration [config] definiert:
# Der Server für die Steuerberechnung
"server": {
"urlServer": "http://127.0.0.1:5000",
"authBasic": True,
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
"calculate-tax": "/calculate-tax",
"get-admindata": "/get-admindata"
}
},
- (Fortsetzung)
- Zeilen 9–12: die beiden URL des Webservers;
- Zeilen 37–44: Der Dienst URL wird synchron abgefragt;
- Zeilen 46–42: Falls in der Konfiguration vorgesehen, wird die Antwort des Servers protokolliert;
- Zeile 57: Es ist bekannt, dass der Server eine Zeichenkette jSON aus einem Wörterbuch gesendet hat;
- Zeilen 58–60: Wenn der Status HTTP der Antwort nicht 200 ist, wird eine Ausnahme ausgelöst;
- Zeilen 61–62: Das Objekt [AdminData], das die vom Server gesendeten Daten der Steuerbehörde enthält, wird zurückgegeben;
29.8. Die Skripte [main, main2]
Das Skript [main] stammt aus der vorherigen Version. Es nutzt die Methode [calculate_tax_in_bulk_mode] der Schicht [dao] und greift somit auf die Schicht [métier] des Servers zu;
Das Skript [main2] führt dieselben Aufgaben aus wie das Skript [main], nutzt dabei jedoch die Client-Schicht [métier]:
# Konfiguration der Anwendung
import config
config = config.configure({})
# Abhängigkeiten
from ImpôtsError import ImpôtsError
from Logger import Logger
logger = None
# Code
try:
# Logger
logger = Logger(config["logsFilename"])
# wird in der Konfiguration gespeichert
config["logger"] = logger
# Startprotokoll
logger.write("début du calcul de l'impôt des contribuables\n")
# die Ebene [dao] wird abgerufen
dao = config["layers"]["dao"]
# die Steuerzahler werden abgerufen
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# der Steuerzahler?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# Es werden die Daten der Steuerbehörde abgerufen
admindata = dao.get_admindata()
# Berechnung der Steuer der Steuerzahler durch die Ebene [métier]
métier = config['layers']['métier']
for taxpayer in taxpayers:
métier.calculate_tax(taxpayer, admindata)
# Die Ergebnisse werden in der Datei jSON gespeichert
dao.write_taxpayers_results(taxpayers)
# außer BaseException als Fehler:
# # Anzeige des Fehlers
# print(f"Der folgende Fehler ist aufgetreten: {Fehler}")
finally:
# Das Logging wird beendet
if logger:
# Protokollende
logger.write("fin du calcul de l'impôt des contribuables\n")
# Logger wird geschlossen
logger.close()
# Fertig
print("Travail terminé...")
- Zeilen 26–27: Die Daten der Steuerbehörde werden vom Server abgerufen;
- Zeilen 28–31: Anschließend erfolgt die Berechnung der Steuer der Steuerzahler lokal;
29.9. Client-Tests
In jedem der Skripte [main, main2] werden der Beginn und das Ende des Skripts protokolliert. So lässt sich die Ausführungsdauer des Skripts berechnen. Machen wir einige Prognosen:
- Das Skript [main] aus der vorherigen Version:
- erzeugt N Threads, die gleichzeitig ausgeführt werden;
- Jeder Thread verarbeitet eine Gruppe von Steuerzahlern, deren Steuer er über eine einzige Abfrage an den Server berechnen lässt;
- da die N Threads gleichzeitig ausgeführt werden, wird die N+1-Anfrage gestartet, bevor die N-Anfrage ihre Antwort erhalten hat. Somit sind die N Anfragen teurer als eine einzige Anfrage, aber wahrscheinlich nicht wesentlich teurer. Außerdem gibt es 11 (die Anzahl der Steuerzahler) Fachberechnungen auf dem Server;
- Das Skript [main2] dieser Version:
- führt eine einzige Abfrage an den Server durch;
- führt 11 Geschäftsberechnungen lokal auf dem Client durch;
Die Fachberechnungen dauern sowohl auf dem Server als auch auf dem Client gleich lang. Der Unterschied liegt also bei den Abfragen. Es ist daher zu erwarten, dass die Ausführungszeit von [main] etwas länger ist als die von [main2].
Wir starten den Server der Version 11, den SGBD und den Mail-Server [hMailServer]. Auf der Serverseite setzen wir den Parameter [sleep_time] auf Null, damit beide Tests unter denselben Bedingungen ausgeführt werden.
Ausführung 1 [main]
Die Ausführung von [main] liefert die folgenden Protokolle:
2020-07-29 14:35:50.016079, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:35:50.016079, Thread-1 : début du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.016079, Thread-2 : début du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.016079, Thread-3 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.016079, Thread-4 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.024426, Thread-5 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.050473, Thread-1 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.050473, Thread-1 : fin du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.050473, Thread-3 : {"réponse": {"results": [{"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-3 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-5 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-5 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-2 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-2 : fin du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.051214, Thread-4 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-4 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, MainThread : fin du calcul de l'impôt des contribuables
Die Ausführungsdauer betrug [051214-016079] Nanosekunden (Zeile 17 – Zeile 1), d. h. 35 Millisekunden und 135 Nanosekunden.
Man sieht, dass zwischen der ersten Anfrage an den Server und der letzten vom Client empfangenen Antwort dieselbe Dauer [051214-016079] (Zeile 15 – Zeile 1) liegt, nämlich 35 Millisekunden und 135 Nanosekunden.
Ausführung 2 [main2]
Die Ausführung von [main2] liefert folgende Protokolle:
2020-07-29 14:41:03.303520, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:41:03.345084, MainThread : {"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}
2020-07-29 14:41:03.349975, MainThread : fin du calcul de l'impôt des contribuables
Die Ausführungsdauer betrug [349975-303520] Nanosekunden (Zeile 3 – Zeile 1), d. h. 46 Millisekunden und 455 Nanosekunden. Völlig unerwartet ist [main] schneller als [main2].
Man sieht, dass die einzige Abfrage von [main2] [345084-303520] (Zeile 2 – Zeile 1) gedauert hat, d. h. 41 Millisekunden und 564 Nanosekunden. Die Steuerberechnung dauerte anschließend [349975-345084] (Zeile 3 – Zeile 2), d. h. 4 Millisekunden und 91 Nanosekunden. Die Abfrage HTTP ist für die Ausführungsdauer verantwortlich. Überraschenderweise zeigt sich hier, dass die einzelne Abfrage von [main2] länger gedauert hat als die vier gleichzeitigen Abfragen von [main] und [35 millisecondes].
Auf der Serverseite sehen die Protokolle wie folgt aus:
2020-07-29 14:35:27.047721, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:27.140927, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:28.790716, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:28.847518, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:50.039178, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.039178, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.043220, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.044307, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.045796, Thread-2 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.045796, Thread-3 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.046825, Thread-6 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}, {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}, {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-4 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-5 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:41:03.341582, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/get-admindata' [GET]>
2020-07-29 14:41:03.341582, Thread-7 : [index] {'réponse': {'result': {'limites': [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], 'coeffr': [0.0, 0.14, 0.3, 0.41, 0.45], 'coeffn': [0.0, 1394.96, 5798.0, 13913.7, 20163.4], 'plafond_decote_couple': 1970.0, 'valeur_reduc_demi_part': 3797.0, 'plafond_revenus_celibataire_pour_reduction': 21037.0, 'plafond_qf_demi_part': 1551.0, 'abattement_dixpourcent_max': 12502.0, 'plafond_impot_celibataire_pour_decote': 1595.0, 'plafond_decote_celibataire': 1196.0, 'plafond_revenus_couple_pour_reduction': 42074.0, 'id': 1, 'abattement_dixpourcent_min': 437.0, 'plafond_impot_couple_pour_decote': 2627.0}}}
- Zeile 5: die erste Anfrage des Clients [main];
- Zeile 14: die letzte Antwort an den Client [main]. Zwischen den beiden liegen 6 Millisekunden und 647 Nanosekunden;
- Zeilen 15–16: die einzige Anfrage des Clients [main2]. Die Antwort erfolgt sofort;