38. Практичне завдання: версія 18
38.1. Реалізація

Файл [impots/http-servers/13] спочатку створюється шляхом копіювання файлу [impots/http-servers/12], а потім частково модифікується.
Почнемо з додавання нового параметра у файл [configs/parameters]:
…
# токен CSRF
"with_csrftoken": False,
# керовані бази даних MySQL (mysql), PostgreSQL (pgres)
"databases": ["mysql", "pgres"],
# префікс URL додатка
# вказати порожній рядок, якщо префікс не потрібен, або /префікс в іншому випадку
"prefix_url": "/do",
# кореневий URL-адресу сервера Apache — вкажіть порожній рядок для запуску поза Apache
"application_root": "/impots"
…
У рядку 10 параметр [application_root] буде представляти псевдонім WSGI віртуального сервера Apache.
За допомогою цього параметра ми можемо виправити інструкцію [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
- рядок 9: ми додали кореневий каталог додатка на початок адреси перенаправлення URL;
Також потрібно виправити всі фрагменти, щоб URL, які вони містять, починалися з кореневого каталогу додатка (або псевдоніма WSGI):

Фрагмент [v-authentification]
<!-- форма HTML — відправляємо її значення за допомогою дії [authentifier-utilisateur] -->
<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}}">
<!-- повідомлення у 12 стовпчиках на синьому тлі -->
…
</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]
<!-- меню Bootstrap -->
<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,
})
- рядок 15: метод [update_model] призначений для вставлення в шаблон переглядів:
- рядок 24: токен CSRF;
- рядок 26: префікс URL;
- рядок 28: корінь додатка або псевдонім WSGI;
Чотири дочірні класи викликають батьківський клас за допомогою такого коду:
…
# можливі дії з цього перегляду
modèle['actions_possibles'] = ["afficher-vue-authentification", "authentifier-utilisateur"]
# доопрацювання моделі батьківським класом
super().update_model(modèle, config)
# виведення моделі
return modèle
- рядок 6: кожен дочірній клас викликає свій батьківський клас для оновлення створеної ним моделі;
Версія 18 готова. Ми беремо два віртуальні сервери Apache з версії 17 і модифікуємо їх:

Обидва файли [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/шлях
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 Home — потрібно лише у разі, якщо встановлено кілька версій 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 /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>
- у рядку 12 тепер використовується папка [impots/http-servers/13/apache].
Ми готові до тестування версії 18, перенесеної на Apache. Конфігурація така:
- псевдонім WSGI — це /impots в обох файлах конфігурації віртуальних серверів;
- у файлі налаштувань [configs/parameters] параметри такі:
# токен CSRF
"with_csrftoken": False,
# префікс URL додатка
# встановити порожній рядок, якщо префікс не потрібен, або /префікс в іншому випадку
"prefix_url": "/do",
# кореневий URL-адресу сервера Apache — вкажіть порожній рядок для запуску поза Apache
"application_root": "/impots"
Запускаємо сервер Apache, а також обидва SGBD. Надіслали запит на URL та [https://flask-impots-withmysql/impots/do]. Відповідь сервера така:

Ми отримуємо вікно автентифікації, яке не вдалося відкрити в попередній версії. Решта додатка працює нормально.
Тепер перевіряємо інший віртуальний сервер. Надсилаємо запит на URL та [https://flask-impots-withpgres/impots/do]. Відповідь сервера така:

Поняття «псевдонім» WSGI та «префікс» URL виконують одну й ту саму роль. Одне з цих двох понять є зайвим. Отже, щоб додати префікс [/impots/do] до URL сервера Apache, можна скористатися трьома способами:
1 — [WGSIAlias /impots] та [prefix_url=’/do’];
2 — [WGSIAlias /] та [prefix_url=’/impots/do’];
3 — [WGSIAlias /impots/do] та [prefix_url=’’];
38.2. Тести консолі
Знову використовуються консольні тести клієнта [http-clients/09]:

- URL сервера потрібно змінити у конфігураціях [1] та [3];
- необхідно внести зміну до шару [dao], щоб він підтримував протокол HTTPS сервера Apache;
У файлах [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,
- рядок 4: новий 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]: файли cookie, які слід включити в запит
# потрібно мати сесію XML або JSON, інакше не вдасться обробити відповідь
if self.__session_type not in ['json', 'xml']:
raise ImpôtsError(73, "il n'y a pas de session valide en cours")
# до сервісного URL додається токен CSRF
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']
- у рядку 26 додається параметр [verify=False] через протокол HTTPS, який використовує сервер Apache. Модуль [requests] (рядок 19) підтримує протокол HTTPS на рівні ядра. За замовчуванням він перевіряє дійсність сертифіката безпеки, який надсилає йому сервер HTTPS, і генерує виняток, якщо отриманий сертифікат є недійсним. Саме це відбувається в даному випадку, коли сервер Apache Laragon надсилає самопідписаний сертифікат. Щоб уникнути винятку, використовується параметр [verify=False], який вказує модулю [requests] не генерувати виняток. У такому разі [requests] просто виводить попередження (warning) на консоль.
Після внесення цих змін усі консольні тести мають працювати.