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 middlewareO 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 = appTipos 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
environsem 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.







