Server-Side Python: From CGI to Modern WSGI Applications
The Common Gateway Interface (CGI) was the first way to generate dynamic content on the web in the 1990s. Although revolutionary at the time, CGI has a major limitation: every request launches a new Python process, which is inefficient and impossible to scale. WSGI (Web Server Gateway Interface) introduced a more efficient model in 2003. Today, WSGI frameworks like Flask and ASGI frameworks like FastAPI dominate the landscape. This guide traces that evolution and shows you how to migrate from CGI to modern technologies.
1. CGI: The Origins
What Is CGI?
CGI is a standard that defines how a web server (Apache, Nginx) runs an external program (a CGI script) to process an HTTP request and generate a response.
The CGI flow:
- The client sends an HTTP request to the web server
- The web server starts a new process to run the CGI script
- The script reads its input (environment variables, stdin)
- The script generates output (HTTP headers + body)
- The web server sends the response back to the client
- The process terminates
Minimal CGI Example
Suppose a web server is configured to run .cgi scripts in /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
CGI Form with Processing
#!/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>
CGI Limitations
| Limitation | Impact |
|---|---|
| New process per request | Enormous startup cost; impossible to scale |
cgi module deprecated, then removed | Deprecated with a warning since Python 3.11; completely removed in Python 3.13 |
| No persistent state | Database connections are reopened on every request |
| No optimizations | No caching, no worker pool, no concurrency |
| Security left to the developer | No built-in protection against injection, CSRF, etc. |
2. WSGI: A Better Model
What Is WSGI?
WSGI (PEP 3333) is a standardized interface between a web server and a Python application. Unlike CGI, the application stays in memory between requests, which eliminates the startup cost.
A WSGI application is a callable function or class that accepts two 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
WSGI Example with a Form
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
Serving a Raw WSGI Application with 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 for Prototyping
The standard library’s http.server module provides a simple HTTP server, useful for quickly testing a 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: Comparison
| Aspect | CGI | WSGI | ASGI |
|---|---|---|---|
| Process | New one per request | One persistent process | One or more workers |
| Model | Synchronous (bash/Perl/Python) | Synchronous (Python) | Asynchronous (async/await) |
| Startup | Very slow (OS fork) | Fast (in-memory function) | Fast |
| State management | Impossible | Possible (cache, DB) | Possible |
| Scalability | Poor | Good | Excellent |
| Frameworks | None (just cgi) | Flask, Django | FastAPI, Starlette |
| Usage examples | Historical (1990s) | Blogs, MVPs, traditional applications | Real-time APIs, WebSockets |
5. Migrating from CGI to Flask
Here is how to refactor a simple CGI script into modern Flask.
Before: 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>")
After: 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. Best Practices
- Leave CGI behind. The
cgimodule has been removed since Python 3.13. There is no reason to stay on it. - Choose a WSGI framework (Flask, Django) or an ASGI framework (FastAPI, Starlette). Don’t write raw WSGI in production.
- Use an application server: Gunicorn (WSGI) or Uvicorn (ASGI), not the development server.
- Pin your dependencies:
pip freeze > requirements.txt, or use Poetry/Pipenv. - Manage state with a database, not in memory. Workers are stateless.
- Test your application. Write unit and integration tests.
Conclusion
Although historic and elegant in its day, CGI has become obsolete. WSGI brought efficiency through persistent processes. Today, ASGI and frameworks like FastAPI push even further with asynchronous programming and native WebSocket support. If you maintain legacy CGI code, migrating to Flask or FastAPI is an important step toward preserving security, performance, and maintainability. For new projects, start directly with FastAPI to benefit from modern standards and optimal performance.
About InSkillCoach
Training and technology expert
Coach specializing in advanced technologies and AI, backed by GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Google Cloud certifications
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion