22. Usługi internetowe z wykorzystaniem frameworka Flask
Przez usługę internetową rozumiemy tutaj każdą aplikację internetową dostarczającą surowe dane wykorzystywane przez klienta, często skrypt konsolowy w poniższych przykładach. Nie skupiamy się na konkretnej technologii, takiej jak na przykład REST (REpresentational State Transfer) czy SOAP (Simple Object Access Protocol), które dostarczają mniej lub bardziej surowe dane w ściśle zdefiniowanym formacie. REST dostarcza jSON, podczas gdy w przypadku SOAP jest to XML. Każda z tych technologii precyzyjnie określa sposób, w jaki klient powinien wysyłać zapytania do serwera, oraz formę, jaką powinna przybrać odpowiedź serwera. W ramach tego kursu będziemy znacznie bardziej elastyczni, jeśli chodzi o charakter zapytania klienta i odpowiedzi serwera. Jednak napisane skrypty i wykorzystywane narzędzia są zbliżone do tych stosowanych w technologii REST.
22.1. Wprowadzenie
Skrypty w języku Python mogą być wykonywane przez serwer WWW. Taki skrypt staje się programem serwerowym, który może obsługiwać wielu klientów. Z punktu widzenia klienta wywołanie usługi internetowej sprowadza się do zażądania URL tej usługi. Klient może być napisany w dowolnym języku, w szczególności w Pythonie. W tym ostatnim przypadku wykorzystuje się funkcje internetowe, które właśnie omówiliśmy. Musimy ponadto umieć „komunikować się” z usługą internetową, czyli rozumieć protokół HTTP służący do komunikacji między serwerem internetowym a jego klientami. Taki był cel akapitu |le protocole HTTP|. Klienty internetowe opisane w tej części kursu pozwoliły nam zapoznać się z częścią protokołu HTTP.

W najprostszej wersji wymiana danych między klientem a serwerem przebiega następująco:
- klient nawiązuje połączenie z portem 80 serwera internetowego;
- wysyła żądanie dotyczące dokumentu;
- serwer wysyła żądany dokument i zamyka połączenie;
- klient z kolei zamyka połączenie;
Dokument może mieć różny charakter: tekst w formacie HTML, obraz, film... Może to być dokument istniejący (dokument statyczny) lub dokument generowany na bieżąco przez skrypt (dokument dynamiczny). W tym ostatnim przypadku mówimy o programowaniu internetowym. Skrypt do dynamicznego generowania dokumentów może być napisany w różnych językach: PHP, Python, Perl, Java, Ruby, C#, VB.net itp.
W dalszej części będziemy używać skryptów w języku Python do dynamicznego generowania dokumentów tekstowych.

- w [1] klient nawiązuje połączenie z serwerem, żąda skryptu w języku Python, wysyłając lub nie parametry do tego skryptu;
- w [3] serwer WWW uruchamia skrypt w języku Python za pomocą interpretera tego języka. Skrypt generuje dokument, który jest wysyłany do klienta [2];
- serwer zamyka połączenie. Klient robi to samo;
Serwer WWW może obsługiwać wielu klientów jednocześnie.
W dalszej części będziemy korzystać z dwóch serwerów internetowych:
- lekki serwer Werkzeug o nazwie [https://werkzeug.palletsprojects.com/en/1.0.x/]. Serwer ten jest wykorzystywany przez framework internetowy Flask o nazwie [https://flask.palletsprojects.com/en/1.1.x/]. Będziemy go częściej nazywać serwerem Flask;
- serwer Apache 2 [https://httpd.apache.org/];
Serwer Flask będzie wykorzystywany we wszystkich przykładach. Serwer Apache posłuży do hostowania aplikacji internetowej, którą zamierzamy opracować.
Framework Flask został napisany w języku Python. Jest to moduł, który instaluje się w terminalu 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
- wiersz 1: wykonane polecenie;
- wiersz 19: elementy, które zostały zainstalowane:
- [flask-1.1.2]: jest to framework do tworzenia aplikacji internetowych w języku Python;
- [Werkzeug-1.0.1]: to serwer WWW, który będzie odpowiadał na żądania klientów;
- [Jinja2-2.11.2]: to narzędzie umożliwiające wstawianie elementów dynamicznych do stron, które w przeciwnym razie byłyby statyczne;
22.2. skrypty [flask/01]: podstawy programowania stron internetowych

Nasze przykłady będą wykonywane w następującej architekturze:

- w [1] skrypt w języku Python będzie wykonywany tak samo jak klasyczny skrypt konsolowy;
- W przypadku [2], w sposób przezroczysty uruchamiany jest serwer WWW, który oczekuje na żądania. W rzeczywistości przyjmie on tylko jedno żądanie o numerze URL;
- w [3] przeglądarka zażąda od serwera jego unikalnego URL;
- w [4] serwer uruchomi skrypt w języku Python wskazany przez konsolę [1];
- po otrzymaniu kodu [5] skrypt zwróci wyniki do serwera WWW w postaci dokumentu tekstowego;
- w [6] serwer WWW wyśle ten dokument tekstowy do przeglądarki;
22.2.1. skrypt [exemple_01]: podstawy języka HTML
Przeglądarka internetowa może wyświetlać różne dokumenty, z których najpopularniejszym jest dokument HTML (HyperText Markup Language). Jest to tekst sformatowany za pomocą znaczników o postaci <balise>texte</balise>. Na przykład tekst <b>important</b> wyświetli ważny fragment tekstu pogrubioną czcionką. Istnieją również tagi samodzielne, takie jak tag <hr/>, który wyświetla poziomą linię. Nie będziemy omawiać wszystkich tagów, jakie można znaleźć w tekście HTML. Istnieje wiele programów WYSIWYG, które pozwalają na stworzenie strony WEB bez pisania ani jednej linii kodu HTML. Narzędzia te automatycznie generują kod HTML na podstawie układu strony utworzonego za pomocą myszki i gotowych elementów sterujących. Można więc wstawić (za pomocą myszki) do strony tabelę, a następnie przejrzeć kod HTML wygenerowany przez oprogramowanie, aby dowiedzieć się, jakich tagów należy użyć do zdefiniowania tabeli na stronie WEB. To naprawdę nie jest skomplikowane. Ponadto znajomość języka HTML jest niezbędna, ponieważ dynamiczne aplikacje internetowe muszą samodzielnie generować kod HTML, który ma być wysyłany do klientów internetowych. Kod ten jest generowany programowo i oczywiście trzeba wiedzieć, co należy wygenerować, aby klient otrzymał stronę internetową, której oczekuje.
Podsumowując, nie ma potrzeby znajomości całego języka HTML, aby rozpocząć programowanie internetowe. Jednak znajomość ta jest niezbędna i można ją zdobyć poprzez korzystanie z oprogramowania WYSIWYG do tworzenia stron WEB, takiego jak DreamWeaver i dziesiątek innych. Innym sposobem na poznanie subtelności języka HTML jest przeglądanie sieci i wyświetlanie kodu źródłowego stron, które zawierają interesujące i jeszcze nieznane Ci elementy.
Rozważmy poniższy przykład, który przedstawia kilka elementów, jakie można znaleźć w dokumencie internetowym, takich jak:
- tabela;
- obraz;
- link;

Dokument HTML jest ujęty w tagi <html>…</html>. Składa się on z dwóch części:
- <head>…</head>: jest to niewidoczna część dokumentu. Zawiera informacje dla przeglądarki, która wyświetli dokument. Często znajduje się w niej tag <title>…</title>, który określa tekst wyświetlany w pasku tytułu przeglądarki. Można tam również znaleźć inne tagi, w szczególności tagi definiujące słowa kluczowe dokumentu, wykorzystywane następnie przez wyszukiwarki. W tej części można również znaleźć skrypty, najczęściej napisane w języku JavaScript lub VBScript, które zostaną wykonane przez przeglądarkę;
- <body atrybuty>…</body>: jest to część, która zostanie wyświetlona przez przeglądarkę. Tagi zawarte w tej części wskazują przeglądarce „pożądany” wygląd dokumentu. Każda przeglądarka interpretuje te tagi na swój sposób. W związku z tym dwie przeglądarki mogą wyświetlać ten sam dokument internetowy w różny sposób. Jest to zazwyczaj jeden z problemów, z którymi borykają się projektanci stron internetowych;
Kod HTML naszego przykładowego dokumentu wygląda następująco:
<!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>
Elementy | tagów i przykłady HTML |
<title>Kilka znaczników HTML</title> (wiersz 5) tekst [Quelques balises HTML] pojawi się w pasku tytułu przeglądarki wyświetlającej dokument | |
<hr />: wyświetla poziomą linię (wiersz 10) | |
<table atrybuty>….</table>: w celu zdefiniowania tabeli (wiersze 12, 32) <thead>…</thead>: w celu zdefiniowania nagłówków kolumn (wiersze 13, 19) <tbody>…</tbody>: służy do zdefiniowania zawartości tabeli (wiersze 20, 31) <tr atrybuty>…</tr>: do zdefiniowania wiersza (wiersze 21, 25) <td atrybuty>…</td>: do zdefiniowania komórki (wiersz 22) przykłady: <table border="1">…</table>: atrybut border określa grubość obramowania tabeli <td style="text-align: center;">komórka(1,2)</td> (wiersz 23): definiuje komórkę, której zawartością będzie komórka(1,2). Zawartość ta zostanie wyśrodkowana w poziomie (text-align: center). | |
<img border="0" src="/static/images/cerisier.jpg"/> (wiersz 38): definiuje obraz bez obramowania (border=0"), którego plik źródłowy to [/static/images/cerisier.jpg] na serwerze internetowym (src="/static/images/cerisier.jpg"). Jeśli ten link znajduje się w dokumencie internetowym wygenerowanym za pomocą URL [http://server/chemin/balises.html], wówczas przeglądarka zażąda pliku URL [http://server/ static/images/cerisier.jpg], aby uzyskać obraz, do którego odwołuje się ten link. | |
<a href="http://www.polytech-angers.fr/fr/index.html">tutaj</a> (wiersz 43): sprawia, że tekst ici służy jako link do strony http://www.polytech-angers.fr/fr/index.html. | |
<body style="background-image: url(/static/images/standard.jpg)"> (wiersz 8): wskazuje, że obraz, który ma służyć jako tło strony, znajduje się pod adresem URL [/static/images/standard.jpg] na serwerze internetowym. W kontekście naszego przykładu przeglądarka wyśle żądanie do serwera o pliki URL i [http://server/static/images/standard.jpg], aby pobrać ten obraz tła. |
Na tym prostym przykładzie widać, że aby zbudować cały dokument, przeglądarka musi wysłać trzy żądania do serwera:
- [http://server/chemin/balises.html], aby pobrać kod źródłowy HTML dokumentu;
- [http://server/static/images/cerisier.jpg], aby pobrać obraz cerisier.jpg;
- [http://server/static/images/standard.jpg] w celu uzyskania obrazu tła standard.jpg;
Skrypt [exemple_01] pozwoli nam wyświetlić poprzednią stronę statyczną [balises.html]:

- w [1] – skrypt [exemple_01], który zostanie uruchomiony;
- w [3] – dokument HTML, który zostanie wyświetlony przez skrypt;
- w [2] – obrazy z dokumentu HTML;
Skrypt [exemple_01] ma następującą treść:
import os
from flask import Flask, make_response, render_template
# aplikacja Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Strona główna URL
@app.route('/')
def index():
# wyświetlanie strony
return make_response(render_template("balises.html"))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 7: tworzymy instancję aplikacji Flask. Aplikacja Flask to aplikacja internetowa;
- pierwszym parametrem jest nazwa nadana aplikacji. Można nadać dowolną nazwę. W tym przypadku użyto predefiniowanego atrybutu [__name__], którego wartością jest [__main__] (wiersz 18);
- drugi parametr jest parametrem nazwanym, co oznacza, że jego pozycja w kolejności parametrów nie ma znaczenia. Parametr nazwany [template_folder] wskazuje folder, w którym znajdują się strony statyczne aplikacji internetowej. Strony statyczne są dostarczane do przeglądarki w niezmienionej postaci. W tym przypadku strony statyczne będą znajdować się w folderze [templates] w drzewie katalogów projektu. W wierszu 7 podaliśmy ścieżkę względną do folderu [script_dir] zawierającego skrypt [exemple_01], który jest wykonywany;
- trzeci parametr jest również parametrem nazwanym. [static_folder] oznacza folder, w którym znajdują się zasoby dokumentu HTML (obrazy, filmy itp.). Również w tym przypadku podaliśmy ścieżkę względną do folderu [script_dir] zawierającego uruchomiony skrypt [exemple_01];
- wiersze 10–14: definiujemy formaty URL akceptowane przez aplikację internetową. Każdy format URL jest powiązany z funkcją, która uruchamia się, gdy format URL jest żądany przez przeglądarkę internetową;
- wiersz 11: jedyną zmienną URL w aplikacji jest URL [/]. Należy zauważyć, że w [@app.route('/')] zmienna [app] została zainicjowana w wierszu 7. Definicja tras (różnych URL obsługiwanych przez aplikację) musi zatem koniecznie znajdować się po definicji aplikacji [app]. Ta ostatnia nazwa jest dowolna;
- wiersze 12–14: funkcja wykonywana po wysłaniu żądania dotyczącego URL [/] do aplikacji internetowej [exemple_01];
- wiersz 12: funkcja powiązana z URL może nosić dowolną nazwę. Czasami może posiadać parametry służące do pobierania elementów z powiązanego z nią URL. W tym przypadku nie posiada żadnych parametrów;
- wiersz 14:
- funkcja [render_template] zwraca ciąg znaków, który jest dokumentem tekstowym wygenerowanym przez jej parametr. W tym przypadku jest to [balises.html]. Ze względu na [template_folder] z wiersza 7 dokument ten będzie wyszukiwany w folderze [f"{script_dir}/../templates"]. I rzeczywiście tam się znajduje;
- funkcja [make_response] generuje odpowiedź HTTP dla przeglądarki, która zażądała od niej URL [/]. W akapicie |le protocole HTTP| widzieliśmy, że odpowiedź HTTP składa się z dwóch elementów:
- nagłówki HTTP;
- dokument żądany przez przeglądarkę, w tym przypadku dokument HTML;
W wierszu 14 nie przekazano żadnych parametrów do funkcji [make_response] w celu wygenerowania nagłówków HTTP. Funkcja wygeneruje je zatem domyślnie. Później zobaczymy, jak ustawić te nagłówki HTTP.
- Wreszcie, gdy przeglądarka wysyła żądanie dotyczące pliku URL do aplikacji Flask, otrzymuje stronę [balises.html];
- wiersze 17–20: te wiersze służą do uruchomienia serwera WWW, który będzie obsługiwał aplikację internetową [exemple_01];
- wiersz 18: warunek ten jest spełniony tylko wtedy, gdy skrypt [exemple_01] jest uruchamiany w konsoli;
- wiersz 19: aplikacja [app] z wiersza 7 jest skonfigurowana:
- parametr o nazwie [ENV="development"] przełącza serwer internetowy w tryb programistyczny: gdy tylko programista zmieni element aplikacji, jest ona odświeżana i przekazywana do serwera internetowego. Programista nie musi żądać ponownego uruchomienia;
- parametr o nazwie [DEBUG=True] umożliwi programiście umieszczanie punktów zatrzymania w kodzie aplikacji;
- wiersz 20: uruchamiana jest aplikacja internetowa: tworzona jest instancja serwera internetowego, a aplikacja internetowa jest na nim wdrażana w celu obsługi żądań klientów internetowych;
Oto przykładowy przebieg wykonania:

W konsoli wykonania pojawiają się wówczas następujące logi:
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/ (naciśnij CTRL+C, aby zakończyć)
- wiersz 2: serwer wyświetla wykonany skrypt;
- wiersz 3: znajdujemy się w trybie programowania;
- wiersze 4–5: serwer wykrywa, że został uruchomiony w trybie [debug]. Następnie uruchamia się ponownie (wiersz 5). Tryb [debug] nieco spowalnia więc proces uruchamiania;
- wiersz 8: adres URL, pod którym dostępna jest wdrożona aplikacja internetowa [exemple_01];
Za pomocą przeglądarki internetowej wywołajmy URL [http://127.0.0.1:5000/]:

Otrzymujemy oczekiwany dokument o nazwie [balises.html].
22.2.2. Skrypt [exemple_02]: dynamiczne generowanie dokumentu HTML

Skrypt [exemple_02] [1] wygeneruje następujący dokument [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>
Dokument ten ma charakter dynamiczny, ponieważ jego zawartość jest w pełni znana dopiero w momencie, gdy serwer WWW go udostępnia. W wierszach 5 i 8 znajdują się bowiem dwa elementy, które nie były znane w momencie tworzenia strony. Stają się one znane dopiero w momencie wysłania strony do klienta. Zostają wówczas zastąpione swoimi wartościami, które są ciągami znaków.
- wiersze 5, 8: składnia {{wyrażenie}} jest składnią języka szablonów Jinja2 [https://jinja.palletsprojects.com/en/2.11.x/]. Zanim strona zostanie wysłana do klienta, dynamiczne elementy strony (wiersze 5 i 8) są obliczane i zastępowane ich wartościami;
- wiersz 5: zastosowano składnię [page.title]. Założono zatem, że podczas generowania strony przed jej wysłaniem znana jest zmienna [page] – zobaczymy, w jaki sposób. W składni {{wyrażenie}} można używać dowolnych nazw zmiennych. W wierszach 5 i 8 moglibyśmy zatem mieć {{title}} i {{contents}}. Można by zatem powiedzieć, że [title] i [contents] są parametrami strony. W dalszej części będziemy zawsze stosować tę samą technikę:
- jedynym parametrem strony będzie słownik [page];
- atrybuty tego słownika będą wykorzystywane na stronie. W tym przypadku [page.title] w wierszu 5 i [page.contents] w wierszu 8;
Aplikacja internetowa [exemple_02.py] wygląda następująco:
from flask import Flask, make_response, render_template
# aplikacja Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Strona główna URL
@app.route('/')
def index():
# treść strony w postaci słownika
page = {"title": "un titre", "contents": "un contenu"}
# wyświetlanie strony
return make_response(render_template("exemple_02.html", page=page))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- Wyjaśniliśmy to już w poprzednim przykładzie, w wierszach 4–5 i 18–20. W naszych przykładach będziemy zawsze stosować ten schemat;
- wiersz 9: jedynym plikiem URL obsługiwanym przez aplikację internetową jest plik URL /;
- wiersz 14: dokumentem dostarczanym do URL / jest dokument [exemple_02.html], który właśnie omówiliśmy. Wiemy, że posiada on jeden parametr – słownik o nazwie [page];
- wiersz 12: definiujemy słownik, który zostanie przekazany jako parametr do strony [exemple_02.html]. Może on nosić dowolną nazwę. Musi jednak posiadać atrybuty [title, contents] użyte w dokumencie HTML;
- wiersz 14: funkcja [render_template] ma za zadanie zwrócić ciąg znaków z dokumentu [exemple_02.html]. Ponieważ jest to dokument parametryczny, przekazujemy do funkcji [render_template] oczekiwany parametr lub parametry. Robimy to tutaj, nadając wartość parametrowi o nazwie [page]. W operacji [page=page]:
- po lewej stronie znaku = znajduje się parametr [page] używany w dokumencie [exemple_02.html];
- po prawej stronie znaku = znajduje się wartość [page] zdefiniowana w wierszu 12;
- ogólnie rzecz biorąc, jeśli dokument HTML zawiera parametry [param1, param2, …, paramn], ich wartości zostaną przekazane do funkcji [render_template] w postaci [render_template(document, param1=valeur1, param2=valeur2, …];
Przed uruchomieniem funkcji [exemple_02] musimy zatrzymać wykonywanie funkcji [exemple_01]:

Jeśli podczas wykonywania skryptu 1 wydaje się, że to skrypt 2 jest wykonywany, prawdopodobnie wynika to z faktu, że ten skrypt nadal jest w trakcie wykonywania. Aby powrócić do znanego stanu, można zatrzymać wszystkie procesy uruchomione w PyCharm (w prawym górnym rogu okna PyCharm):

Uruchommy skrypt [exemple_02]:

Wynikowe logi konsoli wyglądają następująco:
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/ (naciśnij CTRL+C, aby zakończyć)
Wiersz 8 wskazuje port wdrożenia (5000) aplikacji [exemple_02] (wiersz 1) na maszynie [localhost]. Ponieważ poprzednie wiersze są zawsze takie same, nie będziemy ich już ponownie przedstawiać.
W przeglądarce wywołujemy adres URL [http://localhost:5000/]:

- wyrażenie {{page.title}} dało wynik [1];
- wyrażenie {{page.contents}} dało wynik [2];
22.2.3. skrypt [exemple_03]: użycie fragmentów strony

- W przypadku [1] skrypt [exemple_03.py] wygeneruje dokument dynamiczny [exemple_03.html] [2]. Zostanie on utworzony na podstawie fragmentów stron o numerach [fragment_01.html, fragment_02.html] i [3];
Dokument [exemple_03.html] będzie wyglądał następująco:
<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
- w wierszach 3 i 5 wykorzystano dyrektywę [include] biblioteki Jinja2 w celu włączenia do dokumentu elementów zewnętrznych;
- składnia to {% include … %}. Parametrem dyrektywy [include] jest ścieżka do dokumentu, który ma zostać wbudowany. Ścieżka ta jest względna względem parametru [template_folder] aplikacji Flask:
app = Flask(__name__, template_folder="../templates", static_folder="../static")
W tym przypadku ścieżki do dokumentów są więc mierzone względem folderu [templates].
Fragment o nazwie [fragment_01.html] (nazwy są oczywiście dowolne) wygląda następująco:
<meta charset="UTF-8">
<title>{{page.title}}</title>
Fragment [fragment_02.html] wygląda następująco:
<b>{{page.contents}}</b>
Jeśli zrekonstruujemy dokument [exemple_03.html] na podstawie tych fragmentów, otrzymamy następujący kod:
<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>
Mamy zatem dokument identyczny z [exemple_02.html], ale zbudowany z fragmentów.
Skrypt internetowy [exemple_03.py] wygląda następująco:
import os
from flask import Flask, make_response, render_template
# aplikacja Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")
# Strona główna URL
@app.route('/')
def index():
# treść strony
page = {"title": "un autre titre", "contents": "un autre contenu"}
# wyświetlanie strony
return make_response(render_template("views/exemple_03.html", page=page))
# main
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
Kod jest analogiczny do kodu [exemple_02.py]. W wierszu 16 pokazano, jak można odwoływać się do dokumentów znajdujących się w podfolderach [template_folder] z wiersza 7.
Uruchomienie skryptu [exemple_03.py] daje następujące wyniki w przeglądarce:

22.3. skrypty [flask/02]: usługa internetowa daty i godziny

Dokument [date_time_server.html] ma następującą treść:
<!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>
- wiersz 8: strona akceptuje parametr [page.date_heure];
Usługa internetowa [date_time_server.py] wygląda następująco:
# importy
import os
import time
from flask import Flask, make_response, render_template
# aplikacja Flask
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}")
# Strona główna URL
@app.route('/')
def index():
# wysłanie godziny do klienta
# time.localtime: liczba milisekund od 01.01.1970
# time.strftime umożliwia formatowanie godziny i daty
# format wyświetlania daty i godziny
# d: dzień w formacie dwucyfrowym
# m: miesiąc (2 cyfry)
# y: rok (2 cyfry)
# H: godzina 0,23
# M: minuty
# S: sekundy
# data / godzina bieżąca
time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
# generujemy dokument do wysłania do klienta
page = {"date_heure": time_of_day}
document = render_template("date_time_server.html", page=page)
print("document", type(document), document)
# odpowiedź HTTP dla klienta
response = make_response(document)
print("response", type(response), response)
return response
# tylko ręcznie
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 13: aplikacja internetowa obsługuje wyłącznie URL /;
- wiersze 15–24: wyjaśniają, jak uzyskać datę i godzinę oraz jak je wyświetlić;
- wiersz 27: ciąg znaków reprezentujący aktualną datę i godzinę;
- wiersze 28–30: generujemy dynamiczny dokument [date_time_server.html], przekazując mu słownik [page] z wiersza 29;
- wiersz 31: wyświetlany jest typ dokumentu [document] oraz sam dokument. Chcemy pokazać, że jest to ciąg znaków;
- wiersz 33: generowana jest odpowiedź HTTP, która zostanie wysłana do klienta (nie została jeszcze wysłana);
- wiersz 34: wyświetlany jest jej typ i wartość;
- wiersz 35: odpowiedź HTTP jest wysyłana do klienta;
Wykonanie skryptu daje następujący wynik w przeglądarce:

W konsoli pojawiają się następujące wpisy:
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/ (naciśnij CTRL+C, aby zakończyć)
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]>
- wiersz 10: widać, że typ wartości zwróconej przez [render_template] to [str]. Ten ciąg znaków to nic innego jak dokument [date_time_server.html] po zinterpretowaniu (wiersze 10–19);
- wiersz 20: widać, że typ wartości zwracanej przez funkcję [make_response] to [flask.wrappers.Response]. Funkcja [Response.__str__] została wywołana niejawnie w celu wyświetlenia obiektu [Response]. Ciąg znaków zwrócony przez tę funkcję zawiera dwie informacje dotyczące odpowiedzi HTTP, która zostanie wygenerowana:
- wysłany dokument ma 195 bajtów;
- status odpowiedzi HTTP to [200 OK]. W dalszej części zobaczymy, że mamy dostęp do tego kodu statusu;
22.4. skrypty [flask/03]: usługi internetowe generujące tekst zwykły
W poprzednim przykładzie widzieliśmy, że usługa internetowa dostarczała następujący dokument:
<!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>
Klient internetowy może być zainteresowany wyłącznie informacją [page.date_heure] z wiersza 8, a nie otaczającym ją formatowaniem HTML. Usługa internetowa mogłaby dostarczyć tę informację jako zwykły ciąg znaków. Poniżej przedstawimy przykłady tego typu usług internetowych.
22.4.1. skrypt [main_01]

- [main_01] to usługa internetowa;
- [config] to skrypt konfiguracyjny aplikacji internetowej;
- usługa internetowa wykorzystuje niektóre z jednostek zdefiniowanych w [2];
Skrypt [config] ma następującą treść:
def configure():
# ścieżka bezwzględna odnosząca się do ścieżek względnych w konfiguracji
rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# zależności aplikacji
absolute_dependencies = [
# Osoba, Narzędzia, MyException
f"{rootDir}/classes/02/entities",
]
# ustawiamy ścieżkę systemową
from myutils import set_syspath
set_syspath(absolute_dependencies)
# przekazujemy konfigurację
return {}
Głównym zadaniem tej konfiguracji jest zdefiniowanie ścieżki Python Path dla serwisu internetowego. Musimy mieć możliwość znalezienia elementów [2] (wiersz 8).
Skrypt internetowy [main_01] ma następującą postać:
# konfiguracja aplikacji
import config
config=config.configure()
# importy
from flask import Flask, make_response
from flask_api import status
# zależności
from Personne import Personne
# aplikacja Flask (bez statycznych dokumentów)
app = Flask(__name__)
# Strona główna URL
@app.route('/')
def index():
# osoba
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# odpowiedź HTTP
response = make_response(str(personne))
# nagłówki HTTP
response.headers.set("Content-type", "application/json; charser=utf8")
# przesyłamy odpowiedź HTTP
return response, status.HTTP_200_OK
# tylko główna
if __name__ == '__main__':
# uruchamiamy serwer
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersze 1–3: ustalono ścieżkę Python Path aplikacji;
- wiersze 5–10: importowane są elementy potrzebne skryptowi;
- wiersz 17: usługa internetowa obsługuje wyłącznie URL /;
- wiersz 20: tworzony jest obiekt [Personne];
- wiersz 22: tworzona jest odpowiedź HTTP z ciągiem znaków reprezentującym daną osobę. Zostanie wywołana funkcja [Personne.__str__]. Funkcja ta zwraca ciąg znaków jSON ze słownika [asdict] danej osoby (por. |classe BaseEntity|). Parametrem funkcji [make_response] jest dokument tekstowy wysyłany do klienta, a więc w tym przypadku ciąg znaków jSON dotyczący danej osoby;
- wiersz 24: w nagłówkach odpowiedzi HTTP umieszczamy nagłówek [Content-type], który informuje klienta, jaki typ dokumentu otrzyma, w tym przypadku dokument jSON zakodowany w formacie UTF-8;
- wiersz 26: zwracana jest krotka składająca się z dwóch elementów:
- odpowiedź dla klienta, nagłówki HTTP oraz dokument;
- kod statusu odpowiedzi. W tym przypadku chcemy zwrócić kod statusu [200 OK]. Poszczególne kody statusu są zdefiniowane przez stałe w module [flask_api] zaimportowanym w wierszu 7;
Moduł [flask_api] nie jest dostępny domyślnie. Należy go zainstalować. Robimy to w terminalu 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
Po uruchomieniu skryptu internetowego [main_01] w przeglądarce otrzymujemy następujące wyniki:

- w [2] – otrzymany ciąg znaków jSON;
- w [3-4] wyświetlana jest treść otrzymanego dokumentu. Widać, że nie ma żadnego elementu otaczającego HTML, a jedynie ciąg znaków jSON;
Przyjrzyjmy się teraz roli nagłówka [Content-Type] wysyłanego do klienta przez serwis internetowy. Przełączamy przeglądarkę w tryb programisty (zazwyczaj F12) i ponownie wysyłamy żądanie o ten sam URL. Poniżej znajduje się zrzut ekranu z przeglądarki Chrome:

- w [1] należy wybrać kartę [Network];
- w [2, 4]: żądany przez przeglądarkę URL;
- w [3] należy wybrać zakładkę [Headers] (nagłówki HTTP);
- w [5] – kod statusu otrzymanej odpowiedzi HTTP;
- w [6] nagłówek informujący klienta, że otrzyma tekst jSON. Pozwala to klientowi dostosować się do odpowiedzi. W związku z tym czcionka używana przez przeglądarkę Chrome do wyświetlania odpowiedzi jSON lub zwykłej odpowiedzi tekstowej nie jest taka sama;

- w przypadku [8] należy wybrać zakładkę [Response], aby uzyskać dostęp do dokumentu wysłanego przez serwis internetowy, w tym przypadku prostego ciągu znaków jSON;
22.4.2. Postman
[Postman] to narzędzie, które pozwoli nam wysyłać zapytania do różnych URL w aplikacji internetowej. Umożliwia nam:
- korzystać z dowolnego URL: są one tworzone ręcznie;
- wysyłać zapytania do serwera internetowego za pomocą plików GET, POST, PUT, OPTIONS…;
- określić parametry GET lub POST;
- ustalić nagłówki HTTP żądania;
- otrzymania odpowiedzi w formacie jSON, XML, HTML,
- uzyskanie dostępu do nagłówków odpowiedzi HTTP. W ten sposób uzyskujemy dostęp do pełnej odpowiedzi serwera HTTP;
[Postman] jest doskonałym narzędziem dydaktycznym do zrozumienia komunikacji klient–serwer w protokole HTTP.
[Postman] jest dostępne na stronie URL [https://www.getpostman.com/downloads/]. Proszę przystąpić do instalacji swojej wersji [Postman]. Podczas instalacji pojawi się prośba o utworzenie konta: w tym przypadku nie jest ono potrzebne. Konto [Postman] służy do synchronizacji różnych urządzeń, tak aby konfiguracja jednego z nich została skopiowana na inne. W tym przypadku żadna z tych funkcji nie jest potrzebna.
Po zainstalowaniu aplikacja [Postman] wyświetla następujący interfejs:

- w [2-3] mamy dostęp do ustawień produktu;

- w [6], wersji używanej w niniejszym dokumencie;
W tym miejscu wykorzystamy [Postman] do przetestowania poprzedniej usługi internetowej jSON:
- uruchamiamy skrypt [flask/03/main_01];
- następnie wysyłamy żądanie do URL za pomocą Postmana;
- w [1] tworzymy żądanie;
- w [2] będzie to żądanie HTTP GET;
- w pliku [3] znajduje się plik URL z odpowiadającej usługi internetowej;
- w [4] wysyła się żądanie do serwisu internetowego;
- w [5] wybiera się zakładkę [Body], która wyświetla otrzymany dokument;
- w [6] wybiera się zakładkę [Pretty], która wyświetla otrzymany dokument w odpowiednim formacie, w tym przypadku w formacie odpowiednim dla ciągu znaków jSON;
- w [7] – otrzymany dokument jSON;
- w [8-9] – otrzymany dokument bez formatowania;
- w pliku [10] wyświetlane są nagłówki HTTP otrzymane przez Postman;
- w pliku [11] – status HTTP otrzymanej odpowiedzi;
- w pliku [12] – nagłówki HTTP, które zostały odebrane;
- w [13] – nagłówek [Content-type], dzięki któremu Postman wiedział, że otrzyma ciąg jSON. Postman wykorzystał tę informację do sformatowania w pewien sposób otrzymanego dokumentu;
Istnieje jeszcze jeden sposób korzystania z Postmana. Polega on na użyciu konsoli Postmana (Ctrl-Alt-C). Pozwala ona na podgląd komunikacji między klientem a serwerem. Oprócz skrótu klawiszowego Ctrl-Alt-C konsola Postmana jest dostępna poprzez ikonę w lewym dolnym rogu głównego okna Postmana:

Konsola Postmana rejestruje komunikację między klientem a serwerem, która ma miejsce podczas wykonywania żądania w Postmanie:

- w pliku [3] znajduje się lista żądań wysłanych przez Postman od momentu jego uruchomienia. Najnowsze żądania znajdują się na końcu listy;
- w pliku [4] znajduje się żądanie HTTP wysłane przez Postman;
- w pliku [5-6] – odpowiedź HTTP udzielona przez serwer WWW;
- w [7] można zobaczyć logi w trybie [raw], tj. bez żadnych elementów formatowania;
W trybie [raw] okno konsoli wygląda następująco:

- w [8] – zapytanie HTTP wysłane przez Postmana do serwera WWW;
- w trybie [9] – odpowiedź HTTP wysłana przez serwer WWW;
- w [10] można powrócić do trybu [pretty logs];
Aby ułatwić wyjaśnienia, ponumerujemy wiersze uzyskane z konsoli Postman.
Dla klienta:
Dla serwera:
Od tej pory będziemy używać głównie:
- [Postman] jako klienta internetowego;
- konsolę [Postman] w [raw mode] w celu wyjaśnienia dialogu między klientem a serwerem;
22.4.3. skrypt [main_02]

Skrypt internetowy [main_02] wygląda następująco:
# konfiguruje się aplikację
import config
config=config.configure()
# importy
from flask import Flask, make_response
from flask_api import status
# zależności
from Personne import Personne
# aplikacja Flask
app = Flask(__name__)
# Strona główna URL
@app.route('/')
def index():
# osoba
personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
# treść
response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
# nagłówki HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# odpowiedź HTTP
return response, status.HTTP_200_OK
# tylko strona główna
if __name__ == '__main__':
# uruchamiamy serwer
app.config.update(ENV="development", DEBUG=True)
app.run()
- skrypt [main_02] jest analogiczny do skryptu [main_01]. Różni się od niego w dwóch punktach:
- wiersz 22: dokument wysyłany do klienta jest surowym ciągiem znaków, a nie ciągiem jSON;
- wiersz 24: znajduje to odzwierciedlenie w nagłówku HTTP [Content-Type], który wskazuje typ [text/plain] dla dokumentu;
Uruchamiamy skrypt internetowy [main_02], a następnie używamy [Postman] do wysłania zapytania:

- w [1-3] wysyłamy zapytanie do serwisu internetowego;
- w [5] – status odpowiedzi OK;
- w [4, 6] nagłówki odpowiedzi HTTP;
- w [7] – nagłówek [Content-Type];
- w [8-10] – dokument wysłany przez serwis internetowy, ciąg znaków;
Konsola Postman wyświetla następujące logi:
Żądanie klienta:
Odpowiedź serwera:
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. skrypt [main_03]

Skrypt internetowy [main_03] wygląda następująco:
# konfiguracja aplikacji
import config
config = config.configure()
# importy
from flask import Flask, make_response
from flask_api import status
# zależności
from MyException import MyException
from Personne import Personne
# aplikacja Flask
app = Flask(__name__)
# Strona główna URL
@app.route('/')
def index():
# nieprawidłowa osoba
msg_erreur = None
try:
personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
except MyException as erreur:
msg_erreur = f"{erreur}"
# błąd?
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
# nagłówki HTTP
response.headers.set("Content-Type", "text/plain; charset=utf8")
# odpowiedź HTTP
return response, status_code
# tylko main
if __name__ == '__main__':
# uruchamiamy serwer
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 23: powoduje błąd poprzez instancjonowanie nieprawidłowej osoby;
- wiersze 27–29: z powodu błędu:
- wiersz 28: przygotowuje się odpowiedź HTTP zawierającą komunikat o błędzie;
- wiersz 29: nadaje się kodowi statusu HTTP wartość błędu [500 Internal Server Error];
- wiersz 34: informujemy klienta, że wysyłamy mu tekst zwykły;
- wiersz 36: wysyła się odpowiedź HTTP do klienta;
Uruchamiamy usługę internetową [main_03] i używamy Postmana do wysłania zapytania:

- w [1-3] wysyłamy żądanie;
- w [4] otrzymujemy odpowiedź z kodem statusu [500 INTERNAL SERVER ERROR];
- w [5-7]: odpowiedź stanowi tekst opisujący wystąpiły błąd;

- po wysłaniu żądania [8-10] otrzymujemy nagłówki HTTP z odpowiedzi serwisu internetowego;
W konsoli Postman wyniki w trybie [raw] są następujące:
Żądanie klienta:
Odpowiedź serwera:
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. Skrypty [flask/04]: informacje zawarte w żądaniu

Skrypt [request_parameters.py] ma na celu wykazanie, że serwis internetowy ma dostęp do różnych informacji zawartych w żądaniu klienta internetowego. Kod wygląda następująco:
# import
from flask import Flask, make_response, request
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# Strona główna URL
@app.route('/', methods=['GET', 'POST'])
def index():
# parametry żądania
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
# odpowiedź HTTP
response = make_response(request_data)
# nagłówki HTTP
response.headers["Content-Type"] = "application/json; charset=utf-8"
# wysłanie odpowiedzi HTTP
return response, status.HTTP_200_OK
# główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 9: wprowadzamy zmianę. Określamy, jakie czasowniki są dozwolone w żądaniu klienta. Postman podaje ich listę:

Dwa pierwsze, [GET, POST], są najczęściej używane i będą również jedynymi, które zostaną wykorzystane w tym dokumencie. Wracając do wiersza 9 kodu, parametr [methods] zawiera listę metod z powyższej listy, które są dozwolone przez URL. W przypadku braku tego parametru dozwolona jest wyłącznie metoda [GET]. Tak właśnie było do tej pory;
- wiersz 12: utworzymy słownik [request_data];
- wiersz 13: żądanie klienta jest dostępne w predefiniowanym obiekcie [request], zaimportowanym w wierszu 2, typu [werkzeug.local.LocalProxy]. Kolejne wiersze pobierają różne atrybuty tego obiektu;
- zamiast szczegółowo omawiać każdy atrybut obiektu [request], uruchomimy ten kod i przyjrzymy się wynikom. W ten sposób lepiej zrozumiemy znaczenie poszczególnych wyświetlanych atrybutów;
- wiersz 42: słownik [request_data] będzie treścią odpowiedzi HTTP. Przypomnijmy, że musi to być tekst. Flask automatycznie przekształca słowniki w ciągi znaków jSON;
- wiersz 44: informujemy klienta, że otrzyma jSON;
- wiersz 46: wysyłamy odpowiedź do klienta;
Za pomocą klienta Postman wysyłamy następujące żądanie do powyższej usługi internetowej:

- w formacie [1-2] – wysłane żądanie;
- w [2] żądanie jest skonfigurowane. Parametry są dołączone do URL w postaci [ ?param1=valeur1¶m2=valeur2]. Istnieją dwa sposoby wprowadzenia tych parametrów w Postmanie:
- wpisać je bezpośrednio w URL;
- wpisać je w pliku [3-4];
Obie metody są równoważne;
Dodajemy kolejne parametry do żądania:

- w [5-7] dodajemy parametry w treści (=body) żądania. Podczas gdy parametry z URL są widoczne dla użytkownika przeglądarki internetowej, te znajdujące się w treści żądania nie są widoczne. Przeglądarka (lub w tym przypadku Postman) wysyła je do serwera po nagłówkach HTTP. Żądanie klienta internetowego ma wówczas taką samą strukturę jak odpowiedź serwera internetowego: nagłówki HTTP, po których następuje dokument. Spowoduje to pojawienie się dwóch nowych nagłówków HTTP w żądaniu klienta:
- [Content-Type]: klient informuje serwer, jaki typ dokumentu wysyła;
- [Content-Length]: rozmiar dokumentu w bajtach;
- w [6] – kodowanie, które ma być użyte dla parametrów zadeklarowanych w [7]. Parametry te mogą być kodowane na różne sposoby. [x-www-form-urlencoded] to metoda często stosowana przez przeglądarki;
Można zobaczyć zapytanie, które zostanie wygenerowane:

Odpowiedź na to żądanie wygląda następująco:

- w [1-5] otrzymano ciąg znaków jSON [3];
- serwis internetowy zazwyczaj interesują parametry z URL i [ ?param1=valeur1¶m2=valeur2] oraz te, które zostały przekazane w treści żądania (dokumencie). Zazwyczaj w ten sposób klient przekazuje mu informacje. W [5] widać, że parametry URL są dostępne w [request.args];
Pozostała część odpowiedzi wygląda następująco:

- w [9] atrybuty parametrów umieszczonych w treści żądania:
- [content_type] to typ dokumentu dołączonego do żądania. Zauważyliśmy, że dokument ten zawierał informacje typu [param=valeur] zakodowane w formacie [x-www-form-urlencoded]. Postman wygenerował zatem nagłówek HTTP [Content-Type] wskazujący charakter dokumentu;
- [content_length] to rozmiar tego dokumentu w bajtach;
- w [10] atrybut [request.environ] zawiera wiele informacji na temat środowiska, w którym przetwarzane jest żądanie klienta. Większość tych informacji znajduje się w innych atrybutach obiektu [request];
- w [11] parametry zawarte w treści żądania są dostępne w atrybucie [request.form];
- w [12] – metoda użyta do wysłania żądania, w tym przypadku metoda [GET];
- w [13] atrybut [request.values] stanowi słownik wszystkich parametrów, zarówno tych z URL, jak i tych z treści dokumentu. Aby uzyskać parametry żądania, należy użyć atrybutu:
- [request.args], aby uzyskać te zawarte w URL;
- [request.form], aby uzyskać te zawarte w treści dokumentu;
W konsoli Postman logi wyglądają następująco:
Żądanie klienta:
- wiersz 9: typ dokumentu wysłanego w wierszu 12 do serwera;
- wiersz 11: nagłówki żądania HTTP są oddzielone od wysłanego dokumentu pustym wierszem. W ten sposób serwer rozpoznaje koniec nagłówków HTTP od klienta;
- wiersz 12: dokument zakodowany metodą „url-encode”. Wszystkie znaki z akcentami zostały zakodowane;
Odpowiedź klienta wygląda następująco:
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"
}
}
- wiersze 1–5: nagłówki odpowiedzi HTTP zakończone pustym wierszem;
- wiersze 41–45: znaki z akcentami zostały zakodowane w formacie UTF-8;
Jeśli teraz użyjemy metody [POST] do wysłania tego samego żądania z tymi samymi parametrami, otrzymamy tę samą odpowiedź, z tą różnicą, że w przypadku [12] otrzymamy [‘method’ : ‘POST’].
Jaka jest zatem różnica między metodami GET a POST? Różnica jest niewielka i wynika z historycznego sposobu wykorzystania tych metod przez przeglądarki:
- parametry w URL są praktyczne, ponieważ tak skonfigurowany plik URL może służyć jako link w dokumencie HTML. Użytkownik może również samodzielnie zmienić parametry, aby uzyskać inne odpowiedzi od serwera. W takim przypadku przeglądarki powszechnie stosują metodę [GET], a w żądaniu wysyłanym do serwera WWW nie ma treści (content_length=0) (brak ukrytych parametrów);
- czasami nie chcemy, aby parametry były wyświetlane w treści żądania. Dotyczy to haseł wysyłanych do serwera. Ponadto rozmiar zajmowany przez parametry w treści żądania jest ograniczony (URL nie może przekroczyć określonej wielkości). Parametry w treści żądania nie podlegają temu ograniczeniu. Ponadto duża liczba parametrów w URL sprawia, że staje się on nieczytelny. Weźmy typowy przykład formularza rejestracyjnego na stronie internetowej. Dawniej, gdy strony HTML nie zawierały jeszcze kodu JavaScript, przeglądarki wysyłały wprowadzone informacje za pomocą POST. Mówiono wówczas o wartościach wysyłanych metodą POST;
Tak więc na początku programowania stron internetowych:
- metody GET były raczej związane z żądaniem informacji dostarczanych przez serwer internetowy;
- metody POST były raczej związane z wysyłaniem informacji z przeglądarki do serwera. Serwer był wówczas „wzbogacany” o te dane;
Od tego czasu pojawił się JavaScript. Podczas gdy w poprzednich przykładach programista nie miał nad tym kontroli (kliknięcie linku nieuchronnie uruchamiało metodę GET, a zatwierdzenie formularza nieuchronnie wymagało użycia metody POST), JavaScript przywrócił im tę kontrolę. W tym modelu strona HTML jest powiązana z kodem JavaScript, który może ominąć przeglądarkę. W ten sposób kliknięcie linku może zostać przechwycone przez kod JavaScript, który następnie może wykonać kod wysyłający żądanie do serwera. Żądanie to będzie niewidoczne dla użytkownika. Użytkownik jej nie zobaczy. Kod ten pełni rolę klienta internetowego i, podobnie jak w przypadku narzędzia Postman, programista może utworzyć dowolne żądanie. Wracając do kliknięcia w link, może on wykonać żądanie POST, podczas gdy domyślnie przeglądarka wykonałaby żądanie GET. Te zmiany sprawiły, że różnice między GET a POST stały się mniej istotne.
Jednak programiści często stosują następujące zasady:
- GET nie może zmieniać stanu serwera. Kolejne żądania GET wysyłane z tymi samymi parametrami w żądaniu URL powinny zwracać ten sam dokument. Ponadto GET najczęściej nie ma treści (brak powiązanego dokumentu), a jedynie parametry w URL;
- POST może zmienić stan serwera. Parametry są najczęściej przesyłane w treści żądania. Mówimy wtedy o wartościach wysyłanych metodą POST. Najlepiej ilustruje to przykład formularza: wartości wprowadzone przez użytkownika zostaną umieszczone w treści żądania POST, a serwer zapisze je w odpowiednim miejscu, najczęściej w bazie danych;
W dalszej części dokumentu nie będziemy się trzymać żadnych konkretnych zasad.
22.6. Skrypty [flask-05]: zarządzanie pamięcią użytkownika
22.6.1. Wprowadzenie
W poprzednich przykładach dotyczących relacji klient–serwer mieliśmy do czynienia z następującym schematem działania:
- klient nawiązuje połączenie z portem 80 serwera;
- wysyła sekwencję tekstową: nagłówki HTTP, pusty wiersz, [document];
- w odpowiedzi serwer wysyła sekwencję tego samego typu;
- serwer zamyka połączenie z klientem;
- klient zamyka połączenie z serwerem;
Jeśli ten sam klient wkrótce potem wyśle nowe żądanie do serwera internetowego, tworzone jest nowe połączenie między klientem a serwerem. Serwer nie jest w stanie stwierdzić, czy łączący się klient odwiedzał go już wcześniej, czy też jest to pierwsze żądanie. Pomiędzy dwoma połączeniami serwer „zapomina” o swoim kliencie. Z tego powodu mówi się, że protokół HTTP jest protokołem bezstanowym. Jednakże przydatne jest, aby serwer zapamiętywał swoich klientów. Jeśli więc aplikacja jest zabezpieczona, klient wysyła do serwera nazwę użytkownika i hasło w celu uwierzytelnienia się. Jeśli serwer „zapomni” o swoim kliencie między dwoma połączeniami, klient będzie musiał uwierzytelniać się przy każdym nowym połączeniu, co jest nie do pomyślenia.
Aby śledzić klienta, serwer może postępować na różne sposoby:
- przy pierwszym żądaniu od klienta dołącza do odpowiedzi identyfikator, który klient musi następnie odsyłać przy każdym nowym żądaniu. Dzięki temu identyfikatorowi, innemu dla każdego klienta, serwer może rozpoznać klienta. Może wówczas zarządzać pamięcią dla tego klienta w postaci pamięci jednoznacznie powiązanej z identyfikatorem klienta. Tak działają na przykład usługi PHP;
- przy pierwszym żądaniu od klienta serwer umieszcza w odpowiedzi nie identyfikator, lecz samą pamięć użytkownika. Serwer nie przechowuje żadnych danych. Aby zachować swoją pamięć, klient internetowy musi odsyłać ją przy każdym nowym żądaniu. Pamięć ta jest modyfikowana (lub nie) przy każdym nowym żądaniu i odsyłana (lub nie) do klienta. Jest to metoda stosowana przez framework Flask;
Różnice między tymi dwiema metodami są następujące:
- metoda 1 zużywa mniej przepustowości. Pomiędzy klientem a serwerem wymieniany jest jedynie identyfikator. Gdy pamięć użytkownika się powiększa, nie ma to żadnego wpływu na identyfikator, który pozostaje ten sam. Nie ma to miejsca w przypadku metody 2, gdzie pamięć użytkownika jest wymieniana przy każdym żądaniu i może się powiększać wraz z kolejnymi żądaniami;
- metoda 1 zużywa więcej pamięci. Serwer przechowuje bowiem pamięć użytkownika w swoich systemach plików. Jeśli jest milion użytkowników, może to stanowić problem. Metoda 2 nie przechowuje niczego na serwerze;
Z technicznego punktu widzenia w obu metodach wygląda to następująco:
- w odpowiedzi dla nowego klienta serwer dołącza nagłówek HTTP, [Set-Cookie : MotClé=Identifiant] lub [Set-Cookie : mémoire]. W przypadku metody 1 robi to tylko przy pierwszym żądaniu. W przypadku metody 2 robi to za każdym razem, gdy zmienia się pamięć użytkownika;
- w swoich żądaniach klient zawsze zwraca to, co otrzymał – identyfikator lub pamięć. Robi to za pomocą nagłówka HTTP [Cookie : MotClé=Valeur];
Można się zastanawiać, w jaki sposób serwer rozpoznaje, że ma do czynienia z nowym klientem, a nie z klientem, który już wcześniej odwiedził stronę. Wskazuje na to obecność nagłówka HTTP Cookie w nagłówkach HTTP klienta. W przypadku nowego klienta ten nagłówek nie występuje.
Zbiór wszystkich połączeń danego klienta nazywany jest sesją.
Serwer może utrzymywać inne rodzaje pamięci:

- W przypadku [1] pamięć żądania ma szczególny charakter. Jest ona wykorzystywana, gdy żądanie klienta internetowego jest przetwarzane nie przez jedną usługę (lub aplikację), ale przez kilka. Aby przekazać informacje do usługi i+1, usługa i może wzbogacić przetwarzane żądanie (request) o te informacje. Nazywa się to pamięcią na poziomie żądania. W niniejszym dokumencie nie będziemy korzystać z tego typu pamięci;
- w [2, 4] mamy do czynienia z pamięcią użytkownika, którą właśnie opisaliśmy. Może ona być zaimplementowana lokalnie ([2]) lub utrzymywana za pomocą klienta ([4]);
- w [3] pamięć na poziomie „aplikacji” jest zazwyczaj pamięcią tylko do odczytu. Jest ona współdzielona przez wszystkich użytkowników. Często znajdują się w niej elementy konfiguracji aplikacji internetowej, współdzielone przez wszystkich użytkowników aplikacji. Należy zachować ostrożność w przypadku tego typu pamięci: zapis w niej musi odbywać się w momencie, gdy użytkownicy nie wysłali jeszcze żądań, najczęściej podczas uruchamiania aplikacji. Następnie, gdy pojawiają się żądania, zapis w tej pamięci staje się utrudniony. Gdy serwer internetowy obsługuje jednocześnie wielu użytkowników i dwóch z nich próbuje zapisać dane w pamięci na poziomie „aplikacji”, istnieje ryzyko uszkodzenia tej pamięci. W rzeczywistości, gdy użytkownik 1 rozpoczął zapis w pamięci na poziomie „aplikacji”, może zostać przerwany, zanim jeszcze zakończy operację. W rezultacie otrzymujemy niekompletną pamięć aplikacji. Ponieważ jest ona współdzielona, użytkownik 2 może ją odczytać i uzyskać nieprawidłowy stan;
22.6.2. skrypt [session_scope_01]

Skrypty [session_scope_xx] ilustrują zarządzanie pamięciami użytkowników.
Skrypt [session_scope_01] ma następującą postać:
# konfigurujemy aplikację
import config
config = config.configure()
# zależności
import json
from flask import Flask, make_response, session
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# tajny klucz sesji
app.secret_key = config["SECRET_KEY"]
@app.route('/set-session', methods=['GET'])
def set_session():
# dodajemy coś do sesji
session['nom'] = 'séléné'
# wysyłamy pustą odpowiedź
response = make_response()
response.headers['Content-Length'] = 0
return response, status.HTTP_200_OK
@app.route('/get-session', methods=['GET'])
def get_session():
# pobieramy sesję i wysyłamy odpowiedź
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
# tylko ręcznie
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 11: tworzona jest instancja aplikacji Flask;
- wiersz 14: atrybut [secret_key] tej aplikacji otrzymuje wartość pobraną z pliku konfiguracyjnego wykorzystanego w wierszach 1–3. Sesja Flaska jest możliwa tylko wtedy, gdy ten atrybut zostanie zainicjowany. Można w nim umieścić dowolną treść. Służy on do szyfrowania części „pamięci użytkownika”, która zostanie wysłana do klienta. Zazwyczaj umieszcza się tam coś trudnego do odgadnięcia. W pliku [config] klucz tajny jest zdefiniowany w następujący sposób:
# zwracamy konfigurację
config = {
# konfiguracja Flaska
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
- Po raz pierwszy definiujemy aplikację internetową, która służy do czegoś innego niż URL /
- wiersz 17: URL [/set-session] służy do zainicjowania sesji użytkownika;
- wiersz 27: URL [/get-session] służy do odzyskania pamięci użytkownika (lub sesji użytkownika);
- wiersz 20: umieszczamy coś w pamięci (= sesji) użytkownika, w tym przypadku imię. Sesja działa trochę jak słownik. Nie można umieścić w sesji byle czego. Wartości, które tam umieszczamy, muszą dać się przekształcić w jSON. W przypadku predefiniowanych typów w Pythonie odbywa się to bez udziału programisty. W przypadku obiektów niestandardowych, których Python nie rozpoznaje, należy samodzielnie wykonać konwersję jSON;
- wiersz 22: tworzymy odpowiedź HTTP bez treści (brak parametru w make_response);
- wiersz 23: informujemy klienta, że otrzyma pusty dokument (rozmiar 0 bajtów);
- wiersz 24: wysyłamy odpowiedź HTTP do klienta. URL [/set-session] nie wykonuje zatem nic poza zainicjowaniem sesji użytkownika;
- wiersz 27: URL i [/get-session] pozwalają użytkownikowi sprawdzić, co znajduje się w jego sesji;
- wiersz 30: tworzymy odpowiedź HTTP zawierającą ciąg znaków jSON z sesji użytkownika. W tym przypadku sami utworzyliśmy ciąg znaków jSON, zamiast pozwolić, by wygenerował go Flask. Nie chcemy bowiem, aby znaki z akcentami były zamieniane (ensure_ascii=False);
- wiersz 31: informujemy klienta, że wysyłamy mu jSON;
- wiersz 32: wysyłamy odpowiedź HTTP do klienta;
Celem tego skryptu jest pokazanie, że sesja użytkownika pozwala na powiązanie kolejnych żądań użytkownika:
- żądanie 1 będzie dotyczyło URL [/set-session];
- żądanie 2 zażąda URL [/get-session] i pobierze nazwę zainicjowaną przez żądanie 1;
Skrypt [config], który konfiguruje skrypty w folderze [flask/05], wygląda następująco:
def configure():
# ścieżka bezwzględna jako odniesienie dla ścieżek względnych w konfiguracji
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# zależności aplikacji
absolute_dependencies = [
# Osoby, Narzędzia, MyException
f"{root_dir}/classes/02/entities",
]
# ustawiamy ścieżkę systemową
from myutils import set_syspath
set_syspath(absolute_dependencies)
# udostępnianie konfiguracji
config = {
# konfiguracja Flaska
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
Uruchamiamy skrypt [session_scope_01], a następnie za pomocą Postmana wyślemy żądanie do URL i [/set-session]. Wcześniej sprawdzimy kilka elementów żądania, które zostanie wysłane:
![]()
- w [1] uzyskujemy dostęp do plików cookie w Postmanie;
- w [2-4] sprawdzamy znane pliki cookie Postmana i usuwamy je wszystkie ([4-5]);
Teraz sprawdźmy żądanie HTTP, które zostanie wygenerowane:

- w [9]: część nagłówków HTTP, które Postman umieści w żądaniu na podstawie konfiguracji, którą dla niego ustaliliśmy. Ta weryfikacja pozwala upewnić się, że nie pominięto żadnych parametrów ani nie pozostawiono zbędnych;
Po wykonaniu tej czynności można wysłać żądanie:
![]()
Istnieje kilka sposobów sprawdzenia wyniku. Można najpierw spojrzeć na okno główne:

- w [1-2] – zapytanie wysłane do serwisu internetowego;
- w [3-6] – nagłówki odpowiedzi HTTP;
- w [4], ponieważ w kodzie nie określono typu odpowiedzi, Flask domyślnie użył typu [text/html];
- w [5] klient wie, że w odpowiedzi nie ma dokumentu;
- wiersz 6: nagłówek [Set-Cookie] został wysłany przez serwer Flask. Jego wartość nazywana jest plikiem cookie sesji. Składa się z trzech elementów:
- [session=valeur]: wartość ta reprezentuje dane użytkownika w postaci zakodowanej. Dane te można odkodować (patrz |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session|). Jednak ze względu na tajny klucz używany przez serwer użytkownik nie może modyfikować otrzymanej pamięci, aby następnie odesłać ją z powrotem do serwera. Gdy serwer odbiera sesję, ma w ten sposób pewność, że otrzymuje sesję nieuszkodzoną;
- [HttpOnly]: obecność tego elementu wskazuje przeglądarce, która go odbiera, że plik cookie nie może być dostępny dla kodu JavaScript, który może znajdować się na wyświetlanej stronie;
- [Path=/] to ścieżka, do której należy odesłać plik cookie sesji, a więc w tym przypadku dowolna ścieżka w aplikacji internetowej. Za każdym razem, gdy użytkownik za pomocą klawiatury zwróci się o to wyraźnie (wpisując URL) lub pośrednio (klikając link) o URL z tej domeny, przeglądarka automatycznie odeśle otrzymany plik cookie sesji;
Wadą głównego okna jest brak dostępu do pełnego zapytania, które doprowadziło do tej odpowiedzi. To, co jest wyświetlane w tym oknie, może wprowadzać w błąd:

- W nagłówkach HTTP i [3-4] pojawia się w nagłówku [5] plik cookie sesji. Można by zatem pomyśleć, że Postman umieścił w żądaniu plik cookie sesji, choć tak nie jest. Nagłówki [3] reprezentują w rzeczywistości nagłówki HTTP, które zostaną wysłane podczas następnego żądania, zgodnie z jego obecną konfiguracją. Postman właśnie otrzymał plik cookie sesji, który odeśle podczas następnego żądania. Dlatego mamy [5];
Dostęp do okna dialogowego klient/serwer w konsoli Postmana można uzyskać za pomocą skrótu klawiszowego Ctrl-Alt-C:
GET /set-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 3673b73f-7600-4df4-8c4b-c37973e50df8
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Cookie
Set-Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:32:57 GMT
- wiersz 14: plik cookie sesji wysłany przez serwer;
Teraz wywołajmy URL [/get-session]:
- wiersz 9: klient Postman odesłał do serwera otrzymany plik cookie sesji;
- wiersz 18: ciąg znaków jSON wysłany przez serwer;
Ten przykład pokazuje nam kilka kwestii:
- klient Postman odsyła plik cookie sesji, który otrzymał od serwera Flask. Przeglądarki internetowe zawsze tak postępują;
- widzimy, że żądanie nr 2 o treści [/get-session] pozwoliło odzyskać informację utworzoną podczas żądania nr 1 o treści [/set-session]. Mamy więc do czynienia z pamięcią użytkownika;
- wiersze 11–16: serwer Flask nie odesłał pliku cookie sesji. Nie dzieje się to zawsze. Serwer Flask odsyła plik cookie sesji tylko wtedy, gdy ostatnie żądanie zmieniło pamięć użytkownika;
22.6.3. skrypt [session_scope_02]

Skrypt [session_02] wygląda następująco:
# zależności
import os
from flask import Flask, make_response, session
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# tajny klucz sesji
app.secret_key = os.urandom(12).hex()
# Strona główna URL
@app.route('/', methods=['GET'])
def index():
# zarządzamy trzema licznikami
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
# słownik liczników
compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
# wysyłamy odpowiedź
response = make_response(compteurs)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 11: tutaj klucz tajny jest generowany za pomocą funkcji. Zaletą tej funkcji jest to, że generuje ona losowo złożony ciąg znaków. Przypomnijmy, że zmienna [app] jest instancją klasy Flask utworzoną w wierszu 8;
- wiersz 15: tym razem będzie tylko jedna trasa, trasa /;
- wiersze 17–29: zarządzamy sesją zawierającą trzy liczniki [n1, n2, n3]. Przy pierwszym wywołaniu przez użytkownika [n1, n2, n3] = [0, 10, 100], a następnie przy każdym kolejnym wywołaniu liczniki te są zwiększane o 1;
- wiersz 18: podczas pierwszego żądania sesja aplikacji jest pusta. Wyrażenie [session.get(‘clé’)] zwraca wartość [None]. W przypadku kolejnych żądań wyrażenie to będzie zwracać wartość powiązaną z kluczem;
- wiersz 31: liczniki te są umieszczane w słowniku;
- wiersz 33: ten słownik stanowi dokument odpowiedzi HTTP. Przypomnijmy, że Flask automatycznie przekształca słowniki w ciąg znaków jSON;
- wiersz 34: informujemy klienta internetowego, że otrzyma jSON;
- wiersz 35: wysyłamy odpowiedź HTTP do klienta;
Uruchommy ten skrypt i wypróbujmy utworzoną w ten sposób aplikację internetową za pomocą Postmana po usunięciu wszystkich plików cookie z klienta Postmana [1-3]:

W konsoli Postmana wymiana danych między klientem a serwerem wygląda następująco:
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
}
- w [14] – plik cookie sesji wysłany przez serwer;
- na [18-22] – odpowiedź serwera w postaci ciągu znaków jSON;
Wykonajmy to samo żądanie po raz drugi. Logi zmieniają się w następujący sposób:
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
}
- wiersz 9: klient Postman odsyła otrzymany plik cookie sesji;
- wiersz 15: w swojej odpowiedzi serwer wysyła nowy plik cookie sesji, ponieważ żądanie klienta zmieniło pamięć użytkownika (= sesję);
- wiersze 19–23: nowe wartości liczników;
22.6.4. skrypt [session_scope_03]
Ten nowy skrypt ma na celu pokazanie, że w sesji można umieścić różne typy danych w języku Python: listę, słownik, obiekt. Jedynym ograniczeniem jest to, że obiekty umieszczane w sesji muszą dać się serializować w jSON. Jeśli domyślnie nie są one serializowalne (listy, słowniki), należy samodzielnie przeprowadzić konwersję w jSON.
# konfiguruje się aplikację
import config
config = config.configure()
# zależności
import json
import os
from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne
# aplikacja Flask
app = Flask(__name__)
# tajny klucz sesji
app.secret_key = os.urandom(12).hex()
# Strona główna URL
@app.route('/', methods=['GET'])
def index():
# zarządzanie listą
liste = session.get('liste')
if liste is None:
# pierwsze żądanie
liste = [0, 10, 100]
else:
# kolejne żądania
for i in range(len(liste)):
liste[i] += 1
# ponowne umieszczenie listy w sesji
session['liste'] = liste
# obsługa słownika
dico = session.get('dico')
if not dico:
# pierwsze zapytanie
dico = {"un": 0, "deux": 10, "trois": 100}
else:
# kolejne zapytania
dico = session['dico']
for key in dico.keys():
dico[key] += 1
# słownik jest ponownie umieszczany w sesji
session['dico'] = dico
# zarządzanie danymi osoby
personne_json = session.get('personne')
if personne_json is None:
# pierwsze zapytanie
personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
else:
# kolejne zapytania
personne = Personne().fromjson(personne_json)
personne.âge += 1
# przywracamy osobę do sesji
session['personne'] = personne.asjson()
# słownik wyników
résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}
# wysyłanie odpowiedzi 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
# główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersze 1–3: konfiguracja aplikacji internetowej;
- wiersze 5–11: importowane są zależności;
- wiersz 14: instancja aplikacji Flask jest tworzona;
- wiersz 17: inicjowany jest atrybut [secret_key]. To właśnie on umożliwia korzystanie z sesji;
- wiersz 21: jedyna trasa aplikacji;
- wiersze 23–33: zarządzanie listą w sesji. Umieszczono w niej elementy, które domyślnie można serializować za pomocą jSON;
- wiersze 35–46: zarządzanie słownikiem w sesji. Umieszczono w nim elementy, które domyślnie można serializować do formatu jSON;
- wiersze 48–58: obsługa osoby. Obiekt [Personne] nie jest domyślnie serializowalny w formacie jSON. Należy zatem podjąć odpowiednie środki ostrożności;
- wiersz 58: stosuje się metodę [BaseEntity.asjson] w celu zapisania w sesji ciągu znaków jSON należącego do danej osoby. Należy zauważyć, że można było użyć ciągu [personne.asdict], ponieważ [personne.asdict] jest słownikiem zawierającym wartości, które domyślnie są serializowane do postaci jSON;
- wiersz 55: ponieważ w sesji zapisano ciąg znaków jSON, pobieramy z niej dane osoby, używając metody [BaseEntity.fromjson];
- wiersz 61: tworzymy słownik [résultats], który zostanie wysłany jako odpowiedź do klienta. Wiemy, że w tym przypadku Flask wysyła ciąg znaków jSON z słownika. Dlatego też słownik ten musi domyślnie zawierać wyłącznie wartości, które można serializować w formacie jSON;
- wiersz 64: jawnie umieszczamy ciąg jSON ze słownika [résultats] w odpowiedzi HTTP. Flask zrobiłby to domyślnie. Jednak, również domyślnie, używa on parametru [ensure_ascii=True], co nam nie odpowiadało;
- wiersz 65: informujemy klienta, że otrzyma jSON;
- wiersz 66: wysyłamy mu odpowiedź;
Uruchamiamy aplikację internetową. Usuwamy wszystkie pliki cookie z klienta Postman. Następnie klient żąda URL [http://localhost:5000]. Dialog klient–serwer w konsoli Postman wygląda następująco:
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}}
Wysyłamy żądanie po raz drugi:
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}}
- wiersz 9: klient odsyła otrzymany plik cookie sesji;
- wiersz 15: serwer zwraca mu inny, ponieważ zawartość sesji uległa zmianie (wiersz 19). Przypominamy, że zawartość ta znajduje się w pliku cookie sesji w postaci zaszyfrowanej;
22.7. skrypty [flask/06]: informacje wspólne dla wszystkich użytkowników
22.7.1. Wprowadzenie
Celem tej sekcji jest pokazanie, jak zarządzać informacjami o zasięgu aplikacji, tj. współdzielonymi przez wszystkich użytkowników. Informacje te to zazwyczaj dane konfiguracyjne aplikacji. Widzieliśmy już, że aplikacja internetowa może utrzymywać różne rodzaje pamięci:

W tym miejscu skupiamy się na pamięci aplikacji [3].
22.7.2. skrypt [application_scope_01]

Skrypt [application_scope_01] pokazuje sposób zarządzania danymi o zasięgu „aplikacji”:
# konfiguracja aplikacji
import config
config = config.configure()
# zależności
from flask import Flask, make_response
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# Strona główna URL
@app.route('/', methods=['GET'])
def index():
# celem jest pokazanie, że aplikacja pozostaje w pamięci pomiędzy żądaniami różnych klientów
# każdy klient korzysta z tej samej aplikacji
# app_infos przedstawia informacje na poziomie aplikacji, a nie na poziomie sesji
# tj. dotyczą one wszystkich użytkowników, a nie konkretnego użytkownika
# informacja ta jest tutaj przechowywana w [config] (nieobowiązkowo)
# słownik wyników
résultats = {"config": config}
# wysyłamy odpowiedź
response = make_response(résultats)
response.headers['Content-Type'] = 'application/json; charset=utf-8'
return response, status.HTTP_200_OK
# main
if __name__ == '__main__':
# sprawdzamy, czy ten kod jest wykonywany wielokrotnie
print("application app lancée")
# uruchamiamy aplikację internetową
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersze 1–3: pobieramy słownik konfiguracji. Pokażemy, że kod znajdujący się poza funkcjami routingu jest wykonywany tylko raz. Aplikacja Flask pozostaje w pamięci. Wszystkie informacje zainicjowane poza trasami mają charakter globalny względem tych tras i są im zatem znane. W ten sposób słownik [config] z wiersza 3 zostanie zwrócony przez trasę / (wiersz 24). Pokażemy, że wszyscy klienci sieciowi otrzymają ten sam słownik, a zatem jest on współdzielony przez wszystkich klientów. Jest to zatem informacja o zasięgu „aplikacji”;
- wiersz 35: dodajemy wpis do dziennika, aby sprawdzić, czy kod w wierszach poza funkcją routingu (wiersze 1–10, 32–38) jest wykonywany wielokrotnie;
Konfiguracja [config] wygląda następująco:
def configure():
# zwracamy konfigurację
config = {
# konfiguracja Flaska
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}
return config
Uruchamiamy tę aplikację. Wpisy w dzienniku konsoli PyCharm są następujące:

- w [1] – pierwsze uruchomienie aplikacji;
- w [2], ponieważ zażądano trybu [Debug], aplikacja zostaje ponownie uruchomiona w trybie [Debug];
Teraz w przeglądarce (poniżej Chrome) żądamy URL [http://127.0.0.1:5000/]:

Teraz w przeglądarce Firefox:

Teraz za pomocą klienta Postman:
Teraz wracamy do konsoli [Run] w PyCharm:

- oba wpisy [1, 2] nadal tam są, ale nie ma żadnych innych, mimo że widzimy trzy żądania otrzymane przez serwer WWW;
Aby mieć całkowitą pewność, że aplikacja nie jest ponownie ładowana przy każdym nowym żądaniu, można umieścić licznik w konfiguracji i zwiększać jego wartość przy każdym nowym żądaniu. Zobaczymy wtedy, że każdy klient widzi licznik w stanie, w jakim pozostawił go poprzedni klient. Należy jednak pamiętać, że klienci nie powinni modyfikować danych o zasięgu aplikacji, ponieważ są one współdzielone przez wszystkich klientów, a w sytuacji, gdy serwer obsługuje jednocześnie wielu klientów bez gwarancji, że żądanie danego klienta zostanie wykonane w całości bez przerw, klient 1, który wysłał żądanie 1 przerwane przed zakończeniem, może pozostawić współdzielone dane w stanie uszkodzonym dla kolejnych klientów.
22.7.3. skrypt [application_scope_02]

Skrypt [application_scope_02] spowoduje coś, czego nie należy robić: umożliwi klientom modyfikowanie informacji udostępnianych innym użytkownikom. Udostępnimy licznik między użytkownikami, którzy będą go zwiększać. Zobaczymy, że każdy użytkownik widzi zmiany wprowadzone przez innych użytkowników w liczniku.
Skrypt wygląda następująco:
# zależności
from flask import Flask, make_response
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# dane zakresu aplikacji
config = {
"counter": 0
}
# Strona główna URL
@app.route('/', methods=['GET'])
def index():
# celem jest pokazanie, że słownik [config] jest współdzielony przez wszystkich klientów
# aplikacji internetowej
# zwiększamy licznik
config["counter"] += 1
# wysyłamy odpowiedź
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()
- wiersze 10–12: słownik [config] udostępniony użytkownikom. Zawiera on licznik;
- wiersz 22: za każdym razem, gdy użytkownik zażąda URL /, licznik w konfiguracji zostanie zwiększony;
- wiersze 23–26: ciąg znaków jSON ze słownika jest wysyłany do każdego klienta;
Uruchamiamy ten skrypt. Następnie wysyłamy żądanie URL [http://127.0.0.1:5000/] za pomocą pierwszej przeglądarki:

Następnie wykonujemy tę samą czynność w drugiej przeglądarce:

Następnie po raz trzeci za pomocą Postmana:

Widać, że każdy klient pobiera licznik w stanie, w jakim pozostawił go poprzedni klient. Mają więc dostęp do tych samych informacji.
22.7.4. skrypt [application_scope_03]
Skrypt [application_scope_03] pokazuje, dlaczego informacje udostępniane między użytkownikami muszą być tylko do odczytu.

Skrypt wygląda następująco:
# zależności
import threading
from time import sleep
from flask import Flask, make_response
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# dane zakresu aplikacji
config = {
"counter": 0
}
# Strona główna URL
@app.route('/', methods=['GET'])
def index():
# celem jest wykazanie, że słownik [config] jest współdzielony przez wszystkich klientów
# aplikacji internetowej i że powinien być dostępny tylko do odczytu
# nazwa wątku
thread_name = threading.current_thread().name
# odczytuje się licznik
counter = config["counter"]
print(f"compteur lu : {counter}, par le thread {thread_name}")
# zatrzymujemy się na 5 sekund – dzięki temu obsłużeni zostaną inni klienci
sleep(5)
# zwiększamy licznik konfiguracji
config["counter"] = counter + 1
# log
print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
# wysyłamy odpowiedź
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)
- wiersz 43: zmieniono tryb działania aplikacji internetowej. Wpisano [threaded=True], aby wskazać, że aplikacja ma obsługiwać użytkowników jednocześnie. Odbywa się to za pomocą wątków wykonawczych:
- może istnieć wiele równoczesnych wątków wykonawczych, z których każdy obsługuje jednego użytkownika;
- procesor komputera jest współdzielony przez te wątki;
- wątek może zostać przerwany przed zakończeniem pracy. Zostanie on wznowiony w późniejszym czasie;
- wiersz 19: funkcja [index] może być wykonywana jednocześnie przez wiele wątków;
- wiersz 24: pobieramy nazwę wątku, który wykonuje funkcję [index];
- wiersz 26: odczytuje się wartość licznika. Na potrzeby naszej demonstracji rozkładamy inkrementację licznika w następujący sposób:
- krok 1: odczyt licznika (na przykład 1) przez wątek 1;
- krok 2: wstrzymanie wątku 1 na 5 sekund (wiersz 29). Ponieważ wątek 1 zażądał wstrzymania, procesor zostaje przekazany innemu wątkowi, czyli wątkowi 2. Celem jest, aby ten nowy wątek odczytał tę samą wartość licznika (=1). Następnie on również wstrzymuje się na 5 sekund i traci dostęp do procesora;
- krok 3: zwiększenie wartości licznika, wiersz 31, na podstawie wartości odczytanej w kroku 1 (=1). Wątek 1 robi to jako pierwszy: zmienia wartość licznika na 2, a następnie kończy wykonywanie funkcji [index]. Następnie przychodzi kolej na wątek 2, który budzi się i również ustawia licznik na 2 na podstawie wartości odczytanej w etapie 1 (=1). Ostatecznie, po zakończeniu działania obu wątków, licznik ma wartość 2, podczas gdy powinien wynosić 3;
- wiersz 33: wyświetlamy wartość licznika w celu weryfikacji;
Uruchamiamy skrypt, a następnie wywołujemy adres URL [http://loaclhost :5000/] w dwóch przeglądarkach, a potem za pomocą Postmana. Logi w konsoli PyCharm wyglądają wówczas następująco:
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/ (Naciśnij CTRL+C, aby zakończyć)
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 -
- wiersze 9–10: dwa pierwsze wątki (2 i 4) odczytują tę samą wartość licznika, czyli 0;
- wiersz 11: wątek 2 zmienia wartość licznika na 1;
- wiersz 13: wątek 4 ustawia licznik na 1. Od tego momentu wartość licznika jest nieprawidłowa;
- wiersze 15–16: wątek 5 nie zostaje przerwany i poprawnie obsługuje wartość licznika;
Z tego przykładu wynika, że kod aplikacji internetowej nie powinien modyfikować wartości informacji współdzielonych przez użytkowników.
22.8. skrypty [flask/07]: zarządzanie trasami

W tym miejscu skupiamy się na zarządzaniu trasami w aplikacji, tj. trasami URL obsługiwanymi przez aplikację internetową.
22.8.1. skrypt [main_01]: konfigurowalne trasy
Skrypt [main_01] wprowadza możliwość konfigurowania tras:
from flask import Flask, make_response
from flask_api import status
# aplikacja Flask
app = Flask(__name__)
# wysyłanie odpowiedzi
def send_plain_response(réponse: str):
# wysyłamy odpowiedź
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# /nazwisko/imię
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
# odpowiedź
return send_plain_response(f"{prenom} {nom}")
# init-session
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
# odpowiedź
return send_plain_response(f"/init-session/{type}")
# uwierzytelnianie-użytkownika
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
# odpowiedź
return send_plain_response("/authentifier-utilisateur")
# oblicz-podatek
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
# odpowiedź
return send_plain_response("/calculer-impot")
# wyświetl-symulacje
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
# odpowiedź
return send_plain_response("/lister-simulations")
# usuń-symulację
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
# odpowiedź
return send_plain_response(f"/supprimer-simulation/{numero}")
# zakończenie sesji
@app.route('/fin-session', methods=['GET'])
def fin_session():
# odpowiedź
return send_plain_response(f"/fin-session")
# główna
if __name__ == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run()
- wiersz 17: określamy typ parametrów skryptu URL. Pozwala to Flaskowi na przeprowadzenie weryfikacji. Jeśli parametr nie jest typu oczekiwanego, żądanie klienta zostanie odrzucone (błąd 400 Bad Request). W ten sposób Flask wykonuje część pracy, którą musielibyśmy wykonać sami;
- wiersz 18: w przypadku parametrów należy użyć dokładnie tych samych nazw, co w wierszu 17, ale kolejność nie musi być taka sama;
- wiersz 20: używamy funkcji [send_plain_response], aby wysłać odpowiedź do klienta internetowego;
- wiersz 9: funkcja [send_plain_response] odbiera ciąg znaków, który ma zostać wysłany do klienta;
- wiersz 11: tworzona jest treść odpowiedzi HTTP;
- wiersz 12: informujemy klienta, że wysyłamy mu tekst zwykły;
- wiersz 13: wysyłana jest odpowiedź HTTP;
- wiersze 23–62: inne skonfigurowane trasy, które zostaną wykorzystane później w ćwiczeniu praktycznym;
Uruchamiamy skrypt i wysyłamy zapytanie za pomocą klienta Postman:

22.8.2. skrypt [main_02]: eksport tras
W poprzednim skrypcie [main_01] kod może stać się obszerny, jeśli występuje wiele dróg. Skrypt [main_02] pokazuje, jak wyodrębnić drogi.

Skrypt [routes_02] łączy funkcje związane z trasami z poprzedniego skryptu:
from flask import make_response
from flask_api import status
def send_response(réponse: str):
# wysyłamy odpowiedź
response = make_response(réponse)
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response, status.HTTP_200_OK
# Strona główna URL
def index(nom, prenom):
# odpowiedź
return send_response(f"{prenom} {nom}")
# rozpoczęcie sesji
def init_session(type: str):
# odpowiedź
return send_response(f"/init-session/{type}")
# uwierzytelnianie użytkownika
def authentifier_utilisateur():
# odpowiedź
return send_response("/authentifier-utilisateur")
# oblicz-podatek
def calculer_impot():
# odpowiedź
return send_response("/calculer-impot")
# wyświetl symulacje
def lister_simulations():
# odpowiedź
return send_response("/lister-simulations")
# usuń-symulację
def supprimer_simulation(numero: int):
# odpowiedź
return send_response(f"/supprimer-simulation/{numero}")
# zakończenie sesji
def fin_session():
# odpowiedź
return send_response(f"/fin-session")
Należy zauważyć, że skrypt [routes_02] nie jest skryptem tras. Jest to lista funkcji. To główny skrypt [main_02] łączy trasy z funkcjami:
from flask import Flask
# przenosimy funkcje tras do osobnego skryptu
import routes_02
# aplikacja Flask
app = Flask(__name__)
# powiązania tras z funkcjami
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()
- wiersz 4: importujemy skrypt funkcji powiązanych z trasami;
- wiersze 9–16: powiązanie tras z funkcjami;
Dzięki tej metodzie każda funkcja powiązana z trasą może w razie potrzeby stanowić przedmiot oddzielnego skryptu.
Wyniki są takie same jak te uzyskane przy użyciu poprzedniego skryptu [main_01].