Skip to content

27. تمرین عملی: نسخه ۹

ما به نسخه ۷ تمرین عملی بازمی‌گردیم، و به جای اینکه کلاینت و وب‌سرور رشته‌های jSON را مبادله کنند، اکنون XML را مبادله خواهند کرد.

معماری همان‌طور باقی می‌ماند:

Image

27.1. وب‌سرور

Image

پوشه [http-servers/04] با کپی کردن پوشه [http-servers/02] ایجاد می‌شود، به استثنای زیرپوشه [utilities]. سپس موارد زیر تغییر داده می‌شوند:

Image

فایل [config] به شرح زیر اصلاح می‌شود:


    # وابستگی‌های مطلق
    absolute_dependencies = [
        # پوشه‌های پروژه
        # 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",
        #ثابت‌ها، بازه‌ها
        f"{root_dir}/impots/v05/entities",
        # index_controller
        f"{root_dir}/impots/http-servers/01/controllers",
        # اسکریپت‌ها [config_database, config_layers]
        script_dir,
        # لاگر، SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
    ]
  • خط ۲۱: پوشه‌ای را که حاوی ابزارهای نگهداری‌شده در [http-servers/02] است، مشخص کنید؛

اسکریپت اصلی [main] به شرح زیر به‌روزرسانی می‌شود:


# خانه URL
@app.route('/', methods=['GET'])
@auth.login_required
def index():
    logger = None
    try:
        # لاگ‌گیر
        logger = Logger(config["logsFilename"])
        # این در پیکربندی مرتبط با نخ ذخیره می‌شود
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # درخواست ثبت می‌شود
        logger.write(f"[index] requête : {request}\n")
        
        # درخواست توسط یک کنترل‌کننده اجرا می‌شود
        résultat, status_code = index_controller.execute(request, config)
        #آیا خطای مرگبار رخ داده است؟
        
        #پاسخ ثبت می‌شود
        logger.write(f"[index] {résultat}\n")
        # ارسال پاسخ
        return xml_response(résultat, status_code)
    except BaseException as erreur:
        #خطا در صورت امکان ثبت می‌شود
        if logger:
            logger.write(f"[index] {erreur}")
        # آماده‌سازی پاسخ برای کلاینت
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        #ارسال پاسخ
        return xml_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # فایل لاگ را ببندید اگر باز شده باشد
        if logger:
            logger.close()
  • تنها تغییر در خط ۲۳ است: اکنون یک پاسخ به صورت XML ارسال می‌شود؛

تابع [xml_response] در ماژول [myutils] تعریف شده است:


import xmltodict

def xml_response(résultat: dict, status_code: int) -> tuple:
    # نتیجه: واژه‌نامه به یک رشته تبدیل می‌شود XML
    xmlString = xmltodict.unparse(résultat)
    #پاسخ بازگردانده می‌شود HTTP
    response = make_response(xmlString)
    response.headers['Content-Type'] = 'application/xml; charset=utf-8'
    return response, status_code
  • خط ۳: تابع [xml_response] پارامترهای زیر را دریافت می‌کند:
    • واژه‌نامه [résultat] که باید به XML تبدیل شود؛
    • کد وضعیت [status_code] که باید به کلاینت وب بازگردانده شود؛
  • خط ۵: کتابخانه [xmltodict] برای تولید رشته XML استفاده می‌شود؛
  • خط ۸: هدر [Content-Type] برای اطلاع‌رسانی به کلاینت استفاده می‌شود که XML برای آنها ارسال می‌شود؛

تابع [xml_response] باید به اسکریپت [__init__.py] وارد شود:


from .myutils import set_syspath, json_response, decode_flask_session, xml_response

ماژول [myutils] باید سپس در ماژول‌های در مقیاس ماشین گنجانده شود. این کار در یک ترمینال PyCharm با استفاده از فرمان [pip install .] (در پوشه 'packages') انجام می‌شود.

27.2. کلاینت وب

27.2.1. کد

Image

فایل [http-clients/04] با کپی کردن مشتری [http-clients/02] ایجاد می‌شود. سپس کلاس [ImpôtsDaoWithHttpClient] را به شرح زیر اصلاح می‌کنیم:


#واردات

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

    # سازنده
    def __init__(self, config: dict):
        

    # روش بلااستفاده
    def get_admindata(self) -> AdminData:
        pass

    # محاسبه مالیات
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        ….
        #کد وضعیت پاسخ HTTP
        status_code = response.status_code
        #پاسخ در یک فرهنگ لغت قرار می‌گیرد XML
        résultat = xmltodict.parse(response.text[39:])
        #خطا اگر کد وضعیت 200 نباشد OK
        ….
  • خط ۳۰: پاسخ وب‌سرور 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"?>] دارای ۳۸ کاراکتر است. علاوه بر این، اگر فایل لاگ را با یک ویرایشگر هگز مشاهده کنیم، می‌توانیم ببینیم که این رشته با یک کاراکتر خط جدید \n دنبال می‌شود. این مورد به نوبه خود با پاسخ <response>…</response> دنبال می‌شود. بنابراین، رشته XML که باید آن را تبدیل کنیم، پس از ۳۹ کاراکتر اول رشته XML آغاز می‌شود. این رشته از کاراکتر شماره ۳۹ شروع می‌شود، در حالی که اولین کاراکتر شماره‌گذاری شده ۰ است. این رشته از عبارت [résultat["réponse"]] به دست می‌آید.

اگر کلاینت را اجرا کنیم (طبق رویهٔ مثال‌های قبلی)، همان نتایج را در فایل [résultats.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/?married=yes&children=2&salary=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/?married=yes&children=2&salary=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] همانند نسخهٔ ۷ است و نتایج یکسانی تولید می‌کند.