Skip to content

37. Esercizio pratico: Versione 17

Image

Questa nuova versione introduce le seguenti modifiche:

  • sarà portata su un server Apache/Windows;
  • Per eseguire questo porting, la versione 17 contiene tutte le dipendenze necessarie nella cartella [impots/http-servers/12]. Si noti che le versioni precedenti recuperavano le dipendenze da varie cartelle sparse nell'intero progetto [python-flask-2020];

37.1. Riorganizzazione delle dipendenze dell'applicazione

Image

Si noti che la gestione delle dipendenze dell'applicazione è gestita nello script [syspath]. Nella versione precedente, questo script era il seguente:

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
    }

Dobbiamo spostare tutte le dipendenze il cui percorso assoluto dipende dalla variabile [root_dir] alla riga 8, ovvero le righe 13–26.

Lo script [syspath] per la nuova versione sarà il seguente:

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,
    }
  • righe 8–27: tutte le dipendenze sono ora relative alla variabile [script_dir] alla riga 5;
  • righe 42–45: la variabile [root_dir] è stata rimossa dalla configurazione syspath;
  • riga 10: le entità dell'applicazione si trovano nella cartella [entities] [1];
  • riga 12: il livello [dao] si trova nella cartella [layers/dao] [2];
  • riga 14: il livello [business] si trova nella cartella [layers/business] [2];
  • riga 16: le utility [Logger, SendMail] si trovano nella cartella [utilities] [3];
  • righe 29–40: il Python Path dell'applicazione viene calcolato senza importare il modulo [myutils];

Image

37.2. Test

A questo punto, la versione 17 dovrebbe funzionare. Verificalo.

37.3. Porting di un'applicazione Python/Flask su un server Apache/Windows

37.3.1. Fonti

Per trasferire un'applicazione Flask su Apache/Windows, ho dovuto cercare su Internet. Ecco il link che mi ha aiutato a iniziare: [https://medium.com/@madumalt/flask-app-deployment-in-windows-apache-server-mod-wsgi-82e1cfeeb2ed];

Ho utilizzato le informazioni di questo link tranne che per la configurazione del server Apache. Per quella, ho utilizzato una configurazione di esempio del server Apache da Laragon.

37.3.2. Installazione del modulo Python mod_wsgi

L'applicazione Python/Flask che abbiamo sviluppato utilizzava il server WSGI (Web Server Gateway Interface) [werkzeug] incluso in Flask. Questo server è descritto |qui|. Il link [https://www.fullstackpython.com/wsgi-servers.html] descrive come funzionano i server WSGI. Esistono vari |server WSGI|. Uno di questi è il server Apache in modalità WSGI. Questa è la soluzione adottata in questo caso perché Laragon, che abbiamo installato, include un server Apache.

Affinché il server Apache possa ospitare un'applicazione Python, è necessario installare il modulo Python [mod_wsgi]. L'installazione di questo modulo è complessa perché richiede una compilazione in C++. Per completare l'installazione con successo, è necessario un compilatore Microsoft C++. Una soluzione semplice consiste nell'installare l'ultima versione di Visual Studio Community [https://visualstudio.microsoft.com/fr/vs/community/].

Se non avete bisogno di Visual Studio per scopi diversi da [mod_wsgi], potete limitare l'installazione all'ambiente C++:

Image

Una volta installato il compilatore C++, il modulo [mod_wsgi] viene installato in un terminale 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
  • Riga 1: Impostiamo il valore della variabile d'ambiente [MOD_WSGI_APACHE_ROOTDIR]. Questo valore indica la posizione del server Apache nel file system. In questo caso, la posizione è [<laragon>\bin\apache\httpd-2.4.35-win64-VC15], dove <laragon> è la cartella di installazione di Laragon. È possibile ottenere questa posizione in vari modi. Eccone una ottenuta utilizzando una delle opzioni di Laragon:

Image

In [1-3], il file [httpd.conf] è il file di configurazione principale del server Apache. Il file in questione viene quindi aperto in un editor di testo (Notepad++ di seguito):

Image

In [2], la cartella di installazione di Apache è la parte che precede la stringa [conf\httpd.conf].

Torniamo all'installazione del modulo [mod_wsgi]:

  • Righe 3–9: installazione del modulo [mod_wsgi];

37.3.3. Configurazione del server Apache Laragon

Configureremo il server Apache Laragon. Iniziamo con il suo file di configurazione principale [httpd.conf]:

Image

Andiamo alla fine del file [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"
  • La riga 8 è stata aggiunta al file [httpd.conf] esistente alla fine del file. Indica al server Apache dove trovare un componente del modulo [mod_wsgi] che abbiamo appena installato;

Un modo semplice per ottenere il percorso per la riga 8 è eseguire il seguente comando in un terminale 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"

Alcuni documenti indicano che le righe 2 e 3 dovrebbero essere aggiunte alla fine del file [httpd.conf]. Nel mio caso, la riga 3 sopra riportata ha causato un errore (modulo [encodings] mancante). Pertanto, non è stata inclusa nel file [httpd.conf]. È stata aggiunta solo la riga 2. I significati dei vari parametri del modulo [mod_wsgi] che possono essere utilizzati nei file di configurazione di Apache sono descritti |qui|.

Successivamente, abiliteremo il protocollo HTTPS sul server Apache:

Image

  • Nei punti [1-4], abilitiamo il protocollo HTTPS sul server Apache;

D'ora in poi, potremo utilizzare gli URL [https://serveur/chemin];

Per configurare Apache in modo che ospiti un'applicazione Flask, il link indicato |sopra| utilizza gli host virtuali. Anche Laragon offre funzionalità di gestione degli host virtuali:

Image

  • In [1-3], chiediamo a Laragon di creare automaticamente gli host virtuali;

Il passo successivo consiste nel creare un progetto web con Laragon:

 
  • In [1-3], creare un progetto PHP vuoto;
  • In [4-8], Laragon ha creato un sito virtuale denominato [auto.projet-test.test] configurato dal file [auto.projet-test.test.conf] [8] nella cartella [sites-enabled] [7]. Questa cartella si trova in [<laragon>\etc\apache2\sites-enabled], dove [laragon] è la cartella di installazione di Laragon;

Sebbene questo non faccia parte di ciò che stiamo facendo in questo momento, potresti essere curioso di dare un'occhiata al sito web [test-project] che abbiamo appena creato:

Image

  • In [1-5] è stato creato un progetto vuoto. Si tratta di un progetto PHP situato nella cartella [<laragon>/www], dove [laragon] è la cartella di installazione di Laragon;

Ora esaminiamo il file [auto.projet-test.test.conf] generato da Laragon nella cartella [<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>
  • riga 1: la directory principale del progetto creato nel file system;
  • riga 2: il nome del server virtuale. Gli URL per questo server avranno il formato [http(s)://test-project.test/path];
  • righe 4–12: configurazione del sito virtuale per la porta 80 (riga 4) e il protocollo HTTP;
  • righe 14–27: configurazione dell'host virtuale per la porta 443 (riga 14) e il protocollo HTTPS;

Vediamo come funziona un server virtuale. Per prima cosa, avviamo i server Apache e PHP:

Image

Quindi, utilizzando un browser, richiediamo l'URL [http://projet-test.test/]:

Image

  • in [1], l'URL richiesto;
  • in [2], è stato utilizzato il protocollo HTTP;
  • in [3], poiché il progetto [projet-test] è vuoto, otteniamo l'indice della sua cartella (elenco dei suoi contenuti), un indice vuoto;

Ora richiediamo l'URL sicuro [https://projet-test.test/]:

Image

  • In [1-2], otteniamo la stessa risposta di prima, ma utilizzando il protocollo HTTPS [1];

La creazione del server virtuale [test-project.test] ha creato una nuova voce nel file [<windows>/system32/drivers/etc/hosts]:

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!   
  • Riga 23: L'indirizzo IP per il nome [test-project.test] è 127.0.0.1, ovvero l'indirizzo di [localhost] (riga 20), la macchina locale. Quindi, quando digiti l'URL [http://projet-test.test/chemin] in un browser, la richiesta viene inviata all'indirizzo 127.0.0.1 sulla porta 80. Il server Apache sulla macchina locale (localhost) risponde quindi.

Ci si potrebbe chiedere perché, quando si digita la richiesta [http://projet-test.test/], il server Apache utilizzi la configurazione dal file [<laragon>\etc\apache2\sites-enabled\auto.projet-test.test.conf]:

Image

Per capirlo, dobbiamo vedere cosa invia il browser al server Apache quando effettua questa richiesta. Facciamolo con Postman:

Image

  • In [1-3], inviamo una richiesta HTTPS [1];
  • in [4], Postman indica di non aver riconosciuto il certificato di sicurezza. Il protocollo HTTPS stabilisce una connessione crittografata tra il client web (in questo caso, Postman) e il server Apache. Questa connessione crittografata viene stabilita tramite certificati scambiati tra il client e il server. È il server che avvia il processo di stabilimento della connessione crittografata inviando un certificato di sicurezza al client. Affinché questo certificato venga accettato dal client, deve essere firmato, in altre parole, acquistato da aziende autorizzate a emettere certificati di sicurezza. Quando abbiamo abilitato il protocollo HTTPS di Laragon, Laragon ha creato il certificato di sicurezza autonomamente. Si dice quindi che il certificato sia autofirmato. La maggior parte dei client web emette un avviso quando riceve un certificato autofirmato. Questo è ciò che fa Postman in [4]. La maggior parte dei client web offre quindi la possibilità di disabilitare la verifica del certificato di sicurezza inviato dal server. Questo è ciò che offre Postman in [5];

Clicchiamo sul link [5] per disabilitare la verifica SSL (Secure Sockets Layer). SSL/TLS (Transport Layer Security) è un protocollo di sicurezza che crea un canale di comunicazione sicuro tra due macchine su Internet. Questo è il protocollo utilizzato qui da Apache. La risposta è la seguente:

Image

Riceviamo la stessa pagina che vedremmo con un browser tradizionale. Ora diamo un'occhiata al dialogo client/server nella console di 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
  • Riga 6: L'intestazione HTTP [Host] specifica il nome del server a cui si rivolge il client web. Questo è il principio alla base dei server virtuali. A un singolo indirizzo IP (qui 127.0.0.1), un server web può ospitare più siti web con nomi diversi. L'intestazione HTTP [Host] permette al client di specificare a quale server (qui, quello all'indirizzo 127.0.0.1) si sta rivolgendo;

Allora, cosa fa Apache?

All'avvio, Apache legge tutti i file di configurazione presenti nella directory [[<laragon>\etc\apache2\sites-enabled]]:

Image

Ogni file di configurazione definisce un server virtuale. Ad esempio, nel file [auto.projet-test.test.conf], troverai la seguente riga:


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

La riga 2 definisce il server virtuale [test-project.test]. Il file [auto.projet-test.test.conf] è la configurazione di questo server virtuale. Poiché all'avvio legge tutti i file di configurazione nella cartella [<laragon>\etc\apache2\sites-enabled], il server Apache sa che esiste un server virtuale denominato [projet-test.test]. Pertanto, quando riceve la seguente richiesta HTTPS dal client Postman:

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

Riconosce che la richiesta è indirizzata al server virtuale [test-project.test] (riga 6) e che questo esiste. Utilizza quindi la configurazione del server virtuale [test-project.test] per rispondere al client Postman.

37.4. Creazione del primo server virtuale Apache

Ora che sappiamo a cosa servono i server virtuali e come configurarli, ne creeremo uno. Verrà utilizzato per eseguire un'applicazione Python Flask installata in una cartella [Apache] della versione 17 attualmente in fase di distribuzione sul server Apache:

Abbiamo inserito l'applicazione sviluppata nella sezione |link|—un servizio web di data/ora—nella cartella [http-servers/12/apache/example]:

Image

Il server [date_time_server.py] è il seguente:

#  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()

L'applicazione Flask è indicata dall'identificatore [application] (righe 14, 43, 44). Questo nome è obbligatorio. Se si fa riferimento all'applicazione Flask con un identificatore diverso, l'applicazione non funzionerà e visualizzerà un messaggio di errore indicando che non è in grado di trovare l'URL richiesto. Questo messaggio di errore non fornisce alcuna indicazione sulla causa dell'errore. È quindi necessario prestare attenzione a questo aspetto.

Il file HTML a cui si fa riferimento alla riga 34 è il seguente:


<!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] sarà il server virtuale che ospita questa applicazione. Verrà configurato tramite il file [<laragon>\etc\apache2\sites-enabled\date-time-server.conf] (si noti che questo nome è arbitrario: Apache legge tutti i file in [sites-enabled] per individuare i siti virtuali ospitati);

Per prima cosa otteniamo questo file copiando il file [auto.projet-test.test.conf] e poi lo modifichiamo.

Image

Il file [date-time-server.conf] avrà questo aspetto:

#  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>
  • Riga 7: assegna un nome al server virtuale configurato dal file;
  • riga 2: imposta il valore della variabile [ROOT] utilizzata alle righe 14 e 27;
  • Righe 14 e 27: specificano il percorso dello script Python che deve essere eseguito quando il server virtuale riceve una richiesta. Qui specifichiamo che le richieste per il server [date-time-server] sono gestite dallo script Python [date_time_server.py]. Questa differenza rispetto al file [auto.projet-test.test.conf] deriva dal fatto che il primo configurava un server PHP, mentre il file [date-time-server.conf] configura un server Python;
  • Righe 14 e 27: L'attributo [WSGIScriptAlias /] specifica qui che la radice del server [date-time-server] sarà [/]. Pertanto, gli URL dell'applicazione assumeranno la forma [http(s)://date-time-server/path];
  • Righe 14 e 27: È possibile assegnare una radice diversa all'applicazione, ad esempio [WSGIScriptAlias /show]. In questo caso, gli URL dell'applicazione assumeranno la forma [http(s)://show/date-time-server/path];

Dobbiamo anche aggiungere una riga al file [<windows>/system32/drivers/etc/hosts]:

# 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!

Aggiungiamo la riga 25 per assegnare l'indirizzo IP [127.0.0.1] al server virtuale [date-time-server].

Controlliamo tutto. Avviamo il server Apache:

Image

Successivamente, richiediamo l'URL [https://date-time-server] utilizzando un browser:

  • in [1], l'URL richiesto;
  • in [3], la risposta del server;
  • in [2], il browser indica che la connessione HTTPS non è sicura perché ha rilevato che il certificato inviato dal server Apache era autofirmato;

Ora, nel file [date-time-server.conf], aggiungiamo un alias alle righe 14 e 27:


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

La modifica non viene riconosciuta immediatamente dal server Apache. È necessario ricaricarlo:

Image

Quindi richiediamo l'URL [https://date-time-server/show-date-time]. La risposta del server è la seguente:

Image

37.5. Porting dell'applicazione di calcolo delle imposte su Apache / Windows

La directory [apache] [2] viene inizialmente creata copiando la directory [main]. È importante che si trovino allo stesso livello in modo che i percorsi nello script [syspath.py] copiati da [1] a [2] rimangano validi. Per evitare di interferire con l'applicazione [impots / http-servers/ 12] in esecuzione, inseriamo la configurazione che dovrà essere eseguita dal server Apache in [apache];

Image

  • il file [config] in [2] è lo stesso del file [config] in [1];
  • il file [syspath] in [2] è lo stesso del file [syspath] in [1];
  • il file [main_withmysql] in [2] è il file [main] di [1] con le seguenti modifiche:

Lo script principale [main] riceveva un parametro [mysql / pgres] che gli indicava quale DBMS utilizzare. Lo script [main_withmysql] utilizza il DBMS MySQL:

#  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

La riga 7 imposta il DBMS su MySQL.

  • Il file [main_withpgres] di [2] è il file [main] di [1] con le seguenti modifiche: utilizza il 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

Alla riga 7, impostiamo il DBMS su PostgreSQL.

Una volta fatto questo, creiamo il seguente script [main_withmysql.wsgi] (il suffisso utilizzato non ha importanza):

#  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

Lo script [main_withmysql.wsgi] sarà il target eseguito dal server Apache in modalità WSGI:

  • L'obiettivo del server Apache avrebbe potuto essere lo script [main_withmysql.py], come è stato fatto in precedenza con lo script [date_time_server.py]. Ma ciò avrebbe richiesto una leggera modifica:
    • a differenza di quando si esegue uno script da console, con Apache la directory contenente il target [main_withmysql.py] non fa parte del Python Path. Pertanto, la riga 6 dello script [main_withmysql.py] genera un errore;
    • la seconda modifica che sarebbe stata necessaria è che in [main_withmysql], l'applicazione Flask è referenziata dall'identificatore [app]. Sappiamo che per Apache/WSGI, deve essere referenziata anche da un identificatore [application];
  • Invece di modificare [main_withmysql.py], cambiamo il target di Apache. Ora sarà lo script [main_withmysql.wsgi] sopra riportato:
    • righe 1–7: aggiungiamo la directory dello script al Python Path. Di conseguenza, la riga 6 di [main_withmysql.py] non causa più un errore;
    • righe 9–10: l'importazione di [main_withmysql.py] ne avvia l'esecuzione. Inoltre, facciamo riferimento all'applicazione Flask [app] presente in [main_withmysql.py] utilizzando l'identificatore [application] richiesto da Apache in modalità WSGI;

Facciamo lo stesso con lo script [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

Ora disponiamo dei target eseguibili per il server Apache. Dobbiamo ora creare due server virtuali, uno per ciascun target.

Nella directory [<laragon>\etc\apache2\sites-enabled], creiamo il file [flask-imports-withmysql.conf] (il nome non ha importanza):

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>
  • riga 2: la radice dell'applicazione, la cartella [apache] che abbiamo creato;
  • righe 23, 38: il target [main_withmysql.wsgi] che abbiamo creato:
  • riga 7: il server virtuale si chiamerà [flask-impots-withmysql];
  • riga 13: la direttiva [WSGIPythonPath] ci permette di aggiungere cartelle al Python Path. Qui, Apache non sa che abbiamo usato un ambiente virtuale per sviluppare l'applicazione e che tutti i moduli usati dall'applicazione si trovano in quell'ambiente virtuale. Pertanto, alla riga 13, aggiungiamo la cartella contenente tutti i moduli dell'ambiente virtuale utilizzato. Un'opzione è quella di copiare questa directory in un'altra posizione nel file system e fare riferimento a quella posizione. Un'altra opzione è quella di aggiungere questa directory al Python Path direttamente all'interno del target [main_withmysql.wsgi] (questa è probabilmente una soluzione migliore);
  • riga 16: possiamo indicare ad Apache la directory di installazione di Python nel file system. Di norma, questa si trova nel PATH del sistema e spesso questa riga non è necessaria (come in questo caso). Tuttavia, potrebbero esserci più installazioni di Python sul sistema e quella desiderata potrebbe non essere presente nel PATH. In tal caso, questa riga risolve il problema;

Allo stesso modo, creare un file [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>

Salviamo tutti questi file, avviamo il server Apache e i database MySQL e PostgreSQL. L'applicazione è configurata con il prefisso URL [/do] e [with_csrftoken=False] (nessun token CSRF) in [configs/parameters.py]. Richiediamo l'URL [https://flask-impots-withmysql/do]. La risposta del server è la seguente:

Image

Ora richiediamo l'URL [https://flask-impots-pgres/do]. La risposta è la seguente:

Image

Entrambe le applicazioni funzionano normalmente.

Ora modifichiamo il parametro [WSGIScriptAlias] nel file [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>
  • nelle righe 11 e 20, l'alias WSI è ora [/impots];

Arrestiamo e riavviamo il server Apache, quindi richiediamo l'URL [https://flask-impots-withmysql/impots/do]. La risposta del server è la seguente:

Image

Si verifica un crash. L'URL [1] ci indica la causa. Avrebbe dovuto essere [https://flask-impots-withmysql/impots/do/afficher-vue-authentification]. Manca l'alias WSGI. Si tratta di un errore nella nostra applicazione. Essa sa come gestire un prefisso URL (/do è presente). Si potrebbe pensare che l'aggiunta del prefisso [/impots/do] alla nostra applicazione risolverebbe il problema precedente. Ma no. Ci imbattiamo quindi in altri tipi di problemi. L'alias WSGI non si comporta come un prefisso URL.

Proviamo a capire cosa è successo. Abbiamo richiesto l'URL [https://flask-impots-withmysql/impots/do]. Ci aspettavamo di vedere la vista di autenticazione. Nel punto [1] sopra, vediamo che l'applicazione ha richiesto di visualizzarla, ma non con l'URL corretto. Esaminiamo il percorso della richiesta [https://flask-impots-withmysql/impots/do].

In primo luogo, è stato eseguito il seguente percorso (configs/routes.py):


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

Il percorso alla riga 3 è [https://flask-impots-withmysql/impots/do] nel nostro esempio. Possiamo notare che al percorso è stata rimossa la parte [https://flask-impots-withmysql/impots] per diventare semplicemente [/do]. Per quanto riguarda la parte [https://flask-impots-withmysql], questo è normale; il nome del server non è incluso nel percorso. Ma possiamo vedere che non include nemmeno l'alias WSGI [/imports]. Questo è un punto importante. Anche con un alias WSGI, i nostri percorsi iniziali rimangono validi.

Ora vediamo cosa fa la funzione [index] alla riga 4 (configs/routes_without_csrftoken):

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)

Alla riga 4, veniamo reindirizzati all'URL della funzione [init_session]. Nel file [configs/routes.py], questa funzione è stata associata alla route [/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)

Riga 2: Nel nostro test, [csrftoken_param] è una stringa vuota. L'applicazione non gestisce un token CSRF in questo caso.

La funzione [init_session] è definita come segue (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()

Riga 4: Avviamo la catena di elaborazione dell'azione [init-session]. Questa catena termina come segue 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

L'azione [init-session] è un'azione ADS (Action Do Something) che termina con un reindirizzamento a una vista, riga 9. È proprio qui che risiede il problema. La funzione [redirect] alla riga 9 non aggiunge automaticamente l'alias WSGI all'URL di reindirizzamento. Ciò è mostrato nello screenshot sopra. L'alias /impots manca dall'URL di reindirizzamento.

La versione seguente fornisce una soluzione al problema dell'alias WSGI.