37. تمرين تطبيقي: الإصدار 17

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

تجدر الإشارة إلى أن إدارة تبعيات التطبيق تتم في البرنامج النصي [syspath]. في الإصدار السابق، كان هذا البرنامج النصي كما يلي:
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",
]
# تحديد مسار النظام
from myutils import set_syspath
set_syspath(absolute_dependencies)
# تحديد التكوين
return {
"root_dir": root_dir,
"script_dir": script_dir
}
يجب إعادة تحديد مواقع جميع التبعيات التي يعتمد اسمها المطلق على المتغير [root_dir] في السطر 8، أي الأسطر من 13 إلى 26.
سيكون النص البرمجي [syspath] في الإصدار الجديد كما يلي:
def configure(config: dict) -> dict:
import os
# مجلد هذا الملف
script_dir = os.path.dirname(os.path.abspath(__file__))
# التبعيات
absolute_dependencies = [
# كيانات التطبيق
f"{script_dir}/../entities",
# الطبقة [dao]
f"{script_dir}/../layers/dao",
# طبقة [métier]
f"{script_dir}/../layers/métier",
# الأدوات المساعدة
f"{script_dir}/../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",
]
# يتم تعيين مسار النظام
import sys
# إضافة التبعيات المطلقة للمشروع
for directory in absolute_dependencies:
# التحقق من وجود المجلد
existe = os.path.exists(directory) and os.path.isdir(directory)
if not existe:
# إخطار المطور
raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
else:
# يتم إدراج المجلد في بداية مسار النظام
sys.path.insert(0, directory)
# يتم إجراء التهيئة
return {
"script_dir": script_dir,
}
- الأسطر 8-27: أصبحت جميع التبعيات الآن مرتبطة بالمتغير [script_dir] في السطر 5؛
- الأسطر 42-45: اختفى المتغير [root_dir] من تكوين syspath؛
- السطر 10: كيانات التطبيق موجودة في المجلد [entities] [1]؛
- السطر 12: الطبقة [dao] موجودة في المجلد [layers/dao] [2]؛
- السطر 14: الطبقة [métier] موجودة في المجلد [layers/métier] [2]؛
- السطر 16: الأدوات المساعدة [Logger, SendMail] موجودة في المجلد [utilities] [3]؛
- الأسطر 29-40: يتم حساب مسار Python للتطبيق دون استيراد الوحدة النمطية [myutils]؛

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

بمجرد تثبيت مُجمِّع 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:

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

في [2]، مجلد تثبيت Apache هو ما يسبق السلسلة [conf\httpd.conf].
لنعد إلى تثبيت الوحدة النمطية [mod_wsgi]:
- الأسطر 3-9: تثبيت الوحدة النمطية [mod_wsgi]؛
37.3.3. تكوين خادم Apache في Laragon
سنقوم بتكوين خادم Apache في Laragon. نبدأ بملف التكوين الرئيسي [httpd.conf]:

ننتقل إلى نهاية الملف [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:

- في [1-4]، نقوم بتنشيط بروتوكول HTTPS لخادم Apache؛
من الآن فصاعدًا، سيكون بإمكاننا استخدام URL و [https://serveur/chemin]؛
لتكوين Apache بحيث يقدم تطبيق Flask، يستخدم الرابط المشار إليه |أعلاه| خوادم افتراضية. يوفر Laragon أيضًا إمكانية إدارة الخوادم الافتراضية:

- في [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؛
على الرغم من أن هذا لا يدخل في نطاق ما نقوم به حاليًا، فقد تشعر بالفضول لمعرفة ماهية موقع الويب [projet-test] الذي أنشأناه للتو:

- في [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)://projet-test.test/chemin]؛
- الأسطر 4-12: تكوين الموقع الافتراضي للمنفذ 80 (السطر 4) وبروتوكول HTTP؛
- الأسطر 14-27: تكوين الموقع الافتراضي للمنفذ 443 (السطر 14) وبروتوكول HTTPS؛
لنرى كيف يعمل الخادم الافتراضي. لنقم أولاً بتشغيل خادم Apache و PHP:
ثم نطلب عبر متصفح الويب URL [http://projet-test.test/]:

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

- في [1-2]، نحصل على نفس الإجابة السابقة ولكن باستخدام البروتوكول HTTPS [1]؛
أدى إنشاء الخادم الافتراضي [projet-test.test] إلى إنشاء إدخال جديد في الملف [<windows>/system32/drivers/etc/hosts]:

- السطر 23: عنوان IP الخاص بالاسم [projet-test.test] هو 127.0.0.1، أي عنوان [localhost] (السطر 20)، الجهاز المحلي. وبالتالي، عندما يتم كتابة URL أو [http://projet-test.test/chemin] في متصفح، يتم إرسال الطلب إلى العنوان 127.0.0.1 على المنفذ 80. وعندها يقوم خادم Apache الخاص بالجهاز المحلي (localhost) بالرد.
قد يتساءل المرء عن سبب استخدام خادم Apache لإعدادات ملف [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf] عند كتابة الطلب [http://projet-test.test/]:

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

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

نتلقى نفس الصفحة التي نحصل عليها عند استخدام متصفح تقليدي. والآن لنلقِ نظرة على الحوار بين العميل والخادم في وحدة التحكم Postman (Ctrl-Alt-C):
- السطر 6: يحدد الرأس HTTP [Host] اسم الخادم الذي يستهدفه عميل الويب. هذا هو مبدأ الخوادم الافتراضية. في نفس العنوان IP (هنا 127.0.0.1)، يمكن لخادم الويب استضافة عدة مواقع ويب بأسماء مختلفة. تسمح الرأس HTTP [Host] للعميل بتحديد الخادم (هنا العنوان 127.0.0.1) الذي يتصل به؛
الآن، ماذا يفعل Apache؟
عند بدء تشغيله، يقرأ Apache جميع ملفات التكوين الموجودة في المجلد [[<laragon>\etc\apache2\sites-enabled]:

يحدد كل ملف تكوين خادمًا افتراضيًا. على سبيل المثال، في الملف [auto.projet-test.test.conf]، نجد السطر التالي:
define ROOT "C:/MyPrograms/laragon/www/projet-test/"
define SITE "projet-test.test"
…
السطر 2 يحدد الخادم الافتراضي [projet-test.test]. الملف [auto.projet-test.test.conf] هو ملف تكوين هذا الخادم الافتراضي. ونظرًا لأن خادم Apache يقرأ عند بدء التشغيل جميع ملفات التكوين الموجودة في المجلد [<laragon>\etc\apache2\sites-enabled]، فإنه يدرك وجود خادم افتراضي باسم [projet-test.test]. وبالتالي، عندما يتلقى من عميل Postman الطلب HTTPS:
، فإنه يدرك أن الطلب موجه إلى الخادم الافتراضي [projet-test.test] (السطر 6) وأن هذا الخادم موجود. ثم يستخدم تكوين الخادم الافتراضي [projet-test.test] للرد على عميل Postman.
37.4. إنشاء أول خادم افتراضي Apache
الآن بعد أن عرفنا الغرض من الخوادم الافتراضية وكيفية تعريفها، سنقوم بإنشاء واحدة. ستُستخدم هذه الخادم الافتراضي لتشغيل تطبيق Python Flask المثبت في مجلد [Apache] من الإصدار 17 الذي يجري نشره حاليًا على خادم Apache:
لقد وضعنا في المجلد [http-servers/12/apache/exemple] التطبيق الذي تم تطويره في الفقرة |رابط|، وهو خدمة ويب للتاريخ/الوقت:

الخادم [date_time_server.py] هو التالي:
# الاستيرادات
import os
import sys
import time
# علينا وضع مجلد الوحدات النمطية في مسار Python
# لنقل التطبيق إلى 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
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")
# الصفحة الرئيسية URL
@application.route('/')
def index():
# إرسال الوقت إلى العميل
# time.localtime: عدد الميلي ثانية منذ 01/01/1970
# time.strftime يسمح بتنسيق الوقت والتاريخ
# تنسيق عرض التاريخ والوقت
# d: اليوم برقمين
# m: الشهر برقمين
# y: السنة برقمين
# H: الساعة 0,23
# M: الدقائق
# S: الثواني
# التاريخ/الساعة الحالية
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
# يتم إنشاء المستند المراد إرساله إلى العميل
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# الرد HTTP إلى العميل
response = make_response(document)
print("response", type(response), response)
return response
# للاستخدام الداخلي فقط
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] ثم نقوم بتعديله.

سيكون ملف [date-time-server.conf] كما يلي:
# مجلد البرنامج النصي python-flask الخاص بالتطبيق
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"
# اسم موقع الويب الذي تم تكوينه بواسطة هذا الملف
# هنا سيُسمى date-time-server
# ستكون URL من النوع http(s)://date-time-server/المسار
define SITE "date-time-server"
# أدخل العنوان IP 127.0.0.1 للموقع SITE في c:/windows/system32/drivers/etc/hosts
# URL HTTP
<VirtualHost *:80>
# مع الاسم المستعار / ستكون URL على الشكل http(s)://date-time-server/chemin/...
WSGIScriptAlias / "${ROOT}/date_time_server.py"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL آمنة باستخدام HTTPS
<VirtualHost *:443>
# مع الاسم المستعار / ستكون URL على الشكل http(s)://date-time-server/chemin/...
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/chemin]؛
- في السطرين 14 و27، يمكننا تعيين جذر آخر للتطبيق، على سبيل المثال [WSGIScriptAlias /show]. وبذلك، ستصبح قيم URL الخاصة بالتطبيق على الشكل [http(s)://show/date-time-server/chemin]؛
علينا أيضًا إضافة سطر إلى الملف [<windows>/system32/drivers/etc/hosts]:
نضيف السطر 25، لإعطاء العنوان IP [127.0.0.1] إلى الخادم الافتراضي [date-time-server].
دعونا نتحقق من كل ذلك. نقوم بتشغيل خادم Apache:

ثم نطلب 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. لذا يجب إعادة تحميله:

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

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

- الملف [config] من [2] هو نفسه الملف [config] من [1]؛
- الملف [syspath] التابع لـ [2] هو نفسه الملف [syspath] التابع لـ [1]؛
- الملف [main_withmysql] من [2] هو الملف [main] من [1] مع التعديلات التالية:
كان البرنامج النصي الرئيسي [main] يتلقى معلمة [mysql / pgres] تحدد له ملف SGBD الذي يجب استخدامه. يستخدم البرنامج النصي [main_withmysql] البرنامج النصي SGBD و MySQL:
# ننتظر معلمة mysql أو pgres
import os
import sys
# يتم تكوين التطبيق باستخدام MySQL
import config
config = config.configure({'sgbd': "mysql"})
# التبعيات
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
في السطر 7، يتم تعيين SGBD إلى MySQL.
- الملف [main_withpgres] من [2] هو الملف [main] من [1] مع التعديلات التالية: يستخدم ملف SGBD ملف PostgreSQL:
# يُتوقع معلمة mysql أو pgres
import os
import sys
# يتم تكوين التطبيق باستخدام MySQL
import config
config = config.configure({'sgbd': "pgres"})
# التبعيات
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
…
في السطر 7، يتم تعيين SGBD إلى PostgreSQL.
بعد ذلك، نقوم بإنشاء البرنامج النصي [main_withmysql.wsgi] (لا يهم اللاحقة المستخدمة) على النحو التالي:
سيكون البرنامج النصي [main_withmysql.wsgi] هو الهدف الذي سيقوم خادم Apache بتنفيذه في وضع WSGI:
- كان من الممكن أن يكون الهدف لخادم Apache هو البرنامج النصي [main_withmysql.py] كما تم سابقًا مع البرنامج النصي [date_time_server.py]. لكن كان سيتعين تعديله قليلاً:
- على عكس وضع التنفيذ باستخدام برنامج نصي يعمل على وحدة التحكم، مع Apache، لا يُعد المجلد الذي يحتوي على الهدف [main_withmysql.py] جزءًا من مسار Python. ولذلك، فإن السطر 6 من البرنامج النصي [main_withmysql.py] يتسبب في حدوث خطأ؛
- التعديل الثاني الذي كان يجب إجراؤه هو أن تطبيق Flask في [main_withmysql] يُشار إليه بالمعرف [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]:
# مجلد هذا الملف
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
# نضيفه إلى syspath حتى يصبح الاستيراد التالي ممكنًا
import sys
sys.path.insert(0, script_dir)
# نقوم باستيراد تطبيق Flask [app] مع تسميته بـ [application]
from main_withpgres import app as application
لدينا الآن الأهداف القابلة للتنفيذ لخادم Apache. وعلينا الآن إنشاء خادمين افتراضيين، واحد لكل هدف.
في [<laragon>\etc\apache2\sites-enabled]، نقوم بإنشاء الملف [flask-impots-withmysql.conf] (لا يهم الاسم الذي نطلقه عليه):

# مجلد البرنامج النصي .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
# اسم الموقع الإلكتروني الذي تم تكوينه بواسطة هذا الملف
# هنا سيُسمى flask-impots-withmysql
# ستكون URL من النوع http(s)://flask-impots-withmysql/المسار
define SITE "flask-impots-withmysql"
# أدخل العنوان IP 127.0.0.1 للموقع SITE في الملف c:/windows/system32/drivers/etc/hosts
# أدخل هنا مسارات مكتبات Python المراد استخدامها - وافصل بينها بفواصل
# هنا مكتبات بيئة Python الافتراضية
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# موقع Python الرئيسي - ضروري فقط في حالة تثبيت عدة إصدارات من Python
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# مع الاسم المستعار / فإن URL ستكون على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، سيكون شكل URL كالتالي: /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL المؤمنة باستخدام HTTPS
<VirtualHost *:443>
# مع الاسم المستعار / ستكون URL على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، ستكون عناوين URL على الشكل /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في 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_witmysql.wsgi] الذي أنشأناه:
- السطر 7: سيُسمى الخادم الافتراضي [flask-impots-withmysql]؛
- السطر 13: تسمح التوجيهية [WSGIPythonPath] بإضافة مجلدات إلى مسار Python. هنا، لا يعلم Apache أننا استخدمنا بيئة افتراضية لتطوير التطبيق وأن جميع الوحدات النمطية التي يستخدمها التطبيق موجودة في هذه البيئة الافتراضية. لذلك، في السطر 13، نضيف المجلد الذي يحتوي على جميع الوحدات النمطية للبيئة الافتراضية المستخدمة. إحدى الطرق الممكنة هي نسخ هذا المجلد إلى مكان آخر في نظام الملفات والإشارة إلى هذا المكان. وهناك طريقة أخرى تتمثل في إضافة هذا المجلد إلى مسار Python مباشرةً في الهدف [main_witmysql.wsgi] (وهذا على الأرجح الحل الأفضل)؛
- السطر 16: يمكننا توجيه Apache إلى مجلد تثبيت Python في نظام الملفات. عادةً ما يكون هذا المجلد موجودًا في PATH الخاص بالجهاز، وغالبًا ما يكون هذا السطر غير ضروري (كما كان الحال هنا). ومع ذلك، قد يكون هناك عدة تثبيتات لـ Python على الجهاز، وقد لا يكون التثبيت المطلوب موجودًا في PATH الخاص بالجهاز. لذا، فإن هذا السطر يحل المشكلة؛
نقوم بنفس الطريقة بإنشاء ملف [flask-impots-withpgres.conf]:
# مجلد البرنامج النصي .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
# اسم موقع الويب الذي تم تكوينه بواسطة هذا الملف
# هنا سيُسمى flask-impots-withmysql
# ستكون عناوين URL من النوع http(s)://flask-impots-withmysql/المسار
define SITE "flask-impots-withpgres"
# أدخل العنوان IP 127.0.0.1 للموقع SITE في الملف c:/windows/system32/drivers/etc/hosts
# أدخل هنا مسارات مكتبات Python المراد استخدامها - وافصل بينها بفواصل
# هنا مكتبات بيئة Python الافتراضية
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# موقع Python الرئيسي - ضروري فقط في حالة تثبيت عدة إصدارات من Python
# WSGIPythonHome "C:/Program Files/Python38"
# URL HTTP
<VirtualHost *:80>
# مع الاسم المستعار / ستكون URL على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، سيكون شكل URL كالتالي: /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
DocumentRoot "${ROOT}"
ServerName ${SITE}
ServerAlias *.${SITE}
<Directory "${ROOT}">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# URL المؤمنة باستخدام HTTPS
<VirtualHost *:443>
# مع الاسم المستعار / ستكون URL على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، ستكون عناوين URL على الشكل /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في 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 وملفات SGBD وMySQL وPostgreSQL. يتم تكوين التطبيق باستخدام البادئة URL و[/do] و[with_csrftoken=False] (لا يوجد رمز CSRF) في [configs/parameters.py]. نطلب URL و [https://flask-impots-withmysql/do]. رد الخادم هو كما يلي:

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

يعمل كلا التطبيقين بشكل طبيعي.
الآن لنقوم بتعديل المعلمة [WSGIScriptAlias] إلى [flask-impots-withmysql.conf]:
# مجلد البرنامج النصي .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
…
# URL HTTP
<VirtualHost *:80>
# مع الاسم المستعار / ستكون URL على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، سيكون شكل URL كالتالي: /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
# URL مؤمنة بواسطة HTTPS
<VirtualHost *:443>
# مع الاسم المستعار / أما URL فستكون على الشكل /{prefixe_url}/action/...
# مع الاسم المستعار /impots، سيكون شكل URL كما يلي: /impots/{prefixe_url}/action/...
# حيث يتم تعريف [prefixe_url] في parameters.py
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
- في السطرين 11 و20، أصبح الاسم المستعار WSI هو [/impots]؛
نقوم بإيقاف / إعادة تشغيل خادم Apache ثم نطلب URL [https://flask-impots-withmysql/impots/do]. رد الخادم هو كما يلي:

حدث عطل. يوضح لنا 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):
# مسارات تطبيق Flask
# جذر التطبيق
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):
# جذر التطبيق
def index() -> tuple:
# إعادة التوجيه إلى /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
في السطر 4، يتم إعادة توجيهنا إلى 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):
# init-session
def init_session(type_response: str) -> tuple:
# يتم تنفيذ وحدة التحكم المرتبطة بالإجراء
return front_controller()
في السطر 4، تبدأ سلسلة معالجة الإجراء [init-session]. تنتهي هذه السلسلة على النحو التالي في [responses/HtmlResponse]:
…
# الآن يجب إنشاء رمز إعادة التوجيه URL دون نسيان الرمز المميز CSRF إذا طُلب
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# استجابة إعادة التوجيه
return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND
الإجراء [init-session] هو إجراء ADS (إجراء Do Something) ينتهي بإعادة توجيه إلى عرض، السطر 9. وهنا تكمن المشكلة. لا تضيف الدالة [redirect] في السطر 9 تلقائيًا الاسم المستعار WSGI إلى عملية إعادة التوجيه URL. وهذا ما توضحه لقطة الشاشة أعلاه. حيث ينقص الاسم المستعار /impots في كائن إعادة التوجيه URL.
تقدم النسخة التالية حلاً لمشكلة الاسم المستعار WSGI.


