wsgiref en Python: aplicaciones WSGI

Publicado el: 12/08/2026
Tempo de leitura: 4 minutos
Código de aplicación web que representa WSGI con wsgiref en Python

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 middleware

Debe 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 = app

El 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 environ sin 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026