7 - Gestion sur le serveur
Prérequis :
- Javascript - Création des formulaires
- Javascript - Gérer les formulaires sur le client
- Architecture - Communication Client Serveur
- Python - Stocker dans un dictionnaire
- Python - Enregister et lire des fichiers
Pourquoi autant de prérequis ? Simplement car nous allons créer un site Web dynamique avec notre serveur pédagogique. Une page contenant un formulaire va permettre à un client d'insérer des informations sur des morceaux de musique. Une autre page va permettre de visualiser tous les enregistrements que les utilisateurs précédents ont placé sur le site. Et l'air de rien, cela demande un grand nombre de connaissances accumulées.
Logiciels : Il faudra utiliser un éditeur de code. Atom, Notepad++ sur Windows, Notepadqq sur Linux ou SublimText sont des choix valides.
Evaluation ✎ : questions 12-13
1 - Téléchargement du programme serveur
01° Créer un dossier nommé projetmusique sur votre poste. Créer un fichier Python nommé recup.py contenant ce code
📁 projetmusique
📄 recup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import os
import urllib.request
os.makedirs('www', exist_ok=True)
os.makedirs('donnees', exist_ok=True)
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/javascript/sitemusique/site_musique.py', 'site_musique.py')
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/archi/telechargement/reseau/code_serveur.py', 'code_serveur.py')
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/javascript/sitemusique/www/index.html', 'www/index.html')
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/javascript/sitemusique/www/monstyle.css', 'www/monstyle.css')
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/javascript/sitemusique/www/python.png', 'www/python.png')
urllib.request.urlretrieve('https://doc.infoforall.fr/activite/javascript/sitemusique/donnees/morceaux.csv', 'donnees/morceaux.csv')
|
La fonction makedirs du module os permet de créer des répertoires. Transmettre le paramètre nommé exist_ok à True évite de lever une exception si le répertoire existe déjà.
La fonction urlretrieve du module request contenu dans urllib permet de créer un téléchargement en fournissant l'URL voulue et le nom sous lequel on veut enregistrer le téléchargement.
Après avoir lancé le code, vous devriez obtenir le contenu suivant :
📁 projetmusique
📄 recup.py
📁 donnees
📁 www
Si ce n'est pas le cas, il est temps de créer cette structure à la main et de télécharger les fichiers (clic-droit, télécharger sous).
Regardons maintenant tout ce contenu. Vous aurez à le modifier, à l'améliorer pour en faire un projet personnel, individuellement ou en groupe.
Commençons par la présentation générale.
Description rapide du projet
Le fichier code_serveur.py n'a pas vocation être ouvert. Il contient juste les codes permettant au serveur Web de fonctionner à l'interne.
Le fichier site_musique.py est celui qui contient les instructions permettant à votre site de réagir comme vous l'entendez. Vous aurez à le modifier en partie.
Le répertoire donnees est destiné à contenir les données issues des utilisateurs. On y trouvera notamment un fichier csv (comma separated values) nommé morceaux.csv. Les clients ne peuvent pas accéder directement aux éléments de ce dossier. Vous pouvez voir ce répertoire comme votre base de données.
Le répertoire www est destiné à contenir les fichiers statiques que vous voulez distribuer aux clients : images, fichiers css, fichiers js et même fichiers html pour les pages purement statiques (celles qui sont identiques quelque soit le moment et les clients). Ce répertoire est la racine des fichiers statiques. Cela veut dire que l'adresse absolue du fichier python.png sur notre site est /python.png
et pas /www/python.png
(voir question XXX).
02° Lancer le script site_musique.py.
Vous devriez voir ce message s'afficher dans la console :
::: START ::: Démarrage d'un serveur d'adresse 127.0.0.1:9000. CTRL-C pour stopper
03° Ouvrir votre navigateur Web. Taper ceci dans la barre d'adresses : http://localhost:9000/
.
Quel est le protocole utilisé ? La communication est-elle cryptée entre le client et le serveur ?
Quel est le numéro de port utilisé pour la communication TCP ?
...CORRECTION...
Protocole HTTP, non crypté.
Le port se trouve derrière le : dans le protocole HTTP. Ici, il s'agit du port 9000.
Vous devriez donc obtenir ceci :
2 - Gestion des pages et fichiers statiques
Ouvrons le fichier site_musique.py pour regarder comment il fonctionne.
Commençons par la page d'accueil.
15
16
17
18
19
20
21
22
23
24 | # 1 - Importation
from http.server import HTTPServer
from code_serveur import Gestionnaire
from datetime import datetime
# 2 - Déclarations des constantes
IP = '127.0.0.1'
PORT = 9000
|
04° Où retrouve-t-on le choix de l'adresse Ip et du port observé précédemment pour le serveur Web ?
Quelle est l'adresse IP de localhost
sur le code ?
...CORRECTION...
Ligne 24 : le port est défini comme une constante (on notera l'utilisation des majuscules). Cela veut dire que vous n'êtes pas censé modifier cette valeur ailleurs dans le code. 9000
ici.
Pour l'alias localhost
, on retrouve l'adresse IPv4 127.0.0.1
.
Passons au lancement du serveur en passant la déclaration des fonctions mais en allant voir la fin du code.
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 | def activer_serveur():
"""Création et mise en route du serveur qui surveille ensuite les requêtes qu'on lui communique"""
# 1 - Création de l'objet-serveur httpd
Gestionnaire.GET = reponse_a_GET # on définit la fonction à exécuter sur un GET
Gestionnaire.POST = reponse_a_POST # on définit la fonction à exécuter sur un POST
adresse = (IP, PORT)
httpd = HTTPServer(adresse, Gestionnaire) # HTTP Daemon, le gestionnaire de requêtes du serveur
# 2 - Tentative de mise en route et surveillance des requêtes
print(f"::: START ::: Démarrage d'un serveur d'adresse {IP}:{PORT}. CTRL-C pour stopper")
httpd.serve_forever() # Démarrage et surveillance en boucle des messages arrivant au serveur
print('::: STOP ::: Arrêt du serveur')
httpd.server_close() # Arrêt du serveur
if __name__ == '__main__':
activer_serveur()
|
Nous avions déjà vu ce code-serveur. En substance :
- Ligne 167 : on lance la fonction activer_serveur.
- Lignes 155 et 156 : on donne les références des fonctions à utiliser lorsque le serveur reçoit des requêtes GET ou POST.
- Ligne 157 : on attribue une adresse IP et un PORT que notre serveur acceptera.
- Ligne 158 : on informe le daemon http (httpd) du choix des fonctions de gestion et des adresses choisies
- Ligne 162 : on démarre le serveur qui va alors surveiller en boucle les requêtes qui lui sont envoyées.
Tant qu'on ne stoppe pas le serveur d'une manière ou d'une autre, on n'atteint donc pas la ligne 164.
Regardons maintenant la gestion de la page d'accueil.
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | # 3 - Déclaration des fonctions
def reponse_a_GET(requete):
"""Instructions à effectuer si la requête est de type GET. requete contient les infos sur la requête"""
print("::: SERVEUR ::: Reception d'une requête GET")
# 1 - Récupération optionnelle des sous-informations issues de la requête
chaine_requete = requete.requestline
adresse_client = requete.obtenir_IP_client
port_client = requete.obtenir_PORT_client
chemin = requete.obtenir_chemin
parametres = requete.obtenir_parametres
# 2 - Traitement de la requête
if chemin == '/' :
fichierHTML = open('www/index.html', 'r', encoding="utf-8")
codeHTML = fichierHTML.read()
fichierHTML.close()
requete.creer_entete(200)
requete.repondre(codeHTML)
|
05° Observer le code. Où est défini le contenu de la variable chemin ?
Que veut dire concrétement la ligne 42 ?
Pourquoi 42 ?
...CORRECTION...
Ligne 37, au milieu des autres informations reçues liées à la requête.
On notera qu'on utilise à chaque fois l'objet requete suivi d'un point et du nom de l'attribut qu'on cherche à obtenir.
La ligne 42 est un test sur le chemin demandée par le client. Le SLASH veut ici dire racine. Cela veut dire qu'on ne donne que l'adresse du serveur mais aucune indication particulière. Dans 99,99% des sites, le programmeur a décidé d'y placer la page d'accueil.
42 est la réponse à la Vie, l'Univers et le Reste. Douglas Adams, tout ça.
06° Observer le code.
- Dans quel répertoire est situé le fichier HTML statique de la page d'accueil ?
- Comment se nomme le fichier HTML voulu ?
- L'adresse fournie pour la création de l'objet-fichier fichierHTML est-elle une adresse absolue ou relative ?
- Dans quel mode crée-t-on l'objet-fichier ?
- Comment semble fonctionner ce code ensuite : décrire son fonctionnement.
42
43
44
45
46
47 | if chemin == '/' :
fichierHTML = open('www/index.html', 'r', encoding="utf-8")
codeHTML = fichierHTML.read()
fichierHTML.close()
requete.creer_entete(200)
requete.repondre(codeHTML)
|
...CORRECTION...
fichierHTML = open('www/index.html', 'r', encoding="utf-8")
On voit que le répertoire est le répertoire www
et que le fichier se nomme index.html
.
L'adresse ne commence pas par un slash : il s'agit d'une adresse relative à la position du script en cours. Il faudra donc chercher un repertoire www
présent dans le répertoire projetmusique
.
Le fonctionnement est simple :
- Ligne 43 : on crée l'objet-fichier en lecture.
- Ligne 44 : on utilise la méthode read sur notre objet-fichier pour stocker l'intégralité du fichier texte dans la variable codeHTML. Si nous avions utilisé readline nous n'aurions lu qu'une seule ligne du fichier.
- Ligne 45 : on ferme l'objet-fichier.
- Ligne 46 : on crée et envoie l'entête de réponse HTTP en fournissant un code 200.
- Ligne 47 : on fournit le code HTML en tant que corps de la réponse. Le client va donc recevoir le code HTML que nous avions lu ligne 44.
Comme vous le voyez, nous n'avons rien modifié dynamiquement dans le code HTML transmis. C'est pour cela qu'on parle de page STATIQUE.
07° Visualiser le code HTML du fichier-texte qui se situe dans le dossier www/
.
- Les adresses surlignées sont-elles absolues ou relatives ?
- Comment se nomme en français la balise présente entre les lignes 20 et 31 ?
- A quoi sert-elle ?
- L'envoi se fait-il en GET ou en POST ?
- Vers quelle adresse va être transmise la requête ?
- Comment vont se nommer les valeurs transmises au serveur lorsque l'utilisateur va cliquer sur le bouton SUBMIT ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | <!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href='monstyle.css' />
<meta charset="utf-8" />
</head>
<body>
<header>
<h1>Le super site de musique</h1>
</header>
<nav>
<ul>
<li><a href="/">Accueil</a></li>
<li><a href="/liste">Liste des morceaux</a></li>
<li><a href="/info">Informations</a></li>
</ul>
</nav>
<main>
<p>Sur cette page, vous pouvez nous envoyer des informations sur un morceau de musique que vous voulez voir enregistrer sur ce site.</p>
<form name="nouveau" action='/enregistrer' method='post'>
<p>Pseudo : <input type="text" name="user" placeholder="Insérez votre pseudo"></p>
<p>Mot de passe : <input type ="password" name="pw"></p>
<p>Nom du morceau : <input type ="text" name="nom"></p>
<p>URL du morceau : <input type ="url" name="adresse"></p>
<p>Catégorie musical : <select name="categorie">
<option value="pop">Pop</option>
<option value="rap">Rap</option>
<option value="rock">Rock</option>
</select></p>
<input type="submit" value="Envoyer ces informations au serveur">
</form>
</main>
<footer>
<p>Powered by <img src="python.png"></p>
</footer>
</body>
</html>
|
...CORRECTION...
- Les adresses sont relatives... mais à quoi. En réalité, le code de mon serveur impose que tous les fichiers possèdant une extension soient cherchés directement dans le dossier
www
. Il s'agit donc de la racine des fichiers statiques. Que vous mettiezpython.png
ou/python.png
cela mène donc au même endroit. - La balise
form
est une balise formulaire. - Elle permet de créer une interface homme-machine prédéfinie, bien codée et permettant un préfiltrage.
- On voit ligne 20 qu'elle est configurée pour créer une requête POST.
- L'attribut action indique qu'on va envoyer une requête avec le chemin
/enregistrer
. - On va transmettre au serveur :
- Le nom d'utilisateur sous forme d'un paramètre nommé
user
. - Le mot de passe sous la forme d'un paramètre nommé
pw
. - Le nom du morceau sous la forme d'un paramètre nommé
nom
. - L'URL permettant d'entendre ou d'avoir des infos sous la forme d'un paramètre nommé
adresse
. - La catégorie du morceau sous la forme d'un paramètre nommé
categorie
.
08° Utiliser la page.
Vous allez surement avoir du mal à valider l'URL : il faut fournir une URL avec le protocole. Comme https://www.infoforall.fr
.
La vérification de l'URL en cas de refus se fait-elle en local (sur la machine-client) ou sur la machine-serveur ?
La vérification du mot de passe se fait-elle en local (sur la machine-client) ou sur la machine-serveur ?
...CORRECTION...
La vérification est ici locale : elle se passe sur la machine du client. Ce n'est pas le serveur qui exécute cette vérifcation mais l'interpréteur de votre navigateur.
Pour la vérification du mot de passe, elle pourait se faire en local en javascript mais cela voudrait dire qu'on stocke les mots de passe de tout le monde sur les machines de chaque client qui se connecte. Niveau sécurité, on dépasse le 0 absolu. Si vous observer le code de la page, vous pourrez d'ailleurs constater qu'il n'y a nul trace de javascript. La vérification va donc être faite après envoi. Ca se voit puisqu'on change de page.
3 - Gestion dynamique
09° Aller voir le code de gestion des requêtes POST du fichier site_musique.py.
- Quel est le mot de passe attendu ? Il suffit de trouver la bonne ligne !
- En regardant les lignes 108 et 109, quel est le nom de la structure de données dans laquelle les paramètres de la requête du client ont été placés ?
- Que fait-on sur les lignes 108 à 112 ?
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 | def reponse_a_POST(requete):
"""Instructions à effectuer si la requête est de type POST. requete contient les infos sur la requête"""
print("::: SERVEUR ::: Reception d'une requête POST")
# 1 - Récupération optionnelle des informations issues de la requête
chaine_requete = requete.requestline
adresse_client = requete.obtenir_IP_client
port_client = requete.obtenir_PORT_client
chemin = requete.obtenir_chemin
parametres = requete.obtenir_parametres
# 2 - Traitement de la requête
codeHTML = ""
if chemin == '/enregistrer' :
if 'user' in parametres.keys() and 'pw' in parametres.keys() and parametres['pw'] == "1234franchement?" :
enregistrement = f"{parametres['nom']};{parametres['categorie']};{parametres['adresse']}/n"
fichierCSV = open('donnees/morceaux.csv', 'a', encoding="utf-8")
fichierCSV.write(enregistrement)
fichierCSV.close()
codeHTML = f"""
<DOCTYPE html>
<header>
<link rel="stylesheet" href='monstyle.css' />
<meta charset="utf-8" />
</header>
<html>
<body>
<h1>Bien reçu</h1>
<p>Retour à l'<a href="/">accueil</a></p>
</body>
</html>
"""
requete.creer_entete(200)
requete.repondre(codeHTML)
if codeHTML == '' :
codeHTML = echec()
requete.creer_entete(200)
requete.repondre(codeHTML)
def echec():
codeHTML = f"""
<DOCTYPE html>
<html>
<body>
<h1>Votre requete n'a pas pu aboutir.</h1>
<p>Retour à l'<a href="/">accueil</a></p>
</body>
</html>
"""
return codeHTML
|
...CORRECTION...
On voit que le mot de passe est 1234franchement?.
Nous voyons que le code du serveur fourni les paramètres transmis sous forme d'un dictionnaire.
Ligne 108 : on teste l'existence des paramètres permettant d'identifier l'utilisateur et son mot de passe.
Ligne 109 : on crée un nouvel enregistrement CSV en séparant les attributs par des points-virgules.
Ligne 110 : On crée un objet-fichier en mode append, pour rajouter du contenu plutôt que d'écraser l'ancien contenu.
Ligne 111 : On rajoute l'enregistrement au fichier.
Ligne 112 : On ferme l'objet-fichier.
10° Créer quelques entrées (correctes)à l'aide de l'interface.
11° Créer une nouvelle entrée sans fournir de nom au morceau.
✎ 12° Revenir à la page d'accueil. Utiliser le lien Liste des morceaux.
La page créé est-elle statique ou dynamique ?
Expliquer ce qui se passe en utilisant le code : mettez-vous dans la peau de quelqu'un tentant d'expliquer le code à quelqu'un d'autre.
D'où vient le FEEDLINE lors de la lecture du fichier CSV ? A quoi sert du c oup la ligne url = infos[2].replace('/n','')
?
Il est probable qu'il faille vous renseigner un peu sur la construction d'un tableau en HTML. Lancer donc une recherche sur un navigateur.
Votre explication devra en effet expliquer ce que sont les balises th, tr et td.
4 - Projet
Nous avons fait le tour d'une grande partie du code et des fonctionnalités du serveur. Il vous reste maintenant à créer de nouvelles fonctionnalités et de possibilités nouvelles à votre site.
✎ 13° Fournir le code et les fichiers de votre projet.
5 - FAQ
Rien pour l'instant
Des questions ?
Activité publiée le 29 06 2020
Dernière modification : 29 06 2020
Auteur : ows. h.