WSGI (Web Server Gateway Interface) é uma especificação de interface que permite a comunicação entre o servidor e sua aplicação python, o PEP 333 descreve em detalhes toda especificação do WSGI 1.0 (existe o PEP 3333 que descreve o WSGI 1.0.1, mas o padrão utilizado atualmente ainda é o 1.0).
O Gunicorn é projeto de maior destaque quando se fala de servidor WSGI nativo, com ele não é preciso de nenhum adaptador WSGI para outro servidor web, apesar disso é bem comum que se utilize um servidor web para servir conteúdo estático e passar o conteúdo dinâmico para o servidor WSGI usando o esquema de proxy reverso.
O mod_wsgi e uwsgi são os maiores projetos em termos de adaptadores WSGI, o mod_wsgi roda em conjunto com o Apache, já o uwsgi roda em conjunto com NGINX.
O grande benefício do WSGI é que já existem servidores/adaptadores bastante eficientes, então você só precisa se preocupar com a parte da aplicação em si, além disso, praticamente todos os frameworks web fazem uso do WSGI o que torna ele o padrão para deploy de aplicações.
Um exemplo de hello world com wsgi
# -*- coding: utf-8 -*-
def hello_world_wsgi(environ, start_response):
'''
Uma função simples que demonstra como fazer um hello world com WSGI.
Essa função deve receber dois argumentos environ e start_response.
O environ contém um dicionário em python com valores preenchidos pelo
servidor em cada requisição recebida.
Para saber mais sobre as variáveis que vem no environ visite:
http://www.python.org/dev/peps/pep-0333/#environ-variables
O start_response é um callback enviado pelo servidor para que sua aplicação
responda a requisição.
'''
# o status segue o padrão do w3:
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
status = '200 OK'
# response body é o conteúdo que deve ser retornado
response_body = 'Hello World!'
# response_headers é o cabeçalho de resposta, geralmente contém o tipo
# de conteúdo retornado e o tamanho.
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
]
# a resposta deve ser iniciada utilizando o start_response
start_response(status, response_headers)
# por fim o conteúdo deve ser retornado utilizando alguma estrutura python
# iterável, nesse caso uma lista foi usada.
return [response_body]
# iniciando um servidor wsgi na porta 8080
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, hello_world_wsgi)
srv.serve_forever()
Claro que esse exemplo pode ser melhorado, vejamos outro exemplo mais elaborado
# -*- coding: utf-8 -*-
def application_handler(environ, start_response):
'''
Função responsável por gerenciar as requisições WSGI.
Caso a requisição seja para a raíz do site '/' o index_handler vai ser
carregado, caso contrário o not_found_handler é carregado.
'''
# recebendo o valor do PATH
path = environ.get('PATH_INFO', '')
# verificando se o path é a raíz do site
if path == '/':
return index_handler(environ, start_response)
else:
return not_found_handler(environ, start_response)
def index_handler(environ, start_response):
'''
Retorna a página index do site.
'''
status = '200 OK'
response_body = '<h1>Welcome!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def not_found_handler(environ, start_response):
'''
Retorna a página 404 de um site.
'''
status = '404 NOT FOUND'
response_body = '<h1>Not found!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
# iniciando um servidor wsgi na porta 8080
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, application_handler)
srv.serve_forever()
Podemos melhorar a maneira que as urls são chamadas
# -*- coding: utf-8 -*-
import re
def index_handler(environ, start_response):
'''
Retorna a página index do site.
'''
status = '200 OK'
response_body = '<h1>Welcome!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def hello_handler(environ, start_response):
'''
Retorna a página hello world do site.
'''
status = '200 OK'
response_body = '<h1>Hello World!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def not_found_handler(environ, start_response):
'''
Retorna a página 404 de um site.
'''
status = '404 NOT FOUND'
response_body = '<h1>Not found!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
# Lista de urls, cada elemento é uma tupla com dois valores: uma expressão
# regular da url e a função que vai processar a requisição, muito parecido com
# o esquema do django.
urls = [
(r'^$', index_handler),
(r'^hello/$', hello_handler),
]
def application_handler(environ, start_response, urls=urls):
'''
Função responsável por gerenciar as requisições WSGI.
As requisições são despachadas de acordo com a url da lista de urls.
'''
# recebendo o valor do PATH
path = environ.get('PATH_INFO', '').lstrip('/')
# verificando se o path tem algum handler correspondente, caso contrário
# retorne 404
for regex, callback in urls:
match = re.search(regex, path)
if match is not None:
return callback(environ, start_response)
return not_found_handler(environ, start_response)
# iniciando um servidor wsgi na porta 8080
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, application_handler)
srv.serve_forever()
O código acima tem um pequeno problema, ao acessar o /hello e /hello/ você recebe resultados diferentes, o primeiro retorna 404 enquanto o segundo retorna a página do hello world. Isso acontece por causa do ‘/’ no final da url, seria interessante escrever um middleware que adicionasse essa barra de forma automática se necessário.
# -*- coding: utf-8 -*-
import re
def index_handler(environ, start_response):
'''
Retorna a página index do site.
'''
status = '200 OK'
response_body = '<h1>Welcome!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def hello_handler(environ, start_response):
'''
Retorna a página hello world do site.
'''
status = '200 OK'
response_body = '<h1>Hello World!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def not_found_handler(environ, start_response):
'''
Retorna a página 404 de um site.
'''
status = '404 NOT FOUND'
response_body = '<h1>Not found!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
# Lista de urls, cada elemento é uma tupla com dois valores: uma expressão
# regular da url e a função que vai processar a requisição, muito parecido com
# o esquema do django.
urls = [
(r'^$', index_handler),
(r'^hello/$', hello_handler),
]
def fetch_url(url, urls=urls):
'''
Função para checar se uma url está listada na lista de urls.
Retorna o callback em caso positivo e None em caso negativo.
'''
url = url.lstrip('/')
for regex, callback in urls:
match = re.search(regex, url)
if match:
return callback
return None
def application_handler(environ, start_response):
'''
Função responsável por gerenciar as requisições WSGI.
As requisições são despachadas de acordo com a url da lista de urls.
'''
# recebendo o valor do PATH
path = environ.get('PATH_INFO', '')
# verificando se o path tem algum handler correspondente, caso contrário
# retorne 404
callback = fetch_url(path)
if callback:
return callback(environ, start_response)
return not_found_handler(environ, start_response)
class AddSlashMiddleware(object):
'''
Middleware que verifica se a url requisitada em combinação com o '/' no
final é válida, em caso afirmativo deve ocorrer um redirecionamento.
'''
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
# Recebe o path
path = environ.get('PATH_INFO', '')
if not path.endswith('/'):
# Se o path não termina com '/', verificar se esse path
# combinado com o '/' existe na lista de urls
callback = fetch_url(path + '/')
if callback:
# Redireciona para url
uri = path + '/'
start_response('302 FOUND', [('Location', uri)])
return []
# Passa o controle para aplicação
return self.application(environ, start_response)
# cria um application que passa pelo middleware
application = AddSlashMiddleware(application_handler)
# iniciando um servidor wsgi na porta 8080
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, application)
srv.serve_forever()
Para finalizar, vamos escrever outro middleware, desta vez eu quero que o html retornado fique todo em letra minúscula.
# -*- coding: utf-8 -*-
import re
def index_handler(environ, start_response):
'''
Retorna a página index do site.
'''
status = '200 OK'
response_body = '<h1>Welcome!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def hello_handler(environ, start_response):
'''
Retorna a página hello world do site.
'''
status = '200 OK'
response_body = '<h1>Hello World!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
def not_found_handler(environ, start_response):
'''
Retorna a página 404 de um site.
'''
status = '404 NOT FOUND'
response_body = '<h1>Not found!</h1>'
response_headers = [
('Content-Type', 'text/html'),
('Content-Length', str(len(response_body)))
]
start_response(status, response_headers)
return [response_body]
# Lista de urls, cada elemento é uma tupla com dois valores: uma expressão
# regular da url e a função que vai processar a requisição, muito parecido com
# o esquema do django.
urls = [
(r'^$', index_handler),
(r'^hello/$', hello_handler),
]
def fetch_url(url, urls=urls):
'''
Função para checar se uma url está listada na lista de urls.
Retorna o callback em caso positivo e None em caso negativo.
'''
url = url.lstrip('/')
for regex, callback in urls:
match = re.search(regex, url)
if match:
return callback
return None
def application_handler(environ, start_response):
'''
Função responsável por gerenciar as requisições WSGI.
As requisições são despachadas de acordo com a url da lista de urls.
'''
# recebendo o valor do PATH
path = environ.get('PATH_INFO', '')
# verificando se o path tem algum handler correspondente, caso contrário
# retorne 404
callback = fetch_url(path)
if callback:
return callback(environ, start_response)
return not_found_handler(environ, start_response)
class AddSlashMiddleware(object):
'''
Middleware que verifica se a url requisitada em combinação com o '/' no
final é válida, em caso afirmativo deve ocorrer um redirecionamento.
'''
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
# Recebe o path
path = environ.get('PATH_INFO', '')
if not path.endswith('/'):
# Se o path não termina com '/', verificar se esse path
# combinado com o '/' existe na lista de urls
callback = fetch_url(path + '/')
if callback:
# Redireciona para url
uri = path + '/'
start_response('302 FOUND', [('Location', uri)])
return []
# Passa o controle para aplicação
return self.application(environ, start_response)
class LowercaseMiddleware(object):
'''
Middleware que converte o html todo em letras minúsculas.
'''
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
# Faço uma iteração convertendo o valor obtido para minúsculas
for chunk in self.application(environ, start_response):
yield chunk.lower()
# Aplicando o AddSlashMiddleware
application = AddSlashMiddleware(application_handler)
# Aplicando o LowercaseMiddleware
application = LowercaseMiddleware(application)
# iniciando um servidor wsgi na porta 8080
if __name__ == '__main__':
from wsgiref.simple_server import make_server
srv = make_server('localhost', 8080, application)
srv.serve_forever()
Agora temos dois middlewares que são aplicados ao nosso aplicativo, lembrando que a ordem que os middlewares são aplicados é importante.
Como foi visto, o WSGI permite que se criem aplicações de forma bastante simples, recomendo a leitura do Learn WSGI que foi a referência para esse post.
Curtir isso:
Curtir Carregando...