Nous avons vu dans un article précédent comment apporter un peu de dynamisme à un site web statique, en construisant un programme CGI en python. Il est cependant limité, et nous allons voir ici comment remédier à une des faiblesses de ce dernier.
Rappelons que le script CGI, cible.py, assure le traitement d'un fichier qui est transmis par l'utilisateur par l'intermédiaire d'un formulaire d'une page statique. Lorsque l'utilisateur clique sur le bouton « Envoyer », il transmet le fichier qu'il a sélectionné, et il voit alors s'afficher le message prévu par le script CGI.
Cependant, s'il essaye pour une raison quelconque d'actualiser la page, il verra apparaitre un message d'avertissement, lui indiquant que les données préalablement transmises vont être renvoyées.
Si l'utilisateur clique sur « Annuler », il annule le rafraichissement de la page, et rien ne se passe. En revanche, s'il clique sur « Renvoyer », il va envoyer une nouvelle fois toutes les données au serveur, dont le fichier, et le script CGI cible.py va à nouveau se déclencher et effectuer l'intégralité du traitement prévu. Dans le meilleur des cas, un nouveau fichier est écrit sur le disque du serveur, dans le pire des cas, ce nouveau fichier écrase celui préalablement transmis. C'est le problème des soumissions multiples des formulaires.
C'est une limite de ce script. En effet, la transmission d'un fichier n'est pas nécessairement critique, mais on pourrait imaginer les conséquences du renvoi d'un formulaire portant sur l'achat en ligne d'un objet quelconque.
Pour éviter cela, il est possible de mettre en place le schéma de programmation PRG, PRG pour post-redirect-get. L'idée principale est que, si l'utilisateur cherche à réactualiser la page affichée par le script CGI, son navigateur envoie une requête get en comportant uniquement quelques informations bien choisies, et lieu et place de la requête post qui contient toutes les informations importantes, comme le fichier transmis dans notre cas.
Dans la situation précédente, nous avons l'enchainement d'action suivant :
Si l'utilisateur réactualise la page, l'action est équivalente à rappuyer sur le bouton « Envoyer » et donc à reprendre toutes les étapes de 1 à 4.
Dans le schéma PRG, nous construisons un nouvel enchainement d'action :
Si l'utilisateur réactualise la page, seules les actions 5 et 6 seront reprises, les informations importantes ne sont pas renvoyées au serveur.
Notons que les codes http 200 et 303 correspondent respectivement au succès de la requête et à la redirection vers une autre url.
On voit que le nom du schéma est ici assez évident : une requête post, une redirection puis une requête get, d'où le schéma post-redirect-get.
Le choix a été fait de faire supporter au programme CGI cible.py le traitement des données reçues par la requête post, la génération de la redirection 303 et le traitement de la requête get. Ce programme est donc le seul responsable de tout le schéma PRG dans notre cas. Ceci est un peu complexe, mais permet d'avoir un unique script CGI à écrire.
La première chose à faire est de distinguer les requêtes post et get. Il est possible d'avoir accès au type de requête avec :
import os
method = os.environ['REQUEST_METHOD']
Il reste ensuite à distinguer les deux types de requêtes :
if method == "POST":
# Traite les données et transmet le code http 303
elif method == "GET":
# Affiche la réponse du serveur
else:
# Affiche un message d'erreur
Il est alors possible de découper le programme en plusieurs parties et avoir des comportements différents en fonction de la requête du navigateur.
La réponse 303 apportée par le serveur impose au navigateur d'effectuer une nouvelle requête get vers l'url qui lui transmet. Il s'agit donc de construire cette url. Le cas le plus simple est de fournir une url vers une page statique fournissant un message de succès ou d'échec de l'opération.
Cependant, dans l'article CGI et python, nous avons mis en place une génération dynamique du message. Il est donc intéressant de conserver cette partie du programme précédent, qui permet de fournir à l'utilisateur un message détaillé sur le statut du transfert de son fichier.
Nous allons donc construire une réponse 303 qui contiendra une url contenant un statut et une référence indiquant l'état du transfert :
import urllib.parse
query = urllib.parse.urlencode({'status': status, 'ref': ref})
print("Status: 303 See Other\nLocation: cible.py?{}\n""".format(query))
On obtient alors un header se terminant par ces deux lignes, soit, par exemple :
Status: 303 See Other
Location: cible.py?status=2&ref=46
Un header se termine toujours par une ligne vide, il faut donc l'ajouter à la fin de la commande print.
Lors de la réception du header précédent, le navigateur envoie la requête get permettant d'accéder à la page indiquée. Elle sera donc générée sur la base des variables statut et ref présentes dans l'url de la requête. Ces variables peuvent être récupérées par le script cible.py comme présenté dans le précédent article, vu qu'il n'y a pas de différence entre le traitement des données fournies par une requête post et une requête get.