El paquete wsgiref contiene utilidades y una implementación de referencia de WSGI, la interfaz síncrona tradicional entre aplicaciones web Python y servidores. Permite crear aplicaciones mínimas, preparar entornos de prueba, manipular cabeceras, validar conformidad y ejecutar un servidor HTTP simple durante desarrollo.
La documentación incluye una advertencia importante: wsgiref no está recomendado para producción y solo realiza comprobaciones básicas de seguridad. Úsalo para aprender, probar, validar middleware y crear prototipos locales. Para tráfico público, utiliza un servidor WSGI mantenido con concurrencia, TLS, timeouts, límites y monitorización.
Contrato de una aplicación WSGI
Una aplicación WSGI es un callable que recibe environ y start_response. Debe llamar start_response() con estado y cabeceras y devolver un iterable de bytes.
def app(environ, start_response):
cuerpo = b"Hola, WSGI!\n"
start_response(
"200 OK",
[
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(cuerpo))),
],
)
return [cuerpo]Devolver una cadena Unicode viola la especificación. Los fragmentos del cuerpo deben ser bytes.
Ejecutar el servidor de referencia
from wsgiref.simple_server import make_server
with make_server("127.0.0.1", 8000, app) as servidor:
print("http://127.0.0.1:8000")
servidor.serve_forever()Este servidor sirve para ejemplos y pruebas locales. No ofrece la robustez, el rendimiento ni el endurecimiento de una plataforma de producción.
El diccionario environ
environ contiene variables CGI y claves WSGI. Entre las más comunes están REQUEST_METHOD, PATH_INFO, QUERY_STRING, CONTENT_TYPE, CONTENT_LENGTH, SERVER_NAME, SERVER_PORT, wsgi.input, wsgi.errors y wsgi.url_scheme.
def app(environ, start_response):
metodo = environ.get("REQUEST_METHOD", "GET")
ruta = environ.get("PATH_INFO", "/")
cuerpo = f"{metodo} {ruta}\n".encode("utf-8")
start_response("200 OK", [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(cuerpo))),
])
return [cuerpo]Los valores de la solicitud no son confiables. Valida método, ruta, longitud, cabeceras y cuerpo.
Crear un entorno de prueba
setup_testing_defaults() rellena valores WSGI mínimos y falsos para tests. No debe usarse en servidores reales.
from wsgiref.util import setup_testing_defaults
environ = {}
setup_testing_defaults(environ)
environ["REQUEST_METHOD"] = "POST"
environ["PATH_INFO"] = "/items"Cuando haya cuerpo, proporciona un stream binario como wsgi.input.
Probar sin abrir un puerto
from io import BytesIO
from wsgiref.util import setup_testing_defaults
capturado = {}
def start_response(status, headers, exc_info=None):
capturado["status"] = status
capturado["headers"] = headers
environ = {}
setup_testing_defaults(environ)
environ["PATH_INFO"] = "/test"
environ["wsgi.input"] = BytesIO(b"")
cuerpo = b"".join(app(environ, start_response))
assert capturado["status"] == "200 OK"Las pruebas directas son más rápidas y deterministas que iniciar un servidor en cada caso.
Validar conformidad
wsgiref.validate.validator() envuelve una aplicación y comprueba muchos requisitos del protocolo.
from wsgiref.validate import validator
app_validada = validator(app)Una infracción detectada suele generar AssertionError. Superar el validator no demuestra conformidad completa, pero un error reportado normalmente es real.
Manipular cabeceras
wsgiref.headers.Headers envuelve una lista de pares y trata nombres sin distinguir mayúsculas.
from wsgiref.headers import Headers
headers = Headers([])
headers["Content-Type"] = "text/plain; charset=utf-8"
headers.add_header(
"Content-Disposition",
"attachment",
filename="informe.txt",
)
lista = headers.items()A diferencia de un diccionario, las cabeceras pueden repetirse. Usa get_all() para campos multivalor como Set-Cookie.
Cabeceras hop-by-hop
is_hop_by_hop() identifica cabeceras asociadas a una conexión que no deben emitirse como cabeceras normales de respuesta WSGI.
from wsgiref.util import is_hop_by_hop
print(is_hop_by_hop("Connection"))Reconstruir URLs
request_uri() reconstruye la URI completa. application_uri() devuelve la URI base de la aplicación.
from wsgiref.util import request_uri, application_uri
url = request_uri(environ)
base = application_uri(environ)Detrás de un proxy, el entorno puede describir el salto interno. Confía en cabeceras reenviadas solo con una configuración explícita de proxies fiables.
Enrutar con shift_path_info
shift_path_info() mueve un segmento de PATH_INFO a SCRIPT_NAME y modifica el diccionario.
from wsgiref.util import shift_path_info
def router(environ, start_response):
child = environ.copy()
segmento = shift_path_info(child)
if segmento == "api":
return api_app(child, start_response)
return not_found(child, start_response)Usa una copia si otras capas necesitan la ruta original.
Leer el cuerpo con límites
wsgi.input es un stream de bytes. Lee solo la cantidad declarada y permitida.
def leer_cuerpo(environ, limite=1_000_000):
bruto = environ.get("CONTENT_LENGTH", "")
try:
longitud = int(bruto) if bruto else 0
except ValueError:
raise ValueError("Content-Length inválido")
if longitud > limite:
raise ValueError("cuerpo demasiado grande")
return environ["wsgi.input"].read(longitud)Nunca uses read() sin límite sobre entrada no confiable.
Respuestas de error
def respuesta(status, texto):
cuerpo = texto.encode("utf-8")
return status, [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(cuerpo))),
], [cuerpo]No expongas tracebacks ni variables internas. Registra detalles en un canal protegido y devuelve un mensaje genérico.
Middleware WSGI
Middleware recibe una aplicación y devuelve otra.
def agregar_header(app):
def middleware(environ, start_response):
def iniciar(status, headers, exc_info=None):
headers.append(("X-App", "demo"))
return start_response(status, headers, exc_info)
return app(environ, iniciar)
return middlewareDebe preservar el protocolo, propagar errores correctamente y cerrar iterables cuando corresponda.
Cerrar el iterable
Una aplicación puede devolver un iterable con método close(). Los servidores deben llamarlo y las pruebas manuales también.
resultado = app(environ, start_response)
try:
cuerpo = b"".join(resultado)
finally:
cerrar = getattr(resultado, "close", None)
if cerrar:
cerrar()Servir archivos con FileWrapper
FileWrapper convierte un archivo binario en iterable de bloques.
from wsgiref.util import FileWrapper
archivo = open("informe.pdf", "rb")
return FileWrapper(archivo, blksize=64 * 1024)Valida la ruta y define cabeceras correctas. En producción, el servidor puede ofrecer transmisión más eficiente.
Tipos estáticos
Desde Python 3.11, wsgiref.types incluye protocolos y aliases como WSGIApplication, WSGIEnvironment y StartResponse.
from wsgiref.types import WSGIApplication
aplicacion: WSGIApplication = appEl type checking puede detectar fragmentos Unicode, firmas incompatibles y cabeceras incorrectas.
WSGI es síncrono
WSGI modela una llamada síncrona por solicitud. Sigue siendo relevante para frameworks tradicionales, pero no modela WebSockets ni concurrencia asíncrona nativa como ASGI.
No devuelvas una coroutine desde WSGI. Elige la interfaz compatible con el framework y la carga.
Por qué simple_server no es producción
El servidor de referencia no es una plataforma endurecida para Internet. Carece de estrategias robustas de concurrencia, protección contra clientes lentos, límites completos, gestión de workers, operación TLS y observabilidad.
Ejemplo local completo
from wsgiref.simple_server import make_server
from wsgiref.validate import validator
def app(environ, start_response):
if environ.get("PATH_INFO") != "/":
cuerpo = b"Not Found\n"
start_response("404 Not Found", [
("Content-Type", "text/plain"),
("Content-Length", str(len(cuerpo))),
])
return [cuerpo]
cuerpo = b"Hello from WSGI\n"
start_response("200 OK", [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(cuerpo))),
])
return [cuerpo]
with make_server("127.0.0.1", 8000, validator(app)) as servidor:
servidor.serve_forever()Errores frecuentes
- Devolver strings en vez de bytes.
- Usar el servidor de referencia en producción.
- Leer cuerpos sin límite.
- Confiar en cabeceras de proxy arbitrarias.
- Exponer tracebacks.
- Modificar
environsin considerar middleware. - Ignorar el cierre del iterable.
Buenas prácticas
- Usa
validator()en tests. - Prueba la aplicación directamente.
- Define Content-Type y Content-Length.
- Devuelve bytes.
- Impone límites de entrada.
- Despliega con un servidor WSGI de producción.
- Elige ASGI para async nativo.
Guías relacionadas
Continúa con selectors en Python, contextvars en Python, platform en Python, sysconfig en Python y pydoc en Python.
Consulta la documentación oficial de wsgiref y la PEP 3333.
Conclusión
wsgiref es excelente para aprender WSGI, probar aplicaciones y validar conformidad. Expone claramente el contrato entre servidor y aplicación, pero su servidor simple nunca debe publicarse como servicio de producción. Trátalo como referencia y herramienta de desarrollo.







