0%
Server-Side Python: From CGI to Modern WSGI Applications

Server-Side Python: From CGI to Modern WSGI Applications

Explore the evolution of web development in Python: CGI, its limitations, WSGI, and the transition to Flask/FastAPI for modern, high-performance applications.

I

InSkillCoach

· min

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:

  1. The client sends an HTTP request to the web server
  2. The web server starts a new process to run the CGI script
  3. The script reads its input (environment variables, stdin)
  4. The script generates output (HTTP headers + body)
  5. The web server sends the response back to the client
  6. 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

LimitationImpact
New process per requestEnormous startup cost; impossible to scale
cgi module deprecated, then removedDeprecated with a warning since Python 3.11; completely removed in Python 3.13
No persistent stateDatabase connections are reopened on every request
No optimizationsNo caching, no worker pool, no concurrency
Security left to the developerNo 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

AspectCGIWSGIASGI
ProcessNew one per requestOne persistent processOne or more workers
ModelSynchronous (bash/Perl/Python)Synchronous (Python)Asynchronous (async/await)
StartupVery slow (OS fork)Fast (in-memory function)Fast
State managementImpossiblePossible (cache, DB)Possible
ScalabilityPoorGoodExcellent
FrameworksNone (just cgi)Flask, DjangoFastAPI, Starlette
Usage examplesHistorical (1990s)Blogs, MVPs, traditional applicationsReal-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 cgi module 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.

InSkillCoach

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+
1.1k
231

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!