38. تمرین عملی: نسخه ۱۸
38.1. پیادهسازی

فایل [impots/http-servers/13] در ابتدا با کپی کردن فایل [impots/http-servers/12] ایجاد شده و سپس به صورت جزئی اصلاح میشود.
ما با افزودن یک پارامتر جدید به فایل [configs/parameters] شروع میکنیم:
…
# توکن CSRF
"with_csrftoken": False,
# پایگاههای دادهٔ مدیریتشده: MySQL (MySQL)، PostgreSQL (PostgreSQL)
"databases": ["mysql", "pgres"],
# پیشوند برنامه برای URL
# اگر پیشوندی لازم نیست، رشته را خالی تنظیم کنید، در غیر این صورت /prefix
"prefix_url": "/do",
# آدرس ریشهای سرور آپاچی – اگر میخواهید اجرای خارج از آپاچی باشد، رشته را خالی تنظیم کنید
"application_root": "/impots"
…
در خط ۱۰، پارامتر [application_root] نمایندهٔ نام مستعار WSGI برای سرور مجازی آپاچی خواهد بود.
با این پارامتر، میتوانیم دستور [responses/HtmlResponse] را که باعث خطا شده بود، اصلاح کنیم:

…
#اکنون باید ریدایرکت URL را تولید کنید، بدون فراموش کردن توکن CSRF در صورت نیاز
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# پاسخ هدایت
return redirect(f"{config['parameters']['application_root']}{config['parameters']['prefix_url']}{ads['to']}{csrf_token}")
, status.HTTP_302_FOUND
- خط ۹: ریشهٔ برنامه را به ابتدای هدف هدایت URL اضافه کردهایم؛
ما همچنین باید تمام قطعات را اصلاح کنیم تا URL که در آنها وجود دارد با ریشهٔ برنامه (یا نام مستعار WSGI) شروع شود:

قطعه [v-authentification]
<!-- HTML فرم – مقادیر آن را با استفاده از اکشن QZXW2HTMLBW2F1dGhlbnRpZmllci-11dGlsaXNhdGV1cl0ZQX ارسال میکنیم -->
<form method="post" action="{{modèle.application_root}}{{modèle.prefix_url}}/authentifier-utilisateur{{modèle.csrf_token}}"
>
<!-- عنوان -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
قطعه [v-calcul-impot]
<!-- HTML فرم ارسال شد -->
<form method="post" action="{{modèle.application_root}}{{modèle.prefix_url}}/calculer-impot{{modèle.csrf_token}}">
<!-- پیامی در ۱۲ ستون روی پسزمینه آبی -->
…
</form>
قطعه [v-liste-simulations]
…
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
…
<!-- جدول شبیهسازی -->
<table class="table table-sm table-hover table-striped">
…
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="{{modèle.application_root}}{{modèle.prefix_url}}/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
قطعه [v-menu]
<!-- منوی بوتاسترپ -->
<nav class="nav flex-column">
<!--نمایش فهرستی از پیوندها HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{modèle.application_root}}{{modèle.prefix_url}}{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
تمام قطعات بالا از قالب [modèle.application_root] استفاده میکنند. در حال حاضر، کلید [application_root] در قالبهای تولیدشده توسط کلاسهای قالب وجود ندارد.

کلاس [AbstractBaseModelForView]، که کلاس والدین تمام کلاسهایی است که یک قالب تولید میکنند، به صورت زیر در میآید:
from abc import abstractmethod
from flask import Request
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy
from InterfaceModelForView import InterfaceModelForView
class AbstractBaseModelForView(InterfaceModelForView):
@abstractmethod
def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
pass
def update_model(self, modèle: dict, config: dict):
#محاسبه توکن CSRF
if config['parameters']['with_csrftoken']:
csrf_token = f"/{generate_csrf()}"
else:
csrf_token = ""
# بهروزرسانی قالب ارسالشده بهعنوان پارامتر
modèle.update({
#توکن CSRF
'csrf_token': csrf_token,
# prefix_url
'prefix_url': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJwcmVmaXhfdXJsIl0ZQX,
# application_root
'application_root': configQZXW2HTMLBWyJwYXJhbWV0ZXJzIl0ZQXQZXW2HTMLBWyJhcHBsaWNhdGlvbl9yb290Il0ZQX,
})
- خط ۱۵: متد [update_model] مسئول درج ویوها در قالب است:
- خط ۲۴: توکن CSRF;
- خط ۲۶: پیشوند برای URL;
- خط ۲۸: ریشهٔ برنامه یا نام مستعار WSGI;
چهار کلاس فرزند با استفاده از کد زیر کلاس والد را فراخوانی میکنند:
…
# اقدامات موجود در نما
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
#نهاییسازی مدل توسط کلاس والد
super().update_model(modèle, config)
# قالب رندر میشود
return modèle
- خط ۶: هر کلاس فرزند برای بهروزرسانی مدلی که ایجاد کرده است، کلاس والد خود را فراخوانی میکند؛
نسخهٔ ۱۸ آماده است. ما دو سرور مجازی آپاچی نسخهٔ ۱۷ را برداشته و آنها را اصلاح میکنیم:

دو فایل [flask-impots-withXX.conf] تنها در یک نقطه تغییر یافتهاند:
# دایرکتوری اسکریپت wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/13/apache"
# نام وبسایتی که توسط این فایل پیکربندی شده است
# در اینجا flask-impots-withmysql نامیده خواهد شد
# URL به شکل http(s)://flask-impots-withmysql/path خواهد بود
define SITE "flask-impots-withmysql"
#آدرس IP 127.0.0.1 را برای سایت SITE به c:/windows/system32/drivers/etc/hosts اضافه کنید
# مسیرهای کتابخانههای پایتون مورد استفاده را اینجا وارد کنید – آنها را با ویرگول جدا کنید
# کتابخانههای محیط مجازی پایتون باید اینجا قرار گیرند
WSGIPythonPath "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
# خانهٔ پایتون – تنها در صورتی لازم است که چندین نسخه از پایتون نصب شده باشد
# WSGIPythonHome "C:/Program Files/Python38"
#URL HTTP
<VirtualHost *:80>
# با نام مستعار /، آدرسهای URL به شکل /{prefixe_url}/action/... خواهند بود.
#با نام مستعار /impots، آدرسهای URL به شکل /impots/{prefixe_url}/action/... خواهند بود.
#جایی که [prefixe_url] در QZXW2HTMLCcGFyYW1ldGVycy.weQZQX تعریف شده است
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
…
</VirtualHost>
# URL با امنیت HTTPS
<VirtualHost *:443>
#با نام مستعار /، آدرسهای URL به شکل /{prefixe_url}/action/... خواهند بود.
# با نام مستعار /impots، آدرسهای URL به شکل /impots/{prefixe_url}/action/... خواهند بود.
#جایی که [prefixe_url] در QZXW2HTMLCcGFyYW1ldGVycy.weQZQX تعریف شده است
WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
..
</VirtualHost>
- در خط ۱۲، اکنون از پوشه [impots/http-servers/13/apache] استفاده میکنیم.
اکنون آماده آزمایش نسخه ۱۸ در آپاچی هستیم. پیکربندی به شرح زیر است:
- آلیاس WSGI در هر دو فایل پیکربندی سرور مجازی روی /impots تنظیم شده است؛
- در فایل پیکربندی [configs/parameters]، تنظیمات به شرح زیر است:
#توکن CSRF
"with_csrftoken": False,
# پیشوند برنامه برای URL
# اگر پیشوندی لازم نباشد، رشته را خالی تنظیم کنید، در غیر این صورت از /prefix استفاده کنید
"prefix_url": "/do",
# URL ریشهٔ سرور آپاچی – برای اجرای خارج از آپاچی روی رشتهٔ خالی تنظیم شود
"application_root": "/impots"
ما سرور آپاچی و دو فرآیند SGBD را راهاندازی میکنیم. ما فایلهای URL و [https://flask-impots-withmysql/impots/do] را درخواست میکنیم. پاسخ سرور به شرح زیر است:

ما واقعاً صفحه احراز هویت را میبینیم، که در نسخه قبلی قادر به دسترسی به آن نبودیم. بقیه برنامه به طور عادی کار میکند.
اکنون سرور مجازی دیگر را آزمایش میکنیم. ما URL و [https://flask-impots-withpgres/impots/do] را درخواست میکنیم. پاسخ سرور به شرح زیر است:

مفاهیم alias WSGI و پیشوند URL یک هدف را دنبال میکنند. یکی از این دو مفهوم زائد است. بنابراین، برای پیشوندگذاری URL از سرور آپاچی با رشته [/impots/do]، سه رویکرد ممکن وجود دارد:
۱ – [WGSIAlias /impots] و [prefix_url=’/do’]؛
۲ – [WGSIAlias /] و [prefix_url=’/impots/do’];
۳ – [WGSIAlias /impots/do] و [prefix_url=’’];
38.2. آزمایشهای کنسول
ما بار دیگر از تستهای کنسول برای مشتری [http-clients/09] استفاده میکنیم:

- URL سرور باید در پیکربندیهای [1] و [3] اصلاح شود؛
- باید تغییری در لایه [dao] ایجاد شود تا از پروتکل HTTPS سرور آپاچی پشتیبانی کند؛
در فایلهای [config]، URL سرور به شرح زیر تغییر مییابد:
"server": {
# "urlServer": "http://127.0.0.1:5000",
# "urlServer": "http://127.0.0.1:5000/do",
"urlServer": "https://flask-impots-withmysql/impots/do",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
…
}
},
# حالت اشکالزدایی
"debug": True,
# csrf_token
"with_csrftoken": False,
- خط ۴: URL جدید سرور. برای اولین بار در این سند، کلاینت از پروتکل HTTPS استفاده میکند؛
کلاس [ImpôtsDaoWihHttpSession] از لایه [dao] به شرح زیر تغییر میکند:
…
# مرحله درخواست/پاسخ
def get_response(self, method: str, url_service: str, data_value: dict = None, json_value=None):
# [method]: روش HTTP GET یا POST
#[url_service]: سرویس URL
# [data]: پارامترها برای POST در قالب x-www-form-urlencoded
# [json]: پارامترهای POST در قالب JSON
# [cookies]: کوکیهایی که باید در درخواست گنجانده شوند
#شما باید یک جلسه (XML یا JSON) داشته باشید؛ در غیر این صورت، قادر به پردازش پاسخ نخواهید بود
if self.__session_type not in ['json', 'xml']:
raise ImpôtsError(73, "il n'y a pas de session valide en cours")
#توکن CSRF را به توکن سرویس URL اضافه کنید
if self.__csrf_token:
url_service = f"{url_service}/{self.__csrf_token}"
#درخواست را اجرا کنید
response = requests.request(method,
url_service,
data=data_value,
json=json_value,
cookies=self.__cookies,
allow_redirects=True,
#برای پروتکل HTTPS
verify=False)
# حالت اشکالزدایی؟
if self.__debug:
#کاربر
if not self.__logger:
self.__logger = self.__config['logger']
# وارد شدن
self.__logger.write(f"{response.text}\n")
…
# نتیجه بازگردانده میشود
return résultat['réponse']
- در خط ۲۶، پارامتر [verify=False] به دلیل پروتکل HTTPS که توسط سرور آپاچی استفاده میشود، اضافه شده است. ماژول [requests] (خط ۱۹) بهطور بومی از پروتکل HTTPS پشتیبانی میکند. بهطور پیشفرض، اعتبار گواهی امنیتی ارسالشده از سوی سرور HTTPS را بررسی میکند و در صورت نامعتبر بودن گواهی دریافتی، یک استثنا ایجاد میکند. در اینجا نیز همین وضعیت وجود دارد، جایی که سرور لارگون آپاچی یک گواهی خودامضا ارسال میکند. برای جلوگیری از این خطا، از پارامتر [verify=False] استفاده میکنیم تا به ماژول [requests] دستور دهیم که خطا را پرتاب نکند. سپس [requests] به سادگی یک هشدار را روی کنسول نمایش میدهد.
پس از اعمال این تغییرات، تمام تستهای کنسول باید به درستی کار کنند.