Python côté Serveur : du CGI aux Applications WSGI Modernes
L’interface Common Gateway Interface (CGI) a été la première façon de générer du contenu dynamique sur le web dans les années 1990. Bien que révolutionnaire à l’époque, CGI souffre de limitationsmajeure : chaque requête lance un nouveau processus Python, ce qui est inefficace et impossible à l’échelle. WSGI (Web Server Gateway Interface) a introduit un modèle plus efficace en 2003. Aujourd’hui, les frameworks WSGI comme Flask et les serveurs ASGI comme FastAPI dominent le paysage. Ce guide trace cette évolution et vous montre comment migrer du CGI vers les technologies modernes.
1. CGI : Les Origines
Qu’est-ce que CGI ?
CGI est un standard qui définit comment un serveur web (Apache, Nginx) exécute un programme externe (script CGI) pour traiter une requête HTTP et générer une réponse.
Le flux CGI :
- Client envoie une requête HTTP au serveur web
- Serveur web démarre un nouveau processus pour exécuter le script CGI
- Le script lit l’entrée (variables d’environnement, stdin)
- Le script génère une sortie (headers HTTP + body)
- Le serveur web renvoie la réponse au client
- Le processus se termine
Exemple minimal CGI
Supposons un serveur web configuré pour exécuter les scripts .cgi dans /cgi-bin/.
#!/usr/bin/env python
# Fichier : /cgi-bin/hello.cgi
import cgi
import cgitb
# Activer les rapports d'erreur CGI
cgitb.enable()
print("Content-Type: text/html; charset=utf-8")
print()
print("<html><body>")
print("<h1>Bonjour depuis CGI !</h1>")
print("</body></html>")
# Résultat : le serveur web envoie une réponse HTML au navigateur
Formulaire CGI avec traitement
#!/usr/bin/env python
# Fichier : /cgi-bin/form_handler.cgi
import cgi
import html
# Parser les données du formulaire
form = cgi.FieldStorage()
nom = form.getvalue("nom", "Visiteur inconnu")
# Échapper le contenu pour éviter les injections XSS
nom_safe = html.escape(nom)
print("Content-Type: text/html; charset=utf-8")
print()
print("<html><body>")
print(f"<h1>Bienvenue, {nom_safe} !</h1>")
print("</body></html>")
# Requête : GET /form_handler.cgi?nom=Alice
# Réponse : <h1>Bienvenue, Alice !</h1>
Limitations du CGI
| Limitation | Impact |
|---|---|
| Nouveau processus par requête | Coût de démarrage énorme ; impossible à l’échelle |
Module cgi déprécié puis retiré | Depuis Python 3.11, avec avertissement ; totalement supprimé en Python 3.13 |
| Pas d’état persistant | Les connexions à la base de données se réouvrent à chaque requête |
| Pas d’optimisations | Pas de cache, pas de pool de workers, pas de concurrence |
| Sécurité à la charge du développeur | Pas de protection intégrée contre les injections, CSRF, etc. |
2. WSGI : Un Modèle Meilleur
Qu’est-ce que WSGI ?
WSGI (PEP 3333) est une interface standardisée entre un serveur web et une application Python. Contrairement à CGI, l’application reste en mémoire entre les requêtes, ce qui élimine le coût de démarrage.
Une application WSGI est une fonction ou classe callable qui accepte deux arguments :
def application(environ, start_response):
"""
environ : dictionnaire contenant les infos de la requête HTTP
start_response : fonction pour envoyer les headers de réponse
Retour : itérable de bytes (le body de la réponse)
"""
status = "200 OK"
response_headers = [("Content-Type", "text/html; charset=utf-8")]
start_response(status, response_headers)
body = "<html><body><h1>Bonjour WSGI !</h1></body></html>"
return [body.encode("utf-8")]
# Résultat : application plus efficace que CGI
Exemple WSGI avec formulaire
from urllib.parse import parse_qs
import html
def app(environ, start_response):
"""Application WSGI simple avec traitement de formulaire."""
method = environ["REQUEST_METHOD"]
path = environ.get("PATH_INFO", "/")
if method == "GET" and path == "/":
# Page d'accueil avec formulaire
body = """
<html>
<body>
<h1>Entrez votre nom</h1>
<form method="post" action="/submit">
<input type="text" name="nom" required>
<button type="submit">Envoyer</button>
</form>
</body>
</html>
""".encode("utf-8")
start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])
return [body]
elif method == "POST" and path == "/submit":
# Traiter les données du formulaire
content_length = int(environ.get("CONTENT_LENGTH", 0))
body = environ["wsgi.input"].read(content_length).decode("utf-8")
data = parse_qs(body)
nom = data.get("nom", ["Visiteur"])[0]
nom_safe = html.escape(nom)
response = f"<html><body><h1>Bonjour, {nom_safe} !</h1></body></html>".encode("utf-8")
start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])
return [response]
else:
start_response("404 Not Found", [("Content-Type", "text/plain")])
return [b"Page non trouvée"]
# Exécuter avec wsgiref (serveur de développement inclus)
# python -m wsgiref.simple_server --port 8000 app:app
# Accédez à http://localhost:8000
Servir une application WSGI brute avec wsgiref
# Fichier : myapp.py
def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
return [b"Hello from WSGI!"]
if __name__ == "__main__":
from wsgiref.simple_server import make_server
server = make_server("localhost", 8000, app)
print("Serveur WSGI lancé sur http://localhost:8000")
server.serve_forever()
# Exécution :
# python myapp.py
# Puis accédez à http://localhost:8000
3. http.server pour le Prototypage
Le module http.server de la stdlib offre un serveur HTTP simple, utile pour tester rapidement un prototype.
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class MonHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(b"<h1>Accueil</h1>")
elif self.path == "/api/info":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
data = {"status": "ok", "message": "Réponse JSON"}
self.wfile.write(json.dumps(data).encode("utf-8"))
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not found")
if __name__ == "__main__":
server = HTTPServer(("localhost", 8000), MonHandler)
print("Serveur lancé sur http://localhost:8000")
server.serve_forever()
# Résultat : serveur simple mais sans cache, gestion d'erreur minimale, etc.
4. CGI vs WSGI vs ASGI : Comparaison
| Aspect | CGI | WSGI | ASGI |
|---|---|---|---|
| Processus | Nouveau par requête | Un processus persistant | Un/plusieurs workers |
| Modèle | Synchrone (bash/Perl/Python) | Synchrone (Python) | Asynchrone (async/await) |
| Démarrage | Très lent (fork OS) | Rapide (fonction en mémoire) | Rapide |
| Gestion d’état | Impossible | Possible (cache, BD) | Possible |
| Scalabilité | Mauvaise | Bonne | Excellente |
| Frameworks | Aucun (juste cgi) | Flask, Django | FastAPI, Starlette |
| Exemples d’usage | Historique (1990s) | Blogs, MVPs, applications classiques | APIs temps réel, WebSockets |
5. Migration CGI vers Flask
Voici comment refactoriser un script CGI simple en Flask moderne.
Avant : CGI
#!/usr/bin/env python
# cgi-bin/calculator.cgi
import cgi
form = cgi.FieldStorage()
a = float(form.getvalue("a", 0))
b = float(form.getvalue("b", 0))
operation = form.getvalue("op", "add")
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
else:
result = 0
print("Content-Type: text/html; charset=utf-8")
print()
print(f"<h1>Résultat : {result}</h1>")
Après : Flask
# app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/calculate", methods=["GET", "POST"])
def calculate():
a = float(request.args.get("a", 0))
b = float(request.args.get("b", 0))
operation = request.args.get("op", "add")
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
else:
result = 0
# Répondre en JSON (moderne) au lieu d'HTML
return jsonify({"a": a, "b": b, "operation": operation, "result": result})
if __name__ == "__main__":
# pip install flask
# python app.py
# Accédez à http://localhost:5000/calculate?a=10&b=5&op=add
app.run(debug=True, port=5000)
# Avantages :
# - Pas de parsing manuel
# - Réponse JSON structurée
# - Gestion d'erreur intégrée
# - Extensible facilement (middleware, templates, BD)
6. Bonnes Pratiques
- Quittez CGI. Le module
cgiest supprimé depuis Python 3.13. Aucune raison de rester dessus. - Choisissez un framework WSGI (Flask, Django) ou ASGI (FastAPI, Starlette). Ne codez pas de WSGI brut en production.
- Utilisez un serveur applicatif : Gunicorn (WSGI) ou Uvicorn (ASGI), pas le serveur de développement.
- Versionnez vos dépendances :
pip freeze > requirements.txtou utilisez Poetry/Pipenv. - Gérez l’état avec une base de données, pas en mémoire. Les workers sont stateless.
- Testez votre application. Écrivez des tests unitaires et d’intégration.
Conclusion
Le CGI, bien qu’historique et élégant à son époque, est devenu obsolète. WSGI a apporté l’efficacité grâce à des processus persistants. Aujourd’hui, ASGI et des frameworks comme FastAPI poussent encore plus loin avec l’asynchrone et le support natif des WebSockets. Si vous maintenez du code CGI hérité, la migration vers Flask ou FastAPI est une étape importante pour maintenir la sécurité, la performance et la maintenabilité. Pour les nouveaux projets, partez directement de FastAPI pour bénéficier des standards modernes et des performances optimales.
À propos de InSkillCoach
Expert en formation et technologies
Coach spécialisé dans les technologies avancées et l'IA, porté par GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Certifications Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion