Skip to content

27. Ćwiczenie praktyczne: wersja 9

Wracamy do wersji 7 ćwiczenia praktycznego i zamiast wymiany ciągów znaków typu jSON między klientem a serwerem WWW, tym razem będą one wymieniać ciągi typu XML.

Architektura pozostaje taka sama:

Image

27.1. Serwer WWW

Image

Folder [http-servers/04] uzyskuje się poprzez skopiowanie folderu [http-servers/02] z wyjątkiem podfolderu [utilities]. Następnie zmienia się następujące elementy:

Image

Plik [config] zostaje zmodyfikowany w następujący sposób:


    # zależności bezwzględne
    absolute_dependencies = [
        # foldery projektu
        # 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",
        # Stałe, przedziały
        f"{root_dir}/impots/v05/entities",
        # index_controller
        f"{root_dir}/impots/http-servers/01/controllers",
        # skrypty [config_database, config_layers]
        script_dir,
        # Rejestrator, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
    ]
  • wiersz 21: należy wskazać folder z narzędziami, które pozostały w pliku [http-servers/02];

Główny skrypt [main] zmienia się w następujący sposób:


# Strona główna URL
@app.route('/', methods=['GET'])
@auth.login_required
def index():
    logger = None
    try:
        # logger
        logger = Logger(config["logsFilename"])
        # zapisujemy to w konfiguracji powiązanej z wątkiem
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # rejestruje się żądanie
        logger.write(f"[index] requête : {request}\n")
        
        # zlecamy wykonanie żądania kontrolerowi
        résultat, status_code = index_controller.execute(request, config)
        # czy wystąpił błąd krytyczny?
        
        # rejestruje się odpowiedź
        logger.write(f"[index] {résultat}\n")
        # wysyłamy odpowiedź
        return xml_response(résultat, status_code)
    except BaseException as erreur:
        # rejestruje się błąd, jeśli to możliwe
        if logger:
            logger.write(f"[index] {erreur}")
        # przygotowuje się odpowiedź dla klienta
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        # wysyłamy odpowiedź
        return xml_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # zamykamy plik dziennika, jeśli został otwarty
        if logger:
            logger.close()
  • Jedyna zmiana dotyczy wiersza 23: teraz wysyłana jest odpowiedź XML;

Funkcja [xml_response] jest zdefiniowana w module [myutils]:


import xmltodict

def xml_response(résultat: dict, status_code: int) -> tuple:
    # wynik: słownik do przekształcenia w ciąg znaków XML
    xmlString = xmltodict.unparse(résultat)
    # zwracamy odpowiedź HTTP
    response = make_response(xmlString)
    response.headers['Content-Type'] = 'application/xml; charset=utf-8'
    return response, status_code
  • wiersz 3: funkcja [xml_response] otrzymuje jako parametry:
    • słownik [résultat], który ma zostać przekształcony w XML;
    • kod statusu [status_code], który ma zostać zwrócony do klienta internetowego;
  • wiersz 5: wykorzystuje się bibliotekę [xmltodict] do wygenerowania ciągu znaków XML;
  • wiersz 8: używa się nagłówka [Content-Type], aby poinformować klienta, że wysyłamy mu XML;

Funkcja [xml_response] musi zostać zaimportowana do skryptu [__init__.py]:


from .myutils import set_syspath, json_response, decode_flask_session, xml_response

Następnie moduł [myutils] należy włączyć do modułów o zasięgu maszynowym. Odbywa się to w terminalu PyCharm za pomocą polecenia [pip install .] (w folderze packages).

27.2. Klient internetowy

27.2.1. Kod

Image

Folder [http-clients/04] uzyskuje się poprzez skopiowanie klienta [http-clients/02]. Następnie modyfikujemy klasę [ImpôtsDaoWithHttpClient] w następujący sposób:


# importy

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

    # metoda nieużywana
    def get_admindata(self) -> AdminData:
        pass

    # obliczenie podatku
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        ….
        # kod statusu odpowiedzi HTTP
        status_code = response.status_code
        # odpowiedź XML jest umieszczana w słowniku
        résultat = xmltodict.parse(response.text[39:])
        # błąd, jeśli kod statusu różni się od 200 OK
        ….
  • wiersz 30: odpowiedź serwera WWW HTTP [response] jest teraz ciągiem znaków XML. Logi pokazują jego charakter:

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>

Ciąg znaków [<?xml version="1.0" encoding="utf-8"?>] składa się z 38 znaków. Ponadto, jeśli przyjrzymy się plikowi logów za pomocą edytora szesnastkowego, zauważymy, że za tym ciągiem znajduje się znak końca linii \n. Następnie pojawia się odpowiedź <odpowiedź>…</odpowiedź>. Ciąg XML, który musimy przekonwertować, zaczyna się zatem po pierwszych 39 znakach ciągu XML. Zaczyna się od znaku nr 39, przy czym pierwszy znak ma numer 0. Ciąg ten otrzymujemy za pomocą wyrażenia [résultat["réponse"]].

Jeśli uruchomimy klienta (postępując zgodnie z procedurą z poprzednich przykładów), otrzymamy w pliku [résultats.json] te same wyniki, co w poprzednich wersjach. Logi wyglądają następująco:


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]

Po stronie serwera logi wyglądają następująco:


2020-07-27 16:32:04.983020, Thread-46 : [index] requête : <Request 'http://127.0.0.1:5000/?marié=oui&enfants=2&salaire=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/?marié=oui&enfants=2&salaire=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}}}
  • moduł logujący nadal zapisuje słownik odpowiedzi, a nie ciąg znaków XML, który jest wysyłany do klienta. Nie jest to błąd i jest to zamierzone zachowanie;

27.2.2. Test warstwy klienckiej [dao]

Image

Klasa testowa [TestHttpClientDao] jest taka sama jak w przypadku |version 7| i daje te same wyniki.