Skip to content

37. تمرين عملي: الإصدار 17

Image

يقدم هذا الإصدار الجديد التغييرات التالية:

  • سيتم نقله إلى خادم Apache/Windows؛
  • لإجراء هذا النقل، يحتوي الإصدار 17 على جميع التبعيات التي يحتاجها في مجلده [impots/http-servers/12]. لاحظ أن الإصدارات السابقة كانت تسترد تبعياتها من مجلدات مختلفة في جميع أنحاء مشروع [python-flask-2020] بأكمله؛

37.1. نقل تبعيات التطبيق

Image

لاحظ أن إدارة تبعيات التطبيق تتم في البرنامج النصي [syspath]. في الإصدار السابق، كان هذا البرنامج النصي كما يلي:

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

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  root path
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    #  dependencies
    absolute_dependencies = [
        #  project files
        #  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",
        #  Constants, slices
        f"{root_dir}/impots/v05/entities",
        #  Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }

نحتاج إلى إعادة تحديد موقع جميع التبعيات التي يعتمد مسارها المطلق على متغير [root_dir] في السطر 8، أي الأسطر من 13 إلى 26.

سيكون البرنامج النصي [syspath] للإصدار الجديد كما يلي:

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

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  dependencies
    absolute_dependencies = [
        #  application entities
        f"{script_dir}/../entities",
        #  layer [dao]
        f"{script_dir}/../layers/dao",
        #  business] layer
        f"{script_dir}/../layers/métier",
        #  utilities
        f"{script_dir}/../utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    import sys
    #  we add the project's absolute dependencies
    for directory in absolute_dependencies:
        #  we check the existence of the file
        existe = os.path.exists(directory) and os.path.isdir(directory)
        if not existe:
            #  the developer is notified
            raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
        else:
            #  insert the folder at the beginning of the syspath
            sys.path.insert(0, directory)

    #  we return the configuration
    return {
        "script_dir": script_dir,
    }
  • الأسطر 8–27: أصبحت جميع التبعيات الآن نسبية بالنسبة للمتغير [script_dir] في السطر 5؛
  • الأسطر 42–45: تمت إزالة المتغير [root_dir] من تكوين syspath؛
  • السطر 10: كيانات التطبيق موجودة في المجلد [entities] [1]؛
  • السطر 12: توجد طبقة [dao] في المجلد [layers/dao] [2]؛
  • السطر 14: توجد طبقة [business] في المجلد [layers/business] [2]؛
  • السطر 16: توجد أدوات [Logger، SendMail] في المجلد [utilities] [3]؛
  • الأسطر 29–40: يتم حساب مسار Python للتطبيق دون استيراد الوحدة النمطية [myutils]؛

Image

37.2. الاختبارات

في هذه المرحلة، يجب أن تعمل الإصدارة 17. تحقق من ذلك.

37.3. نقل تطبيق Python/Flask إلى خادم Apache/Windows

37.3.1. المصادر

لنقل تطبيق Flask إلى Apache/Windows، اضطررت إلى البحث في الإنترنت. إليك الرابط الذي ساعدني على البدء: [https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];

استخدمت المعلومات الواردة في هذا الرابط باستثناء تكوين خادم Apache. لذلك، استخدمت نموذجًا لتكوين خادم Apache من Laragon.

37.3.2. تثبيت وحدة Python mod_wsgi

استخدم تطبيق Python/Flask الذي قمنا بتطويره خادم WSGI (Web Server Gateway Interface) [werkzeug] المضمن في Flask. يرد وصف هذا الخادم |هنا|. يصف الرابط [https://www.fullstackpython.com/wsgi-servers.html] كيفية عمل خوادم WSGI. هناك العديد من |خوادم WSGI|. أحدها هو خادم Apache الذي يعمل في وضع WSGI. هذا هو الحل الذي تم اعتماده هنا لأن Laragon، الذي قمنا بتثبيته، يأتي مع خادم Apache.

لكي يستضيف خادم Apache تطبيق Python، نحتاج إلى تثبيت وحدة Python [mod_wsgi]. يعد تثبيت هذه الوحدة أمرًا صعبًا لأنه يتطلب ترجمة C++. لإكمال التثبيت بنجاح، تحتاج إلى مترجم Microsoft C++. الحل البسيط هو تثبيت أحدث إصدار من Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].

إذا لم تكن بحاجة إلى Visual Studio لأي غرض آخر غير [mod_wsgi]، فيمكنك قصر التثبيت على بيئة C++:

Image

بمجرد تثبيت مترجم C++، يتم تثبيت وحدة [mod_wsgi] في محطة PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>SET MOD_WSGI_APACHE_ROOTDIR=C:\MyPrograms\laragon\bin\apache\httpd-2.4.35-win64-VC15
 
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>pip install mod_wsgi
Collecting mod_wsgi
  Using cached mod_wsgi-4.7.1.tar.gz (498 kB)
Using legacy setup.py install for mod-wsgi, since package 'wheel' is not installed.
Installing collected packages: mod-wsgi
    Running setup.py install for mod-wsgi ... done
Successfully installed mod-wsgi-4.7.1
  • السطر 1: نقوم بتعيين قيمة متغير البيئة [MOD_WSGI_APACHE_ROOTDIR]. هذه القيمة هي موقع خادم Apache في نظام الملفات. هنا، هذا الموقع هو [<laragon>\bin\apache\httpd-2.4.35-win64-VC15حيث <laragon> هو مجلد تثبيت Laragon. يمكنك الحصول على هذا الموقع بطرق مختلفة. فيما يلي أحد الخيارات التي تم الحصول عليها باستخدام إحدى خيارات Laragon:

Image

في [1-3]، يمثل ملف [httpd.conf] ملف التكوين الرئيسي لخادم Apache. ثم يتم فتح الملف المعني في محرر نصوص (Notepad++ أدناه):

Image

في [2]، مجلد تثبيت Apache هو الجزء الذي يسبق السلسلة [conf\httpd.conf].

لنعد إلى تثبيت الوحدة النمطية [mod_wsgi]:

  • الأسطر 3–9: تثبيت الوحدة النمطية [mod_wsgi]؛

37.3.3. تكوين خادم Apache في Laragon

سنقوم بتكوين خادم Laragon Apache. نبدأ بملف التكوين الرئيسي [httpd.conf]:

Image

ننتقل إلى نهاية ملف [httpd.conf]:



IncludeOptional "C:/MyPrograms/laragon/etc/apache2/alias/*.conf"
IncludeOptional "C:/MyPrograms/laragon/etc/apache2/sites-enabled/*.conf"
Include "C:/MyPrograms/laragon/etc/apache2/httpd-ssl.conf"
Include "C:/MyPrograms/laragon/etc/apache2/mod_php.conf"
 
# python mod_wsgi
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
  • تمت إضافة السطر 8 إلى ملف [httpd.conf] الموجود في نهاية الملف. وهو يحدد لخادم Apache مكان العثور على مكون من مكونات وحدة [mod_wsgi] التي قمنا بتثبيتها للتو؛

طريقة سهلة للحصول على المسار للسطر 8 هي تشغيل الأمر التالي في محطة PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\impots\http-servers\11>mod_wsgi-express module-config
LoadModule wsgi_module "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages/mod_wsgi/server/mod_wsgi.cp38-win_amd64.pyd"
WSGIPythonHome "c:/data/st-2020/dev/python/cours-2020/python3-flask-2020/venv"

تشير بعض الوثائق إلى أنه يجب إضافة السطرين 2 و 3 إلى نهاية ملف [httpd.conf]. في حالتي، تسبب السطر 3 أعلاه في حدوث خطأ (نقص وحدة [encodings]). لذلك، لم يتم تضمينه في ملف [httpd.conf]. تمت إضافة السطر 2 فقط. يتم وصف معاني معلمات وحدة [mod_wsgi] المختلفة التي يمكن استخدامها في ملفات تكوين Apache |هنا|.

بعد ذلك، سنقوم بتمكين بروتوكول HTTPS على خادم Apache:

Image

  • في [1-4]، نقوم بتمكين بروتوكول HTTPS على خادم Apache؛

من الآن فصاعدًا، سنتمكن من استخدام عناوين URL [https://serveur/chemin]؛

لتكوين Apache لتشغيل تطبيق Flask، يستخدم الرابط المشار إليه |أعلاه| المضيفات الافتراضية. كما يوفر Laragon إمكانية إدارة المضيفات الافتراضية:

Image

  • في [1-3]، نطلب من Laragon إنشاء مضيفات افتراضية تلقائيًا؛

الخطوة التالية هي إنشاء مشروع ويب باستخدام Laragon:

 
  • في [1-3]، قم بإنشاء مشروع PHP فارغ؛
  • في [4-8]، أنشأ Laragon موقعًا افتراضيًا باسم [auto.projet-test.test] تم تكوينه بواسطة الملف [auto.projet-test.test.conf] [8] الموجود في المجلد [sites-enabled] [7]. يقع هذا المجلد في [<laragon>\etc\apache2\sites-enabledحيث [laragon] هو مجلد تثبيت Laragon؛

على الرغم من أن هذا ليس جزءًا مما نقوم به الآن، فقد تكون مهتمًا بالاطلاع على موقع [test-project] الذي أنشأناه للتو:

Image

  • في [1-5]، تم إنشاء مشروع فارغ. وهو مشروع PHP موجود في المجلد [<laragon>/www]، حيث [laragon] هو مجلد تثبيت Laragon؛

الآن دعونا نفحص ملف [auto.projet-test.test.conf] الذي أنشأه Laragon في المجلد [<laragon>\etc\apache2\sites-enabled]:


define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
 
<VirtualHost *:80> 
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
<VirtualHost *:443>
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
 
    SSLEngine on
    SSLCertificateFile      C:/MyPrograms/laragon/etc/ssl/laragon.crt
    SSLCertificateKeyFile   C:/MyPrograms/laragon/etc/ssl/laragon.key
 
</VirtualHost>
  • السطر 1: الدليل الجذر للمشروع الذي تم إنشاؤه في نظام الملفات؛
  • السطر 2: اسم الخادم الافتراضي. ستكون عناوين URL لهذا الخادم بالصيغة [http(s)://test-project.test/path]؛
  • الأسطر 4–12: تكوين الموقع الافتراضي للمنفذ 80 (السطر 4) وبروتوكول HTTP؛
  • الأسطر 14–27: تكوين المضيف الافتراضي للمنفذ 443 (السطر 14) وبروتوكول HTTPS؛

لنرى كيف يعمل الخادم الافتراضي. أولاً، لنبدأ تشغيل خادمي Apache و PHP:

Image

ثم، باستخدام متصفح، نطلب عنوان URL [http://projet-test.test/]:

Image

  • في [1]، عنوان URL المطلوب؛
  • في [2]، تم استخدام بروتوكول HTTP؛
  • في [3]، نظرًا لأن مشروع [projet-test] فارغ، نحصل على فهرس مجلده (قائمة محتوياته)، وهو فهرس فارغ؛

الآن دعونا نطلب عنوان URL الآمن [https://projet-test.test/]:

Image

  • في [1-2]، نحصل على نفس الاستجابة السابقة، ولكن باستخدام بروتوكول HTTPS [1]؛

أدى إنشاء الخادم الافتراضي [test-project.test] إلى إنشاء إدخال جديد في الملف [<windows>/system32/drivers/etc/hosts]:

Image


# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host
 
# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost
 
127.0.0.1      projet-test.test     #laragon magic!   
  • السطر 23: عنوان IP الخاص بالاسم [test-project.test] هو 127.0.0.1، أي عنوان [localhost] (السطر 20)، الجهاز المحلي. لذلك، عندما تكتب عنوان URL [http://projet-test.test/chemin] في متصفح، يتم إرسال الطلب إلى العنوان 127.0.0.1 على المنفذ 80. ثم يستجيب خادم Apache على الجهاز المحلي (localhost).

قد تتساءل لماذا، عند كتابة الطلب [http://projet-test.test/]، يستخدم خادم Apache التكوين من الملف [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Image

لفهم ذلك، نحتاج إلى معرفة ما يرسله المتصفح إلى خادم Apache عند إجراء هذا الطلب. لنفعل ذلك باستخدام Postman:

Image

  • في [1-3]، نرسل طلب HTTPS [1]؛
  • في [4]، يشير Postman إلى أنه لم يتعرف على شهادة الأمان. يُنشئ بروتوكول HTTPS اتصالاً مشفرًا بين عميل الويب (هنا، Postman) وخادم Apache. يتم إنشاء هذا الاتصال المشفر من خلال الشهادات المتبادلة بين العميل والخادم. الخادم هو الذي يبدأ عملية إنشاء الاتصال المشفر عن طريق إرسال شهادة أمان إلى العميل. لكي يقبل العميل هذه الشهادة، يجب أن تكون موقعة — بمعنى آخر، يجب شراؤها من شركات مرخصة لإصدار شهادات الأمان. عندما قمنا بتمكين بروتوكول HTTPS الخاص بـ Laragon، قام Laragon بإنشاء شهادة الأمان بنفسه. ويُقال عندئذٍ إن الشهادة موقعة ذاتيًا. تصدر معظم عملاء الويب تحذيرًا عند تلقيهم شهادة موقعة ذاتيًا. وهذا ما يفعله Postman في [4]. ثم يعرض معظم عملاء الويب تعطيل التحقق من شهادة الأمان المرسلة من الخادم. وهذا ما يقدمه Postman في [5]؛

نضغط على الرابط [5] لتعطيل التحقق من SSL (طبقة المقابس الآمنة). SSL/TLS (أمان طبقة النقل) هو بروتوكول أمان ينشئ قناة اتصال آمنة بين جهازين على الإنترنت. هذا هو البروتوكول المستخدم هنا بواسطة Apache. والاستجابة هي كما يلي:

Image

نتلقى نفس الصفحة كما هو الحال مع المتصفح التقليدي. الآن دعونا نلقي نظرة على حوار العميل/الخادم في وحدة تحكم Postman (Ctrl-Alt-C):

GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: d153f711-ad99-4e1d-93c3-61c25374d1be
Host: projet-test.test
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.1 200 OK
Date: Fri, 14 Aug 2020 09:19:24 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.1b PHP/7.2.19 mod_wsgi/4.7.1 Python/3.8
Content-Length: 161
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html;charset=UTF-8
  • السطر 6: يحدد رأس HTTP [Host] اسم الخادم الذي يستهدفه عميل الويب. هذا هو المبدأ الكامن وراء الخوادم الافتراضية. في عنوان IP واحد (هنا 127.0.0.1)، يمكن لخادم الويب استضافة عدة مواقع ويب بأسماء مختلفة. يسمح رأس HTTP [Host] للعميل بتحديد الخادم (هنا، الخادم الموجود على العنوان 127.0.0.1) الذي يتصل به؛

إذن، ماذا يفعل Apache؟

عند بدء التشغيل، يقرأ Apache جميع ملفات التكوين الموجودة في الدليل [[<laragon>\etc\apache2\sites-enabled]]:

Image

يحدد كل ملف تكوين خادمًا افتراضيًا. على سبيل المثال، في الملف [auto.projet-test.test.conf]، ستجد السطر التالي:


define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"

السطر 2 يحدد الخادم الافتراضي [test-project.test]. الملف [auto.projet-test.test.conf] هو ملف التكوين لهذا الخادم الافتراضي. نظرًا لأنه يقرأ جميع ملفات التكوين الموجودة في المجلد [<laragon>\etc\apache2\sites-enabled] عند بدء التشغيل، فإن خادم Apache يعرف أن هناك خادمًا افتراضيًا باسم [projet-test.test]. وبالتالي، عندما يتلقى طلب HTTPS التالي من عميل Postman:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: d153f711-ad99-4e1d-93c3-61c25374d1be
Host: projet-test.test
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

يتعرف على أن الطلب موجه إلى الخادم الافتراضي [test-project.test] (السطر 6) وأنه موجود. ثم يستخدم تكوين الخادم الافتراضي [test-project.test] للرد على عميل Postman.

37.4. إنشاء أول خادم افتراضي Apache

الآن بعد أن عرفنا الغرض من الخوادم الافتراضية وكيفية تكوينها، سنقوم بإنشاء واحدة. سيتم استخدامها لتشغيل تطبيق Python Flask المثبت في مجلد [Apache] من الإصدار 17 الذي يتم نشره حاليًا على خادم Apache:

لقد وضعنا التطبيق الذي تم تطويره في القسم |link|—خدمة ويب للتاريخ/الوقت—في مجلد [http-servers/12/apache/example]:

Image

خادم [date_time_server.py] هو كما يلي:

#  imports
import os
import sys
import time

#  we need to put the modules folder in the Python Path
#  for porting to apache windows
sys.path.insert(0, "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages")

from flask import Flask, make_response, render_template

#  flask application
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")

#  Home URL
@application.route('/')
def index():
    #  dispatch time to customer
    #  time.localtime: number of milliseconds since 01/01/1970
    #  time.strftime formats time and date
    #  date-time display format
    #  d: 2-digit day
    #  m: 2-digit month
    #  y: 2-digit year
    #  H: hour 0.23
    #  M: minutes
    #  S: seconds

    #  current date / time
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    #  generate the document to be sent to the customer
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    #  HTTP response to customer
    response = make_response(document)
    print("response", type(response), response)
    return response

#  hand only
if __name__ == '__main__':
    application.config.update(ENV="development", DEBUG=True)
    application.run()

يُشار إلى تطبيق Flask بواسطة المعرف [application] (الأسطر 14 و43 و44). هذا الاسم مطلوب. إذا أشرت إلى تطبيق Flask بمعرف مختلف، فلن يعمل التطبيق وسيعرض رسالة خطأ تشير إلى أنه لا يمكنه العثور على عنوان URL المطلوب. لا توفر رسالة الخطأ هذه أي إشارة إلى مصدر الخطأ. لذلك يجب أن تكون حريصًا بشأن هذا الأمر.

ملف HTML المشار إليه في السطر 34 هو كما يلي:


<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Date et heure du moment</title>
</head>
<body>
    <b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
  • [date-time-server] سيكون الخادم الافتراضي الذي يستضيف هذا التطبيق. سيتم تكوينه عبر الملف [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (لاحظ أن هذا الاسم تعسفي — يقرأ Apache جميع الملفات الموجودة في [sites-enabled] لاكتشاف المواقع الافتراضية المستضافة)؛

نحصل أولاً على هذا الملف عن طريق نسخ ملف [auto.projet-test.test.conf] ثم نقوم بتعديله.

Image

سيبدو ملف [date-time-server.conf] كما يلي:

#  application python-flask script file
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"

#  name of the website configured by this file
#  here it will be called date-time-server
#  the URL will be of type http(s)://date-time-server/path
define SITE "date-time-server"

#  set address IP 127.0.0.1 for site SITE in c:/windows/system32/drivers/etc/hosts

#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    WSGIScriptAlias / "${ROOT}/date_time_server.py"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    WSGIScriptAlias / "${ROOT}/date_time_server.py"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>

    SSLEngine on
    SSLCertificateFile      C:/MyPrograms/laragon/etc/ssl/laragon.crt
    SSLCertificateKeyFile   C:/myprograms/laragon/etc/ssl/laragon.key

</VirtualHost>
  • السطر 7: قم بتسمية الخادم الافتراضي الذي تم تكوينه بواسطة الملف؛
  • السطر 2: يحدد قيمة المتغير [ROOT] المستخدم في السطرين 14 و 27؛
  • السطران 14 و27: حدد مسار البرنامج النصي Python الذي يجب تنفيذه عندما يتلقى الخادم الافتراضي طلبًا. هنا، نحدد أن طلبات الخادم [date-time-server] تتم معالجتها بواسطة البرنامج النصي Python [date_time_server.py]. ينبع هذا الاختلاف عن ملف [auto.projet-test.test.conf] من حقيقة أن الملف الأول قام بتكوين خادم PHP، في حين أن ملف [date-time-server.conf] يقوم بتكوين خادم Python؛
  • السطران 14 و27: تحدد السمة [WSGIScriptAlias /] هنا أن جذر خادم [date-time-server] سيكون [/]. وبالتالي، ستتخذ عناوين URL الخاصة بالتطبيق الشكل [http(s)://date-time-server/path]؛
  • السطران 14 و 27: يمكننا تعيين جذر مختلف للتطبيق، على سبيل المثال [WSGIScriptAlias /show]. في هذه الحالة، ستتخذ عناوين URL الخاصة بالتطبيق الشكل [http(s)://show/date-time-server/path]؛

نحتاج أيضًا إلى إضافة سطر إلى ملف [<windows>/system32/drivers/etc/hosts]:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost

127.0.0.1    flask-impots-withmysql        # cours python - appli impôts avec MySQL
127.0.0.1    flask-impots-withpgres        # cours python - appli impôts avec PostgreSQL
127.0.0.1    date-time-server            # cours python - heure et date du moment
127.0.0.1           projet-test.test                #laragon magic!

نضيف السطر 25 لتعيين عنوان IP [127.0.0.1] للخادم الافتراضي [date-time-server].

دعونا نتحقق من كل شيء. نبدأ تشغيل خادم Apache:

Image

بعد ذلك، نطلب عنوان URL [https://date-time-server] باستخدام متصفح:

  • في [1]، عنوان URL المطلوب؛
  • في [3]، استجابة الخادم؛
  • في [2]، يشير المتصفح إلى أن اتصال HTTPS غير آمن لأنه اكتشف أن الشهادة المرسلة من خادم Apache كانت موقعة ذاتيًا؛

الآن، في ملف [date-time-server.conf]، دعونا نضيف اسمًا مستعارًا إلى السطرين 14 و 27:


    WSGIScriptAlias /show-date-time "${ROOT}/date_time_server.py"

لا يتعرف خادم Apache على التغيير على الفور. يجب إعادة تحميله:

Image

ثم نطلب عنوان URL [https://date-time-server/show-date-time]. ويكون رد الخادم كما يلي:

Image

37.5. نقل تطبيق حساب الضرائب إلى Apache / Windows

يتم إنشاء الدليل [apache] [2] مبدئيًا عن طريق نسخ الدليل [main]. من المهم أن يكونا على نفس المستوى حتى تظل المسارات في البرنامج النصي [syspath.py] المنسوخ من [1] إلى [2] صالحة. لتجنب التداخل مع تطبيق [impots / http-servers/ 12] العامل، نضع التكوين الذي سيتم تنفيذه بواسطة خادم Apache في [apache]؛

Image

  • ملف [config] في [2] هو نفسه ملف [config] في [1]؛
  • ملف [syspath] في [2] هو نفسه ملف [syspath] في [1]؛
  • ملف [main_withmysql] في [2] هو ملف [main] من [1] مع التعديلات التالية:

تلقى البرنامج النصي الرئيسي [main] معلمة [mysql / pgres] تحدد نظام إدارة قواعد البيانات (DBMS) الذي يجب استخدامه. يستخدم البرنامج النصي [main_withmysql] نظام إدارة قواعد البيانات MySQL:

#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "mysql"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

السطر 7 يضبط نظام إدارة قواعد البيانات (DBMS) على MySQL.

  • ملف [main_withpgres] من [2] هو ملف [main] من [1] مع التغييرات التالية: يستخدم نظام إدارة قواعد البيانات PostgreSQL:
#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "pgres"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

في السطر 7، قمنا بتعيين نظام إدارة قواعد البيانات (DBMS) إلى PostgreSQL.

بمجرد الانتهاء من ذلك، نقوم بإنشاء البرنامج النصي التالي [main_withmysql.wsgi] (لا يهم اللاحقة المستخدمة):

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withmysql import app as application

سيكون البرنامج النصي [main_withmysql.wsgi] هو الهدف الذي سيقوم خادم Apache بتنفيذه في وضع WSGI:

  • كان من الممكن أن يكون هدف خادم Apache هو البرنامج النصي [main_withmysql.pyكما تم سابقًا مع البرنامج النصي [date_time_server.py]. لكن ذلك كان سيتطلب تعديلًا طفيفًا:
    • على عكس تشغيل البرنامج النصي في وحدة التحكم، مع Apache، لا يكون الدليل الذي يحتوي على الهدف [main_withmysql.py] جزءًا من مسار Python. لذلك، يتسبب السطر 6 من البرنامج النصي [main_withmysql.py] في حدوث خطأ؛
    • التغيير الثاني الذي كان سيكون ضروريًا هو أنه في [main_withmysql]، تتم الإشارة إلى تطبيق Flask بواسطة المعرف [app]. ونحن نعلم أنه بالنسبة لـ Apache/WSGI، يجب أيضًا الإشارة إليه بواسطة معرف [application]؛
  • بدلاً من تعديل [main_withmysql.py]، نقوم بتغيير هدف Apache. سيكون الآن البرنامج النصي [main_withmysql.wsgi] أعلاه:
    • الأسطر 1–7: نضيف دليل البرنامج النصي إلى مسار Python. ونتيجة لذلك، لم يعد السطر 6 من [main_withmysql.py] يسبب خطأً؛
    • الأسطر 9–10: يؤدي استيراد [main_withmysql.py] إلى تشغيله. بالإضافة إلى ذلك، نشير إلى تطبيق Flask [app] الموجود في [main_withmysql.py] باستخدام المعرف [application] المطلوب من قبل Apache في وضع WSGI؛

نقوم بنفس الشيء مع البرنامج النصي [main_withpgres.wsgi]:

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withpgres import app as application

لدينا الآن الأهداف القابلة للتنفيذ لخادم Apache. نحتاج الآن إلى إنشاء خادمين افتراضيين، واحد لكل هدف.

في [<laragon>\etc\apache2\sites-enabled]، نقوم بإنشاء الملف [flask-imports-withmysql.conf] (لا يهم الاسم):

Image


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withmysql"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
 
    SSLEngine on
    SSLCertificateFile      C:/MyPrograms/laragon/etc/ssl/laragon.crt
    SSLCertificateKeyFile   C:/myprograms/laragon/etc/ssl/laragon.key
 
</VirtualHost>
  • السطر 2: جذر التطبيق، المجلد [apache] الذي أنشأناه؛
  • السطران 23 و38: الهدف [main_withmysql.wsgi] الذي أنشأناه:
  • السطر 7: سيتم تسمية الخادم الافتراضي [flask-impots-withmysql]؛
  • السطر 13: تسمح لنا توجيهات [WSGIPythonPath] بإضافة مجلدات إلى مسار Python. هنا، لا يدرك Apache أننا استخدمنا بيئة افتراضية لتطوير التطبيق وأن جميع الوحدات النمطية التي يستخدمها التطبيق موجودة في تلك البيئة الافتراضية. لذلك، في السطر 13، نضيف المجلد الذي يحتوي على جميع الوحدات النمطية من البيئة الافتراضية المستخدمة. أحد الخيارات هو نسخ هذا الدليل إلى موقع آخر في نظام الملفات والإشارة إلى ذلك الموقع. خيار آخر هو إضافة هذا الدليل إلى مسار Python مباشرةً داخل الهدف [main_withmysql.wsgi] (من المرجح أن يكون هذا حلاً أفضل)؛
  • السطر 16: يمكننا تحديد دليل تثبيت Python في نظام الملفات لبرنامج Apache. عادةً ما يكون هذا الدليل موجودًا في مسار PATH الخاص بالجهاز، وغالبًا ما يكون هذا السطر غير ضروري (وهذا ما كان عليه الحال هنا). ومع ذلك، قد توجد عدة إصدارات مثبتة من Python على الجهاز، وقد لا يكون الإصدار المطلوب موجودًا في مسار PATH الخاص بالجهاز. في هذه الحالة، يحل هذا السطر المشكلة؛

وبالمثل، قم بإنشاء ملف [flask-impots-withpgres.conf]:


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withpgres"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
 
    SSLEngine on
    SSLCertificateFile      C:/MyPrograms/laragon/etc/ssl/laragon.crt
    SSLCertificateKeyFile   C:/myprograms/laragon/etc/ssl/laragon.key
 
</VirtualHost>

نحفظ جميع هذه الملفات، ونشغل خادم Apache وقواعد بيانات MySQL و PostgreSQL. يتم تكوين التطبيق ببادئة URL [/do] و [with_csrftoken=False] (بدون رمز CSRF) في [configs/parameters.py]. نطلب عنوان URL [https://flask-impots-withmysql/do]. يكون رد الخادم كما يلي:

Image

نطلب الآن عنوان URL [https://flask-impots-pgres/do]. والاستجابة هي كما يلي:

Image

يعمل كلا التطبيقين بشكل طبيعي.

الآن دعونا نعدل المعلمة [WSGIScriptAlias] في ملف [flask-imports-withmysql.conf]:

#  .wsgi script folder
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"



#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    

</VirtualHost>
  • السطران 11 و 20، أصبح الاسم المستعار لـ WSI الآن [/impots]؛

نوقف خادم Apache ونعيد تشغيله، ثم نطلب عنوان URL [https://flask-impots-withmysql/impots/do]. استجابة الخادم هي كما يلي:

Image

حدث تعطل. يوضح لنا عنوان URL [1] السبب. كان يجب أن يكون [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]. الاسم المستعار لـ WSGI مفقود. هذا خطأ في تطبيقنا. فهو يعرف كيفية التعامل مع بادئة عنوان URL (/do موجودة). قد يعتقد المرء أن إضافة البادئة [/impots/do] إلى تطبيقنا ستحل المشكلة السابقة. لكن لا. سنواجه عندئذ أنواعًا أخرى من المشاكل. لا يتصرف الاسم المستعار WSGI كبادئة عنوان URL.

دعونا نحاول فهم ما حدث. لقد طلبنا عنوان URL [https://flask-impots-withmysql/impots/do]. كنا نتوقع رؤية عرض المصادقة. في [1] أعلاه، نرى أن التطبيق طلب عرضه، ولكن ليس بعنوان URL الصحيح. دعونا نفحص مسار الطلب [https://flask-impots-withmysql/impots/do].

أولاً، تم تنفيذ المسار التالي (configs/routes.py):


    # les routes de l'application Flask
    # racine de l'application
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

المسار في السطر 3 هو [https://flask-impots-withmysql/impots/do] في مثالنا. يمكننا أن نرى أن المسار قد تم تجريده من الجزء [https://flask-impots-withmysql/impots] ليصبح ببساطة [/do]. أما بالنسبة للجزء [https://flask-impots-withmysql]، فهذا أمر طبيعي؛ حيث لا يتم تضمين اسم الخادم في المسار. لكن يمكننا أن نرى أنه لا يتضمن أيضًا الاسم المستعار WSGI [/impots]. هذه نقطة مهمة. حتى مع وجود اسم مستعار WSGI، تظل مساراتنا الأولية صالحة.

الآن دعونا نرى ما تفعله الدالة [index] في السطر 4 (configs/routes_without_csrftoken):

1
2
3
4
#  application root
def index() -> tuple:
    #  redirect to /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

في السطر الرابع، يتم إعادة توجيهنا إلى عنوان URL الخاص بوظيفة [init_session]. وفي ملف [configs/routes.py]، تم ربط هذه الوظيفة بالمسار [/do/init-session/html]:


    # init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

السطر 2: في اختبارنا، [csrftoken_param] هي سلسلة فارغة. لا يتعامل التطبيق مع رمز CSRF هنا.

يتم تعريف الدالة [init_session] على النحو التالي (configs/routes_without_csrftoken):

1
2
3
4
#  init-session
def init_session(type_response: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

السطر 4: نبدأ سلسلة معالجة الإجراء [init-session]. تنتهي هذه السلسلة على النحو التالي في [responses/HtmlResponse]:



# now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""
 
        # redirect response
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

الإجراء [init-session] هو إجراء ADS (Action Do Something) ينتهي بإعادة توجيه إلى عرض، السطر 9. وهنا تكمن المشكلة. لا تضيف الدالة [redirect] في السطر 9 الاسم المستعار WSGI تلقائيًا إلى عنوان URL لإعادة التوجيه. يظهر هذا في لقطة الشاشة أعلاه. الاسم المستعار /impots مفقود من عنوان URL لإعادة التوجيه.

تقدم النسخة التالية حلاً لمشكلة الاسم المستعار WSGI.