Skip to content

22. Servizi web con il framework Flask

Per servizio web si intende qui qualsiasi applicazione web che fornisce dati grezzi utilizzati da un client, spesso uno script da console negli esempi che seguiranno. Non ci interessano tecnologie specifiche, come ad esempio REST (REpresentational State Transfer) o SOAP (Simple Object Access Protocol), che forniscono dati più o meno grezzi in un formato ben definito. REST fornisce jSON, mentre per SOAP si tratta di XML. Ciascuna di queste tecnologie descrive con precisione il modo in cui il client deve interrogare il server e la forma che deve assumere la risposta di quest’ultimo. In questo corso saremo molto più flessibili riguardo alla natura della richiesta del client e a quella della risposta del server. Tuttavia, gli script scritti e gli strumenti utilizzati sono simili a quelli della tecnologia REST.

22.1. Introduzione

Gli script Python possono essere eseguiti da un server web. Uno script di questo tipo diventa un programma server in grado di servire più client. Dal punto di vista del client, richiamare un servizio web equivale a richiedere l’URL di tale servizio. Il client può essere scritto in qualsiasi linguaggio, in particolare in Python. In quest’ultimo caso, si utilizzano quindi le funzioni Internet che abbiamo appena visto. Dobbiamo inoltre sapere come “comunicare” con un servizio web, ovvero comprendere il protocollo HTTP di comunicazione tra un server web e i suoi client. Questo era l’obiettivo del paragrafo |il protocollo HTTP|. I client web descritti in questa parte del corso ci hanno permesso di scoprire una parte del protocollo HTTP.

Image

Nella loro forma più semplice, gli scambi client/server sono i seguenti:

  • il client apre una connessione con la porta 80 del server web;
  • effettua una richiesta relativa a un documento;
  • il server web invia il documento richiesto e chiude la connessione;
  • il client chiude a sua volta la connessione;

Il documento può essere di varia natura: un testo in formato HTML, un'immagine, un video, ... Può trattarsi di un documento esistente (documento statico) oppure di un documento generato al volo da uno script (documento dinamico). In quest'ultimo caso, si parla di programmazione web. Lo script per la generazione dinamica dei documenti può essere scritto in diversi linguaggi: PHP, Python, Perl, Java, Ruby, C#, VB.net, ...

Di seguito utilizzeremo script Python per generare dinamicamente documenti di testo.

Image

  • in [1], il client apre una connessione con il server, richiede uno script Python, inviando o meno dei parametri a tale script;
  • in [3], il server web fa eseguire lo script Python tramite l’interprete Python. Lo script genera un documento che viene inviato al client [2];
  • il server chiude la connessione. Il client fa lo stesso;

Il server web può gestire più client contemporaneamente.

Di seguito utilizzeremo due server web:

  • il server leggero Werkzeug [https://werkzeug.palletsprojects.com/en/1.0.x/]. Questo server è utilizzato dal framework web Flask [https://flask.palletsprojects.com/en/1.1.x/]. Lo chiameremo più spesso server Flask;
  • il server Apache 2 [https://httpd.apache.org/];

Il server Flask verrà utilizzato in tutti gli esempi. Il server Apache verrà utilizzato per ospitare l’applicazione web che svilupperemo.

Il framework Flask è sviluppato in Python. Si tratta di un modulo che si installa in un terminale PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask
Collecting flask
  Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
     || 94 kB 1.1 MB/s
Collecting click>=5.1
  Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
     || 82 kB 5.8 MB/s
Collecting itsdangerous>=0.24
  Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Jinja2>=2.10.1
  Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
     || 125 kB 6.4 MB/s
Collecting Werkzeug>=0.15
  Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
     || 298 kB 6.4 MB/s
Collecting MarkupSafe>=0.23
  Downloading MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl (16 kB)
Installing collected packages: click, itsdangerous, MarkupSafe, Jinja2, Werkzeug, flask
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 flask-1.1.2 itsdangerous-1.1.0
  • riga 1: il comando eseguito;
  • riga 19: gli elementi che sono stati installati:
    • [flask-1.1.2]: è un framework di sviluppo web in Python;
    • [Werkzeug-1.0.1]: è il server web che risponderà alle richieste dei client;
    • [Jinja2-2.11.2]: è uno strumento che consente di inserire elementi dinamici in pagine che altrimenti sarebbero statiche;

22.2. script [flask/01]: primi elementi di programmazione web

Image

I nostri esempi verranno eseguiti nella seguente architettura:

Image

  • in [1], uno script Python verrà eseguito come un classico script da console;
  • in [2], in modo trasparente, viene istanziato un server web che attende le richieste. In realtà accetterà solo un unico URL;
  • in [3], il browser richiederà al server la sua unica URL;
  • in [4], il server eseguirà lo script Python indicato dalla console [1];
  • in [5], lo script restituirà i propri risultati al server web, un documento di testo;
  • in [6], il server web invierà al browser questo documento di testo;

22.2.1. script [exemple_01]: nozioni di base sul linguaggio HTML

Un browser web può visualizzare diversi tipi di documenti, il più comune dei quali è il documento HTML (HyperText Markup Language). Si tratta di un testo formattato con tag della forma <balise>texte</balise>. Pertanto, il testo <b>important</b> visualizzerà il testo importante in grassetto. Esistono tag singoli, come il tag <hr/> che visualizza una linea orizzontale. Non esamineremo i tag che si possono trovare in un testo HTML. Esistono numerosi software WYSIWYG che consentono di creare una pagina WEB senza scrivere una sola riga di codice HTML. Questi strumenti generano automaticamente il codice HTML di un layout realizzato con il mouse e controlli predefiniti. È quindi possibile inserire (con il mouse) una tabella nella pagina e poi consultare il codice HTML generato dal software per scoprire i tag da utilizzare per definire una tabella in una pagina WEB. Non è più complicato di così. Inoltre, la conoscenza del linguaggio HTML è indispensabile, poiché le applicazioni web dinamiche devono generare autonomamente il codice HTML da inviare ai client web. Questo codice viene generato dal programma e, ovviamente, è necessario sapere cosa generare affinché il client ottenga la pagina web desiderata.

In sintesi, non è affatto necessario conoscere l’intero linguaggio HTML per iniziare a programmare per il web. Tuttavia, tale conoscenza è necessaria e può essere acquisita utilizzando software WYSIWYG per la creazione di pagine WEB, come DreamWeaver e decine di altri. Un altro modo per scoprire le sottigliezze del linguaggio HTML è navigare sul web e visualizzare il codice sorgente delle pagine che presentano caratteristiche interessanti e ancora sconosciute.

Consideriamo il seguente esempio che presenta alcuni elementi che si possono trovare in un documento web, quali:

  • una tabella;
  • un'immagine;
  • un link;

Image

Un documento HTML è racchiuso tra i tag <html>…</html>. È composto da due parti:

  • <head>…</head>: è la parte non visibile del documento. Fornisce informazioni al browser che visualizzerà il documento. Spesso contiene il tag <title>…</title>, che definisce il testo che verrà visualizzato nella barra del titolo del browser. Può contenere anche altri tag, in particolare quelli che definiscono le parole chiave del documento, parole chiave che verranno poi utilizzate dai motori di ricerca. In questa parte si possono trovare anche degli script, scritti per lo più in JavaScript o VBScript, che verranno eseguiti dal browser;
  • <body attributi>…</body>: è la parte che verrà visualizzata dal browser. I tag HTML contenuti in questa sezione indicano al browser l’aspetto visivo “desiderato” per il documento. Ogni browser interpreterà questi tag a modo suo. Due browser possono quindi visualizzare in modo diverso lo stesso documento web. Questo rappresenta generalmente uno dei rompicapo dei web designer;

Il codice HTML del nostro documento di esempio è il seguente:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Quelques balises HTML</title>
</head>

<body style="background-image: url(/static/images/standard.jpg)">
  <h1 style="text-align: left">Quelques balises HTML</h1>
  <hr />

  <table border="1">
    <thead>
      <tr>
        <th>Colonne 1</th>
        <th>Colonne 2</th>
        <th>Colonne 3</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>cellule(1,1)</td>
        <td style="text-align: center;">cellule(1,2)</td>
        <td>cellule(1,3)</td>
      </tr>
      <tr>
        <td>cellule(2,1)</td>
        <td>cellule(2,2)</td>
        <td>cellule(2,3</td>
      </tr>
    </tbody>
  </table>
  <br /><br />
  <table border="0">
    <tr>
      <td>Une image</td>
      <td>
        <img border="0" src="/static/images/cerisier.jpg" />
      </td>
    </tr>
    <tr>
      <td>Le site de Polytech'Angers</td>
      <td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
    </tr>
  </table>
</body>
</html>
Elementi
tag ed esempi HTML
titre du document
<title>Alcuni tag HTML</title> (riga 5)
il testo [Quelques balises HTML] apparirà nella barra del titolo del browser che visualizzerà il documento
barre horizontale
<hr />: visualizza una linea orizzontale (riga 10)
tableau
<attributi tabella>….</table>: per definire la tabella (righe 12, 32)
<thead>…</thead>: per definire le intestazioni delle colonne (righe 13, 19)
<tbody>…</tbody>: per definire il contenuto della tabella (righe 20, 31)
<tr attributi>…</tr>: per definire una riga (righe 21, 25)
<td attributi>…</td>: per definire una cella (riga 22)
esempi:
<table border="1">…</table>: l'attributo border definisce lo spessore del bordo della tabella
<td style="text-align: center;">cella(1,2)</td> (riga 23): definisce una cella il cui contenuto sarà cella(1,2). Questo contenuto sarà centrato orizzontalmente (text-align: center).
image
<img border="0" src="/static/images/cerisier.jpg"/> (riga 38): definisce un'immagine senza bordo (border="0") il cui file sorgente è [/static/images/cerisier.jpg] sul server web (src="/static/images/cerisier.jpg"). Se questo collegamento si trova in un documento web generato con URL [http://server/chemin/balises.html], il browser richiederà URL [http://server/ static/images/cerisier.jpg] per ottenere l'immagine qui indicata.
lien
<a href="http://www.polytech-angers.fr/fr/index.html">qui</a> (riga 43): fa sì che il testo ici funga da link verso l'URL http://www.polytech-angers.fr/fr/index.html.
fond de page
<body style="background-image: url(/static/images/standard.jpg)"> (riga 8): indica che l'immagine da utilizzare come sfondo della pagina si trova all'indirizzo URL [/static/images/standard.jpg] sul server web. Nel contesto del nostro esempio, il browser richiederà i file URL e [http://server/static/images/standard.jpg] per ottenere questa immagine di sfondo.

Da questo semplice esempio si evince che, per costruire l’intero documento, il browser deve effettuare tre richieste al server:

  • [http://server/chemin/balises.html] per ottenere il codice sorgente HTML del documento;
  • [http://server/static/images/cerisier.jpg] per ottenere l'immagine cerisier.jpg;
  • [http://server/static/images/standard.jpg] per ottenere l’immagine di sfondo standard.jpg;

Lo script [exemple_01] ci consentirà di visualizzare la pagina statica precedente [balises.html]:

Image

  • in [1], lo script [exemple_01] che verrà eseguito;
  • in [3], il documento HTML che verrà visualizzato dallo script;
  • in [2], le immagini del documento HTML;

Lo script [exemple_01] è il seguente:


import os

from flask import Flask, make_response, render_template

# applicazione Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Home URL
@app.route('/')
def index():
    # visualizzazione della pagina
    return make_response(render_template("balises.html"))


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 7: si istanzia un'applicazione Flask. Un'applicazione Flask è un'applicazione web;
    • il primo parametro è il nome assegnato all’applicazione. È possibile assegnare il nome che si desidera. In questo caso è stato utilizzato l’attributo predefinito [__name__], il cui valore è [__main__] (riga 18);
    • il secondo parametro è un parametro denominato, ovvero la sua posizione nell’ordine dei parametri non ha importanza. Il parametro denominato [template_folder] indica la cartella in cui si trovano le pagine statiche dell’applicazione web. Le pagine statiche vengono fornite così come sono al browser. In questo caso, le pagine statiche si trovano nella cartella [templates] della struttura del progetto. Alla riga 7 abbiamo specificato un percorso relativo alla cartella [script_dir] contenente lo script [exemple_01] da eseguire;
    • anche il terzo parametro è un parametro con nome. [static_folder] indica la cartella in cui si trovano le risorse del documento HTML (immagini, video, ecc.). Anche in questo caso abbiamo specificato un percorso relativo alla cartella [script_dir] contenente lo script [exemple_01] eseguito;
  • righe 10-14: si definiscono i URL accettati dall’applicazione web. Ogni URL è associato a una funzione che viene eseguita quando il URL viene richiesto da un browser web;
  • riga 11: l'unico URL dell'applicazione è il URL [/]. Si noti che in [@app.route('/')], [app] è la variabile inizializzata alla riga 7. La definizione dei percorsi (i diversi URL gestiti dall’applicazione) viene quindi necessariamente dopo la definizione dell’applicazione [app]. Quest’ultimo nome è a scelta;
  • righe 12-14: la funzione che viene eseguita quando si richiede l’URL [/] all’applicazione web [exemple_01];
  • riga 12: la funzione associata a un URL può avere un nome qualsiasi. A volte può avere dei parametri per recuperare elementi dal URL ad essa associato. In questo caso non ne ha;
  • riga 14:
    • la funzione [render_template] restituisce una stringa di caratteri che corrisponde al documento di testo generato dal suo parametro. In questo caso, il parametro è [balises.html]. A causa del [template_folder] della riga 7, questo documento verrà cercato nella cartella [f"{script_dir}/../templates"]. È effettivamente lì che si trova;
    • la funzione [make_response] genera una risposta HTTP per il browser che le ha richiesto l’URL [/]. Abbiamo visto nel paragrafo |il protocollo HTTP| che una risposta HTTP è composta da due elementi:
      • le intestazioni HTTP;
      • il documento richiesto dal browser, in questo caso un documento HTML;

Alla riga 14, non è stato fornito alcun parametro alla funzione [make_response] per generare le intestazioni HTTP. La funzione genererà quindi quelle predefinite. Vedremo in seguito come impostare queste intestazioni HTTP.

  • Infine, quando il browser richiede l’URL all’applicazione Flask, ottiene la pagina [balises.html];
  • righe 17-20: queste righe servono ad avviare il server web che eseguirà l’applicazione web [exemple_01];
    • riga 18: questa condizione è vera solo quando lo script [exemple_01] viene eseguito all’interno di una console;
    • riga 19: l’applicazione [app] della riga 7 viene configurata:
    • il parametro denominato [ENV="development"] imposta il server web in modalità sviluppo: non appena lo sviluppatore modifica un elemento dell’applicazione, questa viene rigenerata e inviata al server web. Lo sviluppatore non ha bisogno di richiedere una nuova esecuzione;
    • il parametro denominato [DEBUG=True] consentirà allo sviluppatore di inserire dei punti di interruzione nel codice dell’applicazione;
    • riga 20: l'applicazione web viene avviata: viene istanziato un server web e l'applicazione web viene distribuita su di esso per rispondere alle richieste dei client web;

Ecco un esempio di esecuzione:

Image

Nella console di esecuzione compaiono quindi i seguenti log:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_01.py
 * Serving Flask app "exemple_01" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Premere CTRL+C per uscire)
  • riga 2: il server visualizza lo script eseguito;
  • riga 3: ci si trova in modalità sviluppo;
  • righe 4-5: il server rileva che è stato avviato in modalità [debug]. Si riavvia quindi (riga 5). La modalità [debug] rallenta quindi leggermente l’avvio;
  • riga 8: l’URL dove è disponibile l’applicazione web distribuita [exemple_01];

Con un browser web, accediamo a URL [http://127.0.0.1:5000/]:

Image

Si ottiene effettivamente il documento [balises.html] previsto.

22.2.2. script [exemple_02]: generare dinamicamente un documento HTML

Image

Lo script [exemple_02] [1] genererà il seguente documento [exemple_02.html] [2]:


<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>{{page.title}}</title>
</head>
<body>
    <b>{{page.contents}}</b>
</body>
</html>

Questo documento è dinamico perché il suo contenuto è noto nella sua interezza solo nel momento in cui il server web lo serve. Infatti, alle righe 5 e 8 sono presenti due elementi non noti al momento della scrittura della pagina. Essi diventano noti solo nel momento in cui la pagina viene inviata a un client. A quel punto vengono sostituiti dai loro valori, che sono stringhe di caratteri.

  • righe 5, 8: la sintassi {{espressione}} è una sintassi del linguaggio di template Jinja2 [https://jinja.palletsprojects.com/en/2.11.x/]. Prima che la pagina venga inviata a un client, gli elementi dinamici della pagina (righe 5 e 8) vengono valutati e sostituiti con i relativi valori;
  • riga 5: è stata utilizzata la sintassi [page.title]. Si è quindi supposto che, al momento della generazione della pagina prima del suo invio, una variabile [page] sia nota; vedremo come. Nella sintassi {{espressione}} è possibile utilizzare i nomi delle variabili che si desidera. Alle righe 5 e 8, si potrebbero quindi avere {{title}} e {{contents}}. Si potrebbe quindi affermare che [title] e [contents] sono parametri della pagina. Di seguito, useremo sempre la stessa tecnica:
    • l’unico parametro della pagina sarà un dizionario [page];
    • gli attributi di questo dizionario saranno utilizzati nella pagina. In questo caso [page.title] alla riga 5 e [page.contents] alla riga 8;

L’applicazione web [exemple_02.py] è la seguente:


from flask import Flask, make_response, render_template

# applicazione Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Home URL
@app.route('/')
def index():
    # contenuto della pagina sotto forma di dizionario
    page = {"title": "un titre", "contents": "un contenu"}
    # visualizzazione della pagina
    return make_response(render_template("exemple_02.html", page=page))


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • abbiamo già spiegato nell’esempio precedente le righe 4-5 e 18-20. Continueremo a utilizzare questo schema nei nostri esempi;
  • riga 9: l’unico URL fornito dall’applicazione web è l’URL /;
  • riga 14: il documento fornito a URL / è il documento [exemple_02.html] che abbiamo appena commentato. Sappiamo che ha un parametro, un dizionario chiamato [page];
  • riga 12: definiamo il dizionario che verrà passato come parametro alla pagina [exemple_02.html]. Può avere qualsiasi nome. Deve tuttavia avere gli attributi [title, contents] utilizzati nel documento HTML;
  • riga 14: la funzione [render_template] ha il compito di generare la stringa di caratteri del documento [exemple_02.html]. Poiché si tratta di un documento parametrizzato, si trasmettono alla funzione [render_template] i parametri richiesti. Lo facciamo qui assegnando un valore al parametro denominato [page]. Nell’operazione [page=page]:
    • a sinistra del segno =, c’è il parametro [page] utilizzato nel documento [exemple_02.html];
    • a destra del segno =, c’è il valore [page] definito alla riga 12;
    • in generale, se un documento HTML presenta i parametri [param1, param2, …, paramn], i relativi valori verranno passati alla funzione [render_template] nella forma [render_template(document, param1=valeur1, param2=valeur2, …];

Prima di eseguire [exemple_02], dobbiamo interrompere l'esecuzione di [exemple_01]:

Image

Se durante l’esecuzione di uno script 1 avete l’impressione che sia in esecuzione uno script 2, è probabile che quest’ultimo sia ancora in esecuzione. Per tornare a uno stato conosciuto, è possibile arrestare tutti i processi in esecuzione in PyCharm (in alto a destra nella finestra PyCharm):

Image

Eseguiamo lo script [exemple_02]:

Image

I log della console sono quindi i seguenti:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_02.py
 * Serving Flask app "exemple_02" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Premere CTRL+C per uscire)

La riga 8 indica la porta di distribuzione (5000) dell’applicazione [exemple_02] (riga 1) sulla macchina [localhost]. Poiché le righe precedenti sono sempre le stesse, non le riporteremo più.

Con un browser, richiediamo l’URL [http://localhost:5000/]:

Image

  • l’espressione {{page.title}} ha prodotto [1];
  • l’espressione {{page.contents}} ha prodotto [2];

22.2.3. script [exemple_03]: utilizzare frammenti di pagina

Image

  • in [1], lo script [exemple_03.py] genererà il documento dinamico [exemple_03.html] [2]. Quest’ultimo sarà costruito a partire dai frammenti di pagina [fragment_01.html, fragment_02.html] e [3];

Il documento [exemple_03.html] sarà il seguente:


<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
  • nelle righe 3 e 5 si utilizza la direttiva [include] di Jinja2 per includere nel documento elementi esterni ad esso;
  • la sintassi è {% include … %}. Il parametro della direttiva [include] è il percorso del documento da incorporare. Questo percorso è relativo al parametro [template_folder] dell’applicazione Flask:

app = Flask(__name__, template_folder="../templates", static_folder="../static")

Pertanto, in questo caso, i percorsi dei documenti sono misurati rispetto alla cartella [templates].

Il frammento [fragment_01.html] (i nomi sono ovviamente a scelta) è il seguente:


<meta charset="UTF-8">
<title>{{page.title}}</title>

Il frammento [fragment_02.html] è il seguente:


<b>{{page.contents}}</b>

Se si ricompone il documento [exemple_03.html] con questi frammenti, si ottiene il codice seguente:


<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>

Si ottiene quindi un documento identico a [exemple_02.html], ma costruito a partire da frammenti.

Lo script web [exemple_03.py] è il seguente:


import os

from flask import Flask, make_response, render_template

# applicazione Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Home URL
@app.route('/')
def index():
    # contenuto della pagina
    page = {"title": "un autre titre", "contents": "un autre contenu"}
    # visualizzazione della pagina
    return make_response(render_template("views/exemple_03.html", page=page))


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Il codice è analogo a quello di [exemple_02.py]. Alla riga 16 si mostra come è possibile fare riferimento a documenti presenti nelle sottocartelle di [template_folder] della riga 7.

L'esecuzione dello script [exemple_03.py] produce i seguenti risultati nel browser:

Image

22.3. script [flask/02]: servizio web per data e ora

Image

Il documento [date_time_server.html] è 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>
  • riga 8: la pagina accetta il parametro [page.date_heure];

Il servizio web [date_time_server.py] è il seguente:


# importazioni
import os
import time

from flask import Flask, make_response, render_template

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


# Home URL
@app.route('/')
def index():
    # invio dell'ora al cliente
    # time.localtime: numero di millisecondi dal 01/01/1970
    # time.strftime consente di formattare l'ora e la data
    # formato di visualizzazione data-ora
    # d: giorno a 2 cifre
    # m: mese a 2 cifre
    # y: anno a 2 cifre
    # H: ora 0,23
    # M: minuti
    # S: secondi

    # data/ora del momento
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    # si genera il documento da inviare al cliente
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    # risposta HTTP al cliente
    response = make_response(document)
    print("response", type(response), response)
    return response


# solo mano
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 13: l'applicazione web supporta solo URL /;
  • righe 15-24: spiegano come ottenere data e ora e come visualizzarle;
  • riga 27: stringa di caratteri che rappresenta la data e l’ora correnti;
  • righe 28-30: si genera il documento dinamico [date_time_server.html] passandole il dizionario [page] della riga 29;
  • riga 31: si visualizza il tipo di [document] e il documento stesso. Si vuole mostrare che si tratta di una stringa di caratteri;
  • riga 33: si genera la risposta HTTP che verrà inviata al cliente (non è ancora stata inviata);
  • riga 34: si visualizzano il tipo e il valore della risposta;
  • riga 35: la risposta HTTP viene inviata al client;

L'esecuzione dello script produce il seguente risultato in un browser:

Image

I log nella console sono i seguenti:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\flask\02\date_time_server.py
 * Serving Flask app "date_time_server" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Premere CTRL+C per uscire)
127.0.0.1 - - [10/Jul/2020 09:32:09] "GET / HTTP/1.1" 200 -
document <class 'str'> <!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 : 10/07/20 09:42:33</b>
</body>
</html>
response <class 'flask.wrappers.Response'> <Response 195 bytes [200 OK]>
  • riga 10: si vede che il tipo del valore restituito da [render_template] è di tipo [str]. Questa stringa di caratteri non è altro che il documento [date_time_server.html] una volta interpretato (righe 10-19);
  • riga 20: si nota che il tipo del valore restituito da [make_response] è di tipo [flask.wrappers.Response]. La funzione [Response.__str__] è stata implicitamente chiamata per visualizzare l’oggetto [Response]. La stringa restituita da questa funzione fornisce due informazioni sulla risposta HTTP che verrà generata:
    • il documento inviato è di 195 byte;
    • lo stato della risposta HTTP è [200 OK]. Vedremo in seguito che è possibile accedere a questo codice di stato;

22.4. script [flask/03]: servizi web che generano testo in chiaro

Abbiamo visto in un esempio precedente che il servizio web forniva il seguente documento:


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

Un client web potrebbe essere interessato solo all’informazione [page.date_heure] della riga 8 e non al formato HTML che la circonda. Il servizio web potrebbe fornire questa informazione come una semplice stringa di caratteri. Presenteremo qui alcuni esempi di questo tipo di servizio web.

22.4.1. script [main_01]

Image

  • [main_01] è il servizio web;
  • [config] è lo script di configurazione dell’applicazione web;
  • il servizio web utilizza alcune delle entità definite in [2];

Lo script [config] è il seguente:


def configure():
    # percorso assoluto di riferimento per i percorsi relativi della configurazione
    rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # dipendenze dell'applicazione
    absolute_dependencies = [
        # Persone, Utili, MyException
        f"{rootDir}/classes/02/entities",

    ]
    # si imposta il syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # si rende la configurazione
    return {}

Lo scopo principale di questa configurazione è definire il Python Path del servizio web. È necessario che le entità [2] (riga 8) siano reperibili.

Lo script web [main_01] è il seguente:


# si configura l'applicazione
import config
config=config.configure()

# importazioni
from flask import Flask, make_response
from flask_api import status

# dipendenze
from Personne import Personne

# applicazione Flask (qui non ci sono documenti statici)
app = Flask(__name__)


# Home URL
@app.route('/')
def index():
    # una persona
    personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
    # risposta HTTP
    response = make_response(str(personne))
    # intestazioni HTTP
    response.headers.set("Content-type", "application/json; charser=utf8")
    # si restituisce la risposta HTTP
    return response, status.HTTP_200_OK


# solo main
if __name__ == '__main__':
    # si avvia il server
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • righe 1-3: viene impostato il Python Path dell'applicazione;
  • righe 5-10: si importano gli elementi necessari allo script;
  • riga 17: il servizio web serve solo URL /;
  • riga 20: si crea un oggetto [Personne];
  • riga 22: si crea una risposta HTTP con la stringa di caratteri che rappresenta la persona. Verrà chiamata la funzione [Personne.__str__]. Questa restituisce la stringa jSON dal dizionario [asdict] della persona (cfr. |classe BaseEntity|). Il parametro della funzione [make_response] è il documento di testo inviato al cliente, quindi in questo caso la stringa jSON relativa a una persona;
  • riga 24: nelle intestazioni HTTP della risposta si inserisce un'intestazione [Content-type] che indica al cliente quale tipo di documento riceverà, in questo caso un documento jSON codificato in UTF-8;
  • riga 26: si restituisce una tupla di due elementi:
    • la risposta al cliente, le intestazioni HTTP e il documento;
    • il codice di stato della risposta. In questo caso si desidera restituire il codice di stato [200 OK]. I diversi codici di stato sono definiti da costanti nel modulo [flask_api] importato alla riga 7;

Il modulo [flask_api] non è disponibile in modo nativo. È necessario installarlo. Si esegue l’operazione in un terminale PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask_api
Collecting flask_api
  Downloading Flask_API-2.0-py3-none-any.whl (119 kB)
     || 119 kB 544 kB/s
Requirement already satisfied: Flask>=1.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_api) (1.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.0.1)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (7.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.1.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask>=1.1->flask_api) (1.1.1
)
Installing collected packages: flask-api
Successfully installed flask-api-2.0

Quando si esegue lo script web [main_01], si ottengono i seguenti risultati in un browser:

Image

  • in [2], la stringa jSON ricevuta;
  • in [3-4], viene visualizzato il contenuto del documento ricevuto. Si nota che non è presente alcun markup HTML, ma solo la stringa jSON;

Vediamo ora il ruolo dell’intestazione [Content-Type] inviata al client dal servizio web. Impostiamo il browser in modalità sviluppatore (di solito F12) e richiediamo nuovamente la stessa URL. Di seguito è riportata una schermata del browser Chrome:

Image

  • in [1], selezionare la scheda [Network];
  • in [2, 4]: l’URL richiesto dal browser;
  • in [3], selezionare la scheda [Headers] (intestazioni HTTP);
  • in [5], il codice di stato della risposta HTTP ricevuta;
  • in [6], l'intestazione che indica al client che riceverà un testo jSON. Ciò consente al client di adattarsi alla risposta. Pertanto, il tipo di carattere utilizzato da Chrome per visualizzare una risposta jSON o una risposta di testo semplice non è lo stesso;

Image

  • in [8], si seleziona la scheda [Response] per accedere al documento inviato dal servizio web, in questo caso una semplice stringa jSON;

22.4.2. Postman

[Postman] è lo strumento che ci permetterà di interrogare i diversi URL di un'applicazione web. Ci permette di:

  • di utilizzare qualsiasi URL: questi sono generati manualmente;
  • di inviare richieste al server web tramite un GET, POST, PUT, OPTIONS…;
  • specificare i parametri di GET o di POST;
  • di impostare le intestazioni HTTP della richiesta;
  • ricevere una risposta nei formati jSON, XML, HTML,
  • di avere accesso alle intestazioni HTTP della risposta. Si ha quindi accesso alla risposta completa HTTP del server;

[Postman] è un eccellente strumento didattico per comprendere la comunicazione client/server del protocollo HTTP.

[Postman] è disponibile su URL [https://www.getpostman.com/downloads/]. Procedete all’installazione della vostra versione di [Postman]. Durante l’installazione, vi verrà chiesto di creare un account: in questo caso non servirà. L’account [Postman] serve a sincronizzare diversi dispositivi in modo che la configurazione di uno venga replicata su un altro. Niente di tutto ciò è utile in questo caso.

Una volta installato, [Postman] presenta la seguente interfaccia:

Image

  • in [2-3] si ha accesso alle impostazioni del prodotto;

Image

  • in [6], la versione utilizzata in questo documento;

Qui utilizzeremo [Postman] per testare il servizio web jSON precedente:

  • eseguiamo lo script [flask/03/main_01];
  • quindi richiediamo URL [http://localhost:5000/] con Postman; Image
  • in [1], creiamo una richiesta;
  • in [2], sarà una richiesta HTTP GET;
  • in [3], l’URL del servizio web interrogato;
  • in [4], si invia la richiesta al servizio web; Image
  • in [5], si seleziona la scheda [Body] che visualizza il documento ricevuto;
  • in [6], si seleziona la scheda [Pretty] che visualizza il documento ricevuto con una formattazione appropriata, in questo caso una formattazione adatta a una stringa jSON;
  • in [7], il documento jSON ricevuto;
  • in [8-9], il documento ricevuto senza formattazione; Image
  • in [10], vengono visualizzate le intestazioni HTTP ricevute da Postman;
  • in [11], lo stato HTTP della risposta ricevuta;
  • in [12], le intestazioni HTTP ricevute;
  • in [13], l’intestazione [Content-type] che ha permesso a Postman di sapere che avrebbe ricevuto una stringa jSON. Postman ha utilizzato questa informazione per formattare, in un certo senso, il documento ricevuto;

Esiste un altro modo di utilizzare Postman. Consiste nell’utilizzare la console di Postman (Ctrl-Alt-C). Questa consente di visualizzare il dialogo client/server. Oltre alla sequenza Ctrl-Alt-C, la console di Postman è accessibile tramite un’icona in basso a sinistra nella finestra principale di Postman:

Image

La console di Postman memorizza le comunicazioni client/server che avvengono quando viene eseguita una richiesta Postman:

Image

  • in [3], l’elenco delle richieste effettuate da Postman dal momento del suo avvio. Le più recenti si trovano in fondo all’elenco;
  • in [4], la richiesta HTTP effettuata da Postman;
  • in [5-6], la risposta HTTP fornita dal server web;
  • in [7], è possibile visualizzare i log in modalità [raw], ovvero senza alcuna formattazione;

In modalità [raw] la finestra della console diventa la seguente:

Image

  • in [8], la richiesta HTTP effettuata da Postman al server web;
  • in [9], la risposta HTTP fornita dal server web;
  • in [10], è possibile tornare alla modalità [pretty logs];

Per facilitare la comprensione, numereremo le righe ottenute dalla console di Postman.

Per il client:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 70e2acaa-b3e5-46f6-8375-989e6b94e694
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Per il server:

1
2
3
4
5
6
HTTP/1.0 200 OK
Content-type: application/json; charser=utf8
Content-Length: 56
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:19:56 GMT
{"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87}

D'ora in poi utilizzeremo principalmente:

  • [Postman] come client web;
  • la console [Postman] in [raw mode] per illustrare il dialogo client/server;

22.4.3. script [main_02]

Image

Lo script web [main_02] è il seguente:


# si configura l'applicazione
import config
config=config.configure()

# importazioni
from flask import Flask, make_response
from flask_api import status

# dipendenze
from Personne import Personne

# applicazione Flask
app = Flask(__name__)


# Home URL
@app.route('/')
def index():
    # una persona
    personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
    # contenuto
    response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
    # intestazioni HTTP
    response.headers.set("Content-Type", "text/plain; charset=utf8")
    # risposta HTTP
    return response, status.HTTP_200_OK


# solo main
if __name__ == '__main__':
    # si avvia il server
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • lo script [main_02] è analogo allo script [main_01]. Si differenzia da esso in due punti:
    • riga 22: il documento inviato al client è una stringa di caratteri grezza, non una stringa jSON;
    • riga 24: ciò si riflette nell’intestazione HTTP [Content-Type] che indica il tipo [text/plain] per il documento;

Eseguiamo lo script web [main_02], quindi utilizziamo [Postman] per interrogarlo:

Image

  • in [1-3], si invia la richiesta al servizio web;
  • in [5], lo stato OK della risposta;
  • in [4, 6], le intestazioni HTTP della risposta;
  • in [7], l'intestazione [Content-Type];
  • in [8-10], il documento inviato dal servizio web, una stringa di caratteri;

La console Postman fornisce i seguenti log:

Richiesta del client:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 7c7fc9f3-8df8-49ae-9dc8-53c2d87d111a
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Risposta del server:


HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf8
Content-Length: 34
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:34:22 GMT

personne[Aglaë, de la Hûche, 87]

22.4.4. script [main_03]

Image

Lo script web [main_03] è il seguente:


# si configura l'applicazione
import config
config = config.configure()

# importazioni
from flask import Flask, make_response
from flask_api import status

# dipendenze
from MyException import MyException
from Personne import Personne

# applicazione Flask
app = Flask(__name__)


# Home URL
@app.route('/')
def index():
    # dati errati
    msg_erreur = None
    try:
        personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
    except MyException as erreur:
        msg_erreur = f"{erreur}"
    # errore?
    if msg_erreur:
        response = make_response(msg_erreur)
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    else:
        response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
        status_code = status.HTTP_200_OK
    # intestazioni HTTP
    response.headers.set("Content-Type", "text/plain; charset=utf8")
    # risposta HTTP
    return response, status_code


# solo main
if __name__ == '__main__':
    # si avvia il server
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 23: si genera un errore istanziando una persona errata;
  • righe 27-29: a causa dell’errore:
    • riga 28: si prepara una risposta HTTP contenente il messaggio di errore;
    • riga 29: si assegna al codice di stato HTTP un valore di errore [500 Internal Server Error];
  • riga 34: si comunica al client che gli verrà inviato un testo in formato testo semplice;
  • riga 36: si invia la risposta HTTP al cliente;

Avviamo il servizio web [main_03] e utilizziamo Postman per interrogarlo:

Image

  • in [1-3], inviamo la richiesta;
  • in [4], si ottiene una risposta con un codice di stato [500 INTERNAL SERVER ERROR];
  • in [5-7]: la risposta è un testo che descrive l’errore verificatosi;

Image

  • con [8-10], le intestazioni HTTP della risposta del servizio web;

Nella console Postman, i risultati in modalità [raw] sono i seguenti:

Richiesta del client:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 925ff036-a360-47af-adf6-78173c01a247
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Risposta del server:


HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/plain; charset=utf8
Content-Length: 74
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:39:24 GMT

MyException[11, Le prénom doit être une chaîne de caractères non vide]

22.5. script [flask/04]: informazioni incapsulate nella richiesta

Image

Lo script [request_parameters.py] ha lo scopo di dimostrare che il servizio web ha accesso a varie informazioni incapsulate nella richiesta di un client web. Il codice è il seguente:


# import
from flask import Flask, make_response, request
from flask_api import status
# applicazione Flask
app = Flask(__name__)


# Home URL
@app.route('/', methods=['GET', 'POST'])
def index():
    # parametri della richiesta
    request_data = {}
    request_data["environ"] = f"{request.environ}"
    request_data["path"] = request.path
    request_data["full_path"] = request.full_path
    request_data["script_root"] = request.script_root
    request_data["url"] = request.url
    request_data["base_url"] = request.base_url
    request_data["url_root"] = request.url_root
    request_data["accept_charsets"] = request.accept_charsets
    request_data["accept_encodings"] = request.accept_encodings
    request_data["accept_languages"] = request.accept_languages
    request_data["accept_mimetypes"] = request.accept_mimetypes
    request_data["args"] = request.args
    request_data["content_encoding"] = request.content_encoding
    request_data["content_length"] = request.content_length
    request_data["content_type"] = request.content_type
    request_data["endpoint"] = request.endpoint
    request_data["files"] = request.files
    request_data["form"] = request.form
    request_data["host"] = request.host
    request_data["method"] = request.method
    request_data["query_string"] = request.query_string.decode()
    request_data["referrer"] = request.referrer
    request_data["remote_addr"] = request.remote_addr
    request_data["remote_user"] = request.remote_user
    request_data["scheme"] = request.scheme
    request_data["script_root"] = request.script_root
    request_data["user_agent"] = f"{request.user_agent}"
    request_data["values"] = request.values
    # risposta HTTP
    response = make_response(request_data)
    # intestazioni HTTP
    response.headers["Content-Type"] = "application/json; charset=utf-8"
    # invio della risposta HTTP
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 9: introduciamo una modifica. Specifichiamo quali sono i verbi consentiti nella richiesta del client. Postman ne fornisce l’elenco:

Image

I primi due, [GET, POST], sono i più utilizzati e saranno anche gli unici ad essere impiegati in questo documento. Tornando alla riga 9 del codice, il parametro [methods] contiene l’elenco dei metodi dell’elenco sopra riportato autorizzati dal URL. In assenza di questo parametro, è autorizzato solo il metodo [GET]. È ciò che è accaduto finora;

  • riga 12: creeremo il dizionario [request_data];
  • riga 13: la richiesta del cliente è disponibile in un oggetto predefinito [request], importato alla riga 2, di tipo [werkzeug.local.LocalProxy]. Le righe successive recuperano vari attributi di questo oggetto;
  • anziché descrivere in dettaglio ogni attributo dell’oggetto [request], eseguiremo questo codice e osserveremo i risultati. In questo modo si comprenderà meglio il significato dei diversi attributi visualizzati;
  • riga 42: il dizionario [request_data] costituirà il contenuto della risposta HTTP. Ricordiamo che quest’ultimo deve essere del testo. Flask converte automaticamente i dizionari in stringhe jSON;
  • riga 44: si comunica al client che riceverà jSON;
  • riga 46: si invia la risposta al client;

Con il client Postman, inviamo la seguente richiesta al servizio web precedente:

Image

  • in [1-2], la richiesta inviata;
  • in [2], la richiesta è configurata. I parametri sono aggiunti a URL sotto la forma [ ?param1=valeur1&param2=valeur2]. Esistono due modi per inserire questi parametri in Postman:
    • scriverli direttamente in URL;
    • inserirli nel [3-4];

Entrambi i metodi sono equivalenti;

Aggiungiamo altri parametri alla richiesta:

Image

  • in [5-7], aggiungiamo i parametri nel corpo (=body) della richiesta. Mentre i parametri di URL sono visibili all’utente di un browser web, quelli che fanno parte del corpo della richiesta non sono visibili. Il browser (o, in questo caso, Postman) li invia al server dopo le intestazioni HTTP. La richiesta del client web presenta quindi la stessa struttura della risposta del server web: intestazioni HTTP seguite da un documento. Ciò farà apparire due nuove intestazioni HTTP nella richiesta del client:
    • [Content-Type]: il client comunica al server il tipo di documento che sta inviando;
    • [Content-Length]: la dimensione del documento in byte;
  • in [6], la codifica da utilizzare per i parametri dichiarati in [7]. Questi possono essere codificati in vari modi. [x-www-form-urlencoded] è un metodo utilizzato frequentemente dai browser;

È possibile vedere la richiesta che verrà generata:

Image

La risposta a questa richiesta è la seguente:

Image

  • in [1-5], è stata ricevuta una stringa jSON [3];
  • ciò che generalmente interessa al servizio web sono i parametri di URL [ ?param1=valeur1&param2=valeur2] e quelli che sono stati trasmessi nel corpo della richiesta (documento). È così, in genere, che il client gli trasmette le informazioni. Si vede in [5] che i parametri di URL sono disponibili in [request.args];

Il resto della risposta è il seguente:

Image

  • in [9], gli attributi dei parametri inseriti nel corpo della richiesta:
    • [content_type] è il tipo di documento allegato alla richiesta. Abbiamo visto che questo documento conteneva informazioni di tipo [param=valeur] codificate nella forma [x-www-form-urlencoded]. Postman ha quindi generato un'intestazione HTTP [Content-Type] che indica la natura del documento;
    • [content_length] è la dimensione in byte di questo documento;
  • in [10], l’attributo [request.environ] contiene numerose informazioni sull’ambiente in cui viene elaborata la richiesta del client. La maggior parte di queste informazioni si ritrova negli altri attributi dell’oggetto [request];
  • in [11], i parametri presenti nel corpo della richiesta sono disponibili nell’attributo [request.form];
  • in [12], il metodo utilizzato per inviare la richiesta, in questo caso il metodo [GET];
  • in [13], l’attributo [request.values] è il dizionario di tutti i parametri, sia quelli di URL sia quelli del corpo del documento. Per ottenere i parametri della richiesta, si utilizzerà l’attributo:
    • [request.args] per ottenere quelli presenti in URL;
    • [request.form] per ottenere quelli presenti nel corpo del documento;

Nella console Postman, i log sono i seguenti:

Richiesta del client:

GET /?param1=valeur1&param2=valeur2 HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: cbfac6aa-71a0-4076-a0c3-91d36d74a4c0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 60

nom=s%C3%A9l%C3%A9n%C3%A9&pr%C3%A9nom=agla%C3%AB&%C3%A2ge=77
  • riga 9: il tipo del documento inviato alla riga 12 al server;
  • riga 11: le intestazioni HTTP della richiesta sono separate dal documento inviato da una riga vuota. È in questo modo che il server individua la fine delle intestazioni HTTP del client;
  • riga 12: il documento «codificato con URL». Tutti i caratteri accentati sono stati sottoposti a codifica;

La risposta del client è la seguente:


HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2433
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:09:09 GMT

{
  "accept_charsets": [], 
  "accept_encodings": [
    [
      "gzip", 
      1
    ], 
    [
      "deflate", 
      1
    ], 
    [
      "br", 
      1
    ]
  ], 
  "accept_languages": [], 
  "accept_mimetypes": [
    [
      "*/*", 
      1
    ]
  ], 
  "args": {
    "param1": "valeur1", 
    "param2": "valeur2"
  }, 
  "base_url": "http://localhost:5000/", 
  "content_encoding": null, 
  "content_length": 60, 
  "content_type": "application/x-www-form-urlencoded", 
  "endpoint": "index", 
  "environ": "{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=908>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x00000173CA6E5160>, 'SERVER_SOFTWARE': 'Werkzeug/1.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': 'param1=valeur1&param2=valeur2', 'REQUEST_URI': '/?param1=valeur1&param2=valeur2', 'RAW_URI': '/?param1=valeur1&param2=valeur2', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 50592, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_USER_AGENT': 'PostmanRuntime/7.26.1', 'HTTP_ACCEPT': '*/*', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_POSTMAN_TOKEN': 'cbfac6aa-71a0-4076-a0c3-91d36d74a4c0', 'HTTP_HOST': 'localhost:5000', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 'CONTENT_LENGTH': '60', 'werkzeug.request': <Request 'http://localhost:5000/?param1=valeur1&param2=valeur2' [GET]>}", 
  "files": {}, 
  "form": {
    "nom": "s\u00e9l\u00e9n\u00e9", 
    "pr\u00e9nom": "agla\u00eb", 
    "\u00e2ge": "77"
  }, 
  "full_path": "/?param1=valeur1&param2=valeur2", 
  "host": "localhost:5000", 
  "method": "GET", 
  "path": "/", 
  "query_string": "param1=valeur1&param2=valeur2", 
  "referrer": null, 
  "remote_addr": "127.0.0.1", 
  "remote_user": null, 
  "scheme": "http", 
  "script_root": "", 
  "url": "http://localhost:5000/?param1=valeur1&param2=valeur2", 
  "url_root": "http://localhost:5000/", 
  "user_agent": "PostmanRuntime/7.26.1", 
  "values": {
    "nom": "s\u00e9l\u00e9n\u00e9", 
    "param1": "valeur1", 
    "param2": "valeur2", 
    "pr\u00e9nom": "agla\u00eb", 
    "\u00e2ge": "77"
  }
}
  • righe 1-5: le intestazioni HTTP della risposta, terminate da una riga vuota;
  • righe 41-45: i caratteri accentati sono stati sottoposti a codifica UTF-8;

Se ora si utilizza il metodo [POST] per inviare la stessa richiesta con gli stessi parametri, si otterrà la stessa risposta, tranne che nel caso di [12], dove si avrà [‘method’ : ‘POST’].

Qual è quindi la differenza tra i metodi GET e POST? La differenza è minima ed è stata determinata dall’uso che ne hanno fatto storicamente i browser:

  • i parametri in URL sono pratici perché un URL così configurato può fungere da collegamento in un documento HTML. L’utente può anche modificare autonomamente i parametri per ottenere risposte diverse dal server. In questo caso, i browser utilizzano comunemente il metodo [GET] e non è presente alcun corpo (content_length=0) nella richiesta inviata al server web (nessun parametro nascosto);
  • a volte non si desidera che i parametri vengano visualizzati nel URL. È il caso delle password inviate al server. Inoltre, lo spazio occupato dai parametri del URL è limitato (un URL non può superare una certa dimensione). I parametri nel corpo della richiesta non hanno questa limitazione. Inoltre, la presenza di molti parametri nel URL lo rende illeggibile. Prendiamo il caso comune di un modulo di registrazione a un sito web. Storicamente, quando le pagine HTML non includevano ancora JavaScript, i browser inviavano le informazioni inserite tramite un POST. Si parlava allora di valori inviati via POST;

Quindi, agli albori della programmazione web:

  • i metodi GET erano piuttosto associati alla richiesta di informazioni fornite da un server web;
  • i metodi POST erano invece associati all’invio di informazioni dal browser al server. Il server veniva così “arricchito” da tali informazioni;

Da allora è arrivato JavaScript. Mentre negli esempi precedenti lo sviluppatore non aveva il controllo (cliccare su un link attivava inevitabilmente un GET, inviare un modulo passava inevitabilmente attraverso un POST), JavaScript ha restituito loro il controllo. In questo modello, la pagina HTML è associata a codice JavaScript in grado di aggirare il browser. Pertanto, il clic su un link può essere intercettato dal codice JavaScript, che può poi eseguire un codice che invia una richiesta al server. Questa richiesta sarà trasparente per l’utente. L’utente non se ne accorgerà. Questo codice è un client web e, come abbiamo fatto con Postman, lo sviluppatore può creare la richiesta che desidera. Per tornare al clic su un link, può eseguire un POST mentre, per impostazione predefinita, il browser avrebbe eseguito un GET. Questi sviluppi hanno reso meno rilevanti le differenze tra GET e POST.

Tuttavia, gli sviluppatori adottano spesso le seguenti regole:

  • un GET non deve modificare lo stato del server. GET successivi, effettuati con gli stessi parametri presenti nel URL, devono restituire lo stesso documento. Inoltre, il GET nella maggior parte dei casi non ha un corpo (nessun documento associato), ma solo parametri nel URL;
  • il POST può modificare lo stato del server. I parametri vengono solitamente inviati nel corpo della richiesta. Si parla quindi di valori inviati tramite POST. L’esempio del modulo è il più eloquente: i valori inseriti dall’utente verranno inseriti nel corpo del POST e il server li salverà da qualche parte, spesso in un database;

Nel prosieguo del documento non ci atteniamo a nessuna regola particolare.

22.6. Script [flask-05]: gestione della memoria dell’utente

22.6.1. Introduzione

Negli esempi client/server precedenti il funzionamento era il seguente:

  • il client apre una connessione alla porta 80 del server web;
  • invia la sequenza di testo: intestazioni HTTP, riga vuota, [document];
  • in risposta, il server invia una sequenza dello stesso tipo;
  • il server chiude la connessione con il client;
  • il client chiude la connessione al server;

Se lo stesso client effettua poco dopo una nuova richiesta al server web, viene creata una nuova connessione tra il client e il server. Quest’ultimo non può sapere se il client che si connette si è già collegato in precedenza o se si tratta di una prima richiesta. Tra una connessione e l’altra, il server “dimentica” il proprio client. Per questo motivo, si dice che il protocollo HTTP sia un protocollo senza stato. È tuttavia utile che il server ricordi i propri clienti. Infatti, se un’applicazione è protetta, il cliente invierà al server un nome utente e una password per identificarsi. Se il server “dimentica” il proprio cliente tra una connessione e l’altra, quest’ultimo dovrà identificarsi ad ogni nuova connessione, il che non è praticabile.

Per tenere traccia di un cliente, il server può procedere in vari modi:

  1. in occasione di una prima richiesta da parte di un cliente, include nella sua risposta un identificativo che il cliente dovrà poi rinviargli ad ogni nuova richiesta. Grazie a questo identificativo, diverso per ogni cliente, il server può riconoscere un cliente. Può quindi gestire una memoria per quel cliente sotto forma di una memoria associata in modo univoco all’identificativo del cliente. È così che funzionano, ad esempio, i servizi PHP;
  2. in occasione di una prima richiesta da parte di un cliente, il server include nella risposta non un identificativo, ma la memoria dell’utente stessa. Non conserva nulla sul lato server. Per mantenere la propria memoria, il client web deve rinviare tale memoria ad ogni nuova richiesta. Questa viene modificata (o meno) ad ogni nuova richiesta e rinviata (o meno) al client. È il metodo utilizzato dal framework Flask;

Le differenze tra i due metodi sono le seguenti:

  • il metodo 1 richiede meno larghezza di banda. Tra il client e il server viene scambiato solo un identificativo. Quando la memoria dell’utente cresce, ciò non ha alcuna conseguenza sull’identificativo, che rimane lo stesso. Non è così nel caso del metodo 2, in cui la memoria dell’utente viene scambiata a ogni richiesta e può aumentare con il susseguirsi delle richieste;
  • il metodo 1 richiede più spazio di memoria. Infatti, il server memorizza la memoria dell’utente sui propri file system. Se ci sono un milione di utenti, ciò potrebbe rappresentare un problema. Il metodo 2 non memorizza nulla sul server;

Tecnicamente, in entrambi i metodi avviene quanto segue:

  • nella risposta a un nuovo cliente, il server include l'intestazione HTTP, [Set-Cookie : MotClé=Identifiant] o [Set-Cookie : mémoire]. Con il metodo 1, lo fa solo alla prima richiesta. Con il metodo 2, lo fa ogni volta che la memoria dell’utente cambia;
  • nelle sue richieste, il client rinvia sistematicamente ciò che ha ricevuto, ovvero un identificativo o un dato. Lo fa tramite l’intestazione HTTP [Cookie : MotClé=Valeur];

Ci si potrebbe chiedere come faccia il server a sapere di avere a che fare con un nuovo cliente piuttosto che con uno già presente. È la presenza dell’intestazione HTTP Cookie nelle intestazioni HTTP del cliente a indicarglielo. Per un nuovo cliente, questa intestazione è assente.

L'insieme delle connessioni di un determinato cliente è denominato «sessione».

Il server può gestire altri tipi di memoria:

Image

  • In [1], la memoria della richiesta è particolare. Viene utilizzata quando la richiesta del client web viene elaborata non da un unico servizio (o applicazione), ma da più servizi. Per trasmettere informazioni al servizio i+1, il servizio i può arricchire la richiesta elaborata (request) con tali informazioni. È ciò che viene definito “memoria a livello di richiesta”. In questo documento non utilizzeremo questo tipo di memoria;
  • in [2, 4], la memoria dell’utente che abbiamo appena descritto. Può essere implementata localmente [2] o gestita tramite il client [4];
  • in [3], la memoria a livello di «applicazione» è molto spesso una memoria di sola lettura. È condivisa da tutti gli utenti. Vi si trovano spesso elementi della configurazione dell’applicazione web, configurazione condivisa da tutti gli utenti dell’applicazione. È necessario prestare attenzione a questo tipo di memoria: la scrittura al suo interno deve avvenire in un momento in cui gli utenti non hanno ancora inviato richieste, solitamente all’avvio dell’applicazione. Successivamente, quando arrivano le richieste, è difficile scrivere in questa memoria. Quando il server web serve contemporaneamente più utenti e due di essi vogliono scrivere nella memoria a livello di “applicazione”, c’è il rischio che tale memoria venga corrotta. Infatti, mentre l’utente 1 ha iniziato a scrivere nella memoria a livello di “applicazione”, può essere interrotto prima ancora di aver finito. Si ottiene quindi una memoria dell’applicazione incompleta. Poiché è condivisa, un utente 2 può leggerla e ottenere uno stato errato;

22.6.2. script [session_scope_01]

Image

Gli script [session_scope_xx] illustrano la gestione delle memorie utente.

Lo script [session_scope_01] è il seguente:


# si configura l'applicazione
import config
config = config.configure()

# dipendenze
import json
from flask import Flask, make_response, session
from flask_api import status

# applicazione Flask
app = Flask(__name__)

# chiave segreta della sessione
app.secret_key = config["SECRET_KEY"]


@app.route('/set-session', methods=['GET'])
def set_session():
    # si inserisce qualcosa nella sessione
    session['nom'] = 'séléné'
    # si invia una risposta vuota
    response = make_response()
    response.headers['Content-Length'] = 0
    return response, status.HTTP_200_OK


@app.route('/get-session', methods=['GET'])
def get_session():
    # si recupera la sessione e si invia la risposta
    response = make_response(json.dumps({"nom": session['nom']}, ensure_ascii=False))
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# solo main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 11: viene istanziata un'applicazione Flask;
  • riga 14: l’attributo [secret_key] di questa applicazione riceve un valore tratto dal file di configurazione utilizzato alle righe 1-3. Una sessione Flask è possibile solo se questo attributo è inizializzato. È possibile inserirvi qualsiasi cosa. Serve a crittografare una parte della «memoria utente» che verrà inviata al client. In genere si inserisce qualcosa di difficile da indovinare. Nel file [config], la chiave segreta è definita come segue:

    # si restituisce la configurazione
    config = {
        # configurazione Flask
        "SECRET_KEY": "vibnFfrdWYUp?*LQ"
    }
  • per la prima volta, definiamo un’applicazione web che utilizza un valore diverso da URL /
    • riga 17: l’URL [/set-session] serve a inizializzare la sessione dell’utente;
    • riga 27: il codice URL [/get-session] serve a recuperare la memoria dell’utente (o la sessione dell’utente);
  • riga 20: si inserisce qualcosa nella memoria (= la sessione) dell’utente, in questo caso un nome. La sessione funziona un po’ come un dizionario. Non è possibile inserire qualsiasi cosa nella sessione. I valori inseriti devono poter essere convertiti in jSON. Per i tipi predefiniti di Python, ciò avviene senza l’intervento dello sviluppatore. Per gli oggetti proprietari che Python non riconosce, è necessario effettuare autonomamente la conversione jSON;
  • riga 22: si crea una risposta HTTP senza contenuto (assenza di parametri in make_response);
  • riga 23: si comunica al client che riceverà un documento vuoto (dimensione pari a 0 byte);
  • riga 24: si invia la risposta HTTP al cliente. URL [/set-session] non fa quindi altro che inizializzare una sessione utente;
  • riga 27: URL e [/get-session] consentono all’utente di sapere cosa contiene la sua sessione;
  • riga 30: si crea una risposta HTTP contenente la stringa jSON della sessione dell’utente. In questo caso abbiamo creato noi stessi la stringa jSON invece di lasciare che fosse Flask a generarla. Infatti, non vogliamo che i caratteri accentati vengano sottoposti a escape (ensure_ascii=False);
  • riga 31: si comunica al client che gli verrà inviato jSON;
  • riga 32: inviamo la risposta HTTP al client;

Lo scopo di questo script è dimostrare che la sessione utente consente di collegare tra loro le sue richieste successive:

  • la richiesta 1 richiederà URL [/set-session];
  • la richiesta 2 richiederà URL [/get-session] e recupererà il nome che la richiesta 1 avrà inizializzato;

Lo script [config] che configura gli script della cartella [flask/05] è il seguente:


def configure():
    # percorso assoluto di riferimento per i percorsi relativi della configurazione
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # dipendenze dell'applicazione
    absolute_dependencies = [
        # Persone, Utili, MyException
        f"{root_dir}/classes/02/entities",
    ]
    # si imposta il syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # si rende la configurazione
    config = {
        # configurazione Flask
        "SECRET_KEY": "vibnFfrdWYUp?*LQ"
    }

    return config

Eseguiamo lo script [session_scope_01], quindi con Postman invieremo la richiesta a URL e [/set-session]. Prima di farlo, verificheremo alcuni elementi della richiesta che verrà effettuata:

Image

  • in [1], accediamo ai cookie di Postman; Image
  • in [2-4], verifichiamo i cookie noti di Postman e li eliminiamo tutti [4-5];

Ora verifichiamo la richiesta HTTP che verrà generata:

Image

  • in [9]: una parte delle intestazioni HTTP che Postman inserirà nella richiesta in base alla configurazione che abbiamo impostato per essa. Questa verifica consente di assicurarsi di non aver tralasciato alcun parametro o, al contrario, di non aver lasciato parametri superflui;

Fatto ciò, è possibile eseguire la query:

Image

Esistono diversi modi per verificare il risultato. Si può innanzitutto osservare la finestra principale:

Image

  • in [1-2], la richiesta inviata al servizio web;
  • in [3-6], le intestazioni HTTP della risposta;
  • in [4], poiché nel codice non è stato specificato il tipo di risposta, Flask ha utilizzato per impostazione predefinita il tipo [text/html];
  • in [5], il client sa che nella risposta non è presente alcun documento;
  • riga 6: l’intestazione [Set-Cookie] è stata inviata dal server Flask. Il suo valore è denominato cookie di sessione. È costituito da tre elementi:
    • [session=valeur]: il valore rappresenta la memoria dell’utente in forma codificata. Questa memoria è decodificabile (cfr. |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session|). Tuttavia, a causa della chiave segreta utilizzata dal server, l’utente non può modificare i dati ricevuti per poi rinviarli al server. Quando il server riceve una sessione, ha così la certezza di ricevere una sessione non compromessa;
    • [HttpOnly]: la presenza di questo elemento indica al browser che lo riceve che il cookie non deve essere accessibile al JavaScript che la pagina visualizzata potrebbe contenere;
    • [Path=/] è il percorso a cui deve essere rinviato il cookie di sessione, ovvero, in questo caso, qualsiasi percorso dell’applicazione web. Ogni volta che l’utente, tramite la tastiera, richiederà esplicitamente (digitando un URL) o implicitamente (facendo clic su un link) un URL di questo dominio, il browser rinvierà automaticamente il cookie di sessione che ha ricevuto;

Lo svantaggio della finestra principale è che non si ha accesso alla richiesta completa che ha portato a questa risposta. Ciò che viene presentato in questa finestra può creare confusione:

Image

  • nelle intestazioni HTTP [3-4] viene presentato come [5], un cookie di sessione. Si potrebbe quindi pensare che Postman abbia inserito nella richiesta un cookie di sessione, mentre non è così. Le intestazioni [3] rappresentano infatti le intestazioni HTTP che verranno inviate nella prossima richiesta, così come questa è attualmente configurata. Postman ha appena ricevuto un cookie di sessione che rinvierà nella prossima richiesta. Ecco perché abbiamo [5];

È possibile accedere alla finestra di dialogo client/server nella console di Postman, che si apre con Ctrl-Alt-C:


GET /set-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 3673b73f-7600-4df4-8c4b-c37973e50df8
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Cookie
Set-Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:32:57 GMT
  • riga 14: il cookie di sessione inviato dal server;

Ora richiediamo URL [/get-session]:

GET /get-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: ce991398-2d9a-46d0-9ccd-c7ff3c7f4d6d
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 20
Vary: Cookie
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:36:52 GMT

{"nom": "séléné"}
  • riga 9: il client Postman ha rinviato al server il cookie di sessione che aveva ricevuto;
  • riga 18: la stringa jSON inviata dal server;

Questo esempio ci mostra diversi aspetti:

  • il client Postman rinvia il cookie di sessione che riceve dal server Flask. I browser web procedono sempre in questo modo;
  • vediamo che la richiesta 2 [/get-session] ha permesso di recuperare un'informazione creata durante la richiesta 1 [/set-session]. Abbiamo quindi qui una memoria dell'utente;
  • righe 11-16: il server Flask non ha restituito alcun cookie di sessione. Ciò non avviene sistematicamente. Il server Flask restituisce il cookie di sessione solo se l’ultima richiesta ha modificato la memoria dell’utente;

22.6.3. script [session_scope_02]

Image

Lo script [session_02] è il seguente:


# dipendenze
import os

from flask import Flask, make_response, session
from flask_api import status

# applicazione Flask
app = Flask(__name__)

# chiave segreta della sessione
app.secret_key = os.urandom(12).hex()


# Home URL
@app.route('/', methods=['GET'])
def index():
    # gestiamo tre contatori
    if session.get('n1') is None:
        session['n1'] = 0
    else:
        session['n1'] = session['n1'] + 1
    if session.get('n2') is None:
        session['n2'] = 10
    else:
        session['n2'] = session['n2'] + 1
    if session.get('n3') is None:
        session['n3'] = 100
    else:
        session['n3'] = session['n3'] + 1
    # dizionario dei contatori
    compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
    # si invia la risposta
    response = make_response(compteurs)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 11: qui la chiave segreta viene generata tramite una funzione. Il vantaggio di questa funzione è che genera una stringa di caratteri complessa in modo casuale. Ricordiamo che la variabile [app] è l’istanza della classe Flask creata alla riga 8;
  • riga 15: questa volta ci sarà un solo percorso, ovvero il percorso /;
  • righe 17-29: si gestisce una sessione contenente tre contatori [n1, n2, n3]. Al primo accesso dell’utente [n1, n2, n3]=[0, 10, 100], e successivamente ad ogni accesso, questi contatori vengono incrementati di 1;
  • riga 18: alla prima richiesta, la sessione dell’applicazione è vuota. L’espressione [session.get(‘clé’)] restituisce il valore [None]. Per le richieste successive, questa espressione restituirà il valore associato alla chiave;
  • riga 31: questi contatori vengono inseriti in un dizionario;
  • riga 33: questo dizionario costituisce il documento della risposta HTTP. Si ricorda che Flask converte automaticamente i dizionari in stringhe jSON;
  • riga 34: si comunica al client web che riceverà jSON;
  • riga 35: si invia la risposta HTTP al client;

Eseguiamo questo script e interroghiamo l’applicazione web così creata con Postman dopo aver cancellato tutti i cookie dal client Postman [1-3]:

Image

Nella console Postman, gli scambi client/server sono i seguenti:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: c7db536d-9352-4aa6-9877-04560e03d935
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:50:22 GMT

{
"n1": 0, 
"n2": 10, 
"n3": 100
}
  • in [14], il cookie di sessione inviato dal server;
  • in [18-22], la risposta del server sotto forma di stringa jSON;

Ripetiamo la stessa richiesta una seconda volta. I log si modificano come segue:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 8205ad85-37b3-41f2-a171-70dd3b3a1679
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MSwibjIiOjExLCJuMyI6MTAxfQ.Xw6nsw.OuxIQnGhmhSsan5Qu_FL3Iyu-9k; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:52:35 GMT

{
"n1": 1, 
"n2": 11, 
"n3": 101
}
  • riga 9: il client Postman rinvia il cookie di sessione che ha ricevuto;
  • riga 15: nella sua risposta, il server invia un nuovo cookie di sessione, poiché la richiesta del client ha modificato la memoria dell’utente (= la sessione);
  • righe 19-23: i nuovi valori dei contatori;

22.6.4. script [session_scope_03]

Questo nuovo script ha lo scopo di dimostrare che è possibile inserire diversi tipi Python in una sessione: liste, dizionari, oggetti. L'unico vincolo è che gli oggetti inseriti nella sessione siano serializzabili in jSON. Se non lo sono di default (liste, dizionari), è necessario effettuare autonomamente la conversione in jSON.


# si configura l'applicazione
import config
config = config.configure()

# dipendenze
import json
import os

from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne

# applicazione Flask
app = Flask(__name__)

# chiave segreta della sessione
app.secret_key = os.urandom(12).hex()


# Home URL
@app.route('/', methods=['GET'])
def index():
    # gestione di un elenco
    liste = session.get('liste')
    if liste is None:
        # prima richiesta
        liste = [0, 10, 100]
    else:
        # richieste successive
        for i in range(len(liste)):
            liste[i] += 1
    # si reinserisce l'elenco nella sessione
    session['liste'] = liste

    # gestione di un dizionario
    dico = session.get('dico')
    if not dico:
        # prima richiesta
        dico = {"un": 0, "deux": 10, "trois": 100}
    else:
        # richieste successive
        dico = session['dico']
        for key in dico.keys():
            dico[key] += 1
    # si reinserisce il dizionario nella sessione
    session['dico'] = dico

    # gestione di una persona
    personne_json = session.get('personne')
    if personne_json is None:
        # prima richiesta
        personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
    else:
        # richieste successive
        personne = Personne().fromjson(personne_json)
        personne.âge += 1
    # si reinserisce la persona nella sessione
    session['personne'] = personne.asjson()

    # dizionario dei risultati
    résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}

    # invio di una risposta jSON
    response = make_response(json.dumps(résultats, ensure_ascii=False))
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • righe 1-3: l’applicazione web viene configurata;
  • righe 5-11: vengono importate le dipendenze;
  • riga 14: l'applicazione Flask viene istanziata;
  • riga 17: l’attributo [secret_key] viene inizializzato. È ciò che consente l’utilizzo delle sessioni;
  • riga 21: l'unica route dell'applicazione;
  • righe 23-33: gestione di un elenco nella sessione. In esso sono stati inseriti elementi serializzabili per impostazione predefinita in jSON;
  • righe 35-46: gestione di un dizionario nella sessione. In esso sono stati inseriti elementi serializzabili per impostazione predefinita in jSON;
  • righe 48-58: gestione di una persona. Un oggetto [Personne] non è serializzabile per impostazione predefinita in jSON. È quindi necessario adottare alcune precauzioni;
  • riga 58: si utilizza il metodo [BaseEntity.asjson] per memorizzare nella sessione la stringa jSON relativa alla persona. Si noti che si sarebbe potuto utilizzare [personne.asdict] poiché [personne.asdict] è un dizionario contenente valori serializzabili per impostazione predefinita in jSON;
  • riga 55: poiché nella sessione è stata memorizzata una stringa jSON, si recupera la persona dalla sessione utilizzando il metodo [BaseEntity.fromjson];
  • riga 61: si crea il dizionario [résultats] che verrà inviato come risposta al client. Sappiamo che in questo caso Flask invia la stringa jSON contenuta nel dizionario. È quindi necessario che quest’ultimo contenga solo valori serializzabili per impostazione predefinita in jSON;
  • riga 64: si inserisce esplicitamente la stringa jSON del dizionario [résultats] nella risposta HTTP. Flask lo avrebbe fatto per impostazione predefinita. Tuttavia, sempre per impostazione predefinita, utilizza il parametro [ensure_ascii=True], il che non ci andava bene;
  • riga 65: si comunica al client che riceverà jSON;
  • riga 66: gli inviamo la risposta;

Si avvia l’applicazione web. Si eliminano tutti i cookie dal client Postman. A questo punto, il client richiede URL [http://localhost:5000]. Il dialogo client/server nella console di Postman è il seguente:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5f8b7c63-aa8a-4429-a2fa-62141423d933
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:36:59 GMT

{"liste": [0, 10, 100], "dict": {"un": 0, "deux": 10, "trois": 100}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 70}}

Effettuiamo la richiesta una seconda volta:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 40fd00ea-d45c-46b7-a51e-d4d433a37b5c
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkP2kupv9LtIW2WIBgNGqEg_nu3seQ0b2Zew-zfCa5hlvqBs5aw5-SLolGuUaETgi-7wD0sqaHPk7BJLilGXdEYW-ZqjNxjWhnuwpiWMB3Ti0Haz6MMMfz9EcM5-LrIT7zZjv4F5NYvOQ.Xw6ydQ.PMWRCqKx9HNnb_DyK-ha-9pCF7M; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:38:29 GMT

{"liste": [1, 11, 101], "dict": {"deux": 11, "trois": 101, "un": 1}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 71}}
  • riga 9: il client rinvia il cookie di sessione che ha ricevuto;
  • riga 15: il server gliene invia un altro poiché il contenuto della sessione è cambiato (riga 19). Ricordiamo che tale contenuto è presente nel cookie di sessione in forma codificata;

22.7. script [flask/06]: informazioni condivise da tutti gli utenti

22.7.1. Introduzione

Questa sezione ha lo scopo di illustrare come gestire le informazioni a livello di applicazione, ovvero condivise da tutti gli utenti. Si tratta in genere di informazioni relative alla configurazione dell’applicazione. Abbiamo visto che un’applicazione web può gestire diversi tipi di memoria:

Image

Qui ci interessiamo alla memoria dell’applicazione [3].

22.7.2. script [application_scope_01]

Image

Lo script [application_scope_01] mostra un modo per gestire i dati con ambito «applicazione»:


# si configura l'applicazione
import config
config = config.configure()

# dipendenze
from flask import Flask, make_response
from flask_api import status

# applicazione Flask
app = Flask(__name__)


# Home URL
@app.route('/', methods=['GET'])
def index():
    # si intende dimostrare che l'applicazione rimane in memoria tra una richiesta e l'altra dei diversi clienti
    # ogni client interagisce con la stessa applicazione

    # app_infos rappresenta informazioni a livello di applicazione e non a livello di sessione
    # ovvero riguarda tutti gli utenti e non uno in particolare
    # questa informazione è qui memorizzata in [config] (non obbligatorio)

    # dizionario dei risultati
    résultats = {"config": config}

    # si invia la risposta
    response = make_response(résultats)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    # si verifica se questo codice viene eseguito più volte
    print("application app lancée")
    # si avvia l'applicazione web
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • righe 1-3: si recupera il dizionario dalla configurazione. Dimostreremo che il codice situato al di fuori delle funzioni di routing viene eseguito una sola volta. L’applicazione Flask rimane in memoria. Tutte le informazioni inizializzate al di fuori delle rotte sono globali rispetto a queste ultime e quindi a esse note. Pertanto, il dizionario [config] della riga 3 verrà restituito dalla route / (riga 24). Dimostreremo che tutti i client web riceveranno lo stesso dizionario e che questo è quindi condiviso da tutti i client. Si tratta quindi di un’informazione con ambito «applicazione»;
  • riga 35: si inserisce un log per verificare se il codice delle righe al di fuori della funzione di routing (righe 1-10, 32-38) viene eseguito più volte;

La configurazione [config] è la seguente:


def configure():
    # si imposta la configurazione
    config = {
        # configurazione Flask
        "SECRET_KEY""vibnFfrdWYUp?*LQ"
    }

    return config

Avviamo questa applicazione. I log nella console PyCharm sono i seguenti:

Image

  • in [1], avvio iniziale dell’applicazione;
  • in [2], poiché è stata richiesta la modalità [Debug], l’applicazione viene riavviata in modalità [Debug];

Ora, con un browser (Chrome, come mostrato di seguito), si richiede la modalità URL [http://127.0.0.1:5000/]:

Image

Ora con il browser Firefox:

Image

Ora con il client Postman:

GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 51e75099-8ecb-4f27-ae3b-9386e982ede4
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 39
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 10:34:26 GMT

{
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}

Ora torniamo alla console [Run] di PyCharm:

Image

  • i due log [1, 2] sono ancora presenti, ma non ce ne sono altri, mentre si vedono le tre richieste ricevute dal server web;

Per essere del tutto sicuri che l’applicazione non venga ricaricata ad ogni nuova richiesta, è possibile inserire un contatore nella configurazione e incrementarlo ad ogni nuova richiesta. Si noterà quindi che ogni client vede il contatore nello stato in cui lo ha lasciato il client precedente. Si ricorda tuttavia che i client non dovrebbero modificare i dati a livello di applicazione poiché sono condivisi tra tutti i client e, in un contesto in cui il server serve contemporaneamente più client senza la garanzia che la richiesta di un client venga eseguita interamente senza interruzioni, un client 1 che ha inviato una richiesta 1 interrotta prima del suo completamento può lasciare i dati condivisi in uno stato corrotto per i client successivi.

22.7.3. script [application_scope_02]

Image

Lo script [application_scope_02] farà ciò che non si dovrebbe fare: consentire ai clienti di modificare le informazioni condivise con gli altri utenti. Condivideremo un contatore tra gli utenti, che lo incrementeranno. Vedremo che ogni utente vedrà le modifiche apportate dagli altri utenti al contatore.

Lo script è il seguente:


# dipendenze

from flask import Flask, make_response
from flask_api import status

# applicazione Flask
app = Flask(__name__)

# dati di ambito dell'applicazione
config = {
    "counter": 0
}


# Home URL
@app.route('/', methods=['GET'])
def index():
    # si intende dimostrare che il dizionario [config] è condiviso tra tutti i client
    # dell’applicazione web

    # si incrementa il contatore
    config["counter"] += 1
    # si invia la risposta
    response = make_response(config)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • righe 10-12: il dizionario [config] condiviso dagli utenti. Contiene un contatore;
  • riga 22: ogni volta che un utente richiederà URL /, il contatore della configurazione verrà incrementato;
  • righe 23-26: la stringa jSON del dizionario viene inviata a ciascun client;

Si avvia questo script. Quindi si richiede l’URL [http://127.0.0.1:5000/] con un primo browser:

Image

Si ripete quindi la stessa operazione con un secondo browser:

Image

Poi una terza volta con Postman:

Image

Si nota che ogni client recupera il contatore nello stato in cui lo ha lasciato il client precedente. Hanno quindi effettivamente accesso alla stessa informazione.

22.7.4. script [application_scope_03]

Lo script [application_scope_03] mostra perché le informazioni condivise tra gli utenti devono essere di sola lettura.

Image

Lo script è il seguente:


# dipendenze
import threading
from time import sleep

from flask import Flask, make_response
from flask_api import status

# applicazione Flask
app = Flask(__name__)

# dati nell'ambito dell'applicazione
config = {
    "counter": 0
}


# Home URL
@app.route('/', methods=['GET'])
def index():
    # si intende dimostrare che il dizionario [config] è condiviso tra tutti i client
    # dell’applicazione web e che deve essere in sola lettura

    # nome del thread
    thread_name = threading.current_thread().name
    # si legge il contatore
    counter = config["counter"]
    print(f"compteur lu : {counter}, par le thread {thread_name}")
    # si attende 5 secondi - in questo modo verranno serviti altri clienti
    sleep(5)
    # si incrementa il contatore della configurazione
    config["counter"] = counter + 1
    # log
    print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
    # si invia la risposta
    response = make_response(config)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • riga 43: è stata modificata la modalità di esecuzione dell’applicazione web. È stato scritto [threaded=True] per indicare che l’applicazione doveva servire gli utenti contemporaneamente. Ciò avviene tramite thread di esecuzione:
    • possono esserci più thread di esecuzione simultanei, ciascuno dei quali serve un utente;
    • il processore del computer è condiviso da questi thread;
    • un thread può essere interrotto prima di aver completato il proprio lavoro. Verrà ripreso in un secondo momento;
  • riga 19: la funzione [index] può essere eseguita simultaneamente da più thread;
  • riga 24: si recupera il nome del thread che esegue la funzione [index];
  • riga 26: si legge il valore del contatore. Ai fini della nostra dimostrazione, scomponiamo l’incremento del contatore nel modo seguente:
    • fase 1: lettura del contatore (ad esempio 1) da parte del thread 1;
    • fase 2: il thread 1 va in pausa per 5 secondi (riga 29). Poiché il thread 1 ha richiesto una pausa, il processore viene assegnato a un altro thread, il thread 2. Lo scopo è che questo nuovo thread legga lo stesso valore del contatore (=1). Anche lui, a sua volta, va in pausa per 5 secondi e perde il controllo del processore;
    • fase 3: incremento del contatore, riga 31, a partire dal valore letto nella fase 1 (=1). Il thread 1 è il primo a farlo: porta il contatore a 2 e poi termina l’esecuzione della funzione [index]. Poi è il turno del thread 2 di riattivarsi e di portare anch’esso il contatore a 2 a partire dal valore letto nella fase 1 (=1). Alla fine, dopo il passaggio di entrambi i thread, il contatore è a 2 mentre dovrebbe essere a 3;
  • riga 33: viene visualizzato il valore del contatore a scopo di verifica;

Avviamo lo script, quindi richiediamo l’URL [http://loaclhost :5000/] con due browser e poi con Postman. I log nella console PyCharm sono quindi i seguenti:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/06/application_scope_03.py
 * Serving Flask app "application_scope_03" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Premere CTRL+C per uscire)
compteur lu : 0, par le thread Thread-2
compteur lu : 0, par le thread Thread-4
compteur écrit : 1, par le thread Thread-2
127.0.0.1 - - [16/Jul/2020 08:55:37] "GET / HTTP/1.1" 200 -
compteur écrit : 1, par le thread Thread-4
127.0.0.1 - - [16/Jul/2020 08:55:40] "GET / HTTP/1.1" 200 -
compteur lu : 1, par le thread Thread-5
compteur écrit : 2, par le thread Thread-5
127.0.0.1 - - [16/Jul/2020 08:55:46] "GET / HTTP/1.1" 200 -
  • righe 9-10: i primi due thread, 2 e 4, leggono lo stesso valore 0 del contatore;
  • riga 11: il thread 2 imposta il contatore su 1;
  • riga 13: il thread 4 porta il contatore a 1. Da questo momento in poi il valore del contatore è errato;
  • righe 15-16: il thread 5 non viene interrotto e gestisce correttamente il valore del contatore;

Da questo esempio si evince che il codice di un’applicazione web non deve modificare il valore delle informazioni condivise dagli utenti.

22.8. script [flask/07]: gestione delle strade

Image

In questa sede ci occupiamo della gestione delle rotte di un’applicazione, ovvero delle URL fornite dall’applicazione web.

22.8.1. script [main_01]: percorsi configurati

Lo script [main_01] introduce la possibilità di configurare le rotte:


from flask import Flask, make_response
from flask_api import status

# applicazione Flask
app = Flask(__name__)


# invio della risposta
def send_plain_response(réponse: str):
    # si invia la risposta
    response = make_response(réponse)
    response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    return response, status.HTTP_200_OK


# /cognome/nome
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
    # risposta
    return send_plain_response(f"{prenom} {nom}")


# inizializzazione della sessione
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
    # risposta
    return send_plain_response(f"/init-session/{type}")


# autenticazione-utente
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
    # risposta
    return send_plain_response("/authentifier-utilisateur")


# calcolo-imposta
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
    # risposta
    return send_plain_response("/calculer-impot")


# elenco-simulazioni
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
    # risposta
    return send_plain_response("/lister-simulations")


# elimina-simulazione
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
    # risposta
    return send_plain_response(f"/supprimer-simulation/{numero}")


# fine-sessione
@app.route('/fin-session', methods=['GET'])
def fin_session():
    # risposta
    return send_plain_response(f"/fin-session")


# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 17: si specifica il tipo dei parametri di URL. Ciò consente a Flask di effettuare delle verifiche. Se il parametro non è del tipo previsto, la richiesta del client verrà rifiutata (errore 400 Bad Request). Quindi Flask svolge una parte del lavoro che avremmo dovuto fare noi;
  • riga 18: per i parametri, occorre riprendere i nomi esatti dei parametri della riga 17, ma non necessariamente il loro ordine;
  • riga 20: si utilizza la funzione [send_plain_response] per inviare la risposta al client web;
  • riga 9: la funzione [send_plain_response] riceve la stringa da inviare al client;
  • riga 11: viene costruito il corpo della risposta HTTP;
  • riga 12: si comunica al client che gli verrà inviato del testo in chiaro;
  • riga 13: si invia la risposta HTTP;
  • righe 23-62: altre route configurate che verranno utilizzate in seguito in un esercizio pratico;

Si avvia lo script e lo si interroga con il client Postman:

Image

22.8.2. script [main_02]: esternalizzazione delle route

Nello script [main_01] precedente, il codice può diventare voluminoso se sono presenti molte rotte. Lo script [main_02] mostra come esternalizzare le rotte.

Image

Lo script [routes_02] raggruppa le funzioni relative alle rotte dello script precedente:


from flask import make_response
from flask_api import status


def send_response(réponse: str):
    # invio della risposta
    response = make_response(réponse)
    response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    return response, status.HTTP_200_OK


# Home URL
def index(nom, prenom):
    # risposta
    return send_response(f"{prenom} {nom}")


# avvio sessione
def init_session(type: str):
    # risposta
    return send_response(f"/init-session/{type}")


# autenticazione utente
def authentifier_utilisateur():
    # risposta
    return send_response("/authentifier-utilisateur")


# calcolo-imposta
def calculer_impot():
    # risposta
    return send_response("/calculer-impot")


# elenco-simulazioni
def lister_simulations():
    # risposta
    return send_response("/lister-simulations")


# elimina-simulazione
def supprimer_simulation(numero: int):
    # risposta
    return send_response(f"/supprimer-simulation/{numero}")


# fine-sessione
def fin_session():
    # risposta
    return send_response(f"/fin-session")

Si noti che lo script [routes_02] non è uno script relativo alle rotte. Si tratta di un elenco di funzioni. È lo script principale [main_02] che collega le rotte alle funzioni:


from flask import Flask

# si spostano le funzioni delle rotte nel proprio script
import routes_02

# applicazione Flask
app = Flask(__name__)

# associazioni route/funzioni
app.add_url_rule('/<string:nom>/<string:prenom>', methods=['GET'], view_func=routes_02.index)
app.add_url_rule('/init-session/<string:type>', methods=['GET'], view_func=routes_02.init_session)
app.add_url_rule('/authentifier-utilisateur', methods=['POST'], view_func=routes_02.authentifier_utilisateur)
app.add_url_rule('/calculer-impot', methods=['POST'], view_func=routes_02.calculer_impot)
app.add_url_rule('/lister-simulations', methods=['GET'], view_func=routes_02.lister_simulations)
app.add_url_rule('/supprimer-simulation/<int:numero>', methods=['GET'], view_func=routes_02.supprimer_simulation)
app.add_url_rule('/fin-session', methods=['GET'], view_func=routes_02.fin_session)

# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • riga 4: si importa lo script delle funzioni associate alle rotte;
  • righe 9-16: associazione rotte/funzioni;

Con questo metodo, ogni funzione associata a una rotta può essere oggetto di uno script separato, se necessario.

I risultati sono gli stessi ottenuti con lo script [main_01] precedente.