Web Dynamique Serveur

Identification

Infoforall

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

📄 code_serveur.py

📄 recup.py

📄 site_musique.py

📁 donnees

📄 morceaux.csv

📁 www

📄 index.html

📄 monstyle.css

📄 python.png

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 :

Page d'accueil

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.

  1. Dans quel répertoire est situé le fichier HTML statique de la page d'accueil ?
  2. Comment se nomme le fichier HTML voulu ?
  3. L'adresse fournie pour la création de l'objet-fichier fichierHTML est-elle une adresse absolue ou relative ?
  4. Dans quel mode crée-t-on l'objet-fichier ?
  5. 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/.

  1. Les adresses surlignées sont-elles absolues ou relatives ?
  2. Comment se nomme en français la balise présente entre les lignes 20 et 31 ?
  3. A quoi sert-elle ?
  4. L'envoi se fait-il en GET ou en POST ?
  5. Vers quelle adresse va être transmise la requête ?
  6. 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 mettiez python.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.

  1. Quel est le mot de passe attendu ? Il suffit de trouver la bonne ligne !
  2. 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 ?
  3. 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 ?

Nous en avons fini avec le Web interactif pour le moment. Comme vous le voyez, un site Web dynamique n'est pas réellement difficile à créer. Il faut par contre maîtriser les formulaires et un langage côté serveur de façon à traiter les requêtes et enregistrer les résultats.

Pour l'instant, nous stockons les données dans de simples fichiers CSV mais nous pourrons bientôt faire mieux à l'aide des bases de données.

Activité publiée le 29 06 2020
Dernière modification : 29 06 2020
Auteur : ows. h.