accueil   blog   bin   contact

CGI et python

Site statique et site dynamique

Comme discuté dans les articles précédents, les sites statiques présentent bien des avantages par rapport aux sites dynamiques. Mais il est des situations où il faut avoir un minimum d'interactivité ou une action du côté serveur est requise. Plusieurs approches peuvent être considérées, mais il s'agit toujours d'écrire un programme qui sera capable d'avoir un comportement donné en fonction des données transmises par l'utilisateur.

Le besoin conduisant à cet article est le suivant : déposer des fichiers sur un serveur par l'intermédiaire d'un formulaire contenu dans une page html. Pour des raisons de sécurité, il y a un ensemble de données à vérifier, comme la taille du fichier reçu, la validité du nom du fichier, et il est également intéressant de contrôler dans quel répertoire le fichier sera écrit.

Un site statique composé exclusivement de pages html ne pouvant fournir un tel comportement, il faut alors inclure une page dynamique et un programme qui pourra s'adapter aux données fournies par l'utilisateur.

Formulaire dans une page html

Pour transmettre des informations, le plus simple est d'utiliser un formulaire dans lequel l'utilisateur pourra entrer des informations à destination du serveur. Un cours complet sur la syntaxe d'un fichier html n'entrant pas dans l'objet de cet article, le lecteur débutant pourra se reporter au wikibook portant sur ce sujet.

Vu que les données reçues par le serveur vont contenir un fichier, il faut indiquer dans le formulaire que les données reçues peuvent être quelconques avec enctype="multipart/form-data". Le fichier ayant de bonnes chances d'être volumineux, il faut privilégier la méthode de transfert de données post plutôt que get. Il s'agit ensuite de placer un champ input de type file donnant un bouton qui permet de sélectionner un fichier, puis un second champ input de type submit pour envoyer le formulaire au serveur. Si on regroupe toutes ces informations, on aboutit à l'exemple minimal suivant :

<form action="cible.py" method="post" enctype="multipart/form-data">
<p>Votre fichier :
<input type="file" name="myfile" /><br />
<input type="submit" value="Envoyer" /></p>
</form>

Common Gateway Interface (CGI)

La Common Gateway Interface est une interface permettant au serveur web de transmettre des données à un programme, ici appelé cible.py, puis de récupérer les données fournies par celui-ci. C'est un standard indépendant du langage de programmation du programme cible.

On comprend alors que ce mode de fonctionnement permet d'obtenir l'interactivité manquant aux sites purement statiques.

Python et programmation web

Le programme cible.py sera un programme python. Dans l'absolu, celui-ci pourrait être écrit en php, en C, en bash ou n'importe quel autre langage, mais il est généralement conseillé de choisir un langage de programmation avec lequel on est à l'aise.

Python n'est pas le langage de choix pour faire une application web, mais ce langage présente l'avantage d'être simple et puissant, et il inclut une bibliothèque cgi permettant de prendre en charge la réception des données par l'interface CGI.

Premières considérations pour utiliser python avec CGI

Comme tout programme python, il commencera par les deux lignes suivantes, pour indiquer le chemin vers le binaire et l'encodage du fichier.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

Pour récupérer les données du formulaire, on utilise la bibliothèque cgi :

import cgi
form = cgi.FieldStorage()

La variable form contiendra alors toutes les données fournies par le serveur web.

Ayant à gérer les lettres accentuées, il faut s'assurer que toutes les données seront encodées avec utf-8 :

import codecs
import sys
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())

Une différence majeure avec l'écriture d'une simple page html est la construction du header de la requête. En effet, avec un site dynamique, il est possible de contrôler le contenu de ce dernier, et il faut en particulier préciser quel sera le contenu fourni par le programme. Ici, ce sera une page html :

print("Content-type: text/html\n")

La ligne vide ajoutée ici est celle qui sépare le header du code html à proprement parler.

Pour faciliter le débogage du programme, il peut être utile d'afficher les traces des exceptions dans la page html. Ces lignes devront être commentées lors de la mise en production du site, car elles peuvent fournir des informations sur le code exécuté sur le serveur.

import cgitb
cgitb.enable()

Il est à noter qu'il faut d'abord terminer le header puis exécuter la commande permettant d'afficher les traces, sans quoi rien ne s'affichera sur l'écran.

Une fois ces considérations prises en compte, il ne reste plus qu'à construire un programme mettant en œuvre les vérifications d'usage et s'assurer du bon comportement du programme dans toutes les situations.

Quelques commandes intéressantes pour construire le programme

Pour récupérer une variable qui serait transmise par l'utilisateur, il est possible d'employer la commande ci-dessous. Il faudrait alors ajouter un champ dans lequel l'utilisateur peut entrer une donnée.

variable = form.getvalue("variable")

Les données liées au fichier fourni peuvent être stockées dans une variable fileitem :

fileitem = form["myfile"]

Ensuite, ce fichier peut être écrit sur le disque à l'emplacement path/filename à l'aide de l'unique ligne :

open("{}/{}".format(path, filename), 'wb').write(fileitem.file.read())

Il faudra à la fin de l'exécution du script afficher un retour à l'utilisateur. Une méthode pour faire cela est d'afficher sur son écran une page html, dont le message dépendra de la réussite ou de l'échec du transfert de données par exemple :

print("""<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Titre de la page</title>
<link rel="stylesheet" type="text/css" href="/style.css" />
</head>
<body>
<p>{}</p>
<p><a href="/">Retour</a></p>
</body>
</html>""".format(message))

Il faut noter que si cette page affiche des données fournies par l'utilisateur, il est indispensable de se protéger contre la faille XSS. Cela est possible en transformant tous les symboles < > & " qui pourraient être interprétés comme du code html par le navigateur. La bibliothèque html fournit une commande permettant de faire cela simplement :

import html
html.escape("chaine de caractère fournie par l'utilisateur")