Skip to content

Programmation CGI en Python

Scripting web hérité avec Python (Comprendre les concepts CGI)

Section titled “Scripting web hérité avec Python (Comprendre les concepts CGI)”

Remarque : Ce tutoriel aborde les concepts du Common Gateway Interface (CGI) en utilisant Python. Bien que le CGI ait été historiquement important, il n’est plus la norme pour le développement web Python moderne. Des frameworks comme Flask, Django et FastAPI, utilisant les interfaces WSGI ou ASGI, sont l’approche recommandée aujourd’hui. Comprendre les concepts du CGI peut fournir un contexte historique utile et un aperçu de la manière dont les serveurs web interagissent avec les scripts backend.

Le Common Gateway Interface (CGI) est un protocole standard qui définit comment les serveurs web peuvent exécuter des scripts externes (comme les programmes Python) pour générer dynamiquement du contenu web. Il spécifie comment les informations sont échangées entre le serveur et le script.

  • Le CGI offrait un moyen standard pour les serveurs web (comme Apache ou Nginx) d’exécuter des programmes externes (scripts CGI) en réponse aux requêtes web entrantes.
  • Au lieu de simplement servir des fichiers statiques, le serveur pouvait exécuter un script, et la sortie de ce script était renvoyée au navigateur de l’utilisateur.

Comprendre l’interaction de base aide à saisir le rôle joué par le CGI :

  • Le navigateur d’un utilisateur demande une URL spécifique à un serveur web.
  • Le serveur web identifie si l’URL pointe vers un fichier statique ou vers un script configuré pour être exécuté via une interface comme le CGI.
  • S’il s’agit d’un script, le serveur l’exécute, transmettant les détails de la requête (comme les données de formulaire ou les paramètres de requête) généralement via des variables d’environnement et l’entrée standard.
  • Le script traite la requête, effectue des actions (par exemple, des requêtes de base de données) et génère une réponse HTTP (y compris des en-têtes comme Content-Type et le corps HTML).
  • Le script affiche cette réponse sur la sortie standard.
  • Le serveur web capture la sortie du script et la renvoie au navigateur.
  • Le navigateur affiche le contenu reçu.

Dans le modèle CGI, chaque requête lançait typiquement un nouveau processus pour le script, ce qui était inefficace. Les approches modernes (WSGI/ASGI) utilisent des serveurs d’applications persistants pour de meilleures performances.

Imaginez ce flux :

  1. Le navigateur envoie la requête (par exemple, pour /scripts/my_script.py) ->
  2. Le serveur web reçoit la requête ->
  3. Le serveur web identifie qu’il doit exécuter my_script.py via CGI ->
  4. Le serveur web démarre my_script.py, transmettant les données de la requête ->
  5. my_script.py s’exécute, génère une sortie HTML ->
  6. my_script.py affiche le HTML sur la sortie standard ->
  7. Le serveur web capture la sortie ->
  8. Le serveur web renvoie la sortie au navigateur ->
  9. Le navigateur affiche le HTML.

Configuration du serveur web (Contexte historique)

Section titled “Configuration du serveur web (Contexte historique)”

Traditionnellement, les serveurs web nécessitaient une configuration spécifique pour activer l’exécution CGI, désignant souvent des répertoires spécifiques (comme cgi-bin) où les scripts exécutables pouvaient être placés. Les permissions devaient également être définies correctement.

Les frameworks web Python modernes s’exécutent typiquement via des serveurs WSGI ou ASGI (comme Gunicorn, uWSGI, Hypercorn, Uvicorn), qui sont configurés différemment et gèrent le cycle de vie de l’application plus efficacement, souvent derrière un proxy inverse comme Nginx.

Exemple de script simple (Illustrant la sortie)

Section titled “Exemple de script simple (Illustrant la sortie)”

Cet exemple montre le type de sortie qu’un script CGI devait produire. Notez l’en-tête Content-Type essentiel suivi d’une ligne vide, puis du corps HTML. Les frameworks modernes gèrent la génération de réponses plus élégamment.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Affiche l'en-tête HTTP obligatoire
# Notez la ligne vide séparant l'en-tête du corps
print("Content-Type: text/html\n")
# Affiche le contenu HTML
print("<!DOCTYPE html>")
print("<html>")
print("<head>")
print("<title>Bonjour depuis le script Python</title>")
print("</head>")
print("<body>")
print("<h2>Bonjour le monde ! Ceci est la sortie du script.</h2>")
print("</body>")
print("</html>")

S’il était exécuté par un serveur web configuré pour le CGI, ce script enverrait une page HTML de base au navigateur.

La ligne Content-Type: text/html est un en-tête HTTP crucial. Elle indique au navigateur le type de contenu à attendre. De nombreux autres en-têtes existent pour contrôler la mise en cache, la redirection, les cookies, etc. Les scripts CGI devaient afficher ces en-têtes manuellement avant le contenu réel.

En-têtes HTTP courants (Conceptuel) :

En-têteDescription
Content-TypeSpécifie le type MIME du corps de la réponse (par exemple, text/html, application/json, image/png). Essentiel.
LocationUtilisé pour la redirection. Indique au navigateur de demander une URL différente.
Set-CookieEnvoie un cookie depuis le serveur pour être stocké par le navigateur.
Cache-ControlFournit des directives pour les mécanismes de mise en cache (par exemple, no-cache).

Les frameworks modernes fournissent des fonctions d’aide ou des objets pour définir les en-têtes de réponse sans impression manuelle.

Variables d’environnement CGI (Conceptuel)

Section titled “Variables d’environnement CGI (Conceptuel)”

Le serveur web transmettait les informations de requête au script CGI via des variables d’environnement. Bien que l’accès direct soit rare aujourd’hui, comprendre ces variables montre quelles données sont disponibles pendant une requête.

Nom de la variable (Conceptuel)Description
REQUEST_METHODLa méthode HTTP utilisée (par exemple, ‘GET’, ‘POST’).
QUERY_STRINGLa partie de l’URL après ’?’ contenant les paramètres GET.
CONTENT_TYPELe type MIME du corps de la requête (important pour les requêtes POST, par exemple, application/x-www-form-urlencoded).
CONTENT_LENGTHLa longueur du corps de la requête en octets (important pour les requêtes POST).
SCRIPT_NAMELe chemin du script en cours d’exécution.
REMOTE_ADDRL’adresse IP du client effectuant la requête.
HTTP_USER_AGENTLa chaîne d’identification du navigateur/client.
HTTP_COOKIECookies envoyés par le client.
SERVER_NAMELe nom d’hôte ou l’adresse IP du serveur.
SERVER_PORTLe port sur lequel le serveur a reçu la requête.

Les frameworks modernes fournissent ces informations via des objets de requête dédiés (par exemple, request.method, request.args, request.headers, request.cookies dans Flask ou Django).

Exemple : Accès aux informations d’environnement (Conceptuel)

#!/usr/bin/env python3
import os
print("Content-Type: text/html\n")
print("<!DOCTYPE html><html><head><title>Infos Environnement</title></head><body>")
print("<h1>Environnement de la requête (Conceptuel)</h1>")
print("<pre>")
# Dans un contexte CGI réel, ces variables seraient remplies par le serveur.
# Dans les frameworks modernes, l'accès se fait via un objet de requête.
for param, value in os.environ.items():
# Affichage de quelques-unes des plus pertinentes pour l'illustration
if param in ['REQUEST_METHOD', 'QUERY_STRING', 'REMOTE_ADDR', 'HTTP_USER_AGENT']:
print(f"**{param}:** {value}<br>")
print("</pre>")
print("</body></html>")

Ce sont les deux méthodes HTTP les plus courantes pour envoyer des données d’un navigateur au serveur :

Les données sont ajoutées à l’URL sous forme de chaîne de requête (par exemple, /search?query=python&page=1).

  • Visible dans la barre d’adresse du navigateur et les journaux du serveur.
  • Convient aux données non sensibles comme les termes de recherche ou les numéros de page.
  • Longueur limitée (dépend du navigateur/serveur, souvent autour de 2048 caractères).
  • Idempotente (la répétition de la requête devrait idéalement avoir le même effet).
  • Dans le CGI, les données se trouvaient dans la variable d’environnement QUERY_STRING.
  • Dans les frameworks modernes, l’accès se fait via des attributs d’objet de requête comme request.args.

Les données sont envoyées dans le corps de la requête HTTP.

  • Non visible dans l’URL.
  • Convient aux données sensibles (comme les mots de passe) ou aux grandes quantités de données (par exemple, téléchargements de fichiers, formulaires longs).
  • Pas de limite de longueur pratique.
  • Pas nécessairement idempotente (la répétition pourrait exécuter à nouveau l’action, par exemple, soumettre une commande).
  • Dans le CGI, les données étaient lues depuis l’entrée standard, avec une longueur spécifiée par CONTENT_LENGTH.
  • Dans les frameworks modernes, l’accès se fait via des attributs comme request.form (pour les données de formulaire) ou request.data/request.json (pour les corps bruts/JSON).

Gestion des données de formulaire (Conceptuel)

Section titled “Gestion des données de formulaire (Conceptuel)”

Les formulaires web permettent aux utilisateurs de saisir des données. Lorsqu’ils sont soumis, ces données sont envoyées en utilisant la méthode GET ou POST.

Exemple de formulaire HTML :

<!DOCTYPE html>
<html>
<head><title>Form Example</title></head>
<body>
<form action="/process-form" method="post">
<label for="fname">First Name:</label>
<input type="text" id="fname" name="first_name"><br><br>
<label for="lname">Last Name:</label>
<input type="text" id="lname" name="last_name"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

Extrait Python (Illustrant la logique d’accès aux données - Style Framework) :

# Supposons que 'request' est un objet fourni par un framework web
# Exemple d'accès aux données POST (par exemple, style Flask/Django)
# first_name = request.form.get('first_name', 'Guest')
# last_name = request.form.get('last_name', '')
# Exemple d'accès aux données GET (par exemple, style Flask/Django)
# query = request.args.get('query', '')
def process_form_data(form_data):
"""Fonction conceptuelle pour traiter les données de formulaire analysées."""
first_name = form_data.get('first_name', 'Guest')
last_name = form_data.get('last_name', '')
# Génère la réponse (les frameworks gèrent les détails de l'en-tête)
response_body = f"<!DOCTYPE html><html><head><title>Formulaire Reçu</title></head>"
response_body += f"<body><h2>Bonjour {first_name} {last_name}</h2></body></html>"
return response_body
# Dans une vraie application, le framework routerait la requête et
# fournirait les données de formulaire analysées (par exemple, request.form) à une fonction de gestion.
# Appel simulé d'exemple :
# received_data = {'first_name': 'Zara', 'last_name': 'Ali'}
# html_output = process_form_data(received_data)
# print(html_output) # Le framework enverrait ceci comme réponse

Gestion des différents éléments de formulaire (Conceptuel)

Section titled “Gestion des différents éléments de formulaire (Conceptuel)”

Les cases à cocher, les boutons radio, les zones de texte et les listes déroulantes sont gérés de manière similaire : leur attribut name devient la clé, et leur attribut value (ou le contenu pour les textarea) devient la valeur. Les cases à cocher peuvent envoyer plusieurs valeurs pour le même nom si elles sont sélectionnées, ou ne pas envoyer la clé du tout si elles sont désélectionnées. Les frameworks les analysent en structures de données appropriées (souvent des objets de type dictionnaire).

Les cookies sont de petits morceaux de données stockés par le navigateur, renvoyés au serveur avec les requêtes ultérieures. Ils sont utilisés pour maintenir l’état (comme les sessions utilisateur) à travers des requêtes HTTP sans état.

  • Définition des cookies : Effectuée via l’en-tête de réponse HTTP Set-Cookie. Inclut le nom/valeur du cookie et des attributs optionnels comme Expires, Max-Age, Domain, Path, Secure, HttpOnly, SameSite.
  • Récupération des cookies : Le navigateur renvoie les cookies stockés au serveur dans l’en-tête de requête HTTP Cookie.
  • Pratique moderne : Les frameworks offrent des méthodes pour définir et récupérer facilement les cookies (par exemple, response.set_cookie(...) et request.cookies.get(...)).

Les formulaires HTML avec enctype="multipart/form-data" et <input type="file"> permettent aux utilisateurs de téléverser des fichiers.

Le serveur reçoit les données du fichier dans le corps de la requête. À l’époque du CGI, l’analyse des données multiparties était complexe. Les frameworks modernes facilitent l’accès aux fichiers téléversés via l’objet de requête (par exemple, request.files dans Flask/Django), gérant les complexités de l’analyse.

Exemple : Traitement d’un fichier téléversé (Logique de style Framework)

# Supposons que 'request' est un objet de requête de framework
# Supposons que 'secure_filename' est un utilitaire pour assainir les noms de fichiers
# Supposons que UPLOAD_FOLDER est un chemin configuré
# file_storage = request.files.get('filename')
def save_uploaded_file(file_storage):
message = "Aucun fichier n'a été téléversé."
if file_storage and file_storage.filename:
try:
# Il est crucial d'assainir les noms de fichiers provenant de l'entrée utilisateur
# filename = secure_filename(file_storage.filename)
# save_path = os.path.join(UPLOAD_FOLDER, filename)
# file_storage.save(save_path)
filename = file_storage.filename # Dans le code réel, assainissez ceci !
message = f'Le fichier "{filename}" a été téléversé avec succès (simulé).'
# Dans une vraie application, vous utiliseriez file_storage.save()
except Exception as e:
message = f"Une erreur s'est produite : {e}"
response_body = f"<!DOCTYPE html><html><body><p>{message}</p></body></html>"
return response_body
# Appel simulé :
# class MockFileStorage:
# filename = 'example.txt'
# def save(self, path): print(f'Sauvegarde simulée vers {path}')
#
# uploaded_file = MockFileStorage()
# html_output = save_uploaded_file(uploaded_file)
# print(html_output)

Note de sécurité : Validez et assainissez toujours les entrées utilisateur, en particulier les noms de fichiers et le contenu des fichiers, afin de prévenir les vulnérabilités de sécurité comme le directory traversal (traversée de répertoire).

Téléchargements de fichiers (Conceptuel)

Section titled “Téléchargements de fichiers (Conceptuel)”

Pour inciter un navigateur à télécharger un fichier au lieu de l’afficher, le serveur envoie des en-têtes HTTP spécifiques :

  • Content-Type: application/octet-stream (ou un type MIME plus spécifique si connu) indique au navigateur qu’il s’agit de données binaires.
  • Content-Disposition: attachment; filename="your_file_name.ext" suggère le nom de fichier pour la boîte de dialogue de téléchargement.

Les frameworks modernes fournissent généralement des fonctions d’aide (par exemple, send_file dans Flask) pour générer ces en-têtes et diffuser correctement le contenu du fichier.

Bien que la programmation CGI directe en Python soit dépassée, les concepts sous-jacents de gestion des requêtes, de traitement des données, de gestion de l’état (cookies) et de génération de réponses restent fondamentaux pour le développement web. Les frameworks web Python modernes s’appuient sur ces concepts, offrant des abstractions beaucoup plus efficaces, sécurisées et conviviales pour les développeurs.

Pour aller plus loin :

  • Explorez les frameworks web Python populaires : Flask, Django, FastAPI.
  • Découvrez WSGI (Web Server Gateway Interface) et ASGI (Asynchronous Server Gateway Interface).