Archi GET POST

Identification

Infoforall

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.

Couche TRANSPORT Protocole : TCP (et +) Adresse : PORT Message : SEGMENT DATA d'un programme SEGMENT TCP Port SRC 443 Port DST 2050 Seq 4 Suiv 2 Flags : ACK CS : 15241 PAQUET IP IP DST 25.25.25.5 IP SRC 45.45.45.2 TRAME (Ethernet ou Wifi) SEQUENCE INTIALE MAC DST MAC SRC CheckSum selon le type de trame Couche RESEAU Protocole : IP (et +) Adresse : IP Message : PAQUET Couche LIAISON Protocole : WLAN ... Adresse : MAC Message : TRAME Couche Application Firefox (client) Protocole : HTTP Couche Application Serveur HTTP Protocole : HTTP Rep http Requete Rep TCP IP Req TCP IP ACK TCP IP
1 Requête - 2 - Traitement - 3 - Réponse
Requête - Traitement - Réponse

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: Fri, 14 Aug 2020 23:08:55 GMT
Accept-Encoding: br,gzip
Connection: Keep-Alive
X-Real-Ip: 34.236.245.255
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: Fri, 14 Aug 2020 23:08:55 GMT
Accept-Encoding: br,gzip
Connection: Keep-Alive
X-Real-Ip: 34.236.245.255
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

    Et c'est ensuite qu'apparaissent les paramètres qu'on veut transmettre au serveur. Voici la codification de leurs passages :

    • ?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

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é 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.

✎ 05° Un serveur reçoit une requête GET de ce type :

www.infoforall.fr/question_trop_difficile/?difficulte=outch&note=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 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.

JS modifiant le HTML côté CLIENT
Modification côté CLIENT
  • 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.
  • Avantages : 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 : aucun code n'a été envoyé au client, à part un code HTML modifié comme il faut. Un programme qui tourne sur le serveur a analysé la requête et les tests ont donc été réalisé sur la machine-serveur AVANT l'envoi du code HTML. Ca, vous ne l'avez pas encore fait.

JS modifiant le HTML côté SERVEUR
Modification côté SERVEUR
  • 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.
  • Avantages : 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.

Voilà pour l'essentiel.

Nous allons maintenant voir comment :

  • (un peu) créer un serveur http en Python
  • (vraiment pas beaucoup) créer des formulaires HTML

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.

📁 votre_dossier_par_exemple_site_dynamique

📄 code_serveur.py

📄 configuration.py

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 le Shell de Thonny

::: START ::: Démarrage d'un serveur d'adresse 127.0.0.1:9000. CTRL-C pour stopper

Questions :

  1. Quelle est l'adresse IP du serveur ?
  2. 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 :

  1. Adresse IP : 127.0.0.1
  2. 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 :

Affichage obtenu avec 127.0.0.1:9000
Affichage avec l'adresse 127.0.0.1:9000

11° Taper maintenant localhost plutôt que l'adresse numérique dans la barre d'adresses.

http://localhost:9000/

Vous devriez obtenir ceci :

Affichage obtenu avec localhost:9000
Affichage avec l'adresse localhost:9000
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 :

Affichage obtenu avec localhost:9000
Affichage avec l'adresse localhost:7000

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/

Affichage obtenu avec 192.168.1.11:9000
Affichage avec l'adresse locale

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 :

Le serveur ne répond pas
Problème avec les données-utilisateur sur le serveur
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()
Attention pour les questions suivantes : pensez à stopper et remettre le serveur en route pour que les modifications soient prises en compte. De la même façon, pensez à vérifier l'IP et le PORT de votre serveur de façon à voir si ce n'est pas ça qui n'est pas conforme avec la requête.

✎ 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.

📄 configuration_avec_formulaire.py

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 :

La page avec le formulaire
La page avec le formulaire

Il ne reste qu'à appuyer sur le bouton Submit.

Et le résultat en image :

La page obtenue en POST
La page avec le formulaire

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

📄 code_serveur.py

📄 configuration.py

📁 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

📄 code_serveur.py

📄 configuration.py

📁 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.