33. Ćwiczenie praktyczne: wersja 13
Wersja 13 wprowadza następujące zmiany w stosunku do wersji 12:
- przeprowadzono refaktoryzację niektórych fragmentów kodu;
- sesja jest zarządzana w inny sposób za pomocą modułu [flask_session];
- w pliku konfiguracyjnym stosuje się zaszyfrowane hasła;
Wersję 13 uzyskuje się początkowo poprzez skopiowanie wersji 12:


- na [1], konfiguracja zostanie poddana refaktoryzacji. W szczególności zostanie ona przeniesiona z folderu [flask];
- W [2] główny skrypt zostanie poddany refaktoryzacji. Zostanie on również przeniesiony z folderu [flask];
- w [3] konfiguracja zostanie podzielona na kilka plików;
- w wersji [4]: główny skrypt [main] zostanie uproszczony poprzez przeniesienie części kodu do innych plików;
- w pliku [5] kontroler uwierzytelniania zostanie zmodyfikowany, ponieważ od tej pory hasła użytkowników będą szyfrowane;
- w [6] główny kontroler przejmie kod, który wcześniej znajdował się w głównym skrypcie [main];
33.1. Refaktoryzacja konfiguracji aplikacji

Pojawiają się trzy nowe pliki konfiguracyjne:
- [mvc]: do konfiguracji architektury aplikacji MVC;
- [parameters]: który będzie zawierał wszystkie stałe aplikacji;
- [syspath]: który konfiguruje ścieżkę Python Path aplikacji;
Plik [syspath.py] ma następującą treść:
def configure(config: dict) -> dict:
import os
# katalog tego pliku
script_dir = os.path.dirname(os.path.abspath(__file__))
# ścieżka główna
root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"
# zależności
absolute_dependencies = [
# foldery projektu
# BaseEntity, MyException
f"{root_dir}/classes/02/entities",
# InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
f"{root_dir}/impots/v04/interfaces",
# AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
f"{root_dir}/impots/v04/services",
# ImpotsDaoWithAdminDataInDatabase
f"{root_dir}/impots/v05/services",
# AdminData, ImpôtsError, TaxPayer
f"{root_dir}/impots/v04/entities",
# Stałe, przedziały
f"{root_dir}/impots/v05/entities",
# Rejestrator, SendAdminMail
f"{root_dir}/impots/http-servers/02/utilities",
# katalog głównego skryptu
script_dir,
# konfiguracje [database, layers, parameters, controllers, views]
f"{script_dir}/../configs",
# kontrolery
f"{script_dir}/../controllers",
# odpowiedzi HTTP
f"{script_dir}/../responses",
# szablony widoków
f"{script_dir}/../models_for_views",
]
# ustawiamy ścieżkę systemową
from myutils import set_syspath
set_syspath(absolute_dependencies)
# udostępnianie konfiguracji
return {
"root_dir": root_dir,
"script_dir": script_dir
}
- skrypt [syspath] służy do konfiguracji ścieżki Python Path aplikacji (wiersze 40–41);
- dostarcza on dwóch informacji przydatnych dla innych skryptów konfiguracyjnych (wiersze 45–46);
Skrypt [mvc] konfiguruje architekturę MVC aplikacji internetowej jSON / XML / HTML:
def configure(config: dict) -> dict:
# konfiguracja aplikacji MVC
# kontrolery
from AfficherCalculImpotController import AfficherCalculImpotController
from AuthentifierUtilisateurController import AuthentifierUtilisateurController
from CalculerImpotController import CalculerImpotController
from CalculerImpotsController import CalculerImpotsController
from FinSessionController import FinSessionController
from GetAdminDataController import GetAdminDataController
from InitSessionController import InitSessionController
from ListerSimulationsController import ListerSimulationsController
from MainController import MainController
from SupprimerSimulationController import SupprimerSimulationController
# odpowiedzi HTTP
from HtmlResponse import HtmlResponse
from JsonResponse import JsonResponse
from XmlResponse import XmlResponse
# szablony widoków
from ModelForAuthentificationView import ModelForAuthentificationView
from ModelForCalculImpotView import ModelForCalculImpotView
from ModelForErreursView import ModelForErreursView
from ModelForListeSimulationsView import ModelForListeSimulationsView
# dozwolone akcje i ich kontrolery
controllers = {
# inicjowanie sesji obliczeniowej
"init-session": InitSessionController(),
# uwierzytelnianie użytkownika
"authentifier-utilisateur": AuthentifierUtilisateurController(),
# obliczanie podatku w trybie indywidualnym
"calculer-impot": CalculerImpotController(),
# obliczenie podatku w trybie zbiorczym
"calculer-impots": CalculerImpotsController(),
# lista symulacji
"lister-simulations": ListerSimulationsController(),
# usunięcie symulacji
"supprimer-simulation": SupprimerSimulationController(),
# zakończenie sesji obliczeniowej
"fin-session": FinSessionController(),
# wyświetlenie widoku obliczeń podatku
"afficher-calcul-impot": AfficherCalculImpotController(),
# pobieranie danych z urzędu skarbowego
"get-admindata": GetAdminDataController(),
# główny kontroler
"main-controller": MainController()
}
# różne typy odpowiedzi (json, xml, html)
responses = {
"json": JsonResponse(),
"html": HtmlResponse(),
"xml": XmlResponse()
}
# widoki HTML i ich szablony zależą od stanu zwracanego przez kontroler
views = [
{
# widok uwierzytelniania
"états": [
700, # /init-session – sukces
201, # /autentyfikacja-użytkownika – niepowodzenie
],
"view_name": "views/vue-authentification.html",
"model_for_view": ModelForAuthentificationView()
},
{
# widok obliczeń podatku
"états": [
200, # /uwierzytelnianie-użytkownika – pomyślnie
300, # /obliczyć-podatek pomyślnie
301, # /obliczyć-podatek – niepowodzenie
800, # /wyświetlenie-obliczeń-podatku pomyślnie
],
"view_name": "views/vue-calcul-impot.html",
"model_for_view": ModelForCalculImpotView()
},
{
# wyświetlenie listy symulacji
"états": [
500, # /wyświetlić-listę-symulacji – pomyślnie
600, # /usuń-symulację – pomyślnie
],
"view_name": "views/vue-liste-simulations.html",
"model_for_view": ModelForListeSimulationsView()
},
]
# widok nieoczekiwanych błędów
view_erreurs = {
"view_name": "views/vue-erreurs.html",
"model_for_view": ModelForErreursView()
}
# przekierowania
redirections = [
{
"états": [
400, # /pomyślne zakończenie sesji
],
# przekierowanie do URL
"to": "/init-session/html",
}
]
# przywracamy konfigurację MVC
return {
# kontrolery
"controllers": controllers,
# odpowiedzi HTTP
"responses": responses,
# widoki i szablony
"views": views,
# lista przekierowań
"redirections": redirections,
# widok nieoczekiwanych błędów
"view_erreurs": view_erreurs
}
- wiersze 1–101: ten kod jest znany;
- wiersze 105–116: przywracamy konfigurację aplikacji MVC;
Skrypt [parameters] gromadzi stałe aplikacji:
def configure(config: dict) -> dict:
# konfiguracja aplikacji
# script_dir
script_dir = config['syspath']['script_dir']
# konfiguracja aplikacji
parameters = {
# użytkownicy uprawnieni do korzystania z aplikacji
"users": [
{
"login": "admin",
"password": "$pbkdf2-sha256$29000$mPM.h3COkTIGYOzde68VIg$7LH5Q7rN/1hW.Xa.6rcmR6h52PntvVqd5.na7EtgQNw"
}
],
# plik logów
"logsFilename": f"{script_dir}/../data/logs/logs.txt",
# konfiguracja serwera SMTP
"adminMail": {
# serwer SMTP
"smtp-server": "localhost",
# port serwera SMTP
"smtp-port": "25",
# administrator
"from": "guest@localhost.com",
"to": "guest@localhost.com",
# temat wiadomości e-mail
"subject": "plantage du serveur de calcul d'impôts",
# tls ustawione na True, jeśli serwer SMTP wymaga uwierzytelnienia, w przeciwnym razie na False
"tls": False
},
# czas przerwy wątku w sekundach
"sleep_time": 0,
# serwer Redis
"redis": {
"host": "127.0.0.1",
"port": 6379
},
}
# ustawienia aplikacji
return parameters
- wiersz 13: od tej pory hasła użytkowników będą szyfrowane;
- wiersze 34–38: konfiguracja serwera [Redis], do której jeszcze wrócimy;
Dzięki tym nowym plikom konfiguracyjnym skrypt [config] wygląda następująco:
def configure(config: dict) -> dict:
# konfiguracja ścieżki systemowej
import syspath
config['syspath'] = syspath.configure(config)
# konfiguracja aplikacji
import parameters
config['parameters'] = parameters.configure(config)
# konfiguracja bazy danych
import database
config["database"] = database.configure(config)
# instancjonowanie warstw aplikacji
import layers
config['layers'] = layers.configure(config)
# konfiguracja warstwy MVC
import mvc
config['mvc'] = mvc.configure(config)
# zwracamy konfigurację
return config
33.2. Refaktoryzacja głównego skryptu [main]

Główny skrypt [main.py] ogranicza się do uruchomienia serwera:
# oczekuje się na parametr mysql lub pgres
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
sgbd = sys.argv[1].lower()
erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
# konfigurujemy aplikację
import config
config = config.configure({'sgbd': sgbd})
# zależności
from SendAdminMail import SendAdminMail
from Logger import Logger
from ImpôtsError import ImpôtsError
import redis
# wysyłanie wiadomości e-mail do administratora
def send_adminmail(config: dict, message: str):
# wysyłanie wiadomości e-mail do administratora aplikacji
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# sprawdzanie pliku logów
logger = None
erreur = False
message_erreur = None
try:
# moduł rejestrujący
logger = Logger(config['parameters']['logsFilename'])
except BaseException as exception:
# log konsoli
print(f"L'erreur suivante s'est produite : {exception}")
# odnotowuje się błąd
erreur = True
message_erreur = f"{exception}"
# zapisujemy logger w konfiguracji
config['logger'] = logger
# obsługa błędu
if erreur:
# wiadomość e-mail do administratora
send_adminmail(config, message_erreur)
# zakończenie działania aplikacji
sys.exit(1)
# log uruchomienia
log = "[serveur] démarrage du serveur"
logger.write(f"{log}\n")
# sprawdzanie dostępności serwera Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
port=config["parameters"]["redis"]["port"])
# wykonywanie polecenia ping do serwera Redis
try:
redis_client.ping()
except BaseException as exception:
# Redis niedostępny
log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
# konsola
print(log)
# log
logger.write(f"{log}\n")
# koniec
sys.exit(1)
# zapisujemy klienta [redis] w konfiguracji
config['redis_client'] = redis_client
# pobieranie danych z urzędu skarbowego
erreur = False
try:
# admindata będzie danymi o zasięgu aplikacji, dostępnymi tylko do odczytu
config["admindata"] = config["layers"]["dao"].get_admindata()
# log pomyślnego wykonania
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# odnotowano błąd
erreur = True
# log błędu
log = f"L'erreur suivante s'est produite : {ex}"
# konsola
print(log)
# plik logów
logger.write(f"{log}\n")
# wiadomość e-mail do administratora
send_adminmail(config, log)
# główny wątek nie potrzebuje już modułu logującego
logger.close()
# w razie błędu następuje zatrzymanie
if erreur:
sys.exit(2)
# import tras aplikacji internetowej
import routes
routes.config=config
routes.execute(__name__)
- wiersze 56–73: dodano serwer Redis i jego klienta;
- wiersze 102–104: trasy aplikacji zostały przeniesione do skryptu [routes];
33.2.1. Moduły [flask_session] i [redis]
Serwer [Redis] będzie służył do przechowywania sesji użytkowników. Do zarządzania tymi sesjami wykorzystamy moduł [flask_session]. Moduł ten może przechowywać sesje użytkowników w kilku lokalizacjach. Jedną z nich jest Redis i to właśnie z niego będziemy korzystać.
Moduł [flask_session] należy zainstalować w terminalu PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask-session
Collecting flask-session
…
Aby komunikować się z serwerem Redis, potrzebujemy klienta Redis. Zapewni nam go moduł [redis], który również instalujemy:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install redis
Collecting redis
…
33.2.2. Serwer Redis
Serwer Redis będzie służył do przechowywania sesji użytkowników. Moduł [flask_session] działa w następujący sposób:
- każdy użytkownik posiada identyfikator sesji i to właśnie on jest wysyłany do klienta – i tylko on. Klient otrzymuje plik cookie sesji tylko raz, po wysłaniu pierwszego żądania. Plik ten zawiera identyfikator sesji użytkownika, który nie ulegnie zmianie w trakcie kolejnych żądań wysyłanych przez klienta. Dlatego serwer nie musi ponownie wysyłać pliku cookie sesji;
- wcześniej zawartość sesji była wysyłana do klienta. Teraz już tak nie będzie. Zawartość sesji użytkownika będzie przechowywana na serwerze Redis;
Laragon jest dostarczany z serwerem Redis, który domyślnie nie jest włączony. Należy więc najpierw go włączyć:

- w [3] włączyć serwer [Redis];
- w [4] pozostawić port [6379], z którego domyślnie korzystają klienci Redis;
Usługi Laragon są automatycznie uruchamiane ponownie po aktywacji Redis:

Z serwerem Redis można komunikować się w trybie poleceń. Otwieramy terminal Laragon [6]:

- w [1] polecenie [redis-cli] uruchamia klienta w trybie poleceń serwera Redis;
W lipcu 2019 r. klient Redis może korzystać ze 172 poleceń do komunikacji z serwerem [https://redis.io/commands#list]. Jedno z nich, [command count] [2], wyświetla tę liczbę: [3].
Zapis w [Redis] odbywa się za pomocą polecenia Redis [set attribut valeur] [4]. Wartość tę można następnie odczytać za pomocą polecenia [get attribut] [5].
Konieczne może być wyczyszczenie pamięci Redis. Odbywa się to za pomocą polecenia [flushdb] [6]. Następnie, jeśli zapytamy o wartość atrybutu za pomocą polecenia [titre] [7], otrzymamy odwołanie [nil] [8] wskazujące, że atrybut nie został znaleziony. Można również użyć polecenia [exists] [9-10], aby sprawdzić, czy dany atrybut istnieje.
Aby zamknąć klienta Redis, należy wpisać polecenie [quit] [11].
Można również skorzystać z interfejsu internetowego do zarządzania kluczami na serwerze Redis. W tym celu należy uruchomić serwer Apache w Laragonie:

Pojawi się następujący interfejs:

- w [1-4], jedna z sesji przechowywanych na serwerze Redis;
33.2.3. Zarządzanie serwerem Redis w skrypcie głównym [main]
Skrypt [main] sprawdza obecność serwera Redis w następujący sposób:
import redis
…
# sprawdzamy dostępność serwera Redis
redis_client = redis.Redis(host=config["parameters"]["redis"]["host"],
port=config["parameters"]["redis"]["port"])
# wykonywany jest ping do serwera Redis
try:
redis_client.ping()
except BaseException as exception:
# Redis niedostępny
log = f"[serveur] Le serveur Redis n'est pas disponible : {exception}"
# konsola
print(log)
# log
logger.write(f"{log}\n")
# koniec
sys.exit(1)
# zapisujemy klienta [redis] w konfiguracji
config['redis_client'] = redis_client
- wiersz 4: konstruktor klasy [redis.Redis] tworzy klienta serwera Redis. Jego parametry (adres, port) znajdują się w skrypcie [parameters];
- wiersz 8: metoda [ping] pozwala sprawdzić obecność serwera Redis;
- wiersze 9–17: jeśli polecenie ping nie zakończy się powodzeniem, rejestruje się błąd i zatrzymuje serwer;
- wiersz 20: identyfikator klienta Redis jest umieszczany w konfiguracji;
33.2.4. Zarządzanie trasami w głównym skrypcie [main]
Zarządzanie trasami w skrypcie [main] ogranicza się do następujących wierszy:
# import tras z aplikacji internetowej
import routes
routes.config=config
routes.execute(__name__)
- wiersz 1: trasy zostały przeniesione do modułu [routes];
- wiersz 3: trasy muszą znać konfigurację uruchomienia;
- wiersz 4: uruchamia się aplikację Flask, przekazując jej nazwę wykonywanego skryptu (__main__);
Skrypt tras wygląda następująco:
# zależności
import os
from flask import Flask, redirect, request, session, url_for
from flask_api import status
from flask_session import Session
# aplikacja Flask
app = Flask(__name__, template_folder="../flask/templates", static_folder="../flask/static")
# konfiguracja aplikacji
config = {}
# kontroler frontowy
def front_controller() -> tuple:
# przekierowujemy żądanie do głównego kontrolera
main_controller = config['mvc']['controllers']['main-controller']
return main_controller.execute(request, session, config)
@app.route('/', methods=['GET'])
def index() -> tuple:
# przekierowanie do /init-session/html
return redirect(url_for("init_session", type_response="html"), status.HTTP_302_FOUND)
# init-session
@app.route('/init-session/<string:type_response>', methods=['GET'])
def init_session(type_response: str) -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# uwierzytelnianie użytkownika
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur() -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# oblicz-podatek
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot() -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# obliczanie podatku partiami
@app.route('/calculer-impots', methods=['POST'])
def calculer_impots():
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetlanie listy symulacji
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations() -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# usuń-symulację
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int) -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# koniec sesji
@app.route('/fin-session', methods=['GET'])
def fin_session() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
# wyświetl-obliczenia-podatku
@app.route('/afficher-calcul-impot', methods=['GET'])
def afficher_calcul_impot() -> tuple:
# uruchamiany jest kontroler powiązany z akcją
return front_controller()
# pobierz-dane-administracyjne
@app.route('/get-admindata', methods=['GET'])
def get_admindata() -> tuple:
# uruchamia się kontroler powiązany z akcją
return front_controller()
def execute(name: str):
# tajny klucz sesji
app.secret_key = os.urandom(12).hex()
# Flask-Session
app.config.update(SESSION_TYPE='redis',
SESSION_REDIS=config['redis_client'])
Session(app)
# przypadek, w którym aplikacja Flask jest uruchamiana za pomocą skryptu konsolowego
if name == '__main__':
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- wiersz 9: tworzona jest instancja aplikacji Flask;
- wiersz 12: konfiguracja aplikacji nie jest jeszcze znana w momencie pisania skryptu. Staje się znana dopiero w momencie jego wykonania;
- wiersze 20–77: trasy aplikacji zgodnie z definicjami z poprzedniej wersji. Nie ulegają one zmianie;
- wiersze 14–18: wszystkie procedury ograniczają się do wywołania funkcji [front_controller]. Usunęliśmy z niej pierwotny kod. Obecnie funkcja ta ogranicza się do wywołania głównego kontrolera aplikacji internetowej;
- wiersze 79–89: [execute] to funkcja wywoływana przez skrypt [main] w celu uruchomienia aplikacji internetowej;
- wiersz 81: moduł [flask_session] wykorzystuje tajny klucz Flaska;
- wiersze 82–84: konfiguracja modułu [flask_session]. Polega ona na dodaniu kluczy [SESSION_TYPE, SESSION_REDIS] do konfiguracji [app.config] aplikacji Flask [app]:
- [SESSION_TYPE]: typ sesji. Istnieje kilka typów. Typ [redis] oznacza, że [flask_session] korzysta z serwera [redis] do przechowywania sesji użytkowników. Z tego powodu należy zdefiniować klucz [SESSION_REDIS], który powinien odnosić się do klienta Redis;
- wiersz 85: sesja [Flask-Session] jest powiązana z aplikacją Flask;
- wiersze 86–89: jeśli parametr [name] z wiersza 79 ma wartość ciągu znaków [__main__], wówczas uruchamiana jest aplikacja Flask;
33.3. Refaktoryzacja głównego kontrolera
Kod, który wcześniej znajdował się w funkcji [front_controller] skryptu [main], został przeniesiony do głównego kontrolera:

# importowanie zależności
import threading
import time
from random import randint
from flask_api import status
from werkzeug.local import LocalProxy
from InterfaceController import InterfaceController
from Logger import Logger
from SendAdminMail import SendAdminMail
def send_adminmail(config: dict, message: str):
# wysyłamy wiadomość e-mail do administratora aplikacji
config_mail = config['parameters']['adminMail']
config_mail["logger"] = config['logger']
SendAdminMail.send(config_mail, message)
# główny kontroler aplikacji
class MainController(InterfaceController):
def execute(self, request: LocalProxy, session: LocalProxy, config: dict) -> (dict, int):
# przetwarzanie żądania
logger = None
action = None
type_response1 = None
try:
# pobierane są elementy ścieżki
params = request.path.split('/')
# akcja jest pierwszym elementem
action = params[1]
# na początku nie ma błędów
erreur = False
# typ sesji musi być znany przed wykonaniem niektórych akcji
type_response1 = session.get('typeResponse')
if type_response1 is None and action != "init-session":
# odnotowujemy błąd
résultat = {"action": action, "état": 101,
"réponse": ["pas de session en cours. Commencer par action [init-session]"]}
erreur = True
# rejestrowanie
logger = Logger(config['parameters']['logsFilename'])
# zapisujemy go w konfiguracji powiązanej z wątkiem
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# rejestruje się żądanie
logger.write(f"[MainController] requête : {request}\n")
# przerywamy wątek, jeśli zostało to zlecone
sleep_time = config['parameters']['sleep_time']
if sleep_time != 0:
# przerwa jest losowa, aby niektóre wątki zostały przerwane, a inne nie
aléa = randint(0, 1)
if aléa == 1:
# logowanie przed przerwą
logger.write(f"[front_controller] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# przerwa
time.sleep(sleep_time)
# w przypadku niektórych działań wymagane jest uwierzytelnienie
user = session.get('user')
if not erreur and user is None and action not in ["init-session", "authentifier-utilisateur"]:
# odnotowujemy błąd
résultat = {"action": action, "état": 101,
"réponse": [f"action [{action}] demandée par utilisateur non authentifié"]}
erreur = True
# czy występują błędy?
if erreur:
# żądanie jest nieprawidłowe
status_code = status.HTTP_400_BAD_REQUEST
else:
# uruchamiany jest kontroler powiązany z akcją
controller = config['mvc']['controllers'][action]
résultat, status_code = controller.execute(request, session, config)
except BaseException as exception:
# inne (nieoczekiwane) wyjątki
résultat = {"action": action, "état": 131, "réponse": [f"{exception}"]}
status_code = status.HTTP_400_BAD_REQUEST
finally:
pass
# rejestruje się wynik wysłany do klienta
log = f"[MainController] {résultat}\n"
logger.write(log)
# czy wystąpił błąd krytyczny?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# wysyłamy wiadomość e-mail do administratora aplikacji
send_adminmail(config, log)
# określa się żądany typ odpowiedzi
type_response2 = session.get('typeResponse')
if type_response2 is None and type_response1 is None:
# typ sesji nie został jeszcze ustalony – będzie to jSON
type_response = 'json'
elif type_response2 is not None:
# typ odpowiedzi jest znany i znajduje się w sesji
type_response = type_response2
else:
# w przeciwnym razie nadal używa się type_response1
type_response = type_response1
# tworzy się odpowiedź do wysłania
response_builder = config['mvc']['responses'][type_response]
response, status_code = response_builder \
.build_http_response(request, session, config, status_code, résultat)
# zamyka się plik logów, jeśli był otwarty
if logger:
logger.close()
# wysyłamy odpowiedź HTTP
return response, status_code
Cały ten kod pojawiał się już wcześniej w różnych momentach.
33.4. Obsługa zaszyfrowanych haseł
Do zarządzania zaszyfrowanymi hasłami wykorzystamy moduł [passlib], który instalujemy z poziomu terminala PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install passlib
Collecting passlib
…
Oto przykład skryptu szyfrującego hasło przekazane mu jako parametr:

Skrypt [create_hashed_password] wygląda następująco (https://passlib.readthedocs.io/en/stable/):
import sys
# funkcja szyfrowania
from passlib.hash import pbkdf2_sha256
# oczekuje się na hasło do zaszyfrowania
syntaxe = f"{sys.argv[0]} password"
erreur = len(sys.argv) != 2
if erreur:
print(f"syntaxe : {syntaxe}")
sys.exit()
else:
password = sys.argv[1]
# szyfruje się hasło
hash = pbkdf2_sha256.hash(password)
print(f"version cryptée de [{password}] = {hash}")
# weryfikacja
correct = pbkdf2_sha256.verify(password, hash)
print(correct)
- wiersz 16: szyfruje się hasło przekazane jako parametr;
- wiersz 20: porównuje się hasło [password] przekazane jako parametr z jego zaszyfrowaną wersją [hash]. Funkcja [verify] szyfruje hasło [password] i porównuje uzyskany zaszyfrowany ciąg znaków z [hash]. Zwraca wartość True, jeśli oba ciągi znaków są równe;
Powyższy skrypt pozwala nam uzyskać zaszyfrowaną wersję hasła [admin]:
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/impots/http-servers/08/passlib/create_hashed_password.py admin
version cryptée de [admin] = $pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek
True
Wiersz 2, wartość, którą wprowadzamy do skryptu [parameters]:
"users": [
{
"login": "admin",
"password": "$pbkdf2-sha256$29000$fU9pTendO6c0ZoyR8r5Xqg$5ZXywIUnbMfN2hPnBaefiuqWjEbmAY.Lu06i4dwcnek"
}
],
Kontroler uwierzytelniający [AuthentifierUtilisateurController] zmienia się w następujący sposób:
from passlib.handlers.pbkdf2 import pbkdf2_sha256
…
# sprawdzana jest poprawność pary (użytkownik, hasło)
users = config['parameters']['users']
i = 0
nbusers = len(users)
trouvé = False
while not trouvé and i < nbusers:
trouvé = user == users[i]["login"] and pbkdf2_sha256.verify(password, users[i]["password"])
i += 1
# znaleziono?
if not trouvé:
…
33.5. Testy
Oprócz testów w przeglądarce można również skorzystać z klientów z folderu [http-servers/07] napisanych dla wersji 12. Powinny one działać również w wersji 13:

- w [1] wszystkie trzy klienckie programy powinny działać;
- w [2] oba testy powinny działać;