wsgiref no Python: aplicações WSGI

Publicado em: 12/08/2026
Tempo de leitura: 5 minutos
Código de aplicação web representando WSGI com wsgiref no Python

O pacote wsgiref contém utilitários e uma implementação de referência da especificação WSGI, a interface tradicional entre servidores web e aplicações Python síncronas. Ele permite construir aplicações mínimas, testar o dicionário environ, manipular headers, validar conformidade e executar um servidor HTTP simples durante desenvolvimento.

A própria documentação alerta: wsgiref não é recomendado para produção e implementa apenas verificações básicas de segurança. Use-o para aprendizado, testes unitários, validação de middleware e protótipos locais. Em produção, escolha um servidor WSGI mantido, configurado para concorrência, TLS, timeouts, limites e observabilidade.

Como funciona uma aplicação WSGI

Uma aplicação WSGI é um callable que recebe environ e start_response. Ela deve chamar start_response() com status e headers e devolver um iterável de bytes.

def app(environ, start_response):
    corpo = b"Ola, WSGI!\n"
    start_response(
        "200 OK",
        [
            ("Content-Type", "text/plain; charset=utf-8"),
            ("Content-Length", str(len(corpo))),
        ],
    )
    return [corpo]

Retornar uma string Unicode viola a especificação. O corpo deve ser bytes. O status precisa conter o código e a frase, como 200 OK.

Executar com simple_server

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()

O servidor é apropriado para testes locais e exemplos. Ele não oferece a robustez, o desempenho ou o endurecimento necessários para tráfego público.

O dicionário environ

environ contém variáveis CGI e chaves WSGI. Entre as mais comuns estão REQUEST_METHOD, PATH_INFO, QUERY_STRING, CONTENT_TYPE, CONTENT_LENGTH, SERVER_NAME, SERVER_PORT, wsgi.input, wsgi.errors e wsgi.url_scheme.

def app(environ, start_response):
    metodo = environ.get("REQUEST_METHOD", "GET")
    caminho = environ.get("PATH_INFO", "/")
    corpo = f"{metodo} {caminho}\n".encode("utf-8")
    start_response("200 OK", [
        ("Content-Type", "text/plain; charset=utf-8"),
        ("Content-Length", str(len(corpo))),
    ])
    return [corpo]

Não confie em valores derivados da requisição. Valide método, caminho, comprimento, conteúdo e headers antes de usar.

Criar environ para testes

setup_testing_defaults() preenche valores mínimos falsos para testes. Ele não deve ser usado por servidores reais.

from wsgiref.util import setup_testing_defaults

ambiente = {}
setup_testing_defaults(ambiente)
ambiente["REQUEST_METHOD"] = "POST"
ambiente["PATH_INFO"] = "/itens"

Em testes, forneça também wsgi.input como stream binário quando houver corpo.

Testar sem abrir uma porta

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

ambiente = {}
setup_testing_defaults(ambiente)
ambiente["PATH_INFO"] = "/teste"
ambiente["wsgi.input"] = BytesIO(b"")

corpo = b"".join(app(ambiente, start_response))
assert capturado["status"] == "200 OK"

Esse formato testa a função diretamente e é mais rápido que iniciar o servidor em cada caso.

Validar conformidade

wsgiref.validate.validator() envolve uma aplicação e verifica diversos requisitos do protocolo.

from wsgiref.validate import validator

app_validada = validator(app)

Quando encontra uma violação, o wrapper normalmente gera AssertionError. A ausência de erros não garante conformidade completa, mas um erro relatado é um forte sinal de problema.

Headers com wsgiref.headers

A classe Headers envolve uma lista de pares e trata nomes sem diferenciar maiúsculas.

from wsgiref.headers import Headers

headers = Headers([])
headers["Content-Type"] = "text/plain; charset=utf-8"
headers.add_header(
    "Content-Disposition",
    "attachment",
    filename="relatorio.txt",
)
lista = headers.items()

Diferentemente de um dicionário, headers podem ter valores repetidos, como Set-Cookie. Use get_all() quando precisar de todos.

Não permitir headers hop-by-hop

is_hop_by_hop() identifica headers que pertencem a uma conexão específica e não devem ser enviados pela aplicação WSGI como headers normais de resposta.

from wsgiref.util import is_hop_by_hop

print(is_hop_by_hop("Connection"))

Um servidor ou middleware deve seguir a especificação e rejeitar headers incompatíveis.

Reconstruir URLs

request_uri() reconstrói a URI completa da requisição. application_uri() produz a URI-base da aplicação.

from wsgiref.util import request_uri, application_uri

url = request_uri(environ)
base = application_uri(environ)

Em aplicações atrás de proxy, o ambiente pode refletir o proxy interno. Não confie em headers encaminhados sem uma política de proxies confiáveis.

Roteamento com shift_path_info

shift_path_info() move um segmento de PATH_INFO para SCRIPT_NAME. Ele modifica o ambiente no lugar.

from wsgiref.util import shift_path_info

def roteador(environ, start_response):
    env = environ.copy()
    segmento = shift_path_info(env)
    if segmento == "api":
        return api_app(env, start_response)
    return not_found(env, start_response)

Use uma cópia quando outras camadas precisarem do caminho original.

Ler o corpo da requisição

O stream wsgi.input fornece bytes. Leia somente a quantidade permitida por CONTENT_LENGTH e imponha um limite próprio.

def ler_corpo(environ, limite=1_000_000):
    bruto = environ.get("CONTENT_LENGTH", "")
    try:
        tamanho = int(bruto) if bruto else 0
    except ValueError:
        raise ValueError("Content-Length inválido")
    if tamanho > limite:
        raise ValueError("corpo muito grande")
    return environ["wsgi.input"].read(tamanho)

Não chame read() sem limite em entradas não confiáveis.

Gerar respostas de erro

def resposta(status, texto):
    corpo = texto.encode("utf-8")
    return status, [
        ("Content-Type", "text/plain; charset=utf-8"),
        ("Content-Length", str(len(corpo))),
    ], [corpo]

Não exponha traceback e variáveis internas ao usuário. Registre detalhes em um canal protegido e devolva uma mensagem genérica.

Middleware WSGI

Middleware recebe uma aplicação e devolve outra aplicação. Ele pode adicionar logs, headers ou contexto.

def adicionar_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

O middleware deve preservar as regras do protocolo, fechar o iterável quando necessário e não consumir o corpo sem repassá-lo corretamente.

Fechar o iterável de resposta

Uma aplicação pode devolver um iterável com método close(). Servidores devem chamá-lo ao finalizar. Em testes manuais, use try/finally.

resultado = app(environ, start_response)
try:
    corpo = b"".join(resultado)
finally:
    fechar = getattr(resultado, "close", None)
    if fechar:
        fechar()

Servir arquivos com FileWrapper

FileWrapper transforma um objeto de arquivo binário em iterável de blocos.

from wsgiref.util import FileWrapper

arquivo = open("relatorio.pdf", "rb")
return FileWrapper(arquivo, blksize=64 * 1024)

Valide o caminho antes de abrir e defina headers corretos. Em produção, servidores e proxies costumam oferecer mecanismos mais eficientes.

Tipos estáticos

Desde Python 3.11, wsgiref.types fornece protocolos e aliases como WSGIApplication, WSGIEnvironment e StartResponse.

from wsgiref.types import WSGIApplication

aplicacao: WSGIApplication = app

Tipos ajudam a detectar retornos Unicode, headers incorretos e assinaturas incompatíveis antes da execução.

WSGI é síncrono

WSGI modela uma chamada síncrona por requisição. Ele continua relevante para frameworks tradicionais, mas não representa WebSockets ou concorrência assíncrona nativa como ASGI.

Não tente retornar uma coroutine de uma aplicação WSGI. Use um framework e servidor compatíveis com a interface necessária.

Por que não usar simple_server em produção

O servidor de referência não é uma plataforma endurecida. Ele não deve ficar diretamente exposto à internet. Faltam recursos esperados de produção, como estratégias robustas de concorrência, proteção contra tráfego lento, gestão completa de limites, TLS operacional, reinicialização de workers e observabilidade.

Exemplo completo

from wsgiref.simple_server import make_server
from wsgiref.validate import validator


def app(environ, start_response):
    if environ.get("PATH_INFO") != "/":
        corpo = b"Not Found\n"
        start_response("404 Not Found", [
            ("Content-Type", "text/plain"),
            ("Content-Length", str(len(corpo))),
        ])
        return [corpo]

    corpo = b"Hello from WSGI\n"
    start_response("200 OK", [
        ("Content-Type", "text/plain; charset=utf-8"),
        ("Content-Length", str(len(corpo))),
    ])
    return [corpo]

with make_server("127.0.0.1", 8000, validator(app)) as httpd:
    httpd.serve_forever()

Erros frequentes

  • Retornar string em vez de bytes.
  • Usar o servidor simples em produção.
  • Ler o corpo sem limite.
  • Confiar em headers de proxy arbitrários.
  • Expor traceback em respostas.
  • Modificar environ sem considerar middleware.
  • Ignorar o fechamento do iterável.

Boas práticas

  • Use validator() nos testes.
  • Teste a aplicação diretamente sem rede.
  • Defina Content-Type e Content-Length.
  • Trabalhe com bytes no corpo.
  • Imponha limites de entrada.
  • Use servidor WSGI adequado em produção.
  • Escolha ASGI quando precisar de async nativo.

Conteúdos relacionados

Veja selectors, contextvars, platform, sysconfig e pydoc.

Consulte a documentação oficial do wsgiref e a PEP 3333.

Conclusão

wsgiref é excelente para aprender WSGI, testar aplicações e validar conformidade. Ele mostra claramente o contrato entre servidor e aplicação, mas seu servidor simples não deve receber tráfego de produção. Use-o como referência e ferramenta de desenvolvimento.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026