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

    Terminal interactivo que representa un REPL personalizado creado con el módulo code en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    code en Python: crea un REPL personalizado

    Aprende el módulo code en Python para crear REPLs personalizados, controlar namespaces, prompts, salida, bloques incompletos y cierre local.

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Protocolo seguro de Internet que representa preparación Unicode con stringprep en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    stringprep en Python: prepara Unicode

    Aprende stringprep en Python para aplicar tablas RFC 3454, mapear Unicode, rechazar caracteres prohibidos y validar reglas bidireccionales.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Red de conexiones que representa I/O no bloqueante con selectors en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    selectors en Python: I/O no bloqueante

    Aprende selectors en Python para monitorizar muchos sockets, eventos de lectura y escritura, timeouts y conexiones no bloqueantes con seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026
    Flujo de datos en red que representa contexto asíncrono con contextvars en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextvars en Python: contexto asíncrono

    Aprende contextvars en Python para guardar estado por tarea, evitar fugas en asyncio, copiar contextos y restaurar valores con tokens.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Código de programación que representa operaciones como funciones con operator en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator en Python: operaciones como funciones

    Aprende operator en Python para usar operaciones como funciones, ordenar campos, acceder a elementos, llamar métodos y crear pipelines claros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026
    Alfabeto tridimensional que representa normalización Unicode con unicodedata en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    unicodedata en Python: normaliza Unicode

    Aprende unicodedata en Python para normalizar Unicode, consultar nombres, categorías, números, marcas combinantes y ancho de visualización.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026