Python en el servidor: del CGI a las aplicaciones WSGI modernas
La interfaz Common Gateway Interface (CGI) fue la primera forma de generar contenido dinámico en la web en los años 90. Aunque resultó revolucionaria en su momento, CGI sufre una limitación importante: cada petición lanza un nuevo proceso de Python, lo que es ineficiente e imposible de escalar. WSGI (Web Server Gateway Interface) introdujo en 2003 un modelo más eficiente. Hoy en día, los frameworks WSGI como Flask y los servidores ASGI como FastAPI dominan el panorama. Esta guía traza esa evolución y te muestra cómo migrar de CGI a las tecnologías modernas.
1. CGI: los orígenes
¿Qué es CGI?
CGI es un estándar que define cómo un servidor web (Apache, Nginx) ejecuta un programa externo (script CGI) para procesar una petición HTTP y generar una respuesta.
El flujo de CGI:
- El cliente envía una petición HTTP al servidor web
- El servidor web inicia un nuevo proceso para ejecutar el script CGI
- El script lee la entrada (variables de entorno, stdin)
- El script genera una salida (cabeceras HTTP + cuerpo)
- El servidor web devuelve la respuesta al cliente
- El proceso termina
Ejemplo mínimo de CGI
Supongamos un servidor web configurado para ejecutar los scripts .cgi que están en /cgi-bin/.
#!/usr/bin/env python
# Fichier : /cgi-bin/hello.cgi
import cgi
import cgitb
# Activar los informes de error de CGI
cgitb.enable()
print("Content-Type: text/html; charset=utf-8")
print()
print("<html><body>")
print("<h1>Bonjour depuis CGI !</h1>")
print("</body></html>")
# Resultado: el servidor web envía una respuesta HTML al navegador
Formulario CGI con procesamiento
#!/usr/bin/env python
# Fichier : /cgi-bin/form_handler.cgi
import cgi
import html
# Parsear los datos del formulario
form = cgi.FieldStorage()
nom = form.getvalue("nom", "Visiteur inconnu")
# Escapar el contenido para evitar inyecciones 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>")
# Petición: GET /form_handler.cgi?nom=Alice
# Respuesta: <h1>Bienvenue, Alice !</h1>
Limitaciones de CGI
| Limitación | Impacto |
|---|---|
| Nuevo proceso por petición | Coste de arranque enorme; imposible de escalar |
Módulo cgi obsoleto y luego eliminado | Obsoleto desde Python 3.11, con advertencia; eliminado por completo en Python 3.13 |
| Sin estado persistente | Las conexiones a la base de datos se vuelven a abrir en cada petición |
| Sin optimizaciones | Sin caché, sin pool de workers, sin concurrencia |
| Seguridad a cargo del desarrollador | Sin protección integrada contra inyecciones, CSRF, etc. |
2. WSGI: un modelo mejor
¿Qué es WSGI?
WSGI (PEP 3333) es una interfaz estandarizada entre un servidor web y una aplicación Python. A diferencia de CGI, la aplicación permanece en memoria entre peticiones, lo que elimina el coste de arranque.
Una aplicación WSGI es una función o clase invocable (callable) que acepta dos argumentos:
def application(environ, start_response):
"""
environ: diccionario con la información de la petición HTTP
start_response: función para enviar las cabeceras de la respuesta
Retorno: iterable de bytes (el cuerpo de la respuesta)
"""
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")]
# Resultado: aplicación más eficiente que CGI
Ejemplo de WSGI con formulario
from urllib.parse import parse_qs
import html
def app(environ, start_response):
"""Aplicación WSGI sencilla con procesamiento de formulario."""
method = environ["REQUEST_METHOD"]
path = environ.get("PATH_INFO", "/")
if method == "GET" and path == "/":
# Página de inicio con formulario
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":
# Procesar los datos del formulario
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"]
# Ejecutar con wsgiref (servidor de desarrollo incluido)
# python -m wsgiref.simple_server --port 8000 app:app
# Accede a http://localhost:8000
Servir una aplicación WSGI pura con 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()
# Ejecución:
# python myapp.py
# Después accede a http://localhost:8000
3. http.server para prototipado
El módulo http.server de la biblioteca estándar ofrece un servidor HTTP sencillo, útil para probar rápidamente un prototipo.
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()
# Resultado: servidor sencillo, pero sin caché, con gestión de errores mínima, etc.
4. CGI vs WSGI vs ASGI: comparación
| Aspecto | CGI | WSGI | ASGI |
|---|---|---|---|
| Procesos | Uno nuevo por petición | Un proceso persistente | Uno o varios workers |
| Modelo | Síncrono (bash/Perl/Python) | Síncrono (Python) | Asíncrono (async/await) |
| Arranque | Muy lento (fork del SO) | Rápido (función en memoria) | Rápido |
| Gestión de estado | Imposible | Posible (caché, BD) | Posible |
| Escalabilidad | Mala | Buena | Excelente |
| Frameworks | Ninguno (solo cgi) | Flask, Django | FastAPI, Starlette |
| Ejemplos de uso | Histórico (años 90) | Blogs, MVP, aplicaciones clásicas | APIs en tiempo real, WebSockets |
5. Migración de CGI a Flask
Veamos cómo refactorizar un script CGI sencillo a Flask moderno.
Antes: 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>")
Despué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
# Responder en JSON (moderno) en lugar de HTML
return jsonify({"a": a, "b": b, "operation": operation, "result": result})
if __name__ == "__main__":
# pip install flask
# python app.py
# Accede a http://localhost:5000/calculate?a=10&b=5&op=add
app.run(debug=True, port=5000)
# Ventajas:
# - Sin parseo manual
# - Respuesta JSON estructurada
# - Gestión de errores integrada
# - Fácilmente extensible (middleware, plantillas, BD)
6. Buenas prácticas
- Abandona CGI. El módulo
cgiestá eliminado desde Python 3.13. No hay ninguna razón para seguir usándolo. - Elige un framework WSGI (Flask, Django) o ASGI (FastAPI, Starlette). No programes WSGI puro en producción.
- Utiliza un servidor de aplicaciones: Gunicorn (WSGI) o Uvicorn (ASGI), no el servidor de desarrollo.
- Versiona tus dependencias:
pip freeze > requirements.txto utiliza Poetry/Pipenv. - Gestiona el estado con una base de datos, no en memoria. Los workers son stateless.
- Prueba tu aplicación. Escribe tests unitarios y de integración.
Conclusión
CGI, aunque histórico y elegante en su época, ha quedado obsoleto. WSGI aportó eficiencia gracias a los procesos persistentes. Hoy, ASGI y frameworks como FastAPI van aún más lejos con la asincronía y el soporte nativo de WebSockets. Si mantienes código CGI heredado, migrar a Flask o FastAPI es un paso importante para mantener la seguridad, el rendimiento y la mantenibilidad. Para los proyectos nuevos, parte directamente de FastAPI para beneficiarte de los estándares modernos y de un rendimiento óptimo.
Acerca de InSkillCoach
Experto en formación y tecnologías
Coach especializado en tecnologías avanzadas e IA, impulsado por GNeurone Inc.
Certificaciones:
- AWS Certified Solutions Architect – Professional
- Certificaciones de Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentarios
Los comentarios funcionan con GitHub Discussions
Inicia sesión con GitHub para participar en la discusión