Skip to content

27. تمرين التطبيق: الإصدار 9

نعود إلى الإصدار 7 من تمرين التطبيق، وبدلاً من تبادل العميل وخادم الويب لسلاسل JSON، سيتبادلان الآن XML.

تظل البنية كما هي:

Image

27.1. خادم الويب

Image

يتم إنشاء المجلد [http-servers/04] عن طريق نسخ المجلد [http-servers/02]، باستثناء المجلد الفرعي [utilities]. ثم يتم تغيير العناصر التالية:

Image

يتم تعديل ملف [config] على النحو التالي:


    # dépendances absolues
    absolute_dependencies = [
        # dossiers du projet
        # 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",
        # Constantes, tranches
        f"{root_dir}/impots/v05/entities",
        # index_controller
        f"{root_dir}/impots/http-servers/01/controllers",
        # scripts [config_database, config_layers]
        script_dir,
        # Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
    ]
  • السطر 21: حدد الدليل الخاص بالأدوات المساعدة التي تبقى في [http-servers/02]؛

يتغير البرنامج النصي الرئيسي [main] على النحو التالي:

#  Home URL
@app.route('/', methods=['GET'])
@auth.login_required
def index():
    logger = None
    try:
        #  logger
        logger = Logger(config["logsFilename"])
        #  we store it in a config associated with the thread
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        #  log the request
        logger.write(f"[index] requête : {request}\n")
        
        #  the request is executed by a controller
        résultat, status_code = index_controller.execute(request, config)
        #  was there a fatal error?
        
        #  we log the answer
        logger.write(f"[index] {résultat}\n")
        #  we send the answer
        return xml_response(résultat, status_code)
    except BaseException as erreur:
        #  log the error if possible
        if logger:
            logger.write(f"[index] {erreur}")
        #  we prepare the response to the customer
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        #  we send the answer
        return xml_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        #  close the log file if it has been opened
        if logger:
            logger.close()
  • التغيير الوحيد هو في السطر 23: نرسل الآن استجابة بتنسيق XML؛

يتم تعريف الدالة [xml_response] في الوحدة النمطية [myutils]:

1
2
3
4
5
6
7
8
9
import xmltodict

def xml_response(résultat: dict, status_code: int) -> tuple:
    #  result: the dictionary to be transformed into the XML string
    xmlString = xmltodict.unparse(résultat)
    #  we return the answer HTTP
    response = make_response(xmlString)
    response.headers['Content-Type'] = 'application/xml; charset=utf-8'
    return response, status_code
  • السطر 3: تأخذ الدالة [xml_response] كمعلمات:
    • قاموس [result] المراد تحويله إلى XML؛
    • رمز الحالة [status_code] الذي سيتم إرجاعه إلى عميل الويب؛
  • السطر 5: نستخدم مكتبة [xmltodict] لتوليد سلسلة XML؛
  • السطر 8: نستخدم رأس [Content-Type] لإخبار العميل بأننا نرسل XML؛

يجب استيراد الدالة [xml_response] إلى البرنامج النصي [__init__.py]:


from .myutils import set_syspath, json_response, decode_flask_session, xml_response

ثم يجب تضمين الوحدة النمطية [myutils] في الوحدات النمطية على مستوى الجهاز. يتم ذلك في محطة PyCharm باستخدام الأمر [pip install .] (في مجلد الحزم).

27.2. عميل الويب

27.2.1. الكود

يتم إنشاء مجلد [http-clients/04] عن طريق نسخ عميل [http-clients/02]. ثم نقوم بتعديل فئة [ImpôtsDaoWithHttpClient] على النحو التالي:

#  imports

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

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

    #  unused method
    def get_admindata(self) -> AdminData:
        pass

    #  tAX CALCULATION
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        .
        #  response status code HTTP
        status_code = response.status_code
        #  we put the response XML in a dictionary
        résultat = xmltodict.parse(response.text[39:])
        #  error if status code other than 200 OK
        .
  • السطر 30: أصبحت استجابة HTTP [response] الواردة من خادم الويب الآن سلسلة XML. تظهر السجلات تنسيقها:

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>

يبلغ طول السلسلة [<?xml version="1.0" encoding="utf-8"?>] 38 حرفًا. علاوة على ذلك، إذا نظرنا إلى ملف السجل باستخدام محرر سداسي عشري، نرى أنه بعد هذه السلسلة يوجد حرف سطر جديد \n. ثم يأتي الرد <response>…</response>. وبالتالي، تبدأ سلسلة XML التي نحتاج إلى تحويلها بعد أول 39 حرفًا من سلسلة XML. تبدأ عند الحرف رقم 39، مع ترقيم الحرف الأول برقم 0. يتم الحصول على هذه السلسلة باستخدام التعبير [response.text[39:]].

إذا قمنا بتشغيل العميل (اتبع الإجراء من الأمثلة السابقة)، فسنحصل على نفس النتائج في ملف [results.json] كما في الإصدارات السابقة. السجلات هي كما يلي:


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]

على جانب الخادم، السجلات هي كما يلي:


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}}}
  • يستمر المسجل في كتابة قاموس الاستجابة بدلاً من سلسلة XML المرسلة إلى العميل. هذا ليس خطأً بل أمر مقصود؛

27.2.2. اختبار طبقة [DAO] للعميل

Image

فئة الاختبار [TestHttpClientDao] هي نفسها الموجودة في |الإصدار 7| وتُنتج النتائج نفسها.