Skip to content

37. Ćwiczenie praktyczne: wersja 17

Image

Ta nowa wersja wprowadza następujące zmiany:

  • zostanie ona przeniesiona na serwer Apache / Windows;
  • aby umożliwić tę migrację, wersja 17 zawiera wszystkie niezbędne zależności w folderze [impots / http-servers/ 12]. Przypominamy, że poprzednie wersje pobierały swoje zależności z różnych folderów w ramach całego projektu [python-flask-2020];

37.1. Przeniesienie zależności aplikacji

Image

Przypominamy, że zarządzanie zależnościami aplikacji odbywa się w skrypcie [syspath]. W poprzedniej wersji skrypt ten wyglądał następująco:


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

    # katalog tego pliku
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # ścieżka główna
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # zależności
    absolute_dependencies = [
        # foldery projektu
        # 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",
        # Stałe, przedziały
        f"{root_dir}/impots/v05/entities",
        # Rejestrator, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        # katalog głównego skryptu
        script_dir,
        # konfiguracje [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # kontrolery
        f"{script_dir}/../controllers",
        # odpowiedzi HTTP
        f"{script_dir}/../responses",
        # szablony widoków
        f"{script_dir}/../models_for_views",
    ]

    # ustawiamy ścieżkę systemową
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # udostępnianie konfiguracji
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }

Należy przenieść wszystkie zależności, których nazwa bezwzględna zależy od zmiennej [root_dir] z linii 8, tj. linie 13–26.

Skrypt [syspath] w nowej wersji będzie wyglądał następująco:


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

    # katalog tego pliku
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # zależności
    absolute_dependencies = [
        # elementy aplikacji
        f"{script_dir}/../entities",
        # warstwa [dao]
        f"{script_dir}/../layers/dao",
        # warstwa [métier]
        f"{script_dir}/../layers/métier",
        # narzędzia
        f"{script_dir}/../utilities",
        # folder głównego skryptu
        script_dir,
        # konfiguracje [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        # kontrolery
        f"{script_dir}/../controllers",
        # odpowiedzi HTTP
        f"{script_dir}/../responses",
        # szablony widoków
        f"{script_dir}/../models_for_views",
    ]

    # ustawiamy ścieżkę systemową
    import sys
    # dodajemy do niego bezwzględne zależności projektu
    for directory in absolute_dependencies:
        # sprawdzanie istnienia folderu
        existe = os.path.exists(directory) and os.path.isdir(directory)
        if not existe:
            # powiadamia się programistę
            raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
        else:
            # wstawia się folder na początek ścieżki syspath
            sys.path.insert(0, directory)

    # przywraca się konfigurację
    return {
        "script_dir": script_dir,
    }
  • wiersze 8–27: wszystkie zależności są teraz względne względem zmiennej [script_dir] z wiersza 5;
  • wiersze 42–45: zmienna [root_dir] zniknęła z konfiguracji ścieżki systemowej (syspath);
  • wiersz 10: elementy aplikacji znajdują się w folderze [entities] [1];
  • wiersz 12: warstwa [dao] znajduje się w folderze [layers/dao] [2];
  • wiersz 14: warstwa [métier] znajduje się w folderze [layers/métier] [2];
  • wiersz 16: narzędzia [Logger, SendMail] znajdują się w folderze [utilities] [3];
  • wiersze 29–40: oblicza się ścieżkę Python Path aplikacji bez importowania modułu [myutils];

Image

37.2. Testy

Na tym etapie wersja 17 powinna działać. Proszę to sprawdzić.

37.3. Przeniesienie aplikacji Python / Flask na serwer Apache / Windows

37.3.1. Źródła

Aby przeprowadzić przeniesienie aplikacji Flask na serwer Apache / Windows, musiałem poszukać informacji w Internecie. Oto link, który pomógł mi zacząć: [https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];

Skorzystałem z informacji zawartych w tym linku, z wyjątkiem konfiguracji serwera Apache. W tym przypadku wykorzystałem przykładową konfigurację serwera Apache z Laragon.

37.3.2. Instalacja modułu Python mod_wsgi

Opracowana przez nas aplikacja w języku Python / Flask korzystała z serwera WSGI (Web Server Gateway Interface) [werkzeug] dostarczanego wraz z Flaskiem. Serwer ten został opisany w |ici|. Link [https://www.fullstackpython.com/wsgi-servers.html] opisuje działanie serwerów WSGI. Istnieje kilka różnych serwerów |serveurs WSGI|. Jednym z nich jest serwer Apache działający w trybie WSGI. Jest to rozwiązanie zastosowane w tym przypadku, ponieważ zainstalowany przez nas Laragon zawiera serwer Apache.

Aby serwer Apache mógł obsługiwać aplikację w języku Python, musimy zainstalować moduł Python o nazwie [mod_wsgi]. Instalacja tego modułu jest skomplikowana, ponieważ w trakcie instalacji odbywa się kompilacja w języku C++. Aby instalacja przebiegła pomyślnie, potrzebny jest kompilator Microsoft C++. Prostym rozwiązaniem jest zainstalowanie aktualnej wersji Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].

Jeśli nie potrzebujemy programu Visual Studio do innych celów niż [mod_wsgi], możemy ograniczyć instalację do środowiska C++:

Image

Po zainstalowaniu kompilatora C++ instalacja modułu [mod_wsgi] odbywa się w terminalu 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
  • wiersz 1: ustalamy wartość zmiennej środowiskowej [MOD_WSGI_APACHE_ROOTDIR]. Wartość ta określa lokalizację serwera Apache w systemie plików. W tym przypadku jest to [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], gdzie <laragon> to folder instalacyjny Laragon. Lokalizację tę można uzyskać na różne sposoby. Oto przykład uzyskania jej za pomocą jednej z opcji Laragon:

Image

W pliku [1-3] plik [httpd.conf] jest głównym plikiem konfiguracyjnym serwera Apache. Plik ten otwiera się następnie w edytorze tekstowym (poniżej Notepad++):

Image

W pliku [2] katalog instalacyjny serwera Apache to część tekstu poprzedzająca ciąg znaków [conf\httpd.conf].

Wróćmy do instalacji modułu [mod_wsgi]:

  • wiersze 3–9: instalacja modułu [mod_wsgi];

37.3.3. Konfiguracja serwera Apache w Laragon

Skonfigurujemy serwer Apache w Laragon. Zaczynamy od jego głównego pliku konfiguracyjnego [httpd.conf]:

Image

Przechodzimy na koniec pliku [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"
  • Wiersz 8 został dodany do istniejącego pliku [httpd.conf] na końcu pliku. Wskazuje on serwerowi Apache, gdzie znajduje się element modułu [mod_wsgi], który właśnie zainstalowaliśmy;

Prostym sposobem na uzyskanie ścieżki do wiersza 8 jest wykonanie następującego polecenia w terminalu 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"

Niektóre dokumentacje podają, że należy dodać linie 2 i 3 na końcu pliku [httpd.conf]. W moim przypadku powyższa linia 3 powodowała błąd (brak modułu [encodings]). Dlatego nie została ona umieszczona w pliku [httpd.conf]. Umieszczono w nim jedynie wiersz 2. Znaczenie poszczególnych parametrów modułu [mod_wsgi], które można wykorzystać w plikach konfiguracyjnych serwera Apache, opisano w pliku |ici|.

Następnie aktywujemy protokół HTTPS na serwerze Apache:

Image

  • w pliku [1-4] włączamy protokół HTTPS serwera Apache;

Teraz będziemy mogli korzystać z URL i [https://serveur/chemin];

Aby skonfigurować serwer Apache do obsługi aplikacji Flask, link |plus haut| wykorzystuje serwery wirtualne. Laragon oferuje również możliwość zarządzania serwerami wirtualnymi:

Image

  • w [1-3] nakazujemy Laragonowi automatyczne utworzenie hostów wirtualnych;

Kolejnym krokiem jest utworzenie projektu internetowego za pomocą Laragona:

 
  • w pliku [1-3] tworzymy pusty projekt o nazwie PHP;
  • w [4-8], Laragon utworzył wirtualną witrynę o nazwie [auto.projet-test.test], skonfigurowaną przez plik [auto.projet-test.test.conf] [8] z folderu [sites-enabled] [7]. Folder ten znajduje się pod adresem [<laragon>\etc\apache2\sites-enabled], gdzie [laragon] to folder instalacyjny programu Laragon;

Chociaż nie ma to związku z tym, nad czym obecnie pracujemy, być może zechcesz z ciekawości zajrzeć na stronę internetową [projet-test], którą właśnie utworzyliśmy:

Image

  • W katalogu [1-5] utworzono pusty projekt. Jest to projekt o nazwie PHP znajdujący się w katalogu [<laragon>/www], gdzie [laragon] to katalog instalacyjny Laragon;

Przyjrzyjmy się teraz plikowi [auto.projet-test.test.conf] wygenerowanemu przez Laragon w folderze [<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>
  • wiersz 1: katalog główny utworzonego projektu w systemie plików;
  • wiersz 2: nazwa serwera wirtualnego. Pliki URL dla tego serwera będą miały postać [http(s)://projet-test.test/chemin];
  • wiersze 4–12: konfiguracja witryny wirtualnej dla portu 80 (wiersz 4) i protokołu HTTP;
  • wiersze 14–27: konfiguracja wirtualnej witryny dla portu 443 (wiersz 14) i protokołu HTTPS;

Zobaczmy, jak działa serwer wirtualny. Najpierw uruchommy serwer Apache i PHP:

Następnie za pomocą przeglądarki wywołujemy adres URL [http://projet-test.test/]:

Image

  • w [1], żądana strona URL;
  • na [2], zastosowano protokół HTTP;
  • w [3], ponieważ projekt [projet-test] jest pusty, otrzymujemy indeks jego folderu (listę zawartości), indeks pusty;

Teraz zażądajmy zabezpieczonego pliku URL z pliku [https://projet-test.test/]:

Image

  • w [1-2] otrzymujemy tę samą odpowiedź co poprzednio, ale z protokołem HTTPS [1];

Utworzenie serwera wirtualnego [projet-test.test] spowodowało dodanie nowego wpisu do pliku [<windows>/system32/drivers/etc/hosts]:

Image

# Copyright (c) 1993–2009 Microsoft Corp.
#
# Jest to przykładowy plik HOSTS używany przez program Microsoft TCP/IP dla systemu Windows.
#
# Ten plik zawiera mapowania adresów IP na nazwy hostów. Każdy
# pozycja powinna znajdować się w osobnym wierszu. Adres IP powinien
# znajdować się w pierwszej kolumnie, a po nim powinna następować odpowiednia nazwa hosta.
# Adres IP i nazwa hosta powinny być oddzielone co najmniej jedną
# spacji.
#
# Ponadto w poszczególnych
# wierszach lub po nazwie komputera, oznaczone symbolem „#”.
#
# Na przykład:
#
#       102.54.9    4.97 rhino.         acme.com # serwer źródłowy
#        38.25.    63.10 x             .acme.com # host klienta x

# rozpoznawanie nazwy localhost odbywa się wewnątrz samego DNS.
#     127.0.0.1       localhost
#     ::1             localhost

127.0.0.1      projet-test.test     #magia Laragona!   
  • wiersz 23: adres IP o nazwie [projet-test.test] to 127.0.0.1, czyli adres [localhost] (wiersz 20), czyli komputer lokalny. W związku z tym, gdy w przeglądarce wpisze się adres URL [http://projet-test.test/chemin], żądanie jest wysyłane na adres 127.0.0.1 na porcie 80. Odpowiada wówczas serwer Apache na komputerze lokalnym (localhost).

Można się zastanawiać, dlaczego po wpisaniu żądania [http://projet-test.test/] serwer Apache korzysta z konfiguracji zawartej w pliku [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Image

Aby to zrozumieć, należy sprawdzić, co przeglądarka wysyła do serwera Apache podczas wysyłania tego żądania. Wykonajmy je za pomocą Postmana:

Image

  • w przypadku [1-3] wysyłamy żądanie HTTPS [1];
  • przy [4] Postman informuje, że nie rozpoznał certyfikatu bezpieczeństwa. Protokół HTTPS ustanawia szyfrowane połączenie między klientem internetowym (w tym przypadku Postmanem) a serwerem Apache. To szyfrowane połączenie jest realizowane za pomocą certyfikatów wymienianych między klientem a serwerem. To serwer inicjuje proces nawiązywania szyfrowanego połączenia, wysyłając do klienta certyfikat bezpieczeństwa. Aby certyfikat ten został zaakceptowany przez klienta, musi być podpisany, czyli zakupiony od firm uprawnionych do wydawania certyfikatów bezpieczeństwa. Kiedy aktywowaliśmy protokół HTTPS w Laragonie, Laragon sam wygenerował certyfikat bezpieczeństwa. Mówimy wówczas, że certyfikat jest samopodpisany. Większość klientów internetowych wyświetla ostrzeżenie po otrzymaniu certyfikatu z podpisem własnym. Tak właśnie zachowuje się Postman w przypadku [4]. Większość klientów internetowych proponuje wówczas wyłączenie weryfikacji certyfikatu bezpieczeństwa przesłanego przez serwer. Taką opcję proponuje Postman w przypadku [5];

Klikamy na link [5], aby wyłączyć weryfikację SSL (Secure Sockets Layer). SSL / TSL (Transport Layer Security) to protokół bezpieczeństwa, który tworzy bezpieczny kanał komunikacyjny między dwoma komputerami w Internecie. Jest to protokół używany w tym przypadku przez serwer Apache. Odpowiedź wygląda następująco:

Image

Otrzymujemy tę samą stronę, co w przypadku tradycyjnej przeglądarki. Teraz przyjrzyjmy się dialogowi klient–serwer w konsoli 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
  • wiersz 6: nagłówek HTTP [Host] określa nazwę serwera, do którego kieruje się klient sieciowy. Na tej zasadzie działają serwery wirtualne. Pod tym samym adresem IP (w tym przypadku 127.0.0.1) serwer WWW może obsługiwać wiele stron internetowych o różnych nazwach. Nagłówek HTTP [Host] pozwala klientowi wskazać, do którego serwera (w tym przypadku o adresie 127.0.0.1) kieruje żądanie;

Co teraz robi Apache?

Po uruchomieniu Apache odczytuje wszystkie pliki konfiguracyjne znajdujące się w folderze [[<laragon>\etc\apache2\sites-enabled]:

Image

Każdy plik konfiguracyjny definiuje serwer wirtualny. Na przykład w pliku [auto.projet-test.test.conf] znajduje się następujący wiersz:


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

Wiersz 2 definiuje serwer wirtualny [projet-test.test]. Plik [auto.projet-test.test.conf] zawiera konfigurację tego serwera wirtualnego. Ponieważ serwer Apache podczas uruchamiania odczytuje wszystkie pliki konfiguracyjne z folderu [<laragon>\etc\apache2\sites-enabled], wie on o istnieniu serwera wirtualnego o nazwie [projet-test.test]. W związku z tym, gdy otrzymuje od klienta Postman żądanie o nazwie HTTPS:

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

rozpoznaje, że żądanie jest skierowane do serwera wirtualnego [projet-test.test] (wiersz 6) i że serwer ten istnieje. Następnie wykorzystuje konfigurację serwera wirtualnego [projet-test.test], aby odpowiedzieć klientowi Postman.

37.4. Tworzenie pierwszego wirtualnego serwera Apache

Teraz, gdy wiemy już, do czego służą serwery wirtualne i jak je definiować, utworzymy jeden z nich. Będzie on służył do uruchamiania aplikacji Python Flask zainstalowanej w folderze [Apache] w wersji 17, która jest obecnie wdrażana na serwerze Apache:

W folderze [http-servers/12/apache/exemple] umieściliśmy aplikację opracowaną w akapicie |lien|, czyli serwis internetowy wyświetlający datę i godzinę:

Image

Serwer [date_time_server.py] wygląda następująco:


# importy
import os
import sys
import time

# musimy dodać folder modułów do ścieżki Python Path
# w celu przeniesienia na Apache w systemie 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

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

# Strona główna URL
@application.route('/')
def index():
    # przesyłanie godziny do klienta
    # time.localtime: liczba milisekund od 01.01.1970
    # time.strftime umożliwia formatowanie godziny i daty
    # format wyświetlania daty i godziny
    # d: dzień w formacie dwucyfrowym
    # m: miesiąc (2 cyfry)
    # y: rok (2 cyfry)
    # H: godzina 0,23
    # M: minuty
    # S: sekundy

    # data / godzina bieżąca
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    # generowany jest dokument do wysłania do klienta
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    # odpowiedź HTTP dla klienta
    response = make_response(document)
    print("response", type(response), response)
    return response

# tylko główna
if __name__ == '__main__':
    application.config.update(ENV="development", DEBUG=True)
    application.run()

Aplikacja Flask jest odwołana za pomocą identyfikatora [application] (wiersze 14, 43, 44). Ta nazwa jest obowiązkowa. Jeśli odwołamy się do aplikacji Flask przy użyciu innego identyfikatora, aplikacja nie będzie działać i wyświetli komunikat o błędzie informujący, że nie można znaleźć żądanego identyfikatora URL. Komunikat ten nie zawiera żadnych wskazówek dotyczących źródła błędu. Należy zatem zachować ostrożność w tej kwestii.

Plik HTML, do którego odwołuje się wiersz 34, wygląda następująco:


<!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] będzie serwerem wirtualnym, na którym będzie hostowana ta aplikacja. Zostanie on skonfigurowany za pomocą pliku [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (przypominamy, że nazwa ta jest dowolna – Apache odczytuje wszystkie pliki znajdujące się w katalogu [sites-enabled] w celu wykrycia hostowanych witryn wirtualnych);

Plik ten uzyskujemy najpierw poprzez skopiowanie pliku [auto.projet-test.test.conf], a następnie go modyfikujemy.

Image

Plik [date-time-server.conf] będzie wyglądał następująco:


# katalog skryptu Python-Flask aplikacji
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"

# nazwa strony internetowej skonfigurowanej przez ten plik
# w tym przypadku nazwa ta będzie brzmiała „date-time-server”
# adresy typu URL będą miały postać http(s)://date-time-server/ścieżka
define SITE "date-time-server"

# należy wpisać adres IP 127.0.0.1 dla strony SITE w pliku c:/windows/system32/drivers/etc/hosts

# URL HTTP
<VirtualHost *:80>
    # z aliasem / adresy URL będą miały postać http(s)://serwer-czasowy/ścieżka/...
    WSGIScriptAlias / "${ROOT}/date_time_server.py"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# URL zabezpieczone za pomocą HTTPS
<VirtualHost *:443>
    # z aliasem / URL będą miały postać http(s)://serwer-daty-i-czasu/ścieżka/...
    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>
  • wiersz 7: nadajemy nazwę serwerowi wirtualnemu skonfigurowanemu przez ten plik;
  • wiersz 2: podajemy wartość zmiennej [ROOT] użytej w wierszach 14 i 27;
  • wiersze 14 i 27: podaje się ścieżkę do skryptu w języku Python, który ma zostać uruchomiony, gdy serwer wirtualny otrzyma żądanie. W tym miejscu określa się, że żądania kierowane do serwera [date-time-server] są przetwarzane przez skrypt w języku Python o nazwie [date_time_server.py]. Różnica w stosunku do pliku [auto.projet-test.test.conf] wynika z faktu, że plik ten konfigurował serwer PHP, podczas gdy plik [date-time-server.conf] konfiguruje serwer Python;
  • wiersze 14 i 27: atrybut [WSGIScriptAlias /] wskazuje tutaj, że katalogiem głównym serwera [date-time-server] będzie [/]. W ten sposób elementy URL w aplikacji będą miały postać [http(s)://date-time-server/chemin];
  • w wierszach 14 i 27 można nadać aplikacji inny katalog główny, na przykład [WSGIScriptAlias /show]. Wówczas elementy URL w aplikacji będą miały postać [http(s)://show/date-time-server/chemin];

Musimy również dodać wiersz do pliku [<windows>/system32/drivers/etc/hosts]:

# Copyright (c) 1993–2009 Microsoft Corp.
#
# Jest to przykładowy plik HOSTS używany przez program Microsoft TCP/IP dla systemu Windows.
#
# Ten plik zawiera mapowania adresów IP na nazwy hostów. Każdy
#powinien znajdować się w osobnym wierszu. Adres IP powinien
# znajdować się w pierwszej kolumnie, a po nim powinna następować odpowiednia nazwa hosta.
# Adres IP i nazwa hosta powinny być oddzielone co najmniej jedną
# spacji.
#
# Ponadto w poszczególnych
# wierszach lub po nazwie komputera, oznaczone symbolem „#”.
#
# Na przykład:
#
#       102.54.9    4.97 rhino.         acme.com # serwer źródłowy
#        38.25.    63.10 x             .acme.com # host klienta x

# rozpoznawanie nazwy localhost odbywa się wewnątrz samego DNS.
#     127.0.0.1       localhost
#     ::1             localhost

127.0.0.1    flask-impots-withmysql        # kurs języka Python – aplikacja podatkowa z wykorzystaniem MySQL
127.0.0.1    flask-impots-withpgres        # kurs Pythona – aplikacja podatkowa z wykorzystaniem PostgreSQL
127.0.0.1    date-time-server            # kurs Pythona – aktualna godzina i data
127.0.0.1           projet-test.test                #magia Laragona!

Dodajemy wiersz 25, aby przypisać adresy IP i [127.0.0.1] do serwera wirtualnego [date-time-server].

Sprawdźmy to wszystko. Uruchamiamy serwer Apache:

Image

Następnie w przeglądarce wpisujemy adres URL [https://date-time-server]:

Image

  • w [1] wyświetla się żądana strona URL;
  • w [3] – odpowiedź serwera;
  • W pliku [2] przeglądarka sygnalizuje, że połączenie HTTPS nie jest bezpieczne, ponieważ wykryła, że certyfikat przesłany przez serwer Apache był certyfikatem z podpisem własnym;

Teraz w pliku [date-time-server.conf] dodajmy alias w wierszach 14 i 27:


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

Zmiana nie jest od razu uwzględniana przez serwer Apache. Należy go ponownie załadować:

Image

Następnie wysyłamy żądanie do serwera o pliki URL i [https://date-time-server/show-date-time]. Odpowiedź serwera jest następująca:

Image

37.5. Portowanie aplikacji do obliczania podatku na Apache / Windows

Folder [apache] [2] uzyskuje się początkowo poprzez skopiowanie folderu [main]. Ważne jest, aby znajdowały się one na tym samym poziomie, aby ścieżki skryptu [syspath.py], skopiowanego z [1] do [2], pozostały prawidłowe. Aby nie zakłócać działania działającej aplikacji [impots / http-servers/ 12], umieszczamy w [apache] konfigurację, która będzie wykonywana przez serwer Apache;

Image

  • plik [config] z [2] jest taki sam jak [config] z [1];
  • plik [syspath] z [2] jest taki sam jak plik [syspath] z [1];
  • plik [main_withmysql] z [2] to plik [main] z [1] z następującymi zmianami:

Główny skrypt [main] otrzymywał parametr [mysql / pgres], który określał, który plik SGBD ma zostać użyty. Skrypt [main_withmysql] wykorzystuje skrypty SGBD i MySQL:


# oczekuje się parametru mysql lub pgres
import os
import sys

# konfigurujemy aplikację za pomocą MySQL
import config
config = config.configure({'sgbd'"mysql"})

# zależności
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

W wierszu 7 ustawia się SGBD na MySQL.

  • Plik [main_withpgres] z pliku [2] to plik [main] z pliku [1] z następującymi zmianami: wykorzystuje plik SGBD oraz PostgreSQL:

# oczekuje się parametru mysql lub pgres
import os
import sys

# konfiguruje się aplikację za pomocą MySQL
import config
config = config.configure({'sgbd'"pgres"})

# zależności
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

W wierszu 7 zmieniamy SGBD na PostgreSQL.

Po wykonaniu tych czynności należy utworzyć skrypt o nazwie [main_withmysql.wsgi] (końcówka nazwy nie ma znaczenia) o następującej treści:

# katalog tego pliku
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

# dodajemy go do ścieżki systemowej, aby umożliwić wykonanie poniższego importu
import sys
sys.path.insert(0, script_dir)

# importujemy aplikację Flask [app], nadając jej nazwę [application]
from main_withmysql import app as application

Skrypt [main_withmysql.wsgi] będzie plikiem docelowym uruchamianym przez serwer Apache w trybie WSGI:

  • celem serwera Apache mógłby być skrypt [main_withmysql.py], tak jak to miało miejsce wcześniej w przypadku skryptu [date_time_server.py]. Trzeba by go jednak nieco zmodyfikować:
    • w przeciwieństwie do trybu wykonywania za pomocą skryptu konsolowego, w przypadku serwera Apache katalog zawierający cel [main_withmysql.py] nie jest częścią ścieżki Python Path. W związku z tym wiersz 6 skryptu [main_withmysql.py] powoduje błąd;
    • drugą zmianą, którą należało wprowadzić, jest to, że w pliku [main_withmysql] aplikacja Flask jest odwołana za pomocą identyfikatora [app]. Wiadomo, że w przypadku Apache / WSGI musi ona być również odwołana za pomocą identyfikatora [application];
  • zamiast modyfikować plik [main_withmysql.py], zmieniamy cel Apache’a. Od tej pory będzie to powyższy skrypt [main_withmysql.wsgi]:
    • wiersze 1–7: dodajemy folder ze skryptem do ścieżki Python Path. Dzięki temu wiersz 6 pliku [main_withmysql.py] nie powoduje już błędu;
    • wiersze 9–10: import pliku [main_withmysql.py] powoduje jego wykonanie. Ponadto odwołujemy się do aplikacji Flask [app] znajdującej się w [main_withmysql.py] za pomocą identyfikatora [application], którego potrzebuje Apache w trybie WSGI;

To samo dotyczy skryptu [main_withpgres.wsgi]:


# katalog tego pliku
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

# dodajemy go do ścieżki syspath, aby umożliwić wykonanie poniższego importu
import sys
sys.path.insert(0, script_dir)

# importujemy aplikację Flask [app], nadając jej nazwę [application]
from main_withpgres import app as application

Mamy teraz pliki wykonywalne dla serwera Apache. Musimy teraz utworzyć dwa serwery wirtualne, po jednym dla każdego z nich.

W pliku [<laragon>\etc\apache2\sites-enabled] tworzymy plik [flask-impots-withmysql.conf] (nazwa nie ma znaczenia):

Image


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

# nazwa strony internetowej skonfigurowanej przez ten plik
# tutaj będzie to flask-impots-withmysql
# adresy typu URL będą miały postać http(s)://flask-impots-withmysql/ścieżka
define SITE "flask-impots-withmysql"

# w pliku c:/windows/system32/drivers/etc/hosts należy wpisać adres 127.0.0.1 dla witryny SITE

# wpisać tutaj ścieżki do bibliotek Python, które mają być używane – oddzielić je przecinkami
# tutaj należy wpisać biblioteki wirtualnego środowiska Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"

# Katalog główny Pythona – wymagane tylko wtedy, gdy zainstalowanych jest kilka wersji Pythona
# WSGIPythonHome „C:/Program Files/Python38”

# URL HTTP
<VirtualHost *:80>
    # z aliasem /, natomiast URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w parameters.py
    WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# URL zabezpieczone za pomocą HTTPS
<VirtualHost *:443>
    # z aliasem /, a URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, a adresy URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w 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>
  • wiersz 2: katalog główny aplikacji, folder [apache], który utworzyliśmy;
  • wiersze 23, 38: utworzony przez nas cel o nazwie [main_witmysql.wsgi]:
  • wiersz 7: serwer wirtualny będzie nosił nazwę [flask-impots-withmysql];
  • wiersz 13: dyrektywa [WSGIPythonPath] pozwala na dodanie folderów do ścieżki Python Path. W tym przypadku serwer Apache nie wie, że do tworzenia aplikacji wykorzystaliśmy środowisko wirtualne i że wszystkie moduły używane przez aplikację znajdują się w tym środowisku wirtualnym. Dlatego w wierszu 13 dodajemy folder zawierający wszystkie moduły z używanego środowiska wirtualnego. Jedną z możliwości jest skopiowanie tego katalogu w inne miejsce w systemie plików i podanie odnośnika do tej lokalizacji. Inną możliwością jest dodanie tego katalogu do ścieżki Python Path bezpośrednio w docelowym pliku [main_witmysql.wsgi] (jest to prawdopodobnie lepsze rozwiązanie);
  • wiersz 16: można wskazać serwerowi Apache katalog instalacyjny Pythona w systemie plików. Zazwyczaj znajduje się on w katalogu PATH na komputerze i często ten wiersz jest zbędny (tak było w tym przypadku). Może jednak istnieć kilka instalacji Pythona na komputerze, a ta, której szukamy, nie znajduje się w katalogu PATH na komputerze. Wówczas ten wiersz rozwiązuje problem;

W ten sam sposób tworzymy plik [flask-impots-withpgres.conf]:


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

# nazwa strony internetowej skonfigurowanej przez ten plik
# tutaj będzie to flask-impots-withmysql
# adresy typu URL będą miały postać http(s)://flask-impots-withmysql/ścieżka
define SITE "flask-impots-withpgres"

# w pliku hosts w katalogu c:/windows/system32/drivers/etc/hosts należy wpisać adres 127.0.0.1 dla strony SITE

# wpisać tutaj ścieżki do bibliotek Python, które mają być używane – oddzielić je przecinkami
# tutaj należy wpisać biblioteki wirtualnego środowiska Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"

# Katalog główny Pythona – wymagane tylko w przypadku zainstalowania wielu wersji Pythona
# WSGIPythonHome „C:/Program Files/Python38”

# URL HTTP
<VirtualHost *:80>
    # z aliasem /, natomiast URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w parameters.py
    WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# URL zabezpieczone za pomocą HTTPS
<VirtualHost *:443>
    # z aliasem /, a URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, a adresy URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w 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>

Zapisujemy wszystkie te pliki, uruchamiamy serwer Apache oraz pliki SGBD, MySQL i PostgreSQL. Aplikacja jest skonfigurowana z prefiksem URL, [/do] i [with_csrftoken=False] (brak tokenu CSRF) w [configs/parameters.py]. Żądamy URL oraz [https://flask-impots-withmysql/do]. Odpowiedź serwera jest następująca:

Image

Teraz wysyłamy żądanie dotyczące URL i [https://flask-impots-pgres/do]. Odpowiedź brzmi następująco:

Image

Obie aplikacje działają normalnie.

Teraz zmieńmy parametr [WSGIScriptAlias] na [flask-impots-withmysql.conf]:


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



# URL HTTP
<VirtualHost *:80>
    # z aliasem /, a adresy URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    …
</VirtualHost>

# URL zabezpieczone za pomocą HTTPS
<VirtualHost *:443>
    # z aliasem /, natomiast URL będą miały postać /{prefixe_url}/action/...
    # z aliasem /impots, a URL będą miały postać /impots/{prefixe_url}/action/...
    #, gdzie [prefixe_url] jest zdefiniowane w parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    …

</VirtualHost>
  • w wierszach 11 i 20 alias WSI to teraz [/impots];

Zatrzymujemy i ponownie uruchamiamy serwer Apache, a następnie wysyłamy żądanie dotyczące URL i [https://flask-impots-withmysql/impots/do]. Odpowiedź serwera jest następująca:

Image

Wystąpiła awaria. Kod błędu URL [1] wskazuje jej przyczynę. Powinien on brzmieć [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]. Brakuje aliasu WSGI. Jest to błąd naszej aplikacji. Aplikacja potrafi obsłużyć prefiks URL (/do jest obecne). Można by pomyśleć, że dodanie prefiksu [/impots/do] do naszej aplikacji rozwiązałoby poprzedni problem. Jednak nie. Pojawiają się wtedy inne rodzaje problemów. Alias WSGI nie zachowuje się jak prefiks URL.

Spróbujmy zrozumieć, co się stało. Poprosiliśmy o URL i [https://flask-impots-withmysql/impots/do]. Spodziewaliśmy się, że pojawi się ekran uwierzytelniania. W przypadku [1], jak widać powyżej, aplikacja zażądała wyświetlenia tego ekranu, ale nie dla właściwego URL. Przyjrzyjmy się przebiegowi żądania [https://flask-impots-withmysql/impots/do].

Najpierw wykonano następującą trasę (configs/routes.py):


    # ścieżki aplikacji Flask
    # katalog główny aplikacji
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

W naszym przykładzie ścieżka linii 3 to [https://flask-impots-withmysql/impots/do]. Widać, że z trasy usunięto część [https://flask-impots-withmysql/impots], w wyniku czego pozostała tylko część [/do]. W przypadku części [https://flask-impots-withmysql] jest to normalne, ponieważ nazwa serwera nie jest uwzględniona w trasie. Widać jednak, że nie uwzględnia ona również aliasu WSGI [/impots]. To ważna kwestia. Nawet w przypadku aliasu WSGI nasze początkowe trasy pozostają ważne.

Zobaczmy teraz, co robi funkcja [index] z linii 4 (configs/routes_without_csrftoken):


# katalog główny aplikacji
def index() -> tuple:
    # przekierowanie do /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

W wierszu 4 następuje przekierowanie do funkcji URL z funkcji [init_session]. W [configs/routes.py] funkcja ta została powiązana ze ścieżką [/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)

W wierszu 2 naszego testu [csrftoken_param] jest pustym ciągiem znaków. Aplikacja nie obsługuje tutaj tokenu CSRF.

Funkcja [init_session] jest zdefiniowana w następujący sposób (configs/routes_without_csrftoken):


# init-session
def init_session(type_response: str) -> tuple:
    # uruchamiany jest kontroler powiązany z akcją
    return front_controller()

W wierszu 4 rozpoczyna się łańcuch przetwarzania akcji [init-session]. Łańcuch ten kończy się w następujący sposób w [responses/HtmlResponse]:



# teraz należy wygenerować adres przekierowania URL, nie zapominając o tokenie CSRF, jeśli jest wymagany
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""

        # odpowiedź przekierowania
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

Akcja [init-session] jest akcją typu ADS (Action Do Something), która kończy się przekierowaniem do widoku (wiersz 9). Właśnie w tym miejscu leży problem. Funkcja [redirect] w wierszu 9 nie dodaje automatycznie aliasu WSGI do obiektu przekierowania URL. Widać to na powyższym zrzucie ekranu. W obiekcie przekierowania URL brakuje aliasu /impots.

Kolejna wersja rozwiązuje problem z aliasem WSGI.