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",
]
# встановлюємо syspath
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",
]
# встановлюємо syspath
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:
# вставляємо папку на початок syspath
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 Path додатка без імпорту модуля [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).
Можна задатися питанням, чому при введенні запиту [http://projet-test.test/] сервер Apache використовує конфігурацію з файлу [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Щоб це зрозуміти, потрібно подивитися, що браузер надсилає серверу Apache під час цього запиту. Зробимо це за допомогою Postman:

- у [1-3] надсилається запит HTTPS [1];
- при [4] Postman повідомляє, що не розпізнав сертифікат безпеки. Протокол HTTPS встановлює зашифроване з’єднання між веб-клієнтом (у даному випадку Postman) та сервером Apache. Це зашифроване з’єднання здійснюється за допомогою сертифікатів, якими обмінюються клієнт і сервер. Саме сервер ініціює діалог щодо встановлення зашифрованого з’єднання, надсилаючи клієнту сертифікат безпеки. Щоб цей сертифікат був прийнятий клієнтом, він має бути підписаний, тобто придбаний у компаній, уповноважених видавати сертифікати безпеки. Коли ми активували протокол HTTPS у Laragon, Laragon самостійно створив сертифікат безпеки. У такому випадку сертифікат називають самопідписаним. Більшість веб-клієнтів видають попередження, коли отримують самопідписаний сертифікат. Саме це робить Postman у випадку з [4]. Більшість веб-клієнтів пропонують тоді вимкнути перевірку сертифіката безпеки, надісланого сервером. Саме це пропонує Postman у випадку з [5];
Ми натискаємо на посилання [5], щоб вимкнути перевірку SSL (Secure Sockets Layer). 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 Path
# для перенесення на 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: місяць (2 цифри)
# y: рік у 2 цифри
# 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 Path. Тому рядок 6 скрипта [main_withmysql.py] викликає помилку;
- друга зміна, яку слід було б внести, полягає в тому, що у файлі [main_withmysql] на додаток Flask посилаються за ідентифікатором [app]. Відомо, що для Apache / WSGI вона також має бути вказана за ідентифікатором [application];
- замість того, щоб змінювати [main_withmysql.py], ми змінюємо ціль Apache. Відтепер нею буде наведений вище скрипт [main_withmysql.wsgi]:
- рядки 1–7: додаємо папку зі скриптом до Python Path. Відтак рядок 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 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 / "${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 Path. Тут Apache не знає, що ми використовували віртуальне середовище для розробки додатка і що всі модулі, які використовує додаток, знаходяться у цьому віртуальному середовищі. Тому в рядку 13 ми додаємо папку, яка містить усі модулі використовуваного віртуального середовища. Один із варіантів — скопіювати цю папку в інше місце файлової системи та вказати посилання на це місце. Інший варіант — додати цю папку до Python Path безпосередньо в цільовому об’єкті [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 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 / "${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 (Action Do Something), яка завершується перенаправленням до подання (view), рядок 9. Саме в цьому полягає проблема. Функція [redirect] у рядку 9 не додає автоматично псевдонім WSGI до об’єкта перенаправлення URL. Це видно на знімку екрана вище. У об’єкті перенаправлення URL відсутній псевдонім /impots.
Наступна версія вирішує проблему з псевдонімом WSGI.


