22. Webservices met het Flask-framework
Onder webservice verstaan we hier elke webapplicatie die ruwe gegevens levert die door een client worden gebruikt, in de volgende voorbeelden vaak een consolescript. We richten ons niet op een specifieke technologie, zoals bijvoorbeeld REST (REpresentational State Transfer) of SOAP (Simple Object Access Protocol), die min of meer ruwe gegevens in een welomschreven formaat leveren. REST levert jSON op, terwijl SOAP XML oplevert. Elk van deze technologieën beschrijft nauwkeurig hoe de client de server moet benaderen en welke vorm het antwoord van de server moet aannemen. In deze cursus zullen we veel flexibeler omgaan met de aard van het verzoek van de client en dat van het antwoord van de server. De geschreven scripts en de gebruikte tools komen echter dicht in de buurt van die van de REST-technologie.
22.1. Inleiding
Python-scripts kunnen door een webserver worden uitgevoerd. Zo’n script wordt een serverprogramma dat meerdere clients kan bedienen. Vanuit het perspectief van de client komt het aanroepen van een webservice neer op het opvragen van de URL van die service. De client kan in elke willekeurige programmeertaal worden geschreven, met name in Python. In dat laatste geval maken we dan gebruik van de internetfuncties die we zojuist hebben besproken. Daarnaast moeten we weten hoe we met een webservice moeten „communiceren”, dat wil zeggen dat we het HTTP-communicatieprotocol tussen een webserver en zijn clients moeten begrijpen. Dat was het doel van de paragraaf |het HTTP-protocol|. Dankzij de webclients die in dit deel van de cursus zijn beschreven, hebben we een deel van het HTTP-protocol kunnen ontdekken.

In hun eenvoudigste vorm verloopt de communicatie tussen client en server als volgt:
- de client opent een verbinding met poort 80 van de webserver;
- de client doet een verzoek om een document;
- de webserver verstuurt het gevraagde document en verbreekt de verbinding;
- de client verbreekt op zijn beurt de verbinding;
Het document kan van verschillende aard zijn: een tekst in het formaat HTML, een afbeelding, een video, ... Het kan een bestaand document zijn (statisch document) of een document dat direct door een script wordt gegenereerd (dynamisch document). In het laatste geval spreken we van webprogrammering. Het script voor het dynamisch genereren van documenten kan in verschillende talen worden geschreven: PHP, Python, Perl, Java, Ruby, C#, VB.net, ...
Hierna zullen we Python-scripts gebruiken om dynamisch tekstdocumenten te genereren.

- in [1] opent de client een verbinding met de server, vraagt een Python-script aan en stuurt al dan niet parameters naar dit script;
- in [3] laat de webserver het Python-script uitvoeren door de Python-interpreter. Het script genereert een document dat naar de client wordt verzonden ([2]);
- de server verbreekt de verbinding. De client doet hetzelfde;
De webserver kan meerdere clients tegelijkertijd bedienen.
Hierna zullen we twee webservers gebruiken:
- de lichte Werkzeug-server [https://werkzeug.palletsprojects.com/en/1.0.x/]. Deze server wordt gebruikt door het Flask-webframework [https://flask.palletsprojects.com/en/1.1.x/]. We zullen deze vaker de Flask-server noemen;
- de Apache 2-server [https://httpd.apache.org/];
De Flask-server zal in alle voorbeelden worden gebruikt. De Apache-server zal worden gebruikt om de webapplicatie te hosten die we gaan ontwikkelen.
Het Flask-framework is ontwikkeld in Python. Het is een module die je in een terminal installeert 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
- regel 1: de uitgevoerde opdracht;
- regel 19: de elementen die zijn geïnstalleerd:
- [flask-1.1.2]: is een framework voor webontwikkeling in Python;
- [Werkzeug-1.0.1]: is de webserver die de verzoeken van klanten zal beantwoorden;
- [Jinja2-2.11.2]: is een tool waarmee dynamische elementen kunnen worden ingevoegd in pagina’s die anders statisch zouden zijn;
22.2. scripts [flask/01]: eerste elementen van webprogrammering

Onze voorbeelden worden uitgevoerd in de volgende architectuur:

- in [1] wordt een Python-script uitgevoerd zoals een klassiek consolescript;
- in [2] wordt op transparante wijze een webserver geïnstantieerd die wacht op verzoeken. In feite accepteert deze slechts één enkel verzoek, namelijk URL;
- in [3] zal de browser de server om zijn enige URL vragen;
- in [4] voert de server het Python-script uit dat door de console [1] is aangewezen;
- in [5] zal het script de resultaten terugsturen naar de webserver, een tekstdocument;
- in [6] zal de webserver dit tekstdocument naar de browser verzenden;
22.2.1. script [exemple_01]: basisbeginselen van de taal HTML
Een webbrowser kan verschillende documenten weergeven, waarvan het meest gangbare het HTML-document is (HyperText Markup Language). Dit is een tekst die is opgemaakt met tags in de vorm <balise>texte</balise>. Zo zal de tekst <b>important</b> de tekst ‘belangrijk’ vetgedrukt weergeven. Er bestaan ook op zichzelf staande tags, zoals de tag <hr/> die een horizontale lijn weergeeft. We zullen niet ingaan op de tags die in een HTML-tekst kunnen voorkomen. Er bestaat veel WYSIWYG-software waarmee je een WEB-pagina kunt bouwen zonder ook maar één regel HTML-code te schrijven. Deze tools genereren automatisch de HTML-code van een lay-out die met de muis en vooraf gedefinieerde besturingselementen is gemaakt. Zo kun je (met de muis) een tabel in de pagina invoegen en vervolgens de door de software gegenereerde HTML-code bekijken om te ontdekken welke tags je moet gebruiken om een tabel in een WEB-pagina te definiëren. Eenvoudiger kan het niet. Bovendien is kennis van de taal HTML onmisbaar, aangezien dynamische webapplicaties zelf de HTML-code moeten genereren die naar de webclients moet worden verzonden. Deze code wordt programmatisch gegenereerd en je moet natuurlijk weten wat er moet worden gegenereerd, zodat de client de gewenste webpagina te zien krijgt.
Kortom, het is helemaal niet nodig om de volledige taal HTML te beheersen om met webprogrammeren te beginnen. Deze kennis is echter wel noodzakelijk en kan worden opgedaan door het gebruik van WYSIWYG-software voor het bouwen van WEB-pagina’s, zoals DreamWeaver en tientallen andere. Een andere manier om de fijne kneepjes van de taal HTML te ontdekken, is door op het web te surfen en de broncode te bekijken van pagina’s die interessante en voor jou nog onbekende kenmerken vertonen.
Laten we eens kijken naar het volgende voorbeeld, dat enkele elementen laat zien die in een webdocument kunnen voorkomen, zoals:
- een tabel;
- een afbeelding;
- een link;

Een HTML-document wordt omgeven door de tags <html>…</html>. Het bestaat uit twee delen:
- <head>…</head>: dit is het niet-weergegeven gedeelte van het document. Het geeft informatie aan de browser die het document gaat weergeven. Hierin staat vaak de tag <title>…</title>, die de tekst bepaalt die in de titelbalk van de browser wordt weergegeven. Er kunnen ook andere tags in staan, met name tags die de trefwoorden van het document definiëren; trefwoorden die vervolgens door zoekmachines worden gebruikt. In dit gedeelte kunnen ook scripts voorkomen, meestal geschreven in JavaScript of VBScript, die door de browser worden uitgevoerd;
- <body-attributen>…</body>: dit is het gedeelte dat door de browser wordt weergegeven. De tags HTML in dit gedeelte geven aan de browser aan hoe het document er visueel „uit moet zien”. Elke browser interpreteert deze tags op zijn eigen manier. Twee browsers kunnen hetzelfde webdocument dus op verschillende manieren weergeven. Dit is doorgaans een van de hoofdbrekers voor webontwerpers;
De code HTML van ons voorbeelddocument is als volgt:
<!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>
Element | tags en voorbeelden HTML |
<title>Enkele tags HTML</title> (regel 5) de tekst [Quelques balises HTML] verschijnt in de titelbalk van de browser die het document weergeeft | |
<hr />: geeft een horizontale lijn weer (regel 10) | |
<table attributen>….</table>: om de tabel te definiëren (regels 12, 32) <thead>…</thead>: om de kolomkoppen te definiëren (regels 13, 19) <tbody>…</tbody>: om de inhoud van de tabel te definiëren (regel 20, 31) <tr attributen>…</tr>: om een rij te definiëren (regels 21, 25) <td attributen>…</td>: om een cel te definiëren (regel 22) voorbeelden: <table border="1">…</table>: het attribuut border bepaalt de dikte van de rand van de tabel <td style="text-align: center;">cel(1,2)</td> (regel 23): definieert een cel waarvan de inhoud cel(1,2) is. Deze inhoud wordt horizontaal gecentreerd (text-align: center). | |
<img border="0" src="/static/images/cerisier.jpg"/> (regel 38): definieert een afbeelding zonder rand (border="0") waarvan het bronbestand [/static/images/cerisier.jpg] op de webserver staat (src="/static/images/cerisier.jpg"). Als deze link voorkomt in een webdocument dat is gegenereerd met de URL [http://server/chemin/balises.html], dan zal de browser de URL [http://server/ static/images/cerisier.jpg] opvragen om de hier genoemde afbeelding te verkrijgen. | |
<a href="http://www.polytech-angers.fr/fr/index.html">hier</a> (regel 43): zorgt ervoor dat de tekst ici als link naar de URL http://www.polytech-angers.fr/fr/index.html fungeert. | |
<body style="background-image: url(/static/images/standard.jpg)"> (regel 8): geeft aan dat de afbeelding die als achtergrond voor de pagina moet dienen, zich op de webserver bevindt op de locatie URL [/static/images/standard.jpg]. In het kader van ons voorbeeld zal de browser de bestanden URL en [http://server/static/images/standard.jpg] opvragen om deze achtergrondafbeelding op te halen. |
Uit dit eenvoudige voorbeeld blijkt dat de browser, om het volledige document op te bouwen, drie verzoeken naar de server moet sturen:
- [http://server/chemin/balises.html] om de broncode HTML van het document op te halen;
- [http://server/static/images/cerisier.jpg] om de afbeelding cerisier.jpg op te halen;
- [http://server/static/images/standard.jpg] om de achtergrondafbeelding standard.jpg op te halen;
Met het script [exemple_01] kunnen we de vorige statische pagina [balises.html] weergeven:

- in [1], het script [exemple_01] dat zal worden uitgevoerd;
- in [3], het document HTML dat door het script zal worden weergegeven;
- in [2], de afbeeldingen van het document HTML;
Het script [exemple_01] is als volgt:
import os
from flask import Flask, make_response, render_template
# Flask-applicatie
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():
# pagina wordt weergegeven
return make_response(render_template("balises.html"))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 7: er wordt een Flask-applicatie geïnstantieerd. Een Flask-applicatie is een webapplicatie;
- de eerste parameter is de naam die aan de applicatie wordt gegeven. Je kunt elke gewenste naam kiezen. Hier is het vooraf gedefinieerde attribuut [__name__] gebruikt, dat gelijk is aan [__main__] (regel 18);
- de tweede parameter is een benoemde parameter, wat betekent dat de volgorde waarin deze in de parameterlijst staat niet van belang is. De benoemde parameter [template_folder] geeft de map aan waarin de statische pagina’s van de webapplicatie te vinden zijn. De statische pagina’s worden ongewijzigd aan de browser geleverd. Hier worden de statische pagina’s gevonden in de map [templates] van de projectstructuur. Op regel 7 hebben we een relatief pad opgegeven naar de map [script_dir] die het uitgevoerde script [exemple_01] bevat;
- de derde parameter is eveneens een benoemde parameter. [static_folder] verwijst naar de map waarin de bronbestanden van het document HTML (afbeeldingen, video’s, …) te vinden zijn. Ook hier hebben we een relatief pad opgegeven naar de map [script_dir] die het uitgevoerde script [exemple_01] bevat;
- regels 10-14: hier worden de URL gedefinieerd die door de webapplicatie worden geaccepteerd. Elke URL is gekoppeld aan een functie die wordt uitgevoerd wanneer de URL door een webbrowser wordt opgevraagd;
- regel 11: de enige URL van de applicatie is de URL [/]. Merk op dat in [@app.route('/')], [app] de variabele is die op regel 7 is geïnitialiseerd. De definitie van de routes (de verschillende URL die door de applicatie worden beheerd) komt dus noodzakelijkerwijs na de definitie van de applicatie [app]. Deze laatste naam is vrij te kiezen;
- regels 12-14: de functie die wordt uitgevoerd wanneer de URL [/] wordt aangevraagd bij de webapplicatie [exemple_01];
- regel 12: de functie die aan een URL is gekoppeld, kan elke willekeurige naam hebben. Soms kan deze functie parameters hebben om elementen op te halen uit de URL waaraan deze is gekoppeld. In dit geval heeft de functie geen parameters;
- regel 14:
- de functie [render_template] retourneert een tekenreeks die het tekstdocument is dat door haar parameter wordt gegenereerd. Deze parameter is hier [balises.html]. Vanwege de [template_folder] in regel 7 wordt dit document gezocht in de map [f"{script_dir}/../templates"]. Daar bevindt het zich inderdaad;
- de functie [make_response] genereert een antwoord HTTP voor de browser die om de URL [/] heeft gevraagd. In de paragraaf |het protocol HTTP| hebben we gezien dat een antwoord HTTP twee elementen bevat:
- HTTP-headers;
- het door de browser opgevraagde document, in dit geval een document HTML;
In regel 14 zijn er geen parameters doorgegeven aan de functie [make_response] om HTTP-headers te genereren. De functie genereert dan standaard headers. We zullen later zien hoe we deze HTTP-headers kunnen instellen.
- Ten slotte, wanneer de browser de URL / opvraagt bij de Flask-applicatie, krijgt hij de pagina [balises.html];
- regels 17-20: deze regels dienen om de webserver te starten die de webapplicatie [exemple_01] zal uitvoeren;
- regel 18: deze voorwaarde is alleen waar wanneer het script [exemple_01] vanuit een console wordt gestart;
- regel 19: de applicatie [app] uit regel 7 wordt geconfigureerd:
- de parameter met de naam [ENV="development"] zet de webserver in de ontwikkelingsmodus: zodra de ontwikkelaar een onderdeel van de applicatie wijzigt, wordt deze opnieuw gegenereerd en naar de webserver verzonden. De ontwikkelaar hoeft geen nieuwe uitvoering aan te vragen;
- de parameter met de naam [DEBUG=True] stelt de ontwikkelaar in staat om breekpunten in de code van de applicatie te plaatsen;
- regel 20: de webapplicatie wordt gestart: er wordt een webserver opgestart en de webapplicatie wordt daarop geïmplementeerd om verzoeken van webclients te verwerken;
Hier volgt een voorbeeld van de uitvoering:

De volgende logberichten verschijnen dan in de uitvoeringsconsole:
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/ (Druk op CTRL+C om af te sluiten)
- regel 2: de server geeft het uitgevoerde script weer;
- regel 3: we bevinden ons in de ontwikkelingsmodus;
- regels 4-5: de server ziet dat deze is gestart in de modus [debug]. De server start vervolgens opnieuw op (regel 5). De modus [debug] vertraagt het opstarten dus enigszins;
- regel 8: de URL waar de geïmplementeerde webapplicatie [exemple_01] beschikbaar is;
Laten we met een webbrowser de URL [http://127.0.0.1:5000/] opvragen:

We krijgen inderdaad het verwachte document [balises.html] te zien.
22.2.2. script [exemple_02]: dynamisch een document HTML genereren

Het script [exemple_02] [1] genereert het volgende document [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>
Dit document is dynamisch omdat de inhoud ervan pas volledig bekend is op het moment dat de webserver het verstuurt. Op regel 5 en 8 staan namelijk twee elementen die op het moment van het schrijven van de pagina nog niet bekend waren. Ze worden pas bekend op het moment dat de pagina naar een client wordt verzonden. Ze worden dan vervangen door hun waarden, die bestaan uit tekenreeksen.
- regels 5, 8: de syntaxis {{uitdrukking}} is een syntaxis van de Jinja2-sjabloontaal [https://jinja.palletsprojects.com/en/2.11.x/]. Voordat de pagina naar een client wordt verzonden, worden de dynamische elementen van de pagina (regels 5 en 8) geëvalueerd en vervangen door hun waarden;
- regel 5: er is gebruikgemaakt van de syntaxis [page.title]. Er is dus aangenomen dat bij het genereren van de pagina, voordat deze wordt verzonden, een variabele [page] bekend is; we zullen zien hoe. In de syntaxis {{uitdrukking}} kunnen we de variabelenamen gebruiken die we willen. Op de regels 5 en 8 zouden we dus {{title}} en {{contents}} kunnen hebben. We zouden dan kunnen zeggen dat [title] en [contents] parameters van de pagina zijn. In het vervolg zullen we altijd dezelfde techniek gebruiken:
- de enige parameter van de pagina is een woordenboek [page];
- de attributen van dit woordenboek worden in de pagina gebruikt. Hier zijn dat [page.title] op regel 5 en [page.contents] op regel 8;
De webapplicatie [exemple_02.py] ziet er als volgt uit:
from flask import Flask, make_response, render_template
# Flask-applicatie
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():
# pagina-inhoud in de vorm van een woordenboek
page = {"title": "un titre", "contents": "un contenu"}
# weergave van de pagina
return make_response(render_template("exemple_02.html", page=page))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- we hebben dit al uitgelegd in het vorige voorbeeld, regels 4-5 en 18-20. We zullen dit schema altijd gebruiken in onze voorbeelden;
- regel 9: de enige URL die door de webapplicatie wordt geleverd, is de URL /;
- regel 14: het document dat wordt geleverd aan de URL / is het document [exemple_02.html] dat we zojuist hebben besproken. We weten dat het één parameter heeft, een woordenboek met de naam [page];
- regel 12: we definiëren het woordenboek dat als parameter aan de pagina [exemple_02.html] zal worden doorgegeven. Het kan elke willekeurige naam hebben. Het moet echter de attributen [title, contents] hebben die in het document HTML worden gebruikt;
- regel 14: de functie [render_template] heeft als taak de tekenreeks van het document [exemple_02.html] weer te geven. Aangezien dit een document met parameters is, geven we de verwachte parameter(s) door aan de functie [render_template]. Dat doen we hier door een waarde toe te kennen aan de parameter met de naam [page]. In de bewerking [page=page]:
- links van het =-teken staat de parameter [page] die in het document [exemple_02.html] wordt gebruikt;
- rechts van het =-teken staat de waarde [page] die in regel 12 is gedefinieerd;
- In het algemeen geldt dat als een document HTML de parameters [param1, param2, …, paramn] heeft, de waarden daarvan in de vorm [render_template(document, param1=valeur1, param2=valeur2, …] worden doorgegeven aan de functie [render_template];
Voordat we [exemple_02] uitvoeren, moeten we de uitvoering van [exemple_01] stoppen:

Als u tijdens de uitvoering van script 1 het gevoel hebt dat script 2 wordt uitgevoerd, komt dat waarschijnlijk doordat dit script nog steeds actief is. Om terug te keren naar een bekende status, kunt u alle lopende processen in PyCharm stoppen (rechtsboven in het venster PyCharm):

Laten we het script [exemple_02] uitvoeren:

De consolelogboeken zien er dan als volgt uit:
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/ (Druk op CTRL+C om af te sluiten)
Regel 8 geeft de implementatiepoort (5000) aan van de applicatie [exemple_02] (regel 1) op de machine [localhost]. Aangezien de voorgaande regels steeds hetzelfde zijn, zullen we deze niet meer weergeven.
Met een browser vragen we de URL [http://localhost:5000/] op:

- de uitdrukking {{page.title}} leverde [1] op;
- de uitdrukking {{page.contents}} leverde [2] op;
22.2.3. script [exemple_03]: paginastukken gebruiken

- in [1] zal het script [exemple_03.py] het dynamische document [exemple_03.html] [2] genereren. Dit document wordt samengesteld uit de paginafragmenten [fragment_01.html, fragment_02.html] en [3];
Het document [exemple_03.html] ziet er als volgt uit:
<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
- in de regels 3 en 5 wordt de Jinja2-richtlijn [include] gebruikt om elementen die buiten het document vallen, in het document op te nemen;
- de syntaxis is {% include … %}. De parameter van de richtlijn [include] is het pad naar het document dat moet worden opgenomen. Dit pad is relatief ten opzichte van de parameter [template_folder] van de Flask-applicatie:
app = Flask(__name__, template_folder="../templates", static_folder="../static")
De paden naar de documenten worden hier dus gemeten ten opzichte van de map [templates].
Het fragment [fragment_01.html] (de namen zijn uiteraard willekeurig) is als volgt:
<meta charset="UTF-8">
<title>{{page.title}}</title>
Het fragment [fragment_02.html] is als volgt:
<b>{{page.contents}}</b>
Als we het document [exemple_03.html] met deze fragmenten reconstrueren, krijgen we de volgende code:
<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>
We hebben dus een document dat identiek is aan [exemple_02.html], maar dat is opgebouwd uit fragmenten.
Het webscript [exemple_03.py] is als volgt:
import os
from flask import Flask, make_response, render_template
# Flask-applicatie
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():
# pagina-inhoud
page = {"title": "un autre titre", "contents": "un autre contenu"}
# weergave van de 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()
De code is vergelijkbaar met die van [exemple_02.py]. In regel 16 wordt getoond hoe er verwezen kan worden naar documenten in submappen van [template_folder] uit regel 7.
Het uitvoeren van het script [exemple_03.py] levert de volgende resultaten op in de browser:

22.3. script [flask/02]: webdienst voor datum en tijd

Het document [date_time_server.html] is als volgt:
<!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>
- regel 8: de pagina ondersteunt de parameter [page.date_heure];
De webservice [date_time_server.py] is als volgt:
# imports
import os
import time
from flask import Flask, make_response, render_template
# Flask-applicatie
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}")
# Home URL
@app.route('/')
def index():
# tijdstip van verzending naar de klant
# time.localtime: aantal milliseconden sinds 01/01/1970
# time.strftime maakt het mogelijk de tijd en datum op te maken
# weergaveformaat voor datum en tijd
# d: dag in twee cijfers
# m: maand in twee cijfers
# y: jaar met 2 cijfers
# H: uur 0,23
# M: minuten
# S: seconden
# datum/tijd van het moment
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
# het document dat naar de klant moet worden verzonden, wordt gegenereerd
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# antwoord HTTP aan de klant
response = make_response(document)
print("response", type(response), response)
return response
# alleen handmatig
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 13: de webapplicatie ondersteunt alleen URL /;
- regels 15-24: leggen uit hoe de datum en tijd kunnen worden verkregen en hoe deze moeten worden weergegeven;
- regel 27: tekenreeks die de huidige datum en tijd weergeeft;
- regels 28-30: we genereren het dynamische document [date_time_server.html] door het woordenboek [page] uit regel 29 door te geven;
- regel 31: het type van [document] en het document zelf worden weergegeven. We willen laten zien dat het een tekenreeks is;
- regel 33: we genereren het antwoord HTTP dat naar de klant zal worden verzonden (het is nog niet verzonden);
- regel 34: het type en de waarde ervan worden weergegeven;
- regel 35: het antwoord HTTP wordt naar de client verzonden;
De uitvoering van het script levert het volgende resultaat op in een browser:

De logberichten in de console zijn als volgt:
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/ (Druk op CTRL+C om af te sluiten)
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]>
- regel 10: we zien dat het type van de waarde die door [render_template] wordt geretourneerd, van het type [str] is. Deze tekenreeks is niets anders dan het document [date_time_server.html] nadat het is geïnterpreteerd (regels 10-19);
- regel 20: we zien dat het type van de waarde die door [make_response] wordt geretourneerd, van het type [flask.wrappers.Response] is. De functie [Response.__str__] is impliciet aangeroepen om het object [Response] weer te geven. De door deze functie geretourneerde tekenreeks geeft twee gegevens over het antwoord HTTP dat zal worden gegeven:
- het verzonden document is 195 bytes groot;
- de status van het antwoord HTTP is [200 OK]. We zullen later zien dat we toegang hebben tot deze statuscode;
22.4. scripts [flask/03]: webservices die platte tekst genereren
In een eerder voorbeeld hebben we gezien dat de webservice het volgende document leverde:
<!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>
Een webclient zou alleen geïnteresseerd kunnen zijn in de informatie [page.date_heure] op regel 8 en niet in de opmaak HTML eromheen. De webservice zou deze informatie als een eenvoudige tekenreeks kunnen leveren. We zullen hier voorbeelden van dit type webservice presenteren.
22.4.1. script [main_01]

- [main_01] is de webservice;
- [config] is het configuratiescript van de webapplicatie;
- de webservice maakt gebruik van bepaalde entiteiten die zijn gedefinieerd in [2];
Het script [config] is als volgt:
def configure():
# absoluut pad als referentie voor de relatieve paden in de configuratie
rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# afhankelijkheden van de applicatie
absolute_dependencies = [
# Personen, Hulpprogramma's, MyException
f"{rootDir}/classes/02/entities",
]
# het syspath wordt ingesteld
from myutils import set_syspath
set_syspath(absolute_dependencies)
# de configuratie wordt opgeslagen
return {}
De belangrijkste functie van deze configuratie is het definiëren van het Python-pad van de webservice. De entiteiten [2] (regel 8) moeten vindbaar zijn.
Het webscript [main_01] is als volgt:
# de applicatie configureren
import config
config=config.configure()
# imports
from flask import Flask, make_response
from flask_api import status
# afhankelijkheden
from Personne import Personne
# Flask-applicatie (hier geen statische documenten)
app = Flask(__name__)
# Home URL
@app.route('/')
def index():
# een persoon
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# antwoord HTTP
response = make_response(str(personne))
# headers HTTP
response.headers.set("Content-type", "application/json; charser=utf8")
# het antwoord wordt teruggestuurd HTTP
return response, status.HTTP_200_OK
# alleen main
if __name__ == '__main__':
# de server wordt gestart
app.config.update(ENV="development", DEBUG=True)
app.run()
- regels 1-3: het Python-pad van de applicatie wordt vastgelegd;
- regels 5-10: de elementen die het script nodig heeft, worden geïmporteerd;
- regel 17: de webservice levert alleen de URL /;
- regel 20: er wordt een object [Personne] aangemaakt;
- regel 22: er wordt een HTTP-antwoord aangemaakt met de tekenreeks die de persoon vertegenwoordigt. De functie [Personne.__str__] wordt aangeroepen. Deze functie retourneert de tekenreeks jSON uit het woordenboek [asdict] van de persoon (zie |klasse BaseEntity|). De parameter van de functie [make_response] is het tekstdocument dat naar de klant wordt verzonden, dus in dit geval de tekenreeks jSON van een persoon;
- regel 24: in de headers HTTP van het antwoord wordt een header [Content-type] opgenomen die de klant aangeeft welk type document hij zal ontvangen, in dit geval een document jSON gecodeerd in UTF-8;
- regel 26: er wordt een tuple met twee elementen teruggestuurd:
- het antwoord aan de klant, de headers HTTP en het document;
- de statuscode van het antwoord. Hier willen we de statuscode [200 OK] weergeven. De verschillende statuscodes worden gedefinieerd door constanten in de module [flask_api], die op regel 7 wordt geïmporteerd;
De module [flask_api] is niet standaard beschikbaar. Deze moet worden geïnstalleerd. Dit doen we in een terminal 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
Wanneer het webscript [main_01] wordt uitgevoerd, krijgt men de volgende resultaten in een browser:

- in [2], de ontvangen tekenreeks jSON;
- in [3-4] wordt de inhoud van het ontvangen document weergegeven. We zien dat er geen opmaak is in HTML, alleen de tekenreeks jSON;
Laten we nu eens kijken naar de rol van de header [Content-Type] die door de webservice naar de client wordt verzonden. We zetten de browser in de ontwikkelaarsmodus (meestal F12) en vragen opnieuw dezelfde URL op. Hieronder een schermafbeelding van een Chrome-browser:

- in [1], selecteer het tabblad [Network];
- in [2, 4]: de door de browser opgevraagde URL;
- in [3], selecteer het tabblad [Headers] (headers HTTP);
- in [5], de statuscode van het ontvangen antwoord HTTP;
- in [6], de header die de klant aangeeft dat hij een tekstbericht jSON zal ontvangen. Hierdoor kan de klant zich aanpassen aan het antwoord. Zo is het lettertype dat Chrome gebruikt om een jSON-antwoord of een standaardtekstantwoord weer te geven niet hetzelfde;

- in [8] selecteer je het tabblad [Response] om toegang te krijgen tot het document dat door de webservice is verzonden, in dit geval een eenvoudige tekenreeks jSON;
22.4.2. Postman
[Postman] is de tool waarmee we de verschillende URL van een webapplicatie kunnen opvragen. Hiermee kunnen we:
- elke willekeurige URL te gebruiken: deze worden handmatig aangemaakt;
- verzoeken te sturen naar de webserver via een GET, POST, PUT, OPTIONS…;
- de parameters van de GET of de POST te specificeren;
- de headers HTTP van het verzoek vast te stellen;
- een antwoord te ontvangen in het formaat jSON, XML, HTML,
- toegang te krijgen tot de headers HTTP van het antwoord. Zo krijgt men dus toegang tot het volledige antwoord HTTP van de server;
[Postman] is een uitstekend educatief hulpmiddel om de client-servercommunicatie van het protocol HTTP te begrijpen.
[Postman] is beschikbaar via URL [https://www.getpostman.com/downloads/]. Ga verder met de installatie van uw versie van [Postman]. Tijdens de installatie wordt u gevraagd een account aan te maken: dit is hier niet nodig. Het [Postman]-account dient om verschillende apparaten te synchroniseren, zodat de configuratie van het ene apparaat naar het andere wordt gekopieerd. Dit is hier allemaal niet nodig.
Na de installatie toont [Postman] de volgende interface:

- in [2-3] heb je toegang tot de instellingen van het product;

- in [6], de versie die in dit document wordt gebruikt;
We gaan hier [Postman] gebruiken om de vorige webservice jSON te testen:
- we voeren het script [flask/03/main_01] uit;
- vervolgens vragen we de URL [http://localhost:5000/] op met Postman;
- in [1] maken we een verzoek aan;
- in [2] wordt dit een verzoek naar HTTP en GET;
- in [3], de URL van de opgevraagde webservice;
- in [4] wordt het verzoek naar de webservice verzonden;
- in [5] selecteert men het tabblad [Body], waarop het ontvangen document wordt weergegeven;
- in [6] selecteert men het tabblad [Pretty], waarop het ontvangen document met de juiste opmaak wordt weergegeven, in dit geval een opmaak die geschikt is voor een reeks jSON;
- in [7], het ontvangen document jSON;
- in [8-9] wordt het ontvangen document zonder opmaak weergegeven;
- in [10] worden de door Postman ontvangen headers HTTP weergegeven;
- in [11], de status HTTP van het ontvangen antwoord;
- in [12] worden de ontvangen headers HTTP weergegeven;
- in [13], de header [Content-type] waarmee Postman wist dat het een string jSON zou ontvangen. Postman heeft deze informatie gebruikt om het ontvangen document op een bepaalde manier op te maken;
Er is nog een andere manier om Postman te gebruiken. Deze bestaat uit het gebruik van de Postman-console (Ctrl-Alt-C). Hiermee kun je de dialoog tussen client en server bekijken. Naast de toetsencombinatie Ctrl-Alt-C is de Postman-console ook beschikbaar via een pictogram linksonder in het hoofdvenster van Postman:

De Postman-console slaat de communicatie tussen client en server op die plaatsvindt wanneer een Postman-verzoek wordt uitgevoerd:

- in [3], de lijst met verzoeken die Postman heeft gedaan sinds het programma is gestart. De meest recente staan onderaan de lijst;
- in [4], het verzoek HTTP dat door Postman is verzonden;
- in [5-6], het antwoord HTTP van de webserver;
- in [7] zijn de logbestanden te zien in de modus [raw], d.w.z. zonder opmaak;
In de modus [raw] ziet het consolevenster er als volgt uit:

- in [8], het verzoek HTTP dat door Postman aan de webserver is verzonden;
- in [9] is het antwoord HTTP dat door de webserver is gegeven;
- in [10] kunnen we terugkeren naar de modus [pretty logs];
Om de uitleg te vergemakkelijken, zullen we de regels die we via de Postman-console hebben verkregen, nummeren.
Voor de client:
Voor de server:
Vanaf nu zullen we voornamelijk het volgende gebruiken:
- [Postman] als webclient;
- de console [Postman] en [raw mode] om de dialoog tussen client en server uit te leggen;
22.4.3. script [main_02]

Het webscript [main_02] is als volgt:
# de applicatie wordt geconfigureerd
import config
config=config.configure()
# imports
from flask import Flask, make_response
from flask_api import status
# afhankelijkheden
from Personne import Personne
# Flask-applicatie
app = Flask(__name__)
# Home URL
@app.route('/')
def index():
# een persoon
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# inhoud
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
# headers HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# antwoord HTTP
return response, status.HTTP_200_OK
# alleen main
if __name__ == '__main__':
# de server wordt gestart
app.config.update(ENV="development", DEBUG=True)
app.run()
- Het script [main_02] is vergelijkbaar met het script [main_01]. Het verschilt op twee punten:
- regel 22: het document dat naar de client wordt verzonden, is een ruwe tekenreeks, geen jSON-tekenreeks;
- regel 24: dit komt tot uiting in de header HTTP [Content-Type], die het type [text/plain] voor het document aangeeft;
We voeren het webscript [main_02] uit en gebruiken vervolgens [Postman] om het op te vragen:

- in [1-3] wordt de aanvraag naar de webservice verzonden;
- in [5], de status OK van het antwoord;
- in [4, 6], de headers HTTP van het antwoord;
- in [7], de header [Content-Type];
- in [8-10], het door de webservice verzonden document, een tekenreeks;
De Postman-console geeft de volgende logboeken weer:
Verzoek van de client:
Antwoord van de 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]

Het webscript [main_03] is als volgt:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# imports
from flask import Flask, make_response
from flask_api import status
# afhankelijkheden
from MyException import MyException
from Personne import Personne
# Flask-applicatie
app = Flask(__name__)
# Home URL
@app.route('/')
def index():
# een onjuiste persoon
msg_erreur = None
try:
personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
except MyException as erreur:
msg_erreur = f"{erreur}"
# fout?
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
# headers HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# antwoord HTTP
return response, status_code
# alleen main
if __name__ == '__main__':
# de server wordt gestart
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 23: er wordt een fout veroorzaakt door een onjuiste persoon te instantiëren;
- regels 27-29: vanwege de fout:
- regel 28: wordt een antwoord HTTP opgesteld met als inhoud de foutmelding;
- regel 29: de statuscode HTTP krijgt de foutwaarde [500 Internal Server Error] toegewezen;
- regel 34: de client wordt geïnformeerd dat er een platte tekst wordt verzonden;
- regel 36: het antwoord HTTP wordt naar de client verzonden;
We starten de webservice [main_03] en gebruiken Postman om deze op te roepen:

- in [1-3] verzenden we het verzoek;
- in [4] krijgen we een antwoord met statuscode [500 INTERNAL SERVER ERROR];
- in [5-7]: het antwoord is een tekst waarin de opgetreden fout wordt beschreven;

- bij [8-10], de headers HTTP van het antwoord van de webservice;
In de Postman-console zijn de resultaten in de modus [raw] als volgt:
Verzoek van de client:
Antwoord van de 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. [flask/04]-scripts: informatie die in het verzoek is opgenomen

Het script [request_parameters.py] is bedoeld om aan te tonen dat de webservice toegang heeft tot diverse gegevens die in het verzoek van een webklant zijn ingesloten. De code is als volgt:
# importeren
from flask import Flask, make_response, request
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# Home URL
@app.route('/', methods=['GET', 'POST'])
def index():
# verzoekparameters
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
# antwoord HTTP
response = make_response(request_data)
# headers HTTP
response.headers["Content-Type"] = "application/json; charset=utf-8"
# verzending van het antwoord HTTP
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 9: we brengen een wijziging aan. We specificeren welke werkwoorden zijn toegestaan in het verzoek van de client. Postman geeft de lijst weer:

De eerste twee, [GET, POST], worden het meest gebruikt en zullen ook de enige zijn die in dit document worden gebruikt. Terugkomend op regel 9 van de code: de parameter [methods] bevat de lijst met methoden uit de bovenstaande lijst die door de URL zijn toegestaan. Bij afwezigheid van deze parameter is alleen de methode [GET] toegestaan. Dit was tot nu toe het geval;
- regel 12: we gaan het woordenboek [request_data] samenstellen;
- regel 13: de verzoek van de client is beschikbaar in een vooraf gedefinieerd object [request], geïmporteerd op regel 2, van het type [werkzeug.local.LocalProxy]. De volgende regels halen diverse attributen van dit object op;
- in plaats van elk attribuut van het object [request] in detail te bespreken, gaan we deze code uitvoeren en de resultaten bekijken. Dan zullen we de betekenis van de verschillende weergegeven attributen beter begrijpen;
- regel 42: het woordenboek [request_data] zal de inhoud vormen van het antwoord HTTP. We herinneren ons dat dit tekst moet zijn. Flask zet woordenboeken automatisch om in tekenreeksen jSON;
- regel 44: de client wordt geïnformeerd dat hij jSON zal ontvangen;
- regel 46: het antwoord wordt naar de client verzonden;
Met de Postman-client sturen we het volgende verzoek naar de vorige webservice:

- in [1-2], het verzonden verzoek;
- in [2] is het verzoek geconfigureerd. De parameters worden aan URL toegevoegd in de vorm [ ?param1=valeur1¶m2=valeur2]. Er zijn twee manieren om deze parameters in Postman in te voeren:
- ze rechtstreeks in URL invoeren;
- ze in [3-4] invoeren;
Beide methoden zijn gelijkwaardig;
We voegen nog meer parameters toe aan het verzoek:

- in [5-7] voegen we parameters toe aan de body van het verzoek. Terwijl de parameters van URL zichtbaar zijn voor de gebruiker van een webbrowser, zijn de parameters die deel uitmaken van de body van het verzoek niet zichtbaar. De browser (of in dit geval Postman) stuurt ze naar de server na de headers HTTP. Het verzoek van de webclient heeft dan dezelfde structuur als het antwoord van de webserver: HTTP-headers gevolgd door een document. Hierdoor verschijnen er twee nieuwe HTTP-headers in het verzoek van de client:
- [Content-Type]: de client vertelt de server welk type document hij verstuurt;
- [Content-Length]: de grootte van het document in bytes;
- in [6] staat de codering die moet worden gebruikt voor de parameters die in [7] zijn opgegeven. Deze kunnen op verschillende manieren worden gecodeerd. [x-www-form-urlencoded] is een methode die vaak door browsers wordt gebruikt;
Hieronder is het verzoek te zien dat zal worden gegenereerd:

Het antwoord op dit verzoek is als volgt:

- in [1-5] hebben we de tekenreeks jSON [3] ontvangen;
- waar de webservice doorgaans in geïnteresseerd is, zijn de parameters van de URL [ ?param1=valeur1¶m2=valeur2] en de parameters die in de hoofdtekst van het verzoek (document) zijn doorgegeven. Zo verstrekt de client doorgaans informatie aan de webservice. We zien in [5] dat de parameters van URL beschikbaar zijn in [request.args];
De rest van het antwoord is als volgt:

- in [9] staan de attributen van de parameters die in de body van het verzoek zijn opgenomen:
- [content_type] is het type document dat bij het verzoek hoort. We hebben gezien dat dit document informatie van het type [param=valeur] bevatte, gecodeerd in de vorm [x-www-form-urlencoded]. Postman heeft daarom een header HTTP [Content-Type] gegenereerd die de aard van het document aangeeft;
- [content_length] is de grootte in bytes van dit document;
- in [10] bevat het attribuut [request.environ] veel informatie over de omgeving waarin het verzoek van de client wordt verwerkt. Het grootste deel van deze informatie is terug te vinden in de andere attributen van het object [request];
- in [11] zijn de parameters in de hoofdtekst van het verzoek beschikbaar in het attribuut [request.form];
- in [12] is de methode die wordt gebruikt om het verzoek te verzenden, in dit geval de methode [GET];
- in [13] is het attribuut [request.values] het woordenboek met alle parameters, zowel die van URL als die uit de hoofdtekst van het document. Om de parameters van het verzoek op te halen, gebruiken we het attribuut:
- [request.args] om de parameters op te halen die in URL aanwezig zijn;
- [request.form] om de parameters op te halen die in de hoofdtekst van het document staan;
In de Postman-console zijn de logs als volgt:
Verzoek van de client:
- regel 9: het type document dat naar de server wordt verzonden;
- regel 11: de headers HTTP van het verzoek worden door een lege regel gescheiden van het verzonden document. Zo herkent de server het einde van de headers HTTP van de client;
- regel 12: het ‘url-gecodeerde’ document. Alle tekens met accenten zijn gecodeerd;
Het antwoord van de client is als volgt:
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¶m2=valeur2', 'REQUEST_URI': '/?param1=valeur1¶m2=valeur2', 'RAW_URI': '/?param1=valeur1¶m2=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¶m2=valeur2' [GET]>}",
"files": {},
"form": {
"nom": "s\u00e9l\u00e9n\u00e9",
"pr\u00e9nom": "agla\u00eb",
"\u00e2ge": "77"
},
"full_path": "/?param1=valeur1¶m2=valeur2",
"host": "localhost:5000",
"method": "GET",
"path": "/",
"query_string": "param1=valeur1¶m2=valeur2",
"referrer": null,
"remote_addr": "127.0.0.1",
"remote_user": null,
"scheme": "http",
"script_root": "",
"url": "http://localhost:5000/?param1=valeur1¶m2=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"
}
}
- regels 1-5: de HTTP-headers van het antwoord, afgesloten met een lege regel;
- regels 41-45: de tekens met accenten zijn gecodeerd als UTF-8;
Als we nu de methode [POST] gebruiken om hetzelfde verzoek met dezelfde parameters te verzenden, krijgen we hetzelfde antwoord, behalve dat we bij [12] [‘method’ : ‘POST’] krijgen.
Wat is dan het verschil tussen de methoden GET en POST? Het verschil is klein en is ontstaan door het historische gebruik ervan door browsers:
- de instellingen in de URL zijn handig omdat een URL met deze instellingen kan dienen als link in een HTML-document. De gebruiker kan de parameters ook zelf wijzigen om andere reacties van de server te krijgen. In dat geval gebruiken browsers doorgaans de methode [GET] en bevat het verzoek dat naar de webserver wordt verzonden geen body (content_length=0) (geen verborgen parameters);
- soms wil men niet dat de parameters in de URL worden weergegeven. Dit is het geval bij wachtwoorden die naar de server worden verzonden. Bovendien is de grootte die de parameters van de URL innemen beperkt (een URL mag een bepaalde grootte niet overschrijden). De parameters in de hoofdtekst van het verzoek hebben deze beperking niet. Ook maken te veel parameters in de URL deze onleesbaar. Laten we het veelvoorkomende geval van een registratieformulier op een website nemen. Vroeger, toen HTML-pagina’s nog geen JavaScript bevatten, verstuurden browsers de ingevoerde gegevens via een POST. Men sprak toen van ‘gepostte waarden’;
Dus in de beginjaren van het webprogrammeren:
- werden de GET-methoden vooral geassocieerd met het opvragen van informatie die door een webserver werd geleverd;
- werden de POST-methoden eerder geassocieerd met het verzenden van informatie van de browser naar de server. De server werd hierdoor ‘verrijkt’;
Sindsdien is JavaScript in beeld gekomen. Terwijl de ontwikkelaar in de voorgaande voorbeelden geen controle had (het klikken op een link leidde onvermijdelijk tot een GET, het verzenden van een formulier verliep onvermijdelijk via een POST), heeft JavaScript hen weer de controle gegeven. In dit model is de pagina HTML gekoppeld aan JavaScript-code die de browser kan omzeilen. Zo kan het klikken op een link worden onderschept door de JavaScript-code, die vervolgens code kan uitvoeren waarmee een verzoek naar de server wordt gestuurd. Dit verzoek zal voor de gebruiker onzichtbaar zijn. Hij zal het niet zien. Deze code is een webclient en net zoals we met Postman hebben gedaan, kan de ontwikkelaar elk gewenst verzoek maken. Om terug te komen op het klikken op een link: hij kan een POST uitvoeren, terwijl de browser standaard een GET zou hebben uitgevoerd. Door deze ontwikkelingen zijn de verschillen tussen GET en POST minder relevant geworden.
Ontwikkelaars hanteren echter vaak de volgende regels:
- een GET mag de status van de server niet wijzigen. Opeenvolgende GET-verzoeken die met dezelfde parameters in de URL worden uitgevoerd, moeten hetzelfde document opleveren. Bovendien heeft de GET meestal geen body (geen bijbehorend document), alleen parameters in de URL;
- het POST-verzoek kan de status van de server wijzigen. De parameters worden meestal in de body van het verzoek verzonden. We spreken dan van geposte waarden. Het voorbeeld van het formulier spreekt voor zich: de door de gebruiker ingevoerde waarden worden in de body van het POST geplaatst en de server slaat ze ergens op, vaak in een database;
In het verdere verloop van dit document houden we ons niet aan specifieke regels.
22.6. [flask-05]-scripts: beheer van het geheugen van de gebruiker
22.6.1. Inleiding
In de voorgaande client/server-voorbeelden werkten we als volgt:
- de client opent een verbinding met poort 80 van de webserver;
- de client verzendt de tekstreeks: headers HTTP, lege regel, [document];
- als reactie stuurt de server een reeks van hetzelfde type;
- de server verbreekt de verbinding met de client;
- de client verbreekt de verbinding met de server;
Als dezelfde client kort daarna een nieuw verzoek naar de webserver stuurt, wordt er een nieuwe verbinding tot stand gebracht tussen de client en de server. De server kan niet weten of de client die verbinding maakt al eerder is geweest of dat dit een eerste verzoek is. Tussen twee verbindingen door „vergeet“ de server zijn client. Om deze reden wordt gezegd dat het protocol HTTP een stateloos protocol is. Het is echter nuttig dat de server zijn klanten onthoudt. Als een applicatie bijvoorbeeld beveiligd is, stuurt de klant een gebruikersnaam en wachtwoord naar de server om zich te identificeren. Als de server zijn klant tussen twee verbindingen ‘vergeet’, moet de klant zich bij elke nieuwe verbinding opnieuw identificeren, wat onhaalbaar is.
Om een klant te volgen, kan de server op verschillende manieren te werk gaan:
- bij een eerste verzoek van een klant neemt de server in zijn antwoord een identificatiecode op die de klant vervolgens bij elk nieuw verzoek moet terugsturen. Dankzij deze identificatiecode, die voor elke klant uniek is, kan de server een klant herkennen. De server kan dan een geheugen voor deze klant beheren in de vorm van een geheugen dat uniek is gekoppeld aan de identificatiecode van de klant. Zo werken bijvoorbeeld de diensten PHP;
- bij een eerste verzoek van een klant neemt de server in zijn antwoord geen identificatiecode op, maar het geheugen van de gebruiker zelf. Er wordt niets op de server bewaard. Om zijn geheugen te behouden, moet de webclient dit geheugen bij elk nieuw verzoek opnieuw versturen. Dit wordt bij elk nieuw verzoek al dan niet gewijzigd en al dan niet teruggestuurd naar de client. Dit is de methode die door het Flask-framework wordt gebruikt;
De verschillen tussen de twee methoden zijn als volgt:
- methode 1 verbruikt minder bandbreedte. Tussen de client en de server wordt alleen een identificatiecode uitgewisseld. Wanneer het geheugen van de gebruiker toeneemt, heeft dit geen gevolgen voor de identificatiecode, die hetzelfde blijft. Dit is niet het geval bij methode 2, waarbij het geheugen van de gebruiker bij elk verzoek wordt uitgewisseld en in de loop van de verzoeken kan toenemen;
- methode 1 neemt meer geheugenruimte in beslag. De server slaat het geheugen van de gebruiker namelijk op in zijn bestandssystemen. Als er een miljoen gebruikers zijn, kan dit mogelijk een probleem vormen. Bij methode 2 wordt er niets op de server opgeslagen;
Technisch gezien verloopt het bij beide methoden als volgt:
- in het antwoord aan een nieuwe klant voegt de server de header HTTP, [Set-Cookie : MotClé=Identifiant] of [Set-Cookie : mémoire] toe. Bij methode 1 gebeurt dit alleen bij het eerste verzoek. Bij methode 2 gebeurt dit telkens wanneer het geheugen van de gebruiker verandert;
- in zijn verzoeken stuurt de client systematisch terug wat hij heeft ontvangen, een identificatiecode of een geheugen. Hij doet dit via de header HTTP [Cookie : MotClé=Valeur];
Je kunt je afvragen hoe de server weet dat hij te maken heeft met een nieuwe klant in plaats van met een klant die al eerder is geweest. Dit wordt aangegeven door de aanwezigheid van de header HTTP Cookie in de headers HTTP van de client. Bij een nieuwe client ontbreekt deze header.
Het geheel van verbindingen van een bepaalde klant wordt een sessie genoemd.
De server kan ook andere soorten geheugen bijhouden:

- In [1] is het verzoekgeheugen bijzonder. Het wordt gebruikt wanneer het verzoek van de webclient niet door één service (of applicatie) maar door meerdere wordt verwerkt. Om informatie door te geven aan service i+1, kan service i het verwerkte verzoek (request) met deze informatie aanvullen. Dit wordt het verzoekgeheugen genoemd. We zullen dit type geheugen in dit document niet gebruiken;
- in [2, 4] is het gebruikersgeheugen dat we zojuist hebben beschreven. Dit kan lokaal worden geïmplementeerd ([2]) of worden beheerd met behulp van de client ([4]);
- in [3] is het geheugen op ‘applicatieniveau’ doorgaans een alleen-lezen geheugen. Het wordt door alle gebruikers gedeeld. Hierin bevinden zich vaak elementen van de configuratie van de webapplicatie, een configuratie die door alle gebruikers van de applicatie wordt gedeeld. Men moet voorzichtig zijn met dit type geheugen: het schrijven erin moet plaatsvinden op een moment dat de gebruikers nog geen verzoeken hebben verzonden, meestal bij het opstarten van de applicatie. Zodra er verzoeken binnenkomen, is het moeilijk om in dit geheugen te schrijven. Wanneer de webserver meerdere gebruikers tegelijk bedient en twee van hen in het ‘applicatie’-geheugen willen schrijven, bestaat het risico dat dit geheugen beschadigd raakt. Het kan namelijk gebeuren dat gebruiker 1 is begonnen met schrijven in het ‘applicatie’-geheugen, maar wordt onderbroken voordat hij klaar is. Het resultaat is dan een onvolledig applicatiegeheugen. Aangezien dit wordt gedeeld, kan gebruiker 2 het lezen en een onjuiste status krijgen;
22.6.2. script [session_scope_01]

De scripts [session_scope_xx] illustreren het beheer van gebruikersgeheugens.
Het script [session_scope_01] luidt als volgt:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# afhankelijkheden
import json
from flask import Flask, make_response, session
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# geheime sleutel van de sessie
app.secret_key = config["SECRET_KEY"]
@app.route('/set-session', methods=['GET'])
def set_session():
# er wordt iets in de sessie geplaatst
session['nom'] = 'séléné'
# we sturen een leeg antwoord
response = make_response()
response.headers['Content-Length'] = 0
return response, status.HTTP_200_OK
@app.route('/get-session', methods=['GET'])
def get_session():
# we halen de sessie op en sturen het antwoord
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
# alleen handmatig
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 11: er wordt een Flask-toepassing geïnstantieerd;
- regel 14: het attribuut [secret_key] van deze applicatie krijgt een waarde uit het configuratiebestand dat in de regels 1-3 wordt gebruikt. Een Flask-sessie is alleen mogelijk als dit attribuut is geïnitialiseerd. Je kunt er van alles in zetten. Het dient om een deel van het ‘gebruikersgeheugen’ te versleutelen dat naar de client wordt verzonden. Meestal wordt er iets ingevuld dat moeilijk te raden is. In het bestand [config] is de geheime sleutel als volgt gedefinieerd:
# we geven de configuratie terug
config = {
# Flask-configuratie
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
- Voor het eerst definiëren we een webapplicatie die iets anders bedient dan de URL /
- regel 17: de URL [/set-session] dient om de sessie van de gebruiker te initialiseren;
- regel 27: de URL [/get-session] dient om het geheugen van de gebruiker (of de gebruikerssessie) op te halen;
- regel 20: er wordt iets in het geheugen (= de sessie) van de gebruiker geplaatst, in dit geval een naam. De sessie werkt ongeveer als een woordenboek. Je kunt niet zomaar alles in de sessie plaatsen. De waarden die je erin plaatst, moeten kunnen worden omgezet naar jSON. Voor de vooraf gedefinieerde typen van Python gebeurt dit zonder tussenkomst van de ontwikkelaar. Voor eigen objecten die Python niet kent, moet je zelf de conversie jSON uitvoeren;
- regel 22: er wordt een HTTP-antwoord zonder inhoud aangemaakt (geen parameter bij make_response);
- regel 23: de client wordt geïnformeerd dat hij een leeg document (grootte 0 byte) zal ontvangen;
- regel 24: het antwoord HTTP wordt naar de klant verzonden. URL en [/set-session] doen dus niets anders dan een gebruikerssessie initialiseren;
- regel 27: met URL en [/get-session] kan de gebruiker zien wat er in zijn sessie staat;
- regel 30: we maken een antwoord HTTP aan dat de tekenreeks jSON uit de sessie van de gebruiker bevat. Hier hebben we de tekenreeks jSON zelf aangemaakt in plaats van Flask deze te laten genereren. We willen namelijk niet dat de tekens met accenten worden geëscape (ensure_ascii=False);
- regel 31: we laten de client weten dat we jSON naar hem sturen;
- regel 32: we sturen het antwoord HTTP naar de client;
Het doel van dit script is om te laten zien dat de gebruikerssessie het mogelijk maakt om een verband te leggen tussen de opeenvolgende verzoeken van de gebruiker:
- verzoek 1 vraagt om URL [/set-session];
- verzoek 2 vraagt om URL [/get-session] en haalt de naam op die in verzoek 1 is geïnitialiseerd;
Het script [config], dat de scripts in de map [flask/05] configureert, is als volgt:
def configure():
# absoluut pad als referentie voor de relatieve paden in de configuratie
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# afhankelijkheden van de applicatie
absolute_dependencies = [
# Personen, Hulpprogramma's, MyException
f"{root_dir}/classes/02/entities",
]
# het syspath wordt ingesteld
from myutils import set_syspath
set_syspath(absolute_dependencies)
# de configuratie wordt weergegeven
config = {
# Flask-configuratie
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
We starten het script [session_scope_01] en vervolgens vragen we met Postman de URL en [/set-session] op. Voorafgaand hieraan controleren we enkele elementen van het verzoek dat zal worden gedaan:
![]()
- in [1]: we openen de cookies van Postman;
- in [2-4] controleren we de bekende cookies van Postman en verwijderen we ze allemaal ([4-5]);
Laten we nu het verzoek HTTP controleren dat zal worden gegenereerd:

- in [9]: een deel van de headers HTTP die Postman in het verzoek zal opnemen op basis van de configuratie die we hiervoor hebben ingesteld. Met deze controle kunt u nagaan of u geen parameters hebt overgeslagen of juist overbodige parameters hebt laten staan;
Zodra dit is gebeurd, kunnen we de query uitvoeren:
![]()
Er zijn verschillende manieren om het resultaat te controleren. We kunnen alvast naar het hoofdvenster kijken:

- in [1-2], de aanvraag die naar de webservice is gestuurd;
- in [3-6], de headers van het antwoord;
- in [4]: aangezien in de code het type van het antwoord niet is gespecificeerd, heeft Flask standaard het type [text/html] gebruikt;
- in [5] weet de client dat er geen document in het antwoord zit;
- regel 6: de header [Set-Cookie] is verzonden door de Flask-server. De waarde hiervan wordt een sessiecookie genoemd. Deze bestaat uit drie elementen:
- [session=valeur]: deze waarde vertegenwoordigt de gegevens van de gebruiker in gecodeerde vorm. Deze gegevens kunnen worden gedecodeerd (zie |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session|). Vanwege de geheime sleutel die door de server wordt gebruikt, kan de gebruiker de ontvangen gegevens echter niet wijzigen om deze vervolgens terug te sturen naar de server. Wanneer de server een sessie ontvangt, is hij er dus zeker van dat hij een ongeschonden sessie ontvangt;
- [HttpOnly]: de aanwezigheid van dit element geeft aan de browser die het ontvangt aan dat de cookie niet toegankelijk mag zijn voor JavaScript dat de weergegeven pagina mogelijk bevat;
- [Path=/] is het pad waarnaar de sessiecookie moet worden teruggestuurd, dus in dit geval elk pad van de webapplicatie. Telkens wanneer de gebruiker via het toetsenbord expliciet (door URL in te typen) of impliciet (door op een link te klikken) een URL van dit domein opvraagt, zal de browser automatisch het ontvangen sessiecookie terugsturen;
Het nadeel van het hoofdvenster is dat we geen toegang hebben tot de volledige verzoek-URL die tot dit antwoord heeft geleid. Wat in dit venster wordt weergegeven, leidt tot verwarring:

- in de headers HTTP [3-4] wordt weergegeven als [5], een sessiecookie. Je zou dan kunnen denken dat Postman een sessiecookie in het verzoek heeft opgenomen, terwijl dat niet het geval is. De headers [3] vertegenwoordigen in feite de headers HTTP die bij het volgende verzoek zullen worden verzonden, zoals dit verzoek momenteel is geconfigureerd. Postman heeft zojuist een sessiecookie ontvangen die het bij het volgende verzoek zal terugsturen. Daarom zien we [5];
We kunnen het client/server-dialoogvenster openen in de Postman-console door op Ctrl-Alt-C te drukken:
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
- regel 14: de sessiecookie die door de server is verzonden;
Laten we nu URL [/get-session] opvragen:
- regel 9: de Postman-client heeft het ontvangen sessiecookie teruggestuurd naar de server;
- regel 18: de tekenreeks jSON, verzonden door de server;
Dit voorbeeld laat verschillende zaken zien:
- de Postman-client stuurt het sessiecookie terug dat hij van de Flask-server ontvangt. Webbrowsers werken altijd op deze manier;
- we zien dat verzoek 2 ([/get-session]) het mogelijk maakte om informatie op te halen die was aangemaakt tijdens verzoek 1 ([/set-session]). We hebben hier dus te maken met een gebruikersgeheugen;
- regels 11-16: de Flask-server heeft geen sessiecookie teruggestuurd. Dit gebeurt niet altijd. De Flask-server stuurt de sessiecookie alleen terug als het laatste verzoek het gebruikersgeheugen heeft gewijzigd;
22.6.3. script [session_scope_02]

Het script [session_02] is als volgt:
# afhankelijkheden
import os
from flask import Flask, make_response, session
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# geheime sessiesleutel
app.secret_key = os.urandom(12).hex()
# Home URL
@app.route('/', methods=['GET'])
def index():
# we beheren drie tellers
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
# woordenboek van de tellers
compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
# het antwoord wordt verzonden
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()
- regel 11: hier wordt de geheime sleutel gegenereerd met behulp van een functie. Het voordeel hiervan is dat er willekeurig een complexe tekenreeks wordt gegenereerd. Ter herinnering: de variabele [app] is de instantie van de Flask-klasse die op regel 8 is aangemaakt;
- regel 15: deze keer is er slechts één route, namelijk de route /;
- regels 17-29: er wordt een sessie beheerd die drie tellers bevat: [n1, n2, n3]. Bij de eerste aanroep van de gebruiker is [n1, n2, n3] = [0, 10, 100] en bij elke volgende aanroep worden deze tellers met 1 verhoogd;
- regel 18: bij het eerste verzoek is de sessie van de applicatie leeg. De uitdrukking [session.get(‘clé’)] levert de waarde [None] op. Bij volgende verzoeken levert deze uitdrukking de waarde op die aan de sleutel is gekoppeld;
- regel 31: deze tellers worden in een woordenboek geplaatst;
- regel 33: dit woordenboek is het antwoorddocument HTTP. Ter herinnering: Flask zet woordenboeken automatisch om in de tekenreeks jSON;
- regel 34: de webclient krijgt te horen dat hij jSON zal ontvangen;
- regel 35: we sturen het antwoord HTTP naar de client;
Laten we dit script uitvoeren en de zo gecreëerde webapplicatie met Postman opvragen, nadat we alle cookies van de Postman-client [1-3] hebben verwijderd:

In de Postman-console zien de client-server-uitwisselingen er als volgt uit:
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], de door de server verzonden sessiecookie;
- in [18-22], het antwoord van de server in de vorm van een tekenreeks jSON;
Laten we hetzelfde verzoek nog een keer uitvoeren. De logbestanden veranderen als volgt:
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
}
- regel 9: de Postman-client stuurt het ontvangen sessiecookie terug;
- regel 15: in zijn antwoord stuurt de server een nieuw sessiecookie, omdat het verzoek van de client het gebruikersgeheugen (= de sessie) heeft gewijzigd;
- regels 19-23: de nieuwe waarden van de tellers;
22.6.4. script [session_scope_03]
Dit nieuwe script is bedoeld om te laten zien dat je verschillende Python-typen in een sessie kunt plaatsen: lijsten, woordenboeken, objecten. De enige voorwaarde is dat de objecten die in de sessie worden geplaatst, serialiseerbaar moeten zijn in jSON. Als ze dat standaard niet zijn (lijsten, woordenboeken), moet je de conversie zelf uitvoeren in jSON.
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# afhankelijkheden
import json
import os
from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne
# Flask-applicatie
app = Flask(__name__)
# geheime sessiesleutel
app.secret_key = os.urandom(12).hex()
# Home URL
@app.route('/', methods=['GET'])
def index():
# lijstbeheer
liste = session.get('liste')
if liste is None:
# eerste verzoek
liste = [0, 10, 100]
else:
# volgende verzoeken
for i in range(len(liste)):
liste[i] += 1
# de lijst wordt weer in de sessie geplaatst
session['liste'] = liste
# beheer van een woordenboek
dico = session.get('dico')
if not dico:
# eerste verzoek
dico = {"un": 0, "deux": 10, "trois": 100}
else:
# volgende verzoeken
dico = session['dico']
for key in dico.keys():
dico[key] += 1
# het woordenboek wordt teruggezet in de sessie
session['dico'] = dico
# beheer van een persoon
personne_json = session.get('personne')
if personne_json is None:
# eerste verzoek
personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
else:
# volgende verzoeken
personne = Personne().fromjson(personne_json)
personne.âge += 1
# de persoon wordt weer in de sessie geplaatst
session['personne'] = personne.asjson()
# resultatenwoordenboek
résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}
# er wordt een antwoord verzonden 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()
- regels 1-3: de webapplicatie wordt geconfigureerd;
- regels 5-11: de afhankelijkheden worden geïmporteerd;
- regel 14: de Flask-applicatie wordt geïnstantieerd;
- regel 17: het attribuut [secret_key] wordt geïnitialiseerd. Dit maakt het gebruik van sessies mogelijk;
- regel 21: de enige route van de applicatie;
- regels 23-33: beheer van een lijst in de sessie. Hierin zijn standaard serialiseerbare elementen in jSON geplaatst;
- regels 35-46: beheer van een woordenboek in de sessie. Hierin zijn standaard serialiseerbare elementen geplaatst in jSON;
- regels 48-58: beheer van een persoon. Een object [Personne] is standaard niet serialiseerbaar in jSON. Er moeten dus voorzorgsmaatregelen worden genomen;
- regel 58: we gebruiken de methode [BaseEntity.asjson] om de tekenreeks jSON van de persoon in de sessie op te slaan. Merk op dat we ook [personne.asdict] hadden kunnen gebruiken, aangezien [personne.asdict] een woordenboek is dat waarden bevat die standaard serialiseerbaar zijn naar jSON;
- regel 55: omdat we de tekenreeks jSON in de sessie hebben opgeslagen, halen we de persoon hieruit op met behulp van de methode [BaseEntity.fromjson];
- regel 61: we maken het woordenboek [résultats] aan, dat als antwoord naar de client wordt verzonden. We weten dat Flask in dit geval de tekenreeks jSON uit het woordenboek verstuurt. Dit woordenboek mag dus standaard alleen serialiseerbare waarden bevatten in jSON;
- regel 64: we plaatsen expliciet de tekenreeks jSON uit het woordenboek [résultats] in het antwoord HTTP. Flask zou dit standaard al hebben gedaan. Alleen gebruikt het, nog steeds standaard, de parameter [ensure_ascii=True], wat voor ons niet geschikt was;
- regel 65: we vertellen de client dat hij jSON zal ontvangen;
- regel 66: we sturen hem het antwoord;
We starten de webapplicatie. We verwijderen alle cookies van de Postman-client. Vervolgens vraagt deze om URL [http://localhost:5000]. De client/server-dialoog in de Postman-console ziet er als volgt uit:
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}}
We voeren het verzoek een tweede keer uit:
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}}
- regel 9: de client stuurt de sessiecookie terug die hij heeft ontvangen;
- regel 15: de server stuurt hem een andere terug omdat de inhoud van de sessie is gewijzigd (regel 19). Ter herinnering: deze inhoud is in gecodeerde vorm aanwezig in de sessiecookie;
22.7. scripts [flask/06]: informatie die door alle gebruikers wordt gedeeld
22.7.1. Inleiding
Dit hoofdstuk laat zien hoe je informatie op applicatieniveau kunt beheren, d.w.z. informatie die door alle gebruikers wordt gedeeld. Deze informatie betreft doorgaans configuratiegegevens van de applicatie. We hebben gezien dat een webapplicatie verschillende soorten geheugen kan onderhouden:

We richten ons hier op het geheugen van de applicatie [3].
22.7.2. script [application_scope_01]

Het script [application_scope_01] laat zien hoe je gegevens met het bereik ‘applicatie’ kunt beheren:
# de applicatie wordt geconfigureerd
import config
config = config.configure()
# afhankelijkheden
from flask import Flask, make_response
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# Home URL
@app.route('/', methods=['GET'])
def index():
# we willen aantonen dat de applicatie in het geheugen blijft tussen de verzoeken van de verschillende klanten
# elke client heeft te maken met dezelfde applicatie
# app_infos vertegenwoordigt informatie op applicatieniveau en niet op sessieniveau
# dat wil zeggen dat deze informatie betrekking heeft op alle gebruikers en niet op één specifieke gebruiker
# deze informatie wordt hier opgeslagen in [config] (niet verplicht)
# resultatenwoordenboek
résultats = {"config": config}
# het antwoord wordt verzonden
response = make_response(résultats)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
# er wordt gecontroleerd of deze code meerdere keren wordt uitgevoerd
print("application app lancée")
# de webapplicatie wordt gestart
app.config.update(ENV="development", DEBUG=True)
app.run()
- regels 1-3: we halen het woordenboek uit de configuratie op. We zullen laten zien dat de code buiten de routeringsfuncties slechts één keer wordt uitgevoerd. De Flask-applicatie blijft in het geheugen staan. Alle informatie die buiten de routes wordt geïnitialiseerd, is globaal voor deze routes en dus bekend bij deze routes. Zo zal het woordenboek [config] uit regel 3 worden weergegeven door de route / (regel 24). We zullen aantonen dat alle webclients hetzelfde woordenboek ontvangen en dat dit dus door alle clients wordt gedeeld. Het is dus informatie met een ‘applicatie’-bereik;
- regel 35: we voegen een log toe om te zien of de code van de regels buiten de routeringsfunctie (regels 1-10, 32-38) meerdere keren wordt uitgevoerd;
De configuratie [config] is als volgt:
def configure():
# de configuratie wordt weergegeven
config = {
# Flask-configuratie
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
We starten deze applicatie. De logberichten in de console PyCharm zijn als volgt:

- in [1], eerste start van de applicatie;
- in [2] omdat de modus [Debug] is aangevraagd, wordt de applicatie opnieuw gestart in de modus [Debug];
Nu vragen we met een browser (hieronder Chrome) om de URL [http://127.0.0.1:5000/]:

Nu met de Firefox-browser:

Nu met de Postman-client:
Nu gaan we terug naar de [Run]-console van PyCharm:

- de twee logs [1, 2] zijn er nog steeds, maar er zijn geen andere, terwijl we de drie verzoeken zien die door de webserver zijn ontvangen;
Om er helemaal zeker van te zijn dat de applicatie niet bij elk nieuw verzoek opnieuw wordt geladen, kunnen we een teller in de configuratie plaatsen en deze bij elk nieuw verzoek verhogen. We zullen dan zien dat elke client de teller ziet in de staat waarin de vorige client deze heeft achtergelaten. We wijzen er echter op dat clients geen gegevens op applicatieniveau mogen wijzigen, omdat deze door alle clients worden gedeeld en omdat, in een context waarin de server gelijktijdig meerdere clients bedient zonder garantie dat het verzoek van een client volledig en zonder onderbreking wordt uitgevoerd, client 1 – die verzoek 1 heeft verzonden dat vóór het einde werd onderbroken – de gedeelde gegevens in een beschadigde toestand kan achterlaten voor de volgende clients.
22.7.3. script [application_scope_02]

Het script [application_scope_02] doet precies wat niet mag: het stelt klanten in staat om informatie te wijzigen die met andere gebruikers wordt gedeeld. We gaan een teller delen tussen de gebruikers, die deze zullen verhogen. We zullen zien dat elke gebruiker de wijzigingen ziet die andere gebruikers aan de teller hebben aangebracht.
Het script is als volgt:
# afhankelijkheden
from flask import Flask, make_response
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# gegevens binnen het bereik van de applicatie
config = {
"counter": 0
}
# Home URL
@app.route('/', methods=['GET'])
def index():
# het doel is om aan te tonen dat het woordenboek [config] door alle clients wordt gedeeld
# van de webapplicatie
# de teller wordt verhoogd
config["counter"] += 1
# we sturen het antwoord
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()
- regels 10-12: het woordenboek [config] dat door de gebruikers wordt gedeeld. Het bevat een teller;
- regel 22: telkens wanneer een gebruiker URL / opvraagt, wordt de teller in de configuratie verhoogd;
- regels 23-26: de tekenreeks jSON uit het woordenboek wordt naar elke client verzonden;
We starten dit script. Vervolgens vragen we de URL [http://127.0.0.1:5000/] op met een eerste browser:

Vervolgens doen we hetzelfde met een tweede browser:

Vervolgens nog een derde keer met Postman:

We zien dat elke client de teller ophaalt in de staat waarin de vorige client deze heeft achtergelaten. Ze hebben dus inderdaad toegang tot dezelfde informatie.
22.7.4. script [application_scope_03]
Het script [application_scope_03] laat zien waarom de informatie die tussen gebruikers wordt gedeeld, alleen-lezen moet zijn.

Het script luidt als volgt:
# afhankelijkheden
import threading
from time import sleep
from flask import Flask, make_response
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# gegevens van het toepassingsbereik
config = {
"counter": 0
}
# Home URL
@app.route('/', methods=['GET'])
def index():
# het doel is om aan te tonen dat het woordenboek [config] door alle clients wordt gedeeld
# van de webapplicatie en dat het alleen-lezen moet zijn
# naam van de thread
thread_name = threading.current_thread().name
# de teller wordt uitgelezen
counter = config["counter"]
print(f"compteur lu : {counter}, par le thread {thread_name}")
# er wordt 5 seconden gepauzeerd – zodat andere clients kunnen worden bediend
sleep(5)
# de teller in de configuratie wordt verhoogd
config["counter"] = counter + 1
# log
print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
# het antwoord wordt verzonden
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)
- regel 43: we hebben de uitvoermodus van de webapplicatie gewijzigd. We hebben [threaded=True] geschreven om aan te geven dat de applicatie gebruikers gelijktijdig moet bedienen. Dit gebeurt door middel van uitvoerthreads:
- er kunnen meerdere gelijktijdige uitvoeringsthreads zijn, die elk één gebruiker bedienen;
- de processor van de machine wordt door deze threads gedeeld;
- een thread kan worden onderbroken voordat deze zijn taak heeft voltooid. Deze wordt later hervat;
- regel 19: de functie [index] kan gelijktijdig door meerdere threads worden uitgevoerd;
- regel 24: we halen de naam op van de thread die de functie [index] uitvoert;
- regel 26: de waarde van de teller wordt uitgelezen. Voor onze demonstratie splitsen we de incrementatie van de teller als volgt op:
- stap 1: thread 1 leest de teller (bijvoorbeeld 1);
- stap 2: thread 1 pauzeert gedurende 5 seconden (regel 29). Omdat thread 1 een pauze heeft aangevraagd, wordt de processor toegewezen aan een andere thread, namelijk thread 2. Het doel is dat deze nieuwe thread dezelfde waarde van de teller leest (=1). Vervolgens pauzeert ook deze thread 5 seconden en verliest hij de processor;
- stap 3: de teller wordt verhoogd, regel 31, op basis van de waarde die in stap 1 is gelezen (=1). Thread 1 is de eerste die dit doet: hij zet de teller op 2 en beëindigt vervolgens de uitvoering van de functie [index]. Vervolgens is het de beurt aan thread 2 om weer actief te worden en ook de teller op 2 te zetten op basis van de waarde die in stap 1 is gelezen (=1). Uiteindelijk, nadat beide threads zijn doorlopen, staat de teller op 2, terwijl deze op 3 zou moeten staan;
- regel 33: we geven de waarde van de teller weer ter controle;
We starten het script en roepen vervolgens de URL [http://loaclhost :5000/] op met twee browsers en daarna met Postman. De logs in de console PyCharm zien er dan als volgt uit:
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/ (Druk op CTRL+C om af te sluiten)
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 -
- regels 9-10: de eerste twee threads, 2 en 4, lezen dezelfde waarde 0 van de teller;
- regel 11: thread 2 zet de teller op 1;
- regel 13: thread 4 zet de teller op 1. Vanaf dit moment is de waarde van de teller onjuist;
- regels 15-16: thread 5 wordt niet onderbroken en verwerkt de waarde van de teller correct;
Uit dit voorbeeld kunnen we concluderen dat de code van een webapplicatie de waarde van door gebruikers gedeelde informatie niet mag wijzigen.
22.8. scripts [flask/07]: routebeheer

We richten ons hier op het beheer van de routes van een applicatie, d.w.z. de URL die door de webapplicatie worden aangeboden.
22.8.1. script [main_01]: geconfigureerde routes
Het script [main_01] introduceert de mogelijkheid om routes te configureren:
from flask import Flask, make_response
from flask_api import status
# Flask-applicatie
app = Flask(__name__)
# het antwoord wordt verzonden
def send_plain_response(réponse: str):
# het antwoord wordt verzonden
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# /achternaam/voornaam
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
# antwoord
return send_plain_response(f"{prenom} {nom}")
# sessie starten
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
# antwoord
return send_plain_response(f"/init-session/{type}")
# gebruiker-authenticeren
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
# antwoord
return send_plain_response("/authentifier-utilisateur")
# belasting-berekenen
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
# antwoord
return send_plain_response("/calculer-impot")
# simulaties weergeven
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
# antwoord
return send_plain_response("/lister-simulations")
# simulatie verwijderen
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
# antwoord
return send_plain_response(f"/supprimer-simulation/{numero}")
# sessie-beëindigen
@app.route('/fin-session', methods=['GET'])
def fin_session():
# antwoord
return send_plain_response(f"/fin-session")
# hoofd
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- regel 17: hier wordt het type van de parameters van URL gespecificeerd. Dit stelt Flask in staat om controles uit te voeren. Als de parameter niet van het verwachte type is, wordt het verzoek van de client afgewezen (fout 400 Bad Request). Flask neemt dus een deel van het werk over dat wij anders hadden moeten doen;
- regel 18: voor de parameters moeten we de exacte namen van de parameters uit regel 17 overnemen, maar niet per se hun volgorde;
- regel 20: we gebruiken de functie [send_plain_response] om het antwoord naar de webclient te sturen;
- regel 9: de functie [send_plain_response] ontvangt de tekenreeks die naar de client moet worden verzonden;
- regel 11: de hoofdtekst van het antwoord HTTP wordt samengesteld;
- regel 12: de client wordt geïnformeerd dat er platte tekst wordt verzonden;
- regel 13: het antwoord HTTP wordt verzonden;
- regels 23-62: andere geconfigureerde routes die later in een praktijkopdracht zullen worden gebruikt;
We starten het script en roepen het op met de Postman-client:

22.8.2. script [main_02]: routes extern maken
In het vorige script [main_01] kan de code omvangrijk worden als er veel routes zijn. Het script [main_02] laat zien hoe routes kunnen worden geëxternaliseerd.

Het script [routes_02] bundelt de functies met betrekking tot de routes uit het vorige script:
from flask import make_response
from flask_api import status
def send_response(réponse: str):
# het antwoord wordt verzonden
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):
# antwoord
return send_response(f"{prenom} {nom}")
# sessie starten
def init_session(type: str):
# antwoord
return send_response(f"/init-session/{type}")
# gebruikersauthenticatie
def authentifier_utilisateur():
# antwoord
return send_response("/authentifier-utilisateur")
# belasting berekenen
def calculer_impot():
# antwoord
return send_response("/calculer-impot")
# simulaties weergeven
def lister_simulations():
# antwoord
return send_response("/lister-simulations")
# simulatie verwijderen
def supprimer_simulation(numero: int):
# antwoord
return send_response(f"/supprimer-simulation/{numero}")
# sessie-beëindigen
def fin_session():
# antwoord
return send_response(f"/fin-session")
Merk op dat het script [routes_02] geen routescript is. Het is een lijst met functies. Het is het hoofdscript [main_02] dat de koppeling legt tussen routes en functies:
from flask import Flask
# we verplaatsen de functies van de routes naar hun eigen script
import routes_02
# Flask-applicatie
app = Flask(__name__)
# koppelingen tussen routes en functies
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()
- regel 4: het script met de aan de routes gekoppelde functies wordt geïmporteerd;
- regels 9-16: koppeling van routes aan functies;
Met deze methode kan elke functie die aan een route is gekoppeld, indien nodig in een apart script worden ondergebracht.
De resultaten zijn dezelfde als die verkregen met het vorige script [main_01].