5 - Communication Client-Serveur
Vous savez maintenant comment fonctionner la communication entre deux programmes à travers Internet : on utilise le protocole TCP.
Vous allez voir aujourd'hui comment le serveur parvient à comprendre les informations que le client lui envoie.
Cela vous permettra de voir concrètement la différence entre les requêtes http GET et POST que peut envoyer un client http.
Et comment il parvient à réaliser des actions sur ces informations avant de répondre de façon adaptée au client.
Cela nous permettra de faire clairement la différence entre les actions réalisées sur l'ordinateur du client (on parle de programme côté client) ou sur l'ordinateur du serveur (on pare de programme côté serveur).
Evaluation ✎ : questions 02-04-05-06-14-16-17-24-25-28-29-30.
Documents de cours :
1 - Client Serveur
La structure commune du Web est l'ensemble client-serveur.

Un programme-serveur tourne sur une machine distance et il est identifié par un PORT sur cet ordinateur qu'on nomme souvent serveur aussi par simplification. Mais c'est juste un ordinateur. Le PORT typique est 80 pour http et 443 pour https.
Etape 1 : un programme-client (un navigateur, un robot-explorateur maison basé sur le module requests, ...), identifié par un PORT aléatoire va envoyer une requête http au serveur.
Etape 2 : le programme-serveur a reçu la requête http et va la traiter. C'est cette étape que nous allons voir aujourd'hui.
Etape 3 : le programme-serveur répond en envoyant sa réponse http au client.
01° Cliquer sur la requête bleue Req de l'animation ci-dessous pour la faire partir. Observer le processus de transfert d'informations et de validation de la demande par acquittement. Si quelque chose vous semble louche, il est temps de m'envoyer un message. C'est que vous avez manqué un bout de l'activité précédente.
Petit rappel sur le fonctionnement de TCP / IP quand tout va bien :
Quel est le type de données envoyées par votre navigateur ? Du texte.
Texte de la requête reçue lorsque vous êtes arrivés ici :
GET /act/archi/communication-client-serveur/ HTTP/1.1↲
Host: www.infoforall.fr↲
User-Agent: CCBot/2.0 (https://commoncrawl.org/faq/)↲
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8↲
Accept-Language: en-US,en;q=0.5↲
If-Modified-Since: Wed, 20 Jan 2021 22:49:15 GMT↲
Accept-Encoding: br,gzip↲
Connection: Keep-Alive↲
X-Real-Ip: 18.204.2.190↲
Via: 1.1 alproxy↲
X-Forwarded-Proto: https↲
BODY VIDE !
FIN DE LA REQUETE
Un exemple de réponse possible envoyée par le serveur :
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 36435
Vary: Cookie
Via: 1.1 alproxy
Date: Thu, 19 Mar 2020 12:54:47 GMT
<!DOCTYPE html... : la suite, c'est le fichier html !
FIN DE LA REQUETE
Alors, quelle est la différence entre GET et POST si dans tous les cas on envoie du texte ?
La différence tient dans l'endroit où on place les paramètres qu'on peut envoyer à la page.
✎ 02° Rester sur la page où vous êtes mais taper ceci dans la barre de navigation :
https://www.infoforall.fr/act/archi/communication-client-serveur/?user=toto&password=1234
Relancer la page en la mettant à jour ET revenir à cette question pour voir l'affichage ci-dessous. Regarder ce qui a changé dans la requête reçue par le serveur.
Question : dans une requête de type GET, les paramètres sont-ils transmis dans l'URL ou sont-ils intégrés au corps (BODY) de la requête ?
GET /act/archi/communication-client-serveur/ HTTP/1.1↲
Host: www.infoforall.fr↲
User-Agent: CCBot/2.0 (https://commoncrawl.org/faq/)↲
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8↲
Accept-Language: en-US,en;q=0.5↲
If-Modified-Since: Wed, 20 Jan 2021 22:49:15 GMT↲
Accept-Encoding: br,gzip↲
Connection: Keep-Alive↲
X-Real-Ip: 18.204.2.190↲
Via: 1.1 alproxy↲
X-Forwarded-Proto: https↲
BODY VIDE !
FIN DE LA REQUETE
Méthode GET : paramètres dans l'URL
La méthode GET permet de discuter très facilement avec le serveur puisqu'on peut placer les paramètres qu'on veut lui transmettre directement dans l'URL.
Petit rappel sur la lecture d'une URL
https://www.infoforall.fr:443/act/archi/communication-client-serveur/?user=toto&password=1234
- En première position, on trouve le protocole utilisé
https://www.infoforall.fr:443/act/archi/communication-client-serveur/?user=toto&password=1234
- Ensuite le nom ou l'adresse IP du service à joindre :
https://www.infoforall.fr:443/act/archi/communication-client-serveur/?user=toto&password=1234
- Sous-domaine à gauche : www
- Domaine à droite : infoforall.fr
- Option : le PORT du service (rajouté automatiquement en fonction du protocole sinon)
https://www.infoforall.fr:443/act/archi/communication-client-serveur/?user=toto&password=1234
- L'adresse absolue de la ressource sur le service (à cause de la présence d'un slash
/
initial de la ressource voulue : https://www.infoforall.fr:443/act/archi/communication-client-serveur/?user=toto&password=1234
?user=toto&password=1234
- On voit donc qu'on transfère un paramètre qu'on note user et qu'on lui affecte la valeur toto
- On voit donc qu'on transfère un paramètre qu'on note password et qu'on lui affecte la valeur 1234
Et c'est ensuite qu'apparaissent les paramètres qu'on veut transmettre au serveur. Voici la codification de leurs passages :
Un exemple d'utilisation de paramètres que nous allons utiliser :
Valeur du paramètre question
:
Valeur du paramètre numero
:
Ce ne sont pas les bons paramètres !
03° La boîte ci-dessus vous permet de voir la valeur des paramètres éventuels question
et numero
envoyés sur cette page. Avez-vous transféré de tels paramètres ou sont-ils inexistants pour l'instant ?
✎ 04° Rester sur la page où vous êtes mais transférer via l'URL de la barre d'adresses le paramètre question
à la valeur corrige
et le paramètre numero
à la valeur 2
. Actualiser la page ou appuyer sur Entrée dans la barre d'adresses. Revenez alors ici. Vous devriez voir un affichage BRAVO ! juste au dessus.
Magie !
Noter sur la copie numérique l'URL que vous avez dû taper pour obtenir BRAVO.
SI VOUS BLOQUEZ : voir le bout de cours juste au dessus, celui nommé Méthode GET : paramètres dans l'URL.
✎ 05° Un serveur reçoit une requête GET de ce type :
www.infoforall.fr/question_trop_difficile/?difficulte=outch¬e=4
A-t-on transmis des paramètres ? Lesquels ? Quelles sont leurs valeurs ?
Pour faire ce tour de magie, il a fallu fournir ou modifier le code HTML. Deux possibilités :
Script côté CLIENT
On a utilisé un code côté client : un code Javascript a été envoyé par le serveur et tourne sur l'ordinateur du client et utilise donc les ressources du client pour réaliser son action. Dans ce cas, avec un petit test if sous Javascript, on peut lire le contenu des paramètres et modifier les balises en cherchant les références à l'aide des attributs name ou avec un getElementById et en modifiant le innerHTML. Le code HTML est donc modifié sur la machine client APRES l'envoi par le serveur. Vous l'avez déjà fait de nombreuses fois.

- Langage usuel car présent de base dans les navigateurs : Javascript. Mais on peut le faire avec d'autres langages en incluant des bibliothèques Javascript chargées de traduire par exemple le code Python en Javascript.
- Avantage : on utilise les ressources du client et on peut réagir à ses actions (lorsqu'il s'approche dangereusement de la case permettant de fermer la fenêtre, lorsqu'il passe longuement la souris sur tel ou tel produit, lorsqu'il clique ou sélectionne un élément...)
- Désavantage : le client peut lire votre code très facilement puisqu'il a accès à tous les codes HTML et JS que vous lui avez transféré ! Du coup, ce n'est pas utilisable pour un mot de passe. Si on peut trouver la valeur avec un simple clic-droit, ben...
Programme côté SERVEUR
On a utilisé un code côté serveur : un programme tourne sur le serveur. Il analyse la requête et réalise un test de correspondance entre alias et mot de passe sur la machine-serveur. Le serveur envoie alors un code HTML en fonction du résultat du test. Ca, vous ne l'avez pas encore fait.

- Plein de langages disponibles : PHP est certainement le plus connu car le premier a avoir permis de faire cela facilement. Wordpress fonctionne en PHP. Mais on peut aussi gérer un serveur HTTP avec Python ou avec du Javascript. Ou même en C pour les plus masos.
- Avantage : le client ne peut aucunement avoir accès à ce code. Et ça, c'est fondamental dès que vous voulez gérer des mots de passe ou stocker des données sur votre machine-serveur.
- Désavantage : c'est le serveur qui travaille. Ce sont donc vos ressources. Beaucoup de clients veut dire achat ou location d'un gros serveur. Et ça coûte cher. ENT tout ça tout ça. Si le nombre de clients venait à augmenter de façon brutale, vous n'avez pas d'autres possibilités que de modifier la structure physique de votre serveur : rajout de matériels.
Si vous le voulez, vous pouvez chercher dans le code de cette page : nous ne trouverez pas le moindre code JS travaillant sur le texte encadré ci-dessus. Moralité : c'est le serveur qui a fait le travail.
Le but de cette activité est de vous montrer comment fonctionne un programme de gestion côté serveur.
En terme de connaissances générales, il reste encore la méthode POST.
Méthode POST : paramètres dans le body de la requête
La méthode GET est pratique mais si vous devez envoyer beaucoup de données, l'URL va être très très longue.
Autre désavantage : si vous passez un mot de passe en GET en https, le message est crypté OK. Personne ne peut lire votre mot de passe sur le réseau. C'est vrai. Mais le mot de passe sera noté en clair dans votre URL. Il suffit donc de regarder par dessus votre épaule pour le connaître !
Dans ces deux cas, on préférera la méthode de transfert vers le serveur en POST : cette fois, le client va transmettre les données fournies (paramètres, fichiers...) dans le BODY. C'est pour cela que le BODY de la méthode GET est vide. On n'y place rien.
✎ 06° Utiliser le formulaire ci-dessous en choisissant des valeurs pour les paramètres question et numero. En appuyant sur SUBMIT, vous allez envoyer les données vers une page acceptant la requête en POST. On y affiche le contenu de la requête.
Deux réponses à fournir sur la copie : En lisant la requête, où voit-on qu'il s'agit d'une requête POST ? Où se trouvent cette fois les informations liées aux paramètres envoyés : dans le corps (body) de la requête ou dans l'URL ?
L'Essentiel à retenir
Il faut donc retenir qu'on peut placer du code :
- Côté client : cela permet de réagir aux actions du client et de faire de la programmation événementielle en fonction des actions réelles de la personne qui utilise votre page. Le programme tourne sur l'ordinateur du client. Langage typique : Javascript.
- Côté serveur : cela permet de stocker et traiter les données envoyées par le client. Le programme tourne sur l'ordinateur du serveur. Langage typique : PHP ou Python.
Pour que le client puisse envoyer des données et des paramètres au serveur, il existe deux méthodes :
- La méthode GET : on transmet directement les paramètres via l'URL de la requête http.
- La méthode POST : on transmet les paramètres dont le corps de la requête : cette fois, il faut lire le contenu de la requête http et pas simplement son en-tête.
Une dernière remarque : dès qu'on a des données-utilisateur à envoyer en POST, on utilise ce qu'on nomme un formulaire.
C'est un objet qu'on crée avec des balises HTML que nous allons voir plus loin.
Lorsque l'utilisateur appuie sur SUBMIT, on active un code JAVASCRIPT (côté client) de façon à vérifier que les données rentrées soient bien correctes : inutiles d'envoyer des données non conformes vers le serveur. Comme ça, c'est l'ordinateur du client qui travaille à cette vérification.
Si les données sont bonnes, le code JS va alors activer la requête POST. Sinon, le code JS redemande à l'utilisateur de vérifier les champs du formulaire.
Le serveur reçoit alors la requête. Et que fait le programme côté serveur ? Il vérifie les données transmises quand même : l'utilisateur a très bien pu modifier le code JS et envoyer des données corrompues : JS tourne sur l'ordinateur client, il peut donc le modifier.
07° Nous avons déjà vu le principe du https : on envoie une communication http mais en cryptant les requêtes et les réponses. Envoyer un formulaire avec mot de passe en POST est-il un bon moyen de fournir un mot de passe ou pas ? Justifier votre réponse.
...CORRECTION...
Non, la communication n'est pas sécurisée :
- Ok, on cache le mot de passe dans le corps de la requête : cela évite que quelqu'un vienne juste lire votre écran ou les logs de connexions du site (avec des mots de passe en GET, il suffit en effet d'aller voir les fichiers texte récapitulant toutes les connexions qui ont eu lieu et c'est bon, vous auriez tout : login et mot de passe)
- Néanmoins, même en POST, le paquet IP va transiter à travers beaucoup d'appareils : la passerelle de votre réseau, les routeurs successifs et la passerelle du serveur http. Tous ces appareils peuvent techniquement lire votre communication puisqu'elle est dans le paquet IP ! Du coup, pour quelqu'un de motivé à trouver les mots de passe, ça ne pose techniquement aucun problème.
HTTPS
Le seul moyen de sécuriser le transfert des données sensibles est donc de passer par le https.
Le client envoie sa requête en la cryptant. Si quelqu'un récupère le contenu de la requête, il n'aura qu'une suite étrange de caractères puisque les valeurs des octets sont transformées.
Le serveur recevant la requête va par contre pouvoir décoder ce message et retrouver le bon message.
Cela fonctionne même avec la méthode GET : l'URL est bien dans les datas du segments. Le paquet IP donc les adresses IP, les PORTS et un contenu crypté.
Si on doit discuter de la confidentialité des échanges sur le Web
- La méthode GET est moins confidentielle que POST puisque les paramètres sont visibles dans l'URL du navigateur
- Ni GET ni FORM ne sont vraiment confidentielles avec du HTTP : les données ne sont pas cryptées et sont donc lisibles par tous les appareils transportant le paquet IP et désirant lire son contenu.
- Le HTTPS est confidentiel puisqu'on crypte le contenu des requêtes et des réponses.
- Même en HTTPS, les paramètres restent visibles à l'écran dans la barre de navigation, attention.
2 - Un serveur http en Python
Voyons comment réaliser un petit serveur Python basique de façon à pouvoir réaliser un site dynamique.
Bien entendu, le serveur que nous allons réaliser n'est pas un serveur réel : il ne faudra pas l'utiliser en production. Que veut dire production ? Cela veut dire que le serveur et le site sont réellement disponibles sur Internet.
Le serveur primitif que nous allons utiliser n'est pas suffisamment sécurisé pour supporter la dure existence d'un serveur réel !
Si vous voulez apprendre, à réaliser un vrai site dynamique en Python, il faudra vous renseigner sur deux modules très efficaces :
- Flask qui permet de faire rapidement un site correct.
- Django qui est un très bon engin, puissant et efficace mais plus délicat à prendre en main : sa puissance vient de sa modularité.
Des fiches sur ces deux modules sont en cours de réalisation sur ce site.
Bon, commençons par le commencement. Allons chercher le code du petit serveur que je vous ai réalisé.
08° Placer les fichiers configuration.py et code_serveur.py dans un dossier de l’ordinateur qui doit servir de serveur Web.
Vous pouvez les télécharger avec un clic-droit - enregistrer sous pour les placer à l'endroit voulu.
Le fichier code_serveur.py n’aura pas à être ouvert ou modifié. Vous n’aurez à agir que sur le fichier configuration.py.
09° Ouvrir configuration.py dans Thonny et lancer le programme.
Vous devriez voir ce message s'ouvrir dans la Console de Thonny
::: START ::: Démarrage d'un serveur d'adresse 127.0.0.1:9000. CTRL-C pour stopper
Questions :
- Quelle est l'adresse IP du serveur ?
- Quel est son PORT d'identification ?
...CORRECTION...
Non, mais oh ! Vous avez cherché ou pas ?
Sinon, c'est que vous avez des gros bouts de la partie précédente et de l'activité précédente.
Bref :
- Adresse IP : 127.0.0.1
- PORT : 9000
10° Ouvrir un nouvel onglet de votre navigateur et taper l'adresse dans la barre ... d'adresses.
http://127.0.0.1:9000/
Vous devriez obtenir ceci :

11° Taper maintenant localhost plutôt que l'adresse numérique dans la barre d'adresses.
http://localhost:9000/
Vous devriez obtenir ceci :

Adresse IP v4 Localhost
Nous verrons les adresses IP dans la prochaine activité, mais vous avez dû les rencontrer de nombreuses fois déjà.
Cette adresse 127.0.0.1 caractérise ce qu'on nomme l'hôte local, le localhost. Votre ordinateur en gros. Quelle que soit l'adresse IP réelle de votre ordinateur, avec 127.0.0.1
, il comprend qu'on parle de lui.
En gros, comprenez 127.0.0.1
comme C'est moi !
.
12° Cette page aurait-elle été réalisable avec un site statique tel que vous en avez déjà réalisé ? Pourquoi ?
...CORRECTION...
Cette page est dynamique car on voit qu'elle change à change fois qu'on met à jour : l'heure change.
En réalité, on aurait pu faire la même chose avec JS avec un script côté client.
Mais vous avez compris le principe : c'est bien une page proposée par un site dynamique puisque le site envoie un code HTML différent en fonction des moments.
13° Modifier le PORT dans la barre d'adresses. Demandez par exemple d'accéder au PORT 7000.
http://localhost:7000/
Vous devriez obtenir ceci :

Comme vous pouvez le voir, il ne s'agit pas d'une erreur 404.
Une erreur 404, c'est quand vous atteignez bien le serveur mais que le serveur vous répond qu'il ne comprend pas la demande car la ressource demandée n'existe pas (ou plus).
Ici, c'est tout simplement que la requête n'obtient AUCUNE réponse. C'est normal : le serveur écoute le PORT 9000.
Et on peut voir cette superbe page sur son smartphone ?
Techniquement, votre site n'est pas accessible sur le Web : vous avez juste créé un serveur pour votre ordinateur. Néanmoins, si votre ordinateur est relié à votre Box par Wifi et que votre smartphone est également relié à votre Box en Wifi, les deux appareils sont sur le même réseau local.
Il est donc possible de voir les pages fournies par le serveur sur votre smartphone
- tant que vous restez chez vous (et si vous lisez ceci autour des mois mars-avril-mai 2020, les chances sont grandes que vous soyez chez vous)
- si vous modifiez l'adresse IP du serveur pour y placer l'adresse locale de votre ordinateur.
Nous verrons dans la prochaine activité (celle sur les IP) pourquoi il faudrait modifier quelques paramètres dans votre Box pour que ce service soit accessible de l'extérieur.
✎ 14° Ouvrir une invite de commande sur votre ordinateur.
Vous aller voir qu'on peut trouver assez facilement son adresse IP (pour la couche Réseau) et son adresse MAC (pour la couche Accès Réseau, c'est l'adresse de votre carte réseau).
Taper ceci :
Windows : vous devriez obtenir une réponse de ce type :
h:\>ipconfig/all
Configuration IP de Windows
Nom de l'hôte : e301-poste12
Carte Ethernet Connexion au réseau local :
Suffixe DNS propre à la connexion : lycee.home
Description : Broadcom NetXtreme Gigabit Ethernet
Adresse physique : 25-CE-55-XX-XX-XX
DHCP activé : Oui
Configuration automatique activée : Oui
Adresse IPv6 de liaison locale : ad41::fe80:b1b1:fe80:fe80%13(préféré)
Adresse IPv4 : 192.168.2.78(préféré)
Masque de sous-réseau : 255.255.0.0
Bail obtenu : jeudi 4 mai 2017 10:58:30
Bail expirant : vendredi 5 mai 2017 10:58:29
Passerelle par défaut : 192.168.0.250
Serveur DHCP : 192.168.0.250
IAID DHCPv6 : 270843397
DUID de client DHCPv6 : 00-01-00-01-17-BD-7B-ED-24-BE-05-10-C2-C2
Serveurs DNS : 192.168.0.253
Serveur WINS principal : 192.168.0.253
NetBIOS sur Tcpip : Activé
Linux ou MAC (systèmes Unix) : vous devriez obtenir une réponse de ce type :
rv@rv-HP2:~$ ifconfig
enp2s0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether 30:e1:71:XXX:XX:XX txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Boucle locale)
RX packets 11492 bytes 4538277 (4.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 11492 bytes 4538277 (4.5 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
wlo1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::bed3:c7ad:XXXX:XXXX prefixlen 64 scopeid 0x20<link>
inet6 2a01:cb0c:96c:d400:f995:XXXX:XXXX:XXXX prefixlen 64 scopeid 0x0<global>
inet6 2a01:cb0c:96c:d400:a38b:XXXX:XXXX:XXXX prefixlen 64 scopeid 0x0<global>
ether 3c:a0:67:XX:XX:XX txqueuelen 1000 (Ethernet)
RX packets 219822 bytes 225401269 (225.4 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 160066 bytes 32570855 (32.5 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Question : Quelle est votre adresse IP locale ? Notez la bien, elle vous servira pour la question suivante.
15° Modifier la ligne 20 de configuration.py en y plaçant l'adresse IP de votre ordinateur. Attention : il faut stopper le serveur et le remettre en route pour que les modifications soient effectives.
Sur votre smartphone ou un autre ordinateur de la maison ou le même ordinateur d'ailleurs, tapez ceci (avec votre adresse locale perso !) dans la barre d'adresses
http://192.168.1.11:9000/
Et voilà : vous devriez pouvoir afficher votre page sur n'importe quel appareil de votre réseau local, tant que vous passez par une liaison interne.
Dans toute la suite de l'activité, je considère une adresse 127.1.1.0. Mais vous pouvez laisser votre adresse locale si vous voulez tester les résultats depuis votre smartphone ou un autre ordinateur.
Bon, comme ça fonctionne Regardons le fichier configuration.py.
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
38
39
40
41
42
43
44
45
46
47
48 | '''
Script qui permet de gérer un serveur dynamique minimaliste
-- Choisir votre IP
-- Choisir la valeur du PORT associé à votre serveur (8000, 8080, 9000 ...)
-- Compléter la fonction reponse_a_GET pour qu'il gère de nouvelles requêtes GET si vous le voulez
-- Compléter la fonction reponse_a_POST pour qu'il gère de nouvelles requêtes POST si vous le voulez
-- Créer les pages HTML correspondantes
-- Vérifier que code_serveur.py est bien présent dans votre dossier
-- Lancer le script configuration.py
'''
# 1 - Importation
from http.server import HTTPServer
from code_serveur import Gestionnaire
from datetime import datetime
# 2 - Déclarations des constantes
IP = '192.168.1.11'
PORT = 9000
# 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'''
def reponse_a_POST(requete):
'''Instructions à effectuer si la requête est de type POST. requete contient les infos sur la requête'''
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()
|
Le code Python est structuré comme d'habitude :
D'abord les importations :
14
15
16 | from http.server import HTTPServer
from code_serveur import Gestionnaire
from datetime import datetime
|
Ici, on importe la classe HTTPServer du module de base de Python nommé http.server.
La classe Gestionnaire provient de notre module maison code_serveur.
La fonction datetime provient du module basique datetime. C'est elle qui permet de récupérer date et heure sur l'ordinateur-serveur.
Ensuite les constantes, les variables qu'on ne doit pas modifier une fois le programme en route.
20
21 | IP = '192.168.1.11'
PORT = 9000
|
Puis les déclarations de fonctions, ici au nombre de trois :
25
26
27
28
29
30
31
32 | def reponse_a_GET(requete):
'''Instructions à effectuer si la requête est de type GET. requete contient les infos sur la requête'''
def reponse_a_POST(requete):
'''Instructions à effectuer si la requête est de type POST. requete contient les infos sur la requête'''
def activer_serveur():
'''Création et mise en route du serveur qui surveille ensuite les requêtes qu'on lui communique'''
|
La première, reponse_a_GET, va nous permettre à gérer les requêtes GET. On voit qu'elle possède un paramètre requete qui contiendra plein d'informations sur la requête du client justement.
Pareil pour reponse_a_POST.
La dernière lance le serveur : activer_serveur.
On voit en ligne 58 que la seule action est ici de lancer la fonction activer_serveur. Regardons ce qu'elle fait.
✎ 16° Répondre aux questions suivantes :
A : Dans notre classe Gestionnaire, quelle est la fonction qu'on associe à la gestion des requêtes GET puis POST ?
B : Comment se nomme le type de la variable adresse ? Un tableau, une liste, un tuple ou un dictionnaire ?
C : Le f devant le string de la ligne 41 permet de signaler qu'il s'agit d'un fString. En regardant l'affichage obtenu dans la console et les accolades dans le fString, comment expliqueriez-vous ce qui s'est passé à quelqu'un qui ne connaît pas ces fameux fString ?
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | 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
|
Note : j'insiste beaucoup sur les fStrings, car vous allez voir qu'ils vont nous être très utiles pour réaliser nos pages HTML dynamiques.
On voit qu'on crée le daemon HTTP en lui fournissant l'adresse (qui comporte donc l'IP pour la couche RESEAU et le PORT pour la couche TRANSPORT) qu'il doit surveiller et le Gestionnaire qu'il doit utiliser pour gérer les requêtes.
Voilà, il ne reste plus qu'à le mettre en route. Cela veut dire le laisser surveiller constamment les requêtes qui vont arriver.
✎ 17° En analysant le code de la fonction ci-dessus, quelle est à votre avis la ligne qui permet de mettre le serveur en route en laissant le daemon travailler en boucle infinie ? Comment arrêter le serveur du coup ?
A partir de là, on passe en programmation événementielle : on ne sait pas quelles requêtes vont arriver, ni quand elles vont arriver. Ni même SI elles vont arriver. Bref, le httpd surveille et travaille dès qu'une requête arrive.
3 - Gestion des requêtes GET
Regardons comme on gère les requêtes GET. Vous allez voir, c'est assez simple avec ce petit module.
Voici le code, les explications et questions sont juste derrière.
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | 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 == '/' :
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Ceci est ma superbe page d'accueil</h1>")
requete.repondre(f"<p>Heure actuelle : {datetime.now().hour} h {datetime.now().minute} min</p>")
requete.repondre("</body>")
requete.repondre("</html>")
elif chemin == '/info':
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Page d'information</h1>")
requete.repondre(f"<p>Requete GET reçue par le serveur : {chaine_requete}</p>")
requete.repondre(f"<p>Chemin détecté : {chemin}</p>")
requete.repondre(f"<p>Paramètres détectés : {parametres}</p>")
requete.repondre(f"<p>Votre adresse IP : {adresse_client}</p>")
requete.repondre(f"<p>Le PORT correspondant à votre navigateur : {port_client}</p>")
requete.repondre("</body>")
requete.repondre("</html>")
elif chemin == '/add':
x = int(parametres['a'])
y = int(parametres['b'])
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Page d'information</h1>")
requete.repondre(f"<p>{x} + {y} = {x+y}</p>")
requete.repondre("</body>")
requete.repondre("</html>")
else :
requete.creer_entete(404) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Mise en place de l'en-tête HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>404 ! Je ne comprends pas votre requête !</h1>")
requete.repondre("</body>")
requete.repondre("</html>")
|
Les premières lignes servent simplement à vous montrer qu'on peut stocker par mal de choses en provenance de la requête. Vous pourrez ainsi les utiliser pour créer vos pages dynamiques si vous avez envie.
6
7
8
9
10
11 | # 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
|
La première variable, chaine_requete, vous permet par exemple de récupérer dans Python le texte de la requête.
18° Que va contenir à votre avis la variable chemin si on utilise une URL http://127.0.0.1:9000/info
?
...CORRECTION...
Dur dur. Surtout avec l'indice en rouge.
On aura donc un contenu string valant "/info"
. Attention à bien prendre en compte le SLASH initial qui indique qu'on a une demande à partir de la "racine" du site.
19° Plus dur : que va contenir la variable chemin si on utilise une URL http://127.0.0.1:9000/
ou http://127.0.0.1:9000
?
...CORRECTION...
Dans les deux cas, on aura juste /
pour indiquer que l'utilisateur n'a fourni aucun chemin. Nous allons donc aller à la racine du site.
Et ensuite, c'est facile : on va tester chemin et en fonction de la ressource demandée, on va générer une page HTML à la volée !
Regardons la structure de la gestion des requêtes :
13
14
15
16
17
18
19
20
21
22
23
35
47
48
| # 2 - Traitement de la requête
if chemin == '/' :
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Ceci est ma superbe page d'accueil</h1>")
requete.repondre(f"<p>Heure actuelle : {datetime.now().hour} h {datetime.now().minute} min</p>")
requete.repondre("</body>")
requete.repondre("</html>")
elif chemin == '/info':
elif chemin == '/add':
else :
requete.creer_entete(404) # Mise en place de l'en-tête HTML
|
Ici, si on ne détecte aucun chemin dans l'URL, on va créer la page d'accueil.
- Ligne 15 : on commence par régler le code http à renvoyer (200) avec la méthode creer_entete.
- Lignes suivantes : on crée simplement la page HTML à fournir, ligne par ligne ! Pour cela, on utilise la méthode repondre à laquelle on fournit la ligne à rajouter sous forme d'un string ou d'un fString.
20° Lancer les requêtes suivantes.
http://127.0.0.1:9000/info
http://127.0.0.1:9000/info?user=toto
Question A : Dans quel type de structure de données sont stockés les paramètres ?
Question B : Expliquer étape par étape si l'affichage obtenu correspond bien ou non à ce qui est demandé. Le principe est de vous forcer à vérifier les lignes une à une pour que vous maîtrisiez vraiment l'utilisation du module.
...CORRECTION...
Affichage pour la requête n°2
Page d'information
Requete GET reçue par le serveur : GET /info?user=toto HTTP/1.1
Chemin détecté : /info
Paramètres détectés : {'user': 'toto'}
Votre adresse IP : 192.168.1.13
Le PORT correspondant à votre navigateur : 34208
Les paramètres sont transférés dans un dictionnaire. Ce sont les accolades qui nous permettent d'affirmer cela.
On remarquera qu'ici nous avons utilisé un appareil différent de l'ordinateur-serveur pour afficher la page puisque l'adresse-client est 192.168.1.13, alors que mon serveur était placé sur 192.168.1.11.
Et pour comprendre pourquoi on obtient cet affichage, il suffit de dérouler le code à partir de la ligne 25.
23
24
25
26
27
28
29
30
31
32
33
34
35 | elif chemin == '/info':
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Page d'information</h1>")
requete.repondre(f"<p>Requete GET reçue par le serveur : {chaine_requete}</p>")
requete.repondre(f"<p>Chemin détecté : {chemin}</p>")
requete.repondre(f"<p>Paramètres détectés : {parametres}</p>")
requete.repondre(f"<p>Votre adresse IP : {adresse_client}</p>")
requete.repondre(f"<p>Le PORT correspondant à votre navigateur : {port_client}</p>")
requete.repondre("</body>")
requete.repondre("</html>")
|
Comme vous avez pu le constater, les fStrings sont très pratiques ici.
Ainsi pour la date et l'heure, il suffit de taper ceci :
f"<p>Heure actuelle : {datetime.now().hour} h {datetime.now().minute} min</p>"
21° Pas si facile : la page HTML va-t-elle être créée avec l’heure et les minutes de l’ordinateur-serveur ou de l’ordinateur-client ?
...CORRECTION...
Le code que vous avez sous les yeux est du code qui s'exécute sur l'ordinateur-serveur.
L'heure affichée est donc celle de l'ordinateur-serveur.
Le client va juste recevoir du code HTML "en dur".
Il est temps de créer une première page paramétrée : une page où l'affichage va dépendre de ce que l'utilisateur envoie.
Et vous avez un exemple dans le test suivant
36
37
38
39
40
41
42
43
44
45
46 | elif chemin == '/add':
x = int(parametres['a'])
y = int(parametres['b'])
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<body>")
requete.repondre("<h1>Page d'information</h1>")
requete.repondre(f"<p>{x} + {y} = {x+y}</p>")
requete.repondre("</body>")
requete.repondre("</html>")
|
Comme parametres est un dictionnaire qui contient les paramètres que le client a fourni, il suffit d'en récupérer les valeurs en utilisant la clé.
D'où le parametres['a']
.
Et pourquoi le int en lignes 36 et 37 ? Simplement car toute la requête n'est qu'une suite d'octets qu'on interprète ensuite en chaînes de caractères. Si on transmet le nombre 5
, on reçoit donc en réalité le string '5'
. Il faut bien convertir du coup.
22° Lancer la requête suivante :
http://192.168.1.11:9000/add?a=45&b=10
23° Et c'est maintenant que le drame arrive : l'utilisateur confond le zéro 0
et la lettre O
!
http://192.168.1.11:9000/add?a=45&b=1O
Vous devriez obtenir un affichage de ce type :

Vérification et validation des données transmises
Avant d'appliquer le moindre traitement aux paramètres et données-utilisateur, il faut leur faire passer des tests de validation. Sinon, votre beau serveur va finir par terre assez vite !
Nous verrons dans quelques temps une activité Python qui vous montrera rapidement différentes techniques permettant de sécuriser les fonctions de plusieurs façons.
Si vous connaissez, vous pouvez utiliser les blocs try - except
pour rendre cette page plus solide.
✎ 24° Répondre alors aux questions suivantes :
- A : Lors de l’envoi de la requête, le paramètre a est-il associé à la chaine '10' ou au nombre 10 ?
- B : Quelle est l’instruction permettant de récupérer la valeur du paramètre a ?
- C : A quoi sert la fonction native int qui contient l’instruction précédente ?
Si vous trouvez lourd de devoir envoyer les lignes une par une, par de problèmes : vous n'avez absolument pas obligation de le faire. On peut créer le code HTML par concaténation et ne l'envoyer qu'à la fin.
Voici le même fichier mais avec cette fois une création du code avant envoi.
Le principe est d'associer à la fois les fString ET les strings définis avec trois '''
qui permettent ainsi d'inclure des "
ou des '
dans les strings sans aucun problème. Et sur plusieurs lignes.
Un exemple pour vous montrer que ça devient plus clair :
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 | elif chemin == '/info':
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<p>Requete GET reçue par le serveur : {chaine_requete}</p>
<p>Chemin détecté : {chemin}</p>
<p>Paramètres détectés : {parametres}</p>
<p>Votre adresse IP : {adresse_client}</p>
<p>Le PORT correspondant à votre navigateur : {port_client}</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
|
On pourrait faire mieux encore en chargeant les codes dans des fichiers HTML à part et les nommés des templates et on est parti pour recréer les solutions comme Flask ! Donc, nous allons en rester là. C'est déjà bien :o)
Vous pouvez télécharger le fichier Python suivant : il vous présente une autre façon de fournir le code HTML.
📄 configuration_plusieurs_lignes.py
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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 | '''
Script qui permet de gérer un serveur dynamique minimaliste
-- Choisir votre IP
-- Choisir la valeur du PORT associé à votre serveur (8000, 8080, 9000 ...)
-- Compléter la fonction reponse_a_GET pour qu'il gère de nouvelles requêtes GET si vous le voulez
-- Compléter la fonction reponse_a_POST pour qu'il gère de nouvelles requêtes POST si vous le voulez
-- Créer les pages HTML correspondantes
-- Vérifier que code_serveur.py est bien présent dans votre dossier
-- Lancer le script configuration.py
Ce code fait référence à une activité se trouvant ici : www.infoforall.fr/act/archi/communication-client-serveur/
'''
# 1 - Importation
from http.server import HTTPServer
from code_serveur import Gestionnaire
from datetime import datetime
# 2 - Déclarations des constantes
IP = '127.1.1.0'
PORT = 9000
# 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 == '/' :
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Ceci est ma superbe page d'accueil</h1>
<p>Heure actuelle : {datetime.now().hour} h {datetime.now().minute} min</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
elif chemin == '/info':
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<p>Requete GET reçue par le serveur : {chaine_requete}</p>
<p>Chemin détecté : {chemin}</p>
<p>Paramètres détectés : {parametres}</p>
<p>Votre adresse IP : {adresse_client}</p>
<p>Le PORT correspondant à votre navigateur : {port_client}</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
elif chemin == '/add':
x = int(parametres['a'])
y = int(parametres['b'])
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'addition</h1>
<p>{x} + {y} = {x+y}</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
else :
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>404 ! Je ne comprends pas votre requête !</h1>
</body>
</html>'''
requete.creer_entete(404) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
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 = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<p>Requete POST reçue par le serveur : {chaine_requete}</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
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()
|
✎ 25° Réaliser (et fournir le code) d'une nouvelle page qui attend un paramètre nom. Elle doit :
- Afficher le nombre de caractères contenus dans le nom (avec la fonction native len pour ceux qui ne suivent pas, là bas au fond)
- Afficher Gentil Jedi si le nom est Luke ou Yoda
- Afficher Méchant méchant ! si le nom est Darkvador ou Empereur
- Afficher Je ne sais pas, je ne suis pas une IA ! sinon
Vous pouvez créer le code ligne par ligne ou en version tout d'un coup. Ou même en trois concaténation si vous voulez.
Pensez juste à générer le code (200) avant d'envoyer le texte avec repondre.
Les plus à l'aise pourraient utiliser la méthode des strings lower() pour faire le test en tout minuscule. Sinon, il suffit de se tromper sur une majuscule - minuscule et Python pensera que les deux chaînes de caractères ne représentent pas le même personnage.
Tout le monde est gentil sur ma page !
Dans ce cas, allez voir la FAQ en bas de cette activité. Ca devrait vous aider...
Dans le dernier cas, c'est que la requête n'est pas connue : on crée donc une page 404 personnalisée. On remarquera qu'on fournit alors un code 404.
4 - Méthode POST
Si vous regardez le code test, vous pourrez constater que c'est pareil qu'avec le GET en fait.
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 | 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 = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<p>Requete POST reçue par le serveur : {chaine_requete}</p>
</body>
</html>'''
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre(codeHTML)
|
Le seul truc, c'est qu'il faut réussir à créer une requête POST. Et pour cela, il va falloir créer une page HTML classique (obtenue avec un GET) et y insérer un formulaire qui va nous permettre d'envoyer des données.
Et pour être autonome là dessus, il va falloir faire l'activité Javascript et HTML qui traite des formulaires.
En attendant, voici un fichier configuration_avec_formulaire.py qui va pour permettre d'envoyer un formulaire et d'afficher son contenu.
26° Télécharger le nouveau fichier Python (toujours avec un clic-droit) puis lancer ce nouveau serveur plutôt que l'ancien.
27° Choisir les éléments que vous voulez dans le formulaire. Ouvrir les outils de développement puis Réseau. Une fois tout cela activé, lancer la requête suivante qui vous permettra d'arriver sur la page du formulaire :
http://127.0.0.1:9000/formulaire
Vous devriez aboutir à ceci :

Il ne reste qu'à appuyer sur le bouton Submit.
Et le résultat en image :

Bon, c'est clair, c'était une requête POST : c'est noté sur la page, c'est noté dans Firefox.
Comment avons-nous fait ? Nous avons utilisé ici une interface utilisateur particulière qui permet de créer des zones dans lesquelles les utilisateurs pourront rentrer leurs données qui seront passées en paramètres.
✎ 28° Les paramètres que vous avez fourni ont-ils été transporté via l'URL ? D'après le cours et les questions que nous avons vues en début d'activité, dans quelle partie de la requête l'information est-elle placée alors ?
Le but de cette activité n'est pas la gestion des formulaires, nous ferons ça dans une autre activité propre à HTML et Javascript. Regardons juste le code HTML de la page où le formulaire apparaît.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | elif chemin == '/formulaire' :
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<form action ="/voir_post" method ="post">
<p>
<select name="question">
<option value="addition">table addition</option>
<option value="multiplication">table multiplication</option>
</select>
</p>
<p><input type="number" name="numero" value=0 min="0" max="10" step="1" ></p>
<p><input type="text" name="remarque" textholder="A remplir si vous avez une question à me poser !">
<p><input type ="submit" value="Submit" ></p>
</form>
</body>
</html>'''
requete.creer_entete(200)
requete.repondre(codeHTML)
|
On voit qu'on a donc crée une balise de type form
, comme formulaire.
Il s'agit d'une structure qui permet de facilement récupérer les données utilisateur et de la formater pour réaliser ici une requête FORM pointant vers l'adresse absolue "/voir_post"
sur le site actuel puisqu'on fait commencer l'adresse par un slash.
Nous allons voir dans l'activité javascript qu'on doit remplir ce formulaire avec d'autres balises spécifiques, des balises qui vont automatiquement créer une interface homme-machine intéressante.
- La première balise select permet de créer un menu déroulant
- La balise input de type number permet de gérer des nombres
- La balise input de type text permet de gérer des textes
- La balise input de type submit permet de créer un bouton qui va créer la requête et faire le lien vers la nouvelle page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | elif chemin == '/formulaire' :
codeHTML = f'''
<!DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<form action ="/voir_post" method ="post">
<p>
<select name="question">
<option value="addition">table addition</option>
<option value="multiplication">table multiplication</option>
</select>
</p>
<p><input type="number" name="numero" value=0 min="0" max="10" step="1" ></p>
<p><input type="text" name="remarque" textholder="A remplir si vous avez une question à me poser !">
<p><input type ="submit" value="Submit" ></p>
</form>
</body>
</html>'''
requete.creer_entete(200)
requete.repondre(codeHTML)
|
Tout çà en quelques lignes de code.
Ce qui m'intéresse aujourd'hui, c'est le nom des paramètres qui vont être générés. On leur définit à l'aide des attributs name.
✎ 29° Donner le nom des 3 paramètres qui seront envoyés dans la requête POST.
Analysons maintenant le code Python de la page créée pour récupérer cette requête POST.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | codeHTML = f'''
<DOCTYPE html>
<html>
<body>
<h1>Page d'information</h1>
<p>Requete POST reçue par le serveur : {chaine_requete}</p>
<p>Voici le contenu des paramètres</p>'''
for cle, valeur in parametres.items():
codeHTML = codeHTML + f'<p>{cle} valant {valeur}</p>'
codeHTML = codeHTML + '''
</body>
</html>
'''
requete.creer_entete(200)
requete.repondre(codeHTML)
|
Comme vous le voyez, nous retrouvons encore un dictionnaire. Cette fois, j'ai décidé de lire ces clés et ses valeurs associées en même temps. A l'aide de la méthode des dictionnaires items .
Et j'ai créé le code HTML en trois temps : haut de la page, concaténation des clés-valeurs puis fin de la page.
Une dernière manipulation pour la route : réalisons la table de 10 en addition ou en multiplication en fonction de ce que l'utilisateur a rentré.
On estimera que les données reçues sont correctes, sans vérifier le type ou les valeurs (c'est mal !).
La puissance des sites dynamiques ? La possibilité de ne pas à avoir à créer toutes les possibilités !
Un élève un peu trop confiant et rapide propose ceci :
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 | 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
valeur = parametres['numero']
codeHTML = f'''
<DOCTYPE html>
<html>
<body>
<h1>Page des {parametres['question']}</h1>
<p>Requete POST reçue par le serveur : {chaine_requete}</p>
<p>Voici la table voulue</p>'''
if parametres['question'] == 'multiplication' :
for x in range(1,11,1):
codeHTML = codeHTML + f'<p>{x} x {valeur} = {x*valeur}</p>'
elif parametres['question'] == 'addition' :
pass
codeHTML = codeHTML + '''
</body>
</html>
'''
requete.creer_entete(200)
requete.repondre(codeHTML)
|
✎ 30° Lancer le programme. Vous allez vous rendre compte qu'il a été un peu trop rapide effectivement. Modifier alors le code pour qu'il gère correctement les multiplications puis les additions également.
5 - FAQ
Pourquoi tout le monde est gentil ?
Vous avez peut-être eu un problème sur la question où il faut détecter les gentils et les méchants.
Beaucoup de gens écrivent ceci :
1 | if parametres['nom'] == 'luke' or 'yoda' :
|
Or, ça, c'est toujours vrai. Pourquoi ?
On teste deux conditions avec un OU. Si l'une est vraie au moins, on répond VRAI. Ok
Quels sont les deux tests :
parametres['nom'] == 'luke'
- OU
'yoda'
Le problème vient de la deuxième partie : ça veut dire quoi évaluer l'expression 'yoda'
, juste 'yoda'
?
Et bien, ça revient à tester ceci :
1 | if parametres['nom'] == 'luke' or 'yoda' == True:
|
Or, en Python, False est codé par un contenu vide ou égal à 0. Du coup, comme le string 'yoda'
n'est pas vide, l'expression de droite sera toujours évaluée à True.
Et comme c'est un OU, l'expression totale sera également TOUJOURS True.
La bonne façon d'écrire le test est donc :
1 | if parametres['nom'] == 'luke' or parametres['nom'] == 'yoda' :
|
Et on peut fournir des fichiers css, js ect... ?
Oui. Vous pouvez demander à notre serveur primitif de délivrer des fichiers statiques. Ils doivent alors se trouver dans un dossier nommé www, pour exprimer le fait que les utilisateurs peuvent accéder en lecture librement à ce qu'on y place.
📁 votre_dossier_par_exemple_site_dynamique
📁 www
📄 turing.jpg
📄 monstyle.css
📄 monscript.js
📄 mapage.html
Attention, lorsque vous donnez l'adresse des documents à télécharger, il ne faut pas préciser le www dans l'adresse : le serveur va aller chercher tout seul comme un grand dans ce dossier. Exemple ci-dessous :
13
14
15
16
17
18
19
20
21
22
23
24 | # 2 - Traitement de la requête
if chemin == '/' :
requete.creer_entete(200) # Mise en place de l'en-tête HTML
requete.repondre("<!DOCTYPE html>") # Création de la réponse HTML
requete.repondre("<html>")
requete.repondre("<head><script src='test.js'></script></head>")
requete.repondre("<body>")
requete.repondre("<h1>Ceci est ma superbe page d'accueil</h1>")
requete.repondre("<p><img src='turing.jpg'></p>")
requete.repondre(f"<p>Heure actuelle : {datetime.now().hour} h {datetime.now().minute} min</p>")
requete.repondre("</body>")
requete.repondre("</html>")
|
Voici un exemple de page dynamique intégrant des demandes de ce type :
Mais vous pouvez aussi fournir des pages HTML purement statiques du coup;:
Voici le contenu d'une page HTML statique qu'on pourra obtenir en tapant l'URL http://localhost:9000/mapage.html
. Encore une fois, attention : on ne précise pas le WWW : c'est juste qu'il faut placer vos fichiers statiques dans ce dossier.
📁 votre_dossier_par_exemple_site_dynamique
📁 www
📄 turing.jpg
📄 monstyle.css
📄 monscript.js
📄 mapage.html
1
2
3
4
5
6
7
8
9
10
11 | <!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href='monstyle.css' />
<script scr="test.js"></script>
</head>
<body>
<p>Une page statique directement transmise par le serveur, sans traitement particulier.</p>
<p><img src="turing.jpg"></p>
</body>
</html>
|
Activité publiée le 25 03 2020
Dernière modification : 25 03 2020
Auteur : ows. h.