Skip to content

33. تمرین عملی: نسخه ۱۳

نسخهٔ ۱۳ نسخهٔ ۱۲ را از جهات زیر اصلاح می‌کند:

  • برخی از بخش‌های کد بازسازی شده‌اند (بازسازی)؛
  • مدیریت جلسه با استفاده از ماژول [flask_session] تغییر یافته است؛
  • از گذرواژه‌های رمزگذاری‌شده در فایل پیکربندی استفاده می‌شود؛

نسخهٔ ۱۳ در ابتدا با کپی‌برداری از نسخهٔ ۱۲ ایجاد می‌شود:

Image

Image

  • در [1]، پیکربندی بازسازی خواهد شد. به طور خاص، آن از پوشه [flask] خارج خواهد شد؛
  • در [2]، اسکریپت اصلی بازسازی خواهد شد. همچنین از پوشه [flask] خارج می‌شود؛
  • در [3]، پیکربندی در چندین فایل تقسیم خواهد شد؛
  • در [4]: اسکریپت اصلی [main] با انتقال کد به فایل‌های دیگر ساده‌تر خواهد شد؛
  • در [5]، کنترل‌کننده احراز هویت اصلاح خواهد شد زیرا رمزهای عبور کاربران اکنون رمزگذاری خواهند شد؛
  • در [6]، کنترلر اصلی کدی را که قبلاً در اسکریپت اصلی [main] قرار داشت، در خود ادغام خواهد کرد؛

33.1. بازسازی پیکربندی برنامه

Image

سه فایل پیکربندی جدید ظاهر می‌شوند:

  • [mvc]: برای پیکربندی معماری MVC برنامه؛
  • [parameters]: که تمام ثابت‌های برنامه را در کنار هم گروه‌بندی می‌کند؛
  • [syspath]: که مسیر پایتون برنامه را پیکربندی می‌کند؛

فایل [syspath.py] به شرح زیر است:


def configure(config: dict) -> dict:
    import os

    # پوشهٔ این فایل
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # مسیر ریشه
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # وابستگی‌ها
    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",
        # لاگر، SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # دایرکتوری اصلی اسکریپت
        script_dir,
        # پیکربندی‌ها [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # کنترل‌کننده‌ها
        f"{script_dir}/../controllers",
        #پاسخ‌ها HTTP
        f"{script_dir}/../responses",
        # قالب‌های نما
        f"{script_dir}/../models_for_views",
    ]

    #تنظیم syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # ذخیره پیکربندی
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }
  • اسکریپت [syspath] برای پیکربندی مسیر پایتون برنامه استفاده می‌شود (خطوط ۴۰–۴۱)؛
  • این دو مورد اطلاعات مفید را برای سایر اسکریپت‌های پیکربندی فراهم می‌کند (خطوط ۴۵–۴۶)؛

اسکریپت [mvc] معماری MVC وب‌اپلیکیشن jSON / XML / HTML را پیکربندی می‌کند:


def configure(config: dict) -> dict:
    #پیکربندی برنامه MVC

    # کنترل‌کننده‌ها
    from AfficherCalculImpotController import AfficherCalculImpotController
    from AuthentifierUtilisateurController import AuthentifierUtilisateurController
    from CalculerImpotController import CalculerImpotController
    from CalculerImpotsController import CalculerImpotsController
    from FinSessionController import FinSessionController
    from GetAdminDataController import GetAdminDataController
    from InitSessionController import InitSessionController
    from ListerSimulationsController import ListerSimulationsController
    from MainController import MainController
    from SupprimerSimulationController import SupprimerSimulationController
    # پاسخ‌ها HTTP
    from HtmlResponse import HtmlResponse
    from JsonResponse import JsonResponse
    from XmlResponse import XmlResponse
    #قالب‌های نما
    from ModelForAuthentificationView import ModelForAuthentificationView
    from ModelForCalculImpotView import ModelForCalculImpotView
    from ModelForErreursView import ModelForErreursView
    from ModelForListeSimulationsView import ModelForListeSimulationsView

    # اقدامات مجاز و کنترل‌کننده‌های آن‌ها
    controllers = {
        # راه‌اندازی یک جلسه محاسباتی
        "init-session": InitSessionController(),
        #احراز هویت کاربر
        "authentifier-utilisateur": AuthentifierUtilisateurController(),
        # محاسبه مالیات در حالت فردی
        "calculer-impot": CalculerImpotController(),
        #محاسبه مالیات به صورت دسته‌ای
        "calculer-impots": CalculerImpotsController(),
        # فهرست شبیه‌سازی‌ها
        "lister-simulations": ListerSimulationsController(),
        # حذف یک شبیه‌سازی
        "supprimer-simulation": SupprimerSimulationController(),
        #پایان جلسه محاسبه
        "fin-session": FinSessionController(),
        #نمایش نمای محاسبه مالیات
        "afficher-calcul-impot": AfficherCalculImpotController(),
        #بازیابی داده‌ها از مراجع مالیاتی
        "get-admindata": GetAdminDataController(),
        # کنترل‌کننده اصلی
        "main-controller": MainController()
    }
    #انواع مختلف پاسخ (json, xml, html)
    responses = {
        "json": JsonResponse(),
        "html": HtmlResponse(),
        "xml": XmlResponse()
    }
    #نماهای HTML و قالب‌های آن‌ها به وضعیت بازگردانده‌شده توسط کنترلر بستگی دارند
    views = [
        {
            #نمای احراز هویت
            "états": [
                700,    # /init-session – موفقیت
                201,    #/احراز-هویت-کاربر – خطا
            ],
            "view_name""views/vue-authentification.html",
            "model_for_view": ModelForAuthentificationView()
        },
        {
            # نماش محاسبه مالیات
            "états"[
                200,    # /احراز-هویت-کاربر موفق
                300,    # /محاسبه-مالیات موفق
                301,    # /محاسبه-مالیات ناموفق
                800,    # /نمایش-محاسبه-مالیات موفق
            ],
            "view_name""views/vue-calcul-impot.html",
            "model_for_view": ModelForCalculImpotView()
        },
        {
            #مشاهده فهرست شبیه‌سازی‌ها
            "états"[
                500,    # /لیست-شبیه‌سازی‌ها (موفق)
                600,    # /حذف-شبیه‌سازی موفق
            ],
            "view_name""views/vue-liste-simulations.html",
            "model_for_view": ModelForListeSimulationsView()
        },

    ]
    #مشاهده خطاهای غیرمنتظره
    view_erreurs = {
        "view_name""views/vue-erreurs.html",
        "model_for_view": ModelForErreursView()
    }
    # بازراسته‌سازی می‌کند
    redirections = [
        {
            "états": [
                400,  # /end-session (موفق)
            ],
            # ارسال مجدد به URL
            "to""/init-session/html",
        }
    ]


    #پیکربندی در حال بازگردانی به MVC است
    return {
        #کنترل‌کننده‌ها
        "controllers": controllers,
        #پاسخ‌ها HTTP
        "responses": responses,
        #نماها و قالب‌ها
        "views": views,
        # فهرست هدایت‌ها
        "redirections": redirections,
        # نمايش خطای غيرمنتظره
        "view_erreurs": view_erreurs
    }
  • خطوط ۱–۱۰۱: این کد شناخته شده است؛
  • خطوط ۱۰۵–۱۱۶: پیکربندی MVC برنامه رندر می‌شود؛

اسکریپت [parameters] ثابت‌های برنامه را جمع‌آوری می‌کند:


def configure(config: dict) -> dict:
    # تنظیمات برنامه

    # script_dir
    script_dir = config['syspath']['script_dir']

    # پیکربندی برنامه
    parameters = {
        # کاربران مجاز به استفاده از برنامه
        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
            }
        ],
        # فایل لاگ
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # پیکربندی سرور SMTP
        "adminMail": {
            # سرور SMTP
            "smtp-server""localhost",
            # پورت سرور SMTP
            "smtp-port""25",
            # مدیر
            "from""guest@localhost.com",
            "to""guest@localhost.com",
            # موضوع ایمیل
            "subject""plantage du serveur de calcul d'impôts",
            # TLS را روی True تنظیم کنید اگر سرور SMTP احراز هویت را الزامی کند، در غیر این صورت روی False تنظیم کنید
            "tls"False
        },
        #مدت زمان توقف نخ به ثانیه
        "sleep_time"0,
        # سرور Redis
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
    }

    # تنظیمات برنامه اعمال می‌شوند
    return parameters
  • خط ۱۳: از این پس، رمزهای عبور کاربر رمزگذاری خواهند شد؛
  • خطوط ۳۴–۳۸: پیکربندی یک سرور، [Redisکه بعداً به آن باز خواهیم گشت؛

با این فایل‌های پیکربندی جدید، اسکریپت [config] به شکل زیر درمی‌آید:


def configure(config: dict) -> dict:
    #پیکربندی syspath
    import syspath
    config['syspath'] = syspath.configure(config)

    # تنظیمات برنامه
    import parameters
    config['parameters'] = parameters.configure(config)

    # پیکربندی پایگاه داده
    import database
    config["database"] = database.configure(config)

    # نمونه سازی لایه‌های برنامه
    import layers
    config['layers'] = layers.configure(config)

    #پیکربندی لایه MVC
    import mvc
    config['mvc'] = mvc.configure(config)

    #پیکربندی بازگردانده می‌شود
    return config

33.2. بازسازی اسکریپت اصلی [main]

Image

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


#انتظار یک پارامتر MySQL یا PostgreSQL
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()

# برنامه پیکربندی شده است
import config
config = config.configure({'sgbd': sgbd})

#وابستگی‌ها
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis

#ارسال ایمیل به مدیر
def send_adminmail(config: dict, message: str):
    # ارسال ایمیل به مدیر برنامه
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

#بررسی فایل لاگ
logger = None
erreur = False
message_erreur = None
try:
    # لاگ‌گیر
    logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
    # لاگ کنسول
    print(f"L'erreur suivante s'est produite : {exception}")
    #ثبت خطا
    erreur = True
    message_erreur = f"{exception}"
#لاگر در پیکربندی ذخیره شده است
config['logger'] = logger
# مدیریت خطا
if erreur:
    # ایمیل به مدیر
    send_adminmail(config, message_erreur)
    # برنامه خاتمه می‌یابد
    sys.exit(1)

# فایل گزارش راه‌اندازی
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")

#بررسی در دسترس بودن سرور Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# پینگ کردن سرور ردیس
try:
    redis_client.ping()
except BaseException as exception:
    # ردیس در دسترس نیست
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    #کنسول
    print(log)
    # لاگ
    logger.write(f"{log}\n")
    #پایان
    sys.exit(1)

#ذخیرهٔ کلاینت [redis] در پیکربندی
config['redis_client'] = redis_client

#بازیابی داده‌ها از مراجع مالیاتی
erreur = False
try:
    # «admindata» یک آیتم داده در سطح برنامه خواهد بود که فقط قابل خواندن است
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # لاگ موفقیت
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # خطا ثبت شد
    erreur = True
    # فایل گزارش خطا
    log = f"L'erreur suivante s'est produite : {ex}"
    #کنسول
    print(log)
    # فایل گزارش
    logger.write(f"{log}\n")
    # ایمیل به مدیر
    send_adminmail(config, log)

# رشته اصلی دیگر به لاگ‌گیر نیاز ندارد
logger.close()

# اگر خطایی رخ داده باشد، فرآیند متوقف می‌شود
if erreur:
    sys.exit(2)

# وارد کردن مسیرهای برنامه وب
import routes
routes.config=config
routes.execute(__name__)
  • خطوط ۵۶–۷۳: یک سرور Redis و کلاینت آن معرفی شده‌اند؛
  • خطوط ۱۰۲–۱۰۴: مسیرهای برنامه به اسکریپت [routes] منتقل شده‌اند؛

33.2.1. ماژول‌های [flask_session] و [redis]

سرور [Redis] برای ذخیره جلسات کاربری استفاده خواهد شد. ما از ماژول [flask_session] برای مدیریت این جلسات استفاده خواهیم کرد. این ماژول می‌تواند جلسات کاربری را در چندین مکان ذخیره کند. ردیس یکی از این مکان‌ها است و ما از آن استفاده خواهیم کرد.

ماژول [flask_session] باید روی یک ترمینال PyCharm نصب شود:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask-session
Collecting flask-session

برای ارتباط با سرور Redis، به یک کلاینت Redis نیاز داریم. این کلاینت توسط ماژول [redis] فراهم می‌شود که آن را نیز نصب خواهیم کرد:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install redis
Collecting redis

33.2.2. سرور ردیس

سرور Redis برای ذخیره‌سازی جلسات کاربران استفاده خواهد شد. ماژول [flask_session] به شرح زیر عمل می‌کند:

  • هر کاربر یک شناسه جلسه (session ID) دارد و این چیزی است که به کلاینت ارسال می‌شود – و فقط همین. کلاینت فقط یک بار، پس از اولین درخواست خود، یک کوکی جلسه دریافت می‌کند. این کوکی حاوی شناسه جلسه کاربر است که در درخواست‌های بعدی کلاینت تغییر نخواهد کرد. به همین دلیل، سرور نیازی به ارسال کوکی جلسه جدید ندارد؛
  • قبلاً، داده‌های جلسه به کلاینت ارسال می‌شد. این دیگر چنین نخواهد بود. داده‌های جلسه کاربر روی سرور Redis ذخیره خواهد شد؛

Laragon با یک سرور Redis عرضه می‌شود که به طور پیش‌فرض فعال نیست. بنابراین باید کار را با فعال کردن آن آغاز کنید:

Image

  • در [3]، سرور [Redis] را فعال کنید؛
  • در [4]، پورت [6379] را که کلاینت‌های Redis به‌طور پیش‌فرض استفاده می‌کنند، بدون تغییر باقی بگذارید؛

خدمات Laragon پس از فعال‌سازی Redis به‌طور خودکار مجدداً راه‌اندازی می‌شوند:

Image

سرور Redis را می‌توان در حالت فرمان جستجو کرد. یک ترمینال Laragon [6] را باز کنید:

Image

  • در [1]، دستور [redis-cli] کلاینت را در حالت فرمان بر روی سرور Redis راه‌اندازی می‌کند؛

در ژوئیه ۲۰۱۹، کلاینت ردیس می‌توانست از ۱۷۲ دستور برای ارتباط با سرور [https://redis.io/commands#list] استفاده کند. یکی از این دستورات، [command count] [2]، عدد [3] را نمایش می‌دهد.

نوشتن به [Redis] با استفاده از فرمان Redis [set attribut valeur] [4] انجام می‌شود. سپس می‌توان مقدار را با استفاده از دستور [get attribut] [5] بازیابی کرد.

ممکن است لازم باشد حافظه ردیس را پاک کنید. این کار با استفاده از دستور [flushdb] [6] انجام می‌شود. سپس، اگر مقدار ویژگی [titre] [7] را استعلام کنید، یک مرجع [nil] [8] دریافت خواهید کرد که نشان می‌دهد ویژگی یافت نشده است. همچنین می‌توانید از دستور [exists] [9-10] برای بررسی وجود یک ویژگی استفاده کنید.

برای خروج از کلاینت ردیس، دستور [quit] [11] را تایپ کنید.

همچنین می‌توانید از رابط وب برای مدیریت کلیدها در سرور Redis استفاده کنید. برای این کار، سرور Apache لارگون باید در حال اجرا باشد:

Image

رابط کاربری زیر ظاهر می‌شود:

Image

  • [1-4]، یکی از جلسات ذخیره‌شده روی سرور Redis؛

33.2.3. مدیریت سرور Redis در اسکریپت اصلی [main]

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


import redis

# بررسی در دسترس بودن سرور Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
                           port=config["parameters"]["redis"]["port"])
# پینگ سرور Redis
try:
    redis_client.ping()
except BaseException as exception:
    # ردیس در دسترس نیست
    log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
    #کنسول
    print(log)
    # لاگ
    logger.write(f"{log}\n")
    #پایان
    sys.exit(1)

#ذخیرهٔ کلاینت [redis] در پیکربندی
config['redis_client'] = redis_client
  • خط ۴: سازنده کلاس [redis.Redis] یک کلاینت سرور ردیس ایجاد می‌کند. جزئیات آن (آدرس، پورت) در اسکریپت [parameters] یافت می‌شود؛
  • خط ۸: متد [ping] برای بررسی اینکه آیا سرور Redis در حال اجرا است، استفاده می‌شود؛
  • خطوط ۹–۱۷: اگر پینگ ناموفق باشد، خطا ثبت می‌شود و سرور خاموش می‌شود؛
  • خط ۲۰: مرجع کلاینت ردیس در پیکربندی تنظیم می‌شود؛

33.2.4. مدیریت مسیر در اسکریپت اصلی [main]

پردازش مسیر در [main] به خطوط زیر محدود است:


# وارد کردن مسیرهای برنامه وب
import routes
routes.config=config
routes.execute(__name__)
  • خط ۱: مسیرها به ماژول [routes] منتقل شده‌اند؛
  • خط ۳: مسیرها باید پیکربندی اجرای را بدانند؛
  • خط ۴: برنامه Flask با ارسال نام اسکریپت در حال اجرا (__main__) راه‌اندازی می‌شود؛

اسکریپت مسیرها به شرح زیر است:


#وابستگی‌ها
import os

from flask import Flask, redirect, request, session, url_for
from flask_api import status
from flask_session import Session

# برنامه Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")

#پیکربندی برنامه
config = {}

# کنترل‌کنندهٔ اصلی
def front_controller() -> tuple:
    # درخواست به کنترلر اصلی ارجاع داده می‌شود
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # ارسال مجدد به /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
    #کنترلر مرتبط با اکشن اجرا می‌شود
    return front_controller()

# احراز هویت کاربر
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#محاسبه-مالیات
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#محاسبه دسته‌ای مالیات
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

# فهرست شبیه‌سازی‌ها
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

# حذف-شبیه‌سازی
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#پایان-جلسه
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

#نمایش-محاسبه-مالیات
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
    # اجرای کنترلر مرتبط با اقدام
    return front_controller()

# get-admindata
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
    # کنترلر مرتبط با اقدام اجرا می‌شود
    return front_controller()

def execute(name: str):
    #کلید مخفی جلسه
    app.secret_key = os.urandom(12).hex()
    # Flask-Session
    app.config.update(SESSION_TYPE='redis',
                      SESSION_REDIS=config['redis_client'])
    Session(app)
    # مواردی که برنامه Flask از طریق یک اسکریپت کنسول اجرا می‌شود
    if name == '__main__':
        app.config.update(ENV="development", DEBUG=True)
        app.run(threaded=True)
  • خط ۹: اپلیکیشن Flask ایجاد می‌شود؛
  • خط ۱۲: پیکربندی برنامه هنگام نوشتن اسکریپت هنوز مشخص نیست. این پیکربندی تنها در زمان اجرا مشخص می‌شود؛
  • خطوط ۲۰–۷۷: مسیرهای برنامه همان‌طور که در نسخه قبلی تعریف شده‌اند. این‌ها بدون تغییر باقی می‌مانند؛
  • خطوط ۱۴–۱۸: تمام مسیرها به سادگی تابع [front_controller] را فراخوانی می‌کنند. ما کد اصلی این تابع را حذف کرده‌ایم. اکنون این تابع به سادگی کنترلر اصلی وب‌اپلیکیشن را فراخوانی می‌کند؛
  • خطوط ۷۹–۸۹: [execute] تابعی است که توسط اسکریپت [main] برای راه‌اندازی برنامه وب فراخوانی می‌شود؛
  • خط ۸۱: ماژول [flask_session] از کلید مخفی Flask استفاده می‌کند؛
  • خطوط ۸۲–۸۴: پیکربندی ماژول [flask_session]. این شامل افزودن کلیدهای [SESSION_TYPE, SESSION_REDIS] به پیکربندی [app.config] برای برنامه Flask [app] است:
  • [SESSION_TYPE]: نوع جلسه. چندین نوع از این وجود دارد. نوع [redis] نشان می‌دهد که [flask_session] از یک سرور [redis] برای ذخیره‌سازی جلسات کاربر استفاده می‌کند. به همین دلیل، باید کلید [SESSION_REDIS] را تعریف کنیم که باید مرجع یک کلاینت Redis باشد؛
  • خط ۸۵: جلسه [Flask-Session] با برنامه Flask مرتبط است؛
  • خطوط ۸۶–۸۹: اگر پارامتر [name] در خط ۷۹ مقدار رشته‌ای [__main__] باشد، آنگاه برنامه Flask راه‌اندازی می‌شود؛

33.3. بازسازی کنترل‌کننده اصلی

کدی که قبلاً در تابع [front_controller] از اسکریپت [main] قرار داشت، به کنترلر اصلی منتقل شده است:

Image


# وارد کردن وابستگی‌ها
import threading
import time
from random import randint

from flask_api import status
from werkzeug.local import LocalProxy

from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail

def send_adminmail(config: dict, message: str):
    #ارسال ایمیل به مدیر برنامه
    config_mail = config['parameters']['adminMail']
    config_mail["logger"] = config['logger']
    SendAdminMail.send(config_mail, message)

# کنترل‌کنندهٔ اصلی برنامه
class MainController(InterfaceController):
    def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
        # پردازش درخواست
        logger = None
        action = None
        type_response1 = None
        try:
            # بازیابی عناصر مسیر
            params = request.path.split('/')

            # عمل عنصر اول است
            action = params[1]

            # در ابتدا هیچ خطایی وجود ندارد
            erreur = False

            # نوع جلسه باید قبل از انجام برخی اقدامات مشخص باشد
            type_response1 = session.get('typeResponse')
            if type_response1 is None and action != "init-session":
                #خطا ثبت می‌شود
                résultat = {"action": action, "état"101,
                            "réponse"["pas de session en cours. Commencer par action [init-session]"]}
                erreur = True

            # لاگ
            logger = Logger(config['parameters']['logsFilename'])

            #آن را در پیکربندی مرتبط با نخ ذخیره کنید
            thread_config = {"logger": logger}
            thread_name = threading.current_thread().name
            config[thread_name] = {"config": thread_config}

            # درخواست را ثبت کنید
            logger.write(f"[MainController] requête : {request}\n")

            # در صورت درخواست، نخ متوقف می‌شود
            sleep_time = config['parameters']['sleep_time']
            if sleep_time != 0:
                #وقفه به‌صورت تصادفی انتخاب می‌شود، به‌طوری که برخی رشته‌ها قطع می‌شوند و برخی دیگر قطع نمی‌شوند
                aléa = randint(01)
                if aléa == 1:
                    #ثبت قبل از مکث
                    logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                    #مکث
                    time.sleep(sleep_time)

            # برای انجام برخی عملیات، باید احراز هویت شوید
            user = session.get('user')
            if not erreur and user is None and action not in ["init-session""authentifier-utilisateur"]:
                # خطا ثبت شده است
                résultat = {"action": action, "état"101,
                            "réponse"[f"action [{action}] demandée par utilisateur non authentifié"]}
                erreur = True

            #آیا خطایی وجود دارد؟
            if erreur:
                # درخواست نامعتبر است
                status_code = status.HTTP_400_BAD_REQUEST
            else:
                # کنترل‌کننده‌ی مرتبط با اکشن اجرا می‌شود
                controller = config['mvc']['controllers'][action]
                résultat, status_code = controller.execute(request, session, config)

        except BaseException as exception:
            # استثناهای دیگر (غیرمنتظره)
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        finally:
            pass

        # نتیجه ارسال‌شده به کلاینت ثبت می‌شود
        log = f"[MainController] {résultat}\n"
        logger.write(log)

        #آیا خطای فاتال رخ داده است؟
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            #ایمیلی برای مدیر برنامه ارسال می‌شود
            send_adminmail(config, log)

        #نوع پاسخ مورد نظر تعیین شده است
        type_response2 = session.get('typeResponse')
        if type_response2 is None and type_response1 is None:
            #نوع جلسه هنوز مشخص نشده است – این jSON خواهد بود
            type_response = 'json'
        elif type_response2 is not None:
            # نوع پاسخ مشخص است و بخشی از جلسه است
            type_response = type_response2
        else:
            #در غیر این صورت، ما به استفاده از type_response1 ادامه می‌دهیم
            type_response = type_response1

        #پاسخ ارسالی ساخته می‌شود
        response_builder = config['mvc']['responses'][type_response]
        response, status_code = response_builder \
            .build_http_response(request, session, config, status_code, résultat)

        # فایل لاگ را ببندید اگر باز باشد
        if logger:
            logger.close()

        #پاسخ ارسال می‌شود HTTP
        return response, status_code

تمام این کدها قبلاً به نحوی پوشش داده شده‌اند.

33.4. مدیریت گذرواژه‌های رمزگذاری‌شده

برای مدیریت گذرواژه‌های رمزگذاری‌شده، ما از ماژول [passlib] استفاده خواهیم کرد که از طریق ترمینال PyCharm نصب می‌شود:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install passlib
Collecting passlib

در اینجا یک اسکریپت نمونه وجود دارد که رمز عبوری را که به عنوان پارامتر به آن داده می‌شود، رمزگذاری می‌کند:

Image

اسکریپت [create_hashed_password] به شرح زیر است (https://passlib.readthedocs.io/en/stable/):


import sys

# توابع رمزگذاری
from passlib.hash import pbkdf2_sha256

#منتظر رمزنگاری رمز عبور می‌ماند
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()
else:
    password = sys.argv[1]

#رمزگذاری رمز عبور
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")

# تأیید
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
  • خط ۱۶: گذرواژه‌ای که به‌عنوان پارامتر ارسال شده، رمزگذاری می‌شود؛
  • خط ۲۰: رمز عبور [password] که به‌عنوان پارامتر ارسال شده، با نسخه رمزگذاری‌شده آن [hash] مقایسه می‌شود. تابع [verify] رمزگذاری رمز عبور [password] را انجام می‌دهد و رشته رمزگذاری‌شده حاصل را با [hash] مقایسه می‌کند. اگر این دو رشته برابر باشند، مقدار True را برمی‌گرداند؛

اسکریپت بالا به ما امکان می‌دهد تا نسخه رمزگذاری‌شده رمز عبور [admin] را به دست آوریم:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/08/passlib/create_hashed_password.py admin
version cryptée de [admin] = $pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek
True

خط ۲: مقداری که ما در اسکریپت وارد می‌کنیم [parameters] است:


        "users"[
            {
                "login""admin",
                "password""$pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek"
            }
        ],

کد دست‌کاری احراز هویت [AuthentifierUtilisateurController] به شرح زیر تغییر می‌کند:


from passlib.handlers.pbkdf2 import pbkdf2_sha256

            # بررسی اعتبار جفت (نام کاربری، رمز عبور)
            users = config['parameters']['users']
            i = 0
            nbusers = len(users)
            trouvé = False
            while not trouvé and i < nbusers:
                trouvé = user == users[i]["login"] and pbkdf2_sha256.verify(password, users[i]["password"])
                i += 1
            # یافت شد؟
            if not trouvé:

33.5. آزمایش‌ها

علاوه بر آزمایش با مرورگر، می‌توانید از کلاینت‌های موجود در پوشه [http-servers/07] که برای نسخه ۱۲ نوشته شده‌اند نیز استفاده کنید. این کلاینت‌ها باید برای نسخه ۱۳ نیز «کار کنند»:

Image

  • در [1]، هر سه کلاینت باید کار کنند؛
  • در [2]، هر دو تست باید کار کنند؛