Skip to content

37. Praxisübung: Version 17

Image

Diese neue Version enthält folgende Änderungen:

  • Sie wird auf einen Apache/Windows-Server portiert;
  • Um diese Portierung durchzuführen, enthält Version 17 alle erforderlichen Abhängigkeiten im Ordner [impots/http-servers/12]. Beachten Sie, dass frühere Versionen ihre Abhängigkeiten aus verschiedenen Ordnern im gesamten [python-flask-2020]-Projekt bezogen haben;

37.1. Verschiebung der Anwendungsabhängigkeiten

Image

Beachten Sie, dass die Verwaltung der Anwendungsabhängigkeiten im Skript [syspath] erfolgt. In der vorherigen Version sah dieses Skript wie folgt aus:

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

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  root path
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    #  dependencies
    absolute_dependencies = [
        #  project files
        #  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",
        #  Constants, slices
        f"{root_dir}/impots/v05/entities",
        #  Logger, SendAdminMail
        f"{root_dir}/impots/http-servers/02/utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    #  we return the configuration
    return {
        "root_dir": root_dir,
        "script_dir": script_dir
    }

Wir müssen alle Abhängigkeiten verschieben, deren absoluter Pfad von der Variablen [root_dir] in Zeile 8 abhängt, d. h. die Zeilen 13–26.

Das Skript [syspath] für die neue Version sieht wie folgt aus:

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

    #  folder of this file
    script_dir = os.path.dirname(os.path.abspath(__file__))

    #  dependencies
    absolute_dependencies = [
        #  application entities
        f"{script_dir}/../entities",
        #  layer [dao]
        f"{script_dir}/../layers/dao",
        #  business] layer
        f"{script_dir}/../layers/métier",
        #  utilities
        f"{script_dir}/../utilities",
        #  main script folder
        script_dir,
        #  configs [database, layers, parameters, controllers, views]
        f"{script_dir}/../configs",
        #  controllers
        f"{script_dir}/../controllers",
        #  answers HTTP
        f"{script_dir}/../responses",
        #  view models
        f"{script_dir}/../models_for_views",
    ]

    #  set the syspath
    import sys
    #  we add the project's absolute dependencies
    for directory in absolute_dependencies:
        #  we check the existence of the file
        existe = os.path.exists(directory) and os.path.isdir(directory)
        if not existe:
            #  the developer is notified
            raise BaseException(f"[set_syspath] le dossier du Python Path [{directory}] n'existe pas")
        else:
            #  insert the folder at the beginning of the syspath
            sys.path.insert(0, directory)

    #  we return the configuration
    return {
        "script_dir": script_dir,
    }
  • Zeilen 8–27: Alle Abhängigkeiten beziehen sich nun auf die Variable [script_dir] in Zeile 5;
  • Zeilen 42–45: Die Variable [root_dir] wurde aus der syspath-Konfiguration entfernt;
  • Zeile 10: Die Anwendungsentitäten befinden sich im Ordner [entities] [1];
  • Zeile 12: Die [dao]-Ebene befindet sich im Ordner [layers/dao] [2];
  • Zeile 14: Die [business]-Schicht befindet sich im Ordner [layers/business] [2];
  • Zeile 16: Die Dienstprogramme [Logger, SendMail] befinden sich im Ordner [utilities] [3];
  • Zeilen 29–40: Der Python-Pfad der Anwendung wird berechnet, ohne das Modul [myutils] zu importieren;

Image

37.2. Tests

Zu diesem Zeitpunkt sollte Version 17 funktionieren. Überprüfen Sie dies.

37.3. Portierung einer Python/Flask-Anwendung auf einen Apache/Windows-Server

37.3.1. Quellen

Um eine Flask-Anwendung auf Apache/Windows zu portieren, musste ich im Internet recherchieren. Hier ist der Link, der mir den Einstieg erleichtert hat: [https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];

Ich habe die Informationen aus diesem Link verwendet, mit Ausnahme der Apache-Serverkonfiguration. Dafür habe ich eine Beispielkonfiguration für den Apache-Server von Laragon verwendet.

37.3.2. Installation des Python-Moduls mod_wsgi

Die von uns entwickelte Python/Flask-Anwendung nutzte den in Flask enthaltenen WSGI-Server (Web Server Gateway Interface) [werkzeug]. Dieser Server wird |hier| beschrieben. Der Link [https://www.fullstackpython.com/wsgi-servers.html] beschreibt, wie WSGI-Server funktionieren. Es gibt verschiedene |WSGI-Server|. Einer davon ist der Apache-Server, der im WSGI-Modus läuft. Dies ist die hier gewählte Lösung, da Laragon, das wir installiert haben, einen Apache-Server enthält.

Damit der Apache-Server eine Python-Anwendung hosten kann, müssen wir das Python-Modul [mod_wsgi] installieren. Die Installation dieses Moduls ist knifflig, da sie eine C++-Kompilierung erfordert. Um die Installation erfolgreich abzuschließen, benötigen Sie einen Microsoft C++-Compiler. Eine einfache Lösung ist die Installation der neuesten Version von Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].

Wenn Sie Visual Studio ausschließlich für [mod_wsgi] benötigen, können Sie die Installation auf die C++-Umgebung beschränken:

Image

Sobald der C++-Compiler installiert ist, wird das [mod_wsgi]-Modul in einem PyCharm-Terminal installiert:


(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
  • Zeile 1: Wir legen den Wert der Umgebungsvariablen [MOD_WSGI_APACHE_ROOTDIR] fest. Dieser Wert gibt den Speicherort des Apache-Servers im Dateisystem an. Hier lautet dieser Speicherort [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], wobei <laragon> der Laragon-Installationsordner ist. Sie können diesen Speicherort auf verschiedene Weise ermitteln. Hier ist ein Beispiel, das mit einer der Optionen von Laragon ermittelt wurde:

Image

In [1-3] ist die Datei [httpd.conf] die Hauptkonfigurationsdatei für den Apache-Server. Die betreffende Datei wird dann in einem Texteditor geöffnet (im folgenden Beispiel Notepad++):

Image

In [2] ist der Apache-Installationsordner der Teil, der der Zeichenfolge [conf\httpd.conf] vorangestellt ist.

Kehren wir nun zur Installation des Moduls [mod_wsgi] zurück:

  • Zeilen 3–9: Installation des Moduls [mod_wsgi];

37.3.3. Konfiguration des Laragon-Apache-Servers

Wir werden den Laragon-Apache-Server konfigurieren. Wir beginnen mit seiner Hauptkonfigurationsdatei [httpd.conf]:

Image

Wir gehen zum Ende der Datei [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"
  • Zeile 8 wurde am Ende der bestehenden [httpd.conf]-Datei hinzugefügt. Sie teilt dem Apache-Server mit, wo er eine Komponente des soeben installierten [mod_wsgi]-Moduls finden kann;

Eine einfache Möglichkeit, den Pfad für Zeile 8 zu ermitteln, besteht darin, den folgenden Befehl in einem PyCharm-Terminal auszuführen:


(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"

In einigen Dokumentationen wird angegeben, dass die Zeilen 2 und 3 am Ende der Datei [httpd.conf] hinzugefügt werden sollten. In meinem Fall verursachte die obige Zeile 3 einen Fehler (fehlendes [encodings]-Modul). Daher wurde sie nicht in die Datei [httpd.conf] aufgenommen. Es wurde nur Zeile 2 hinzugefügt. Die Bedeutungen der verschiedenen [mod_wsgi]-Modulparameter, die in Apache-Konfigurationsdateien verwendet werden können, sind |hier| beschrieben.

Als Nächstes aktivieren wir das HTTPS-Protokoll auf dem Apache-Server:

Image

  • In [1-4] aktivieren wir das HTTPS-Protokoll auf dem Apache-Server;

Ab sofort können wir [https://serveur/chemin]-URLs verwenden;

Um Apache für die Bereitstellung einer Flask-Anwendung zu konfigurieren, werden in dem oben genannten Link virtuelle Hosts verwendet. Laragon bietet ebenfalls Funktionen zur Verwaltung virtueller Hosts:

Image

  • In [1-3] weisen wir Laragon an, virtuelle Hosts automatisch zu erstellen;

Der nächste Schritt besteht darin, ein Webprojekt mit Laragon zu erstellen:

 
  • Erstellen Sie in [1-3] ein leeres PHP-Projekt;
  • In [4-8] hat Laragon eine virtuelle Website namens [auto.projet-test.test] erstellt, die durch die Datei [auto.projet-test.test.conf] [8] im Ordner [sites-enabled] [7] konfiguriert ist. Dieser Ordner befindet sich unter [<laragon>\etc\apache2\sites-enabled], wobei [laragon] der Installationsordner von Laragon ist;

Auch wenn dies nicht Teil unserer aktuellen Aufgabe ist, möchten Sie vielleicht einen Blick auf die soeben erstellte Website [test-project] werfen:

Image

  • In [1-5] wurde ein leeres Projekt erstellt. Es handelt sich um ein PHP-Projekt, das sich im Ordner [<laragon>/www] befindet, wobei [laragon] der Laragon-Installationsordner ist;

Sehen wir uns nun die von Laragon generierte Datei [auto.projet-test.test.conf] im Ordner [<laragon>\etc\apache2\sites-enabled] an:


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>
  • Zeile 1: das Stammverzeichnis des erstellten Projekts im Dateisystem;
  • Zeile 2: Der Name des virtuellen Servers. URLs für diesen Server haben das Format [http(s)://test-project.test/path];
  • Zeilen 4–12: Konfiguration der virtuellen Website für Port 80 (Zeile 4) und das HTTP-Protokoll;
  • Zeilen 14–27: Konfiguration des virtuellen Hosts für Port 443 (Zeile 14) und das HTTPS-Protokoll;

Schauen wir uns an, wie ein virtueller Server funktioniert. Starten wir zunächst die Apache- und PHP-Server:

Image

Anschließend rufen wir über einen Browser die URL [http://projet-test.test/] auf:

Image

  • in [1] die angeforderte URL;
  • in [2] wurde das HTTP-Protokoll verwendet;
  • in [3], da das Projekt [projet-test] leer ist, erhalten wir den Index seines Ordners (Liste seines Inhalts), einen leeren Index;

Nun fordern wir die sichere URL [https://projet-test.test/] an:

Image

  • In [1-2] erhalten wir dieselbe Antwort wie zuvor, jedoch unter Verwendung des HTTPS-Protokolls [1];

Durch die Erstellung des virtuellen Servers [test-project.test] wurde ein neuer Eintrag in der Datei [<windows>/system32/drivers/etc/hosts] erstellt:

Image


# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host
 
# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost
 
127.0.0.1      projet-test.test     #laragon magic!   
  • Zeile 23: Die IP-Adresse für den Namen [test-project.test] lautet 127.0.0.1, d. h. die Adresse von [localhost] (Zeile 20), dem lokalen Rechner. Wenn Sie also die URL [http://projet-test.test/chemin] in einen Browser eingeben, wird die Anfrage an die Adresse 127.0.0.1 auf Port 80 gesendet. Der Apache-Server auf dem lokalen Rechner (localhost) antwortet dann.

Sie fragen sich vielleicht, warum der Apache-Server bei der Eingabe der Anfrage [http://projet-test.test/] die Konfiguration aus der Datei [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf] verwendet:

Image

Um dies zu verstehen, müssen wir uns ansehen, was der Browser bei dieser Anfrage an den Apache-Server sendet. Machen wir das mit Postman:

Image

  • In [1-3] senden wir eine HTTPS-Anfrage [1];
  • in [4] zeigt Postman an, dass es das Sicherheitszertifikat nicht erkannt hat. Das HTTPS-Protokoll stellt eine verschlüsselte Verbindung zwischen dem Web-Client (hier Postman) und dem Apache-Server her. Diese verschlüsselte Verbindung wird durch Zertifikate hergestellt, die zwischen dem Client und dem Server ausgetauscht werden. Es ist der Server, der den Prozess zum Aufbau der verschlüsselten Verbindung initiiert, indem er ein Sicherheitszertifikat an den Client sendet. Damit dieses Zertifikat vom Client akzeptiert wird, muss es signiert sein – mit anderen Worten: von Unternehmen erworben werden, die zur Ausstellung von Sicherheitszertifikaten berechtigt sind. Als wir das HTTPS-Protokoll von Laragon aktiviert haben, hat Laragon das Sicherheitszertifikat selbst erstellt. Das Zertifikat wird dann als selbstsigniert bezeichnet. Die meisten Web-Clients geben eine Warnung aus, wenn sie ein selbstsigniertes Zertifikat erhalten. Genau das tut Postman in [4]. Die meisten Web-Clients bieten dann an, die Überprüfung des vom Server gesendeten Sicherheitszertifikats zu deaktivieren. Dies bietet Postman in [5] an;

Wir klicken auf den Link [5], um die SSL-Überprüfung (Secure Sockets Layer) zu deaktivieren. SSL/TLS (Transport Layer Security) ist ein Sicherheitsprotokoll, das einen sicheren Kommunikationskanal zwischen zwei Rechnern im Internet herstellt. Dies ist das Protokoll, das hier von Apache verwendet wird. Die Antwort lautet wie folgt:

Image

Wir erhalten dieselbe Seite wie mit einem herkömmlichen Browser. Sehen wir uns nun den Client-Server-Dialog in der Postman-Konsole (Strg-Alt-C) an:

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
  • Zeile 6: Der HTTP-Header [Host] gibt den Namen des Servers an, an den sich der Web-Client wendet. Dies ist das Prinzip hinter virtuellen Servern. Unter einer einzigen IP-Adresse (hier 127.0.0.1) kann ein Webserver mehrere Websites mit unterschiedlichen Namen hosten. Der HTTP-Header [Host] ermöglicht es dem Client anzugeben, welchen Server (hier den unter der Adresse 127.0.0.1) er anspricht;

Was macht Apache also?

Beim Start liest Apache alle Konfigurationsdateien, die sich im Verzeichnis [[<laragon>\etc\apache2\sites-enabled]] befinden:

Image

Jede Konfigurationsdatei definiert einen virtuellen Server. In der Datei [auto.projet-test.test.conf] finden Sie beispielsweise die folgende Zeile:


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

Zeile 2 definiert den virtuellen Server [test-project.test]. Die Datei [auto.projet-test.test.conf] enthält die Konfiguration für diesen virtuellen Server. Da der Apache-Server beim Start alle Konfigurationsdateien im Ordner [<laragon>\etc\apache2\sites-enabled] liest, weiß er, dass ein virtueller Server namens [projet-test.test] existiert. Wenn er also die folgende HTTPS-Anfrage vom Postman-Client erhält:

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

Es erkennt, dass die Anfrage an den virtuellen Server [test-project.test] gerichtet ist (Zeile 6) und dass dieser existiert. Anschließend verwendet es die Konfiguration des virtuellen Servers [test-project.test], um dem Postman-Client zu antworten.

37.4. Erstellen Ihres ersten virtuellen Apache-Servers

Nachdem wir nun wissen, wozu virtuelle Server dienen und wie man sie konfiguriert, werden wir einen erstellen. Er wird dazu verwendet, eine Python-Flask-Anwendung auszuführen, die in einem [Apache]-Ordner der Version 17 installiert ist, die derzeit auf dem Apache-Server bereitgestellt wird:

Wir haben die im Abschnitt |link| entwickelte Anwendung – einen Webdienst für Datum und Uhrzeit – im Ordner [http-servers/12/apache/example] abgelegt:

Image

Der Server [date_time_server.py] sieht wie folgt aus:

#  imports
import os
import sys
import time

#  we need to put the modules folder in the Python Path
#  for porting to 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 application
script_dir = os.path.dirname(os.path.abspath(__file__))
application = Flask(__name__, template_folder=f"{script_dir}")

#  Home URL
@application.route('/')
def index():
    #  dispatch time to customer
    #  time.localtime: number of milliseconds since 01/01/1970
    #  time.strftime formats time and date
    #  date-time display format
    #  d: 2-digit day
    #  m: 2-digit month
    #  y: 2-digit year
    #  H: hour 0.23
    #  M: minutes
    #  S: seconds

    #  current date / time
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    #  generate the document to be sent to the customer
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    #  HTTP response to customer
    response = make_response(document)
    print("response", type(response), response)
    return response

#  hand only
if __name__ == '__main__':
    application.config.update(ENV="development", DEBUG=True)
    application.run()

Die Flask-Anwendung wird durch den Bezeichner [application] referenziert (Zeilen 14, 43, 44). Dieser Name ist erforderlich. Wenn Sie die Flask-Anwendung mit einem anderen Bezeichner referenzieren, funktioniert die Anwendung nicht und zeigt eine Fehlermeldung an, die besagt, dass die angeforderte URL nicht gefunden werden kann. Diese Fehlermeldung gibt keinen Hinweis auf die Fehlerquelle. Sie müssen daher hier besonders vorsichtig sein.

Die in Zeile 34 referenzierte HTML-Datei lautet wie folgt:


<!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] ist der virtuelle Server, auf dem diese Anwendung gehostet wird. Er wird über die Datei [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] konfiguriert (beachten Sie, dass dieser Name beliebig ist – Apache liest alle Dateien in [sites-enabled], um die gehosteten virtuellen Sites zu ermitteln);

Wir erstellen diese Datei zunächst, indem wir die Datei [auto.projet-test.test.conf] kopieren und anschließend bearbeiten.

Image

Die Datei [date-time-server.conf] sieht wie folgt aus:

#  application python-flask script file
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache/exemple"

#  name of the website configured by this file
#  here it will be called date-time-server
#  the URL will be of type http(s)://date-time-server/path
define SITE "date-time-server"

#  set address IP 127.0.0.1 for site SITE in c:/windows/system32/drivers/etc/hosts

#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    WSGIScriptAlias / "${ROOT}/date_time_server.py"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will take the form http(s)://date-time-server/path/...
    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>
  • Zeile 7: Geben Sie dem durch die Datei konfigurierten virtuellen Server einen Namen;
  • Zeile 2: Legt den Wert der Variablen [ROOT] fest, die in den Zeilen 14 und 27 verwendet wird;
  • Zeilen 14 und 27: Geben Sie den Pfad zu dem Python-Skript an, das ausgeführt werden soll, wenn der virtuelle Server eine Anfrage erhält. Hier legen wir fest, dass Anfragen für den Server [date-time-server] vom Python-Skript [date_time_server.py] bearbeitet werden. Dieser Unterschied zur Datei [auto.projet-test.test.conf] ergibt sich daraus, dass in der erstgenannten Datei ein PHP-Server konfiguriert wurde, während die Datei [date-time-server.conf] einen Python-Server konfiguriert;
  • Zeilen 14 und 27: Das Attribut [WSGIScriptAlias /] legt hier fest, dass das Stammverzeichnis des Servers [date-time-server] [/] ist. Somit haben die URLs der Anwendung die Form [http(s)://date-time-server/path];
  • Zeilen 14 und 27: Wir können der Anwendung einen anderen Stammordner zuweisen, zum Beispiel [WSGIScriptAlias /show]. In diesem Fall haben die URLs der Anwendung die Form [http(s)://show/date-time-server/path];

Außerdem müssen wir eine Zeile zur Datei [<windows>/system32/drivers/etc/hosts] hinzufügen:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#    127.0.0.1       localhost
#    ::1             localhost

127.0.0.1    flask-impots-withmysql        # cours python - appli impôts avec MySQL
127.0.0.1    flask-impots-withpgres        # cours python - appli impôts avec PostgreSQL
127.0.0.1    date-time-server            # cours python - heure et date du moment
127.0.0.1           projet-test.test                #laragon magic!

Wir fügen Zeile 25 hinzu, um dem virtuellen Server [date-time-server] die IP-Adresse [127.0.0.1] zuzuweisen.

Lassen Sie uns alles überprüfen. Wir starten den Apache-Server:

Image

Als Nächstes rufen wir die URL [https://date-time-server] über einen Browser auf:

  • in [1] die angeforderte URL;
  • in [3] die Antwort des Servers;
  • in [2] zeigt der Browser an, dass die HTTPS-Verbindung nicht sicher ist, da er festgestellt hat, dass das vom Apache-Server gesendete Zertifikat selbstsigniert ist;

Fügen wir nun in der Datei [date-time-server.conf] einen Alias zu den Zeilen 14 und 27 hinzu:


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

Die Änderung wird vom Apache-Server nicht sofort erkannt. Sie müssen ihn neu laden:

Image

Anschließend rufen wir die URL [https://date-time-server/show-date-time] auf. Die Antwort des Servers lautet wie folgt:

Image

37.5. Portierung der Steuerberechnungsanwendung auf Apache / Windows

Das Verzeichnis [apache] [2] wird zunächst durch Kopieren des Verzeichnisses [main] erstellt. Es ist wichtig, dass sie sich auf derselben Ebene befinden, damit die Pfade im Skript [syspath.py], das von [1] nach [2] kopiert wurde, gültig bleiben. Um eine Beeinträchtigung der laufenden Anwendung [impots / http-servers/ 12] zu vermeiden, legen wir die vom Apache-Server auszuführende Konfiguration in [apache] ab;

Image

  • die [config]-Datei in [2] ist identisch mit der [config]-Datei in [1];
  • die Datei [syspath] in [2] ist identisch mit der Datei [syspath] in [1];
  • die Datei [main_withmysql] in [2] ist die Datei [main] aus [1] mit den folgenden Änderungen:

Das Hauptskript [main] erhielt einen [mysql / pgres]-Parameter, der angab, welches DBMS verwendet werden sollte. Das Skript [main_withmysql] verwendet das MySQL-DBMS:

#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "mysql"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

In Zeile 7 wird das DBMS auf MySQL eingestellt.

  • Die Datei [main_withpgres] aus [2] ist die Datei [main] aus [1] mit den folgenden Änderungen: Sie verwendet das DBMS PostgreSQL:
#  a mysql or pgres parameter is expected
import os
import sys

#  configure the application with MySQL
import config
config = config.configure({'sgbd': "pgres"})

#  dependencies
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError

In Zeile 7 legen wir das DBMS auf PostgreSQL fest.

Sobald dies erledigt ist, erstellen wir das folgende Skript [main_withmysql.wsgi] (die verwendete Endung spielt keine Rolle):

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withmysql import app as application

Das Skript [main_withmysql.wsgi] ist das Ziel, das vom Apache-Server im WSGI-Modus ausgeführt wird:

  • Das Ziel des Apache-Servers hätte auch das Skript [main_withmysql.py] sein können, wie es zuvor mit dem Skript [date_time_server.py] der Fall war. Dies hätte jedoch eine geringfügige Änderung erfordert:
    • Anders als bei der Ausführung eines Konsolenskripts ist bei Apache das Verzeichnis, das das Ziel [main_withmysql.py] enthält, nicht Teil des Python-Pfads. Daher verursacht Zeile 6 des Skripts [main_withmysql.py] einen Fehler;
    • die zweite Änderung, die notwendig gewesen wäre, besteht darin, dass in [main_withmysql] die Flask-Anwendung durch den Bezeichner [app] referenziert wird. Wir wissen, dass sie für Apache/WSGI auch durch einen Bezeichner [application] referenziert werden muss;
  • Anstatt [main_withmysql.py] zu ändern, ändern wir das Apache-Ziel. Es ist nun das oben genannte Skript [main_withmysql.wsgi]:
    • Zeilen 1–7: Wir fügen das Skriptverzeichnis zum Python-Pfad hinzu. Dadurch verursacht Zeile 6 von [main_withmysql.py] keinen Fehler mehr;
    • Zeilen 9–10: Das Importieren von [main_withmysql.py] löst dessen Ausführung aus. Zusätzlich verweisen wir auf die Flask-Anwendung [app] in [main_withmysql.py] unter Verwendung des Bezeichners [application], der von Apache im WSGI-Modus benötigt wird;

Wir verfahren genauso mit dem Skript [main_withpgres.wsgi]:

#  folder of this file
import os
script_dir = os.path.dirname(os.path.abspath(__file__))

#  we add it to the syspath so that the following import is possible
import sys
sys.path.insert(0, script_dir)

#  we import the Flask application [app], giving it the name [application]
from main_withpgres import app as application

Wir haben nun die ausführbaren Ziele für den Apache-Server. Jetzt müssen wir zwei virtuelle Server erstellen, einen für jedes Ziel.

In [<laragon>\etc\apache2\sites-enabled] erstellen wir die Datei [flask-imports-withmysql.conf] (der Name spielt keine Rolle):

Image


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withmysql"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withmysql.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans 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>
  • Zeile 2: das Anwendungsverzeichnis, der von uns erstellte Ordner [apache];
  • Zeilen 23, 38: das Ziel [main_withmysql.wsgi], das wir erstellt haben:
  • Zeile 7: Der virtuelle Server erhält den Namen [flask-impots-withmysql];
  • Zeile 13: Die Anweisung [WSGIPythonPath] ermöglicht es uns, Ordner zum Python-Pfad hinzuzufügen. Hier weiß Apache nicht, dass wir eine virtuelle Umgebung zur Entwicklung der Anwendung verwendet haben und dass sich alle von der Anwendung verwendeten Module in dieser virtuellen Umgebung befinden. Daher fügen wir in Zeile 13 den Ordner hinzu, der alle Module aus der verwendeten virtuellen Umgebung enthält. Eine Möglichkeit besteht darin, dieses Verzeichnis an einen anderen Ort im Dateisystem zu kopieren und auf diesen Ort zu verweisen. Eine andere Möglichkeit ist, dieses Verzeichnis direkt innerhalb des Ziels [main_withmysql.wsgi] zum Python-Pfad hinzuzufügen (dies ist wahrscheinlich die bessere Lösung);
  • Zeile 16: Wir können Apache das Verzeichnis der Python-Installation im Dateisystem angeben. Normalerweise befindet sich dieses im PATH des Rechners, und oft ist diese Zeile überflüssig (was hier der Fall war). Es kann jedoch vorkommen, dass auf dem Rechner mehrere Python-Installationen vorhanden sind und die gewünschte nicht im PATH des Rechners enthalten ist. In diesem Fall löst diese Zeile das Problem;

Erstellen Sie auf ähnliche Weise eine Datei [flask-impots-withpgres.conf]:


# dossier du script .wsgi
define ROOT "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/impots/http-servers/12/apache"
 
# nom du site web configuré par ce fichier
# ici il s'appellera flask-impots-withmysql
# les URL seront du type http(s)://flask-impots-withmysql/path
define SITE "flask-impots-withpgres"
 
# mettre l'adresse IP 127.0.0.1 pour site SITE dans c:/windows/system32/drivers/etc/hosts
 
# mettre ici les chemins des bibliothèques Python à utiliser - les séparer par des virgules
# ici les bibliothèques d'un environnement virtuel Python
WSGIPythonPath  "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages"
 
# Python Home - nécessaire uniquement s'il y a plusieurs versions de Python installées
# WSGIPythonHome "C:/Program Files/Python38"
 
# URL HTTP
<VirtualHost *:80>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans parameters.py
    WSGIScriptAlias / "${ROOT}/main_withpgres.wsgi"
    DocumentRoot "${ROOT}"
    ServerName ${SITE}
    ServerAlias *.${SITE}
    <Directory "${ROOT}">
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
 
# URL sécurisées avec HTTPS
<VirtualHost *:443>
    # avec l'alias / les URL auront la forme /{prefixe_url}/action/...
    # avec l'alias /impots les URL auront la forme /impots/{prefixe_url}/action/...
    # où [prefixe_url] est défini dans 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>

Wir speichern alle diese Dateien, starten den Apache-Server sowie die MySQL- und PostgreSQL-Datenbanken. Die Anwendung ist in [configs/parameters.py] mit dem URL-Präfix [/do] und [with_csrftoken=False] (kein CSRF-Token) konfiguriert. Wir rufen die URL [https://flask-impots-withmysql/do] auf. Die Antwort des Servers lautet wie folgt:

Image

Wir rufen nun die URL [https://flask-impots-pgres/do] auf. Die Antwort lautet wie folgt:

Image

Beide Anwendungen funktionieren normal.

Ändern wir nun den Parameter [WSGIScriptAlias] in der Datei [flask-imports-withmysql.conf]:

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



#  URL HTTP
<VirtualHost *:80>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    
</VirtualHost>

#  URL secured with HTTPS
<VirtualHost *:443>
    #  with the alias / the URL will have the form /{prefixe_url}/action/...
    #  with the alias /impots the URL will take the form /impots/{prefixe_url}/action/...
    #  where [prefixe_url] is defined in parameters.py
    WSGIScriptAlias /impots "${ROOT}/main_withmysql.wsgi"
    

</VirtualHost>
  • Zeilen 11 und 20, der WSI-Alias lautet nun [/impots];

Wir stoppen und starten den Apache-Server neu und rufen dann die URL [https://flask-impots-withmysql/impots/do] auf. Die Antwort des Servers lautet wie folgt:

Image

Es kommt zu einem Absturz. Die URL [1] verrät uns die Ursache. Sie hätte [https://flask-impots-withmysql/impots/do/afficher-vue-authentification] lauten müssen. Der WSGI-Alias fehlt. Dies ist ein Fehler in unserer Anwendung. Sie weiß, wie man mit einem URL-Präfix umgeht (/do ist vorhanden). Man könnte meinen, dass das Hinzufügen des Präfixes [/imports/do] zu unserer Anwendung das vorherige Problem lösen würde. Aber nein. Wir stoßen dann auf andere Arten von Problemen. Der WSGI-Alias verhält sich nicht wie ein URL-Präfix.

Versuchen wir zu verstehen, was passiert ist. Wir haben die URL [https://flask-impots-withmysql/impots/do] aufgerufen. Wir erwarteten, die Authentifizierungsansicht zu sehen. In [1] oben sehen wir, dass die Anwendung die Anzeige angefordert hat, jedoch nicht mit der korrekten URL. Betrachten wir den Pfad der Anfrage [https://flask-impots-withmysql/impots/do].

Zunächst wurde die folgende Route (configs/routes.py) ausgeführt:


    # les routes de l'application Flask
    # racine de l'application
    app.add_url_rule(f'{prefix_url}/', methods=['GET'],
                     view_func=routes.index)

Die Route in Zeile 3 lautet in unserem Beispiel [https://flask-impots-withmysql/impots/do]. Wir sehen, dass der Teil [https://flask-impots-withmysql/impots] aus der Route entfernt wurde, sodass nun einfach [/do] übrig bleibt. Was den Teil [https://flask-impots-withmysql] betrifft, so ist dies normal; der Servername ist nicht in der Route enthalten. Wir sehen jedoch, dass auch der WSGI-Alias [/imports] nicht enthalten ist. Dies ist ein wichtiger Punkt. Selbst mit einem WSGI-Alias bleiben unsere ursprünglichen Routen gültig.

Sehen wir uns nun an, was die Funktion [index] in Zeile 4 (configs/routes_without_csrftoken) bewirkt:

1
2
3
4
#  application root
def index() -> tuple:
    #  redirect to /init-session/html
    return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)

In Zeile 4 werden wir zur URL der Funktion [init_session] weitergeleitet. In [configs/routes.py] wurde diese Funktion der Route [/do/init-session/html] zugeordnet:


    # init-session
    app.add_url_rule(f'{prefix_url}/init-session/<string:type_response>{csrftoken_param}', methods=['GET'],
                     view_func=routes.init_session)

Zeile 2: In unserem Test ist [csrftoken_param] eine leere Zeichenkette. Die Anwendung verarbeitet hier kein CSRF-Token.

Die Funktion [init_session] ist wie folgt definiert (configs/routes_without_csrftoken):

1
2
3
4
#  init-session
def init_session(type_response: str) -> tuple:
    #  execute the controller associated with the action
    return front_controller()

Zeile 4: Wir starten die Aktionsverarbeitungskette [init-session]. Diese Kette endet wie folgt in [responses/HtmlResponse]:



# now it's time to generate the URL redirection, not forgetting the CSRF token if requested
        if config['parameters']['with_csrftoken']:
            csrf_token = f"/{generate_csrf()}"
        else:
            csrf_token = ""
 
        # redirect response
        return redirect(f"{config['parameters']['prefix_url']}{ads['to']}{csrf_token}"), status.HTTP_302_FOUND

Die Aktion [init-session] ist eine ADS-Aktion (Action Do Something), die mit einer Weiterleitung zu einer Ansicht endet (Zeile 9). Genau hier liegt das Problem. Die Funktion [redirect] in Zeile 9 fügt den WSGI-Alias nicht automatisch zur Weiterleitungs-URL hinzu. Dies ist im obigen Screenshot zu sehen. Der Alias /impots fehlt in der Weiterleitungs-URL.

Die folgende Version bietet eine Lösung für das WSGI-Alias-Problem.