Neste artigo de duas partes, será em abordado em detalhes o WSGI (Webserver Gateway Interface), que possível usar qualquer aplicação web em Python com qualquer servidor web, através da especificação de uma camada de gateway.
Como um desenvolvedor Python, chega a hora em que você é confrontado com o termo “WSGI” (que depois de seis anos ainda não tem um truncamento oficial. Alguns fazem rima com “Whisky”, outros pronunciam somente a abreviação, outros chamam pelo número PEP 333). Quando foi criado, o WSGI era algo incrível. Ele tornou possível usar qualquer aplicação web em Python com qualquer servidor web, através da especificação de uma camada de gateway.
Na verdade, ele ficou tão popular e bem suportado pelo mundo Python, que protocolos similares foram criados para outras linguagens. Uma das primeiras foi Rack para Ruby, depois Jack/JSGI para JavaScript, PSGI/Plack para Perl e muitas outras. Desde que o WSGI surgiu de um protocolo de nicho, ninguém o conhecia como protocolo de web e as pessoas tentavam mudá-lo e substituí-lo. Por que isso e por que ninguém conseguiu substituí-lo?
O termo WSGI significa “Webserver Gateway Interface”, e era realmente visto como uma ponte entre servidores e aplicativos. Adicionalmente, o PEP explicou como middlewares poderiam ser usados, e isso foi o começo do fim. O PEP também sugeriu que as pessoas deveriam escrever seus frameworks ao redor do WSGI, o que muitas realmente fizeram. Ao longo dos anos, as pessoas tentaram adicionar mais e mais serviços na camada WSGI, com sucesso variável. Sessões de middlewares, sistemas de autenticação, camadas de caching etc. A visão de muitas pessoas é, ou era, usar a camada WSGI como uma forma de combinar aplicações múltiplas.
O WebOb por exemplo criou muita familiaridade com isso. Enquanto seus aplicativos estiverem usando apenas o WebOb, um pedido de objeto pode ser “anexado” em um ambiente WSGI em qualquer ponto na cadeia WSGI, e você está operando basicamente no mesmo pedido de objeto com os mesmos dados na retaguarda. Isso, entretanto, vai além do que o WSGI especifica e encoraja.
Se um aplicativo WebOb for misturado com Django, Werkzeug ou qualquer outra coisa, suas possibilidades ficam altamente reduzidas.
Com o que o WSGI rompeu
Se você der uma olhada nas APIs antes do WSGI, ou mesmo em frameworks novos que não o suportam, um padrão comum é ter um pedido de objeto que não somente permite acessar os dados que estão chegando, mas também permite que os dados sejam enviados de volta para o cliente. Por exemplo, tendo um método write(). O WSGI rompeu cedo essa convenção, e esse foi um grande passo, porque significou ter que usar buffering internamente ou mudar a API para usar generators.
O que acontece com o Python é que não é possível parar manualmente a execução em um frame, a menos que greenlets estejam sendo usados, o que lá atrás, em 2004, não estavam disponíveis. Assim não se poderia, de forma transparente, converter uma API para WSGI como abaixo:
def my_view(request):
request.start_response('200 OK')
request.send_header('Content-Type', 'text/plain')
request.end_headers()
request.write('Hello World!')
request.write('Goodbye World!')
request.end()
O exemplo inteiramente equivalente seria:
def my_view(environ, start_response):
def generate():
yield 'Hello World!'
yield 'Goodbye World!'
start_response('200 OK', [('Content-Type', 'text/plain')])
return generate()
Embora agora seja possível utilizar greenlets para converter chamadas de função múltiplas em yields em um gerador, ainda é algo que não deveria ser feito. Então você pode imaginar que, quando o WSGI foi lançado, era um desafio converter para ele.
Se olharmos outros protocolos inspirados no WSGI, será possível ver que o conceito de interator foi adaptado e modificado. No Python, a iteração funciona chamando um método no iterador até que ele indique que terminou. Com o Ruby, é o oposto. Uma função é fornecida e passada para o iterador, que então chamará a função até terminar. O enfoque do Ruby tem a vantagem bacana de que não são necessários geradores, e que se pode facilmente converter essa interface Rack em um par de método antigo write+flush.
As peculiaridades do WSGI
Quando se pergunta às pessoas sua opinião sobre o WSGI, elas sempre dizem que a start_response() chamada é ruim. E elas estão certas dizendo que é possível fugir dela. Mas antes de jogá-la fora cegamente, é necessário entender por que ela foi criada. A forma comum é chamar a start_response() com o status code como um string+explanation e uma lista de key-values tuples que representam os headers. O que muitas pessoas não percebem é que o start_response() pode fazer muito mais do que isso!
Antes de mais nada, lembremos que não é possível transformar chamadas response.write() de forma transparente em yield statements. Quando o PEP foi escrito, era bastante óbvio que isso seria um problema para aplicativos existentes que precisassem fazer um fluxo de dados via request.write(). E à tal da start-response () era dado um valor de retorno que muitos desenvolvedores não conheciam. O que a start-response() retorna é uma função que escreve diretamente no fluxo do cliente. Certamente esse problema poderia ter sido resolvido de forma diferente, por exemplo colocando essa função em um ambiente WSGI, mas a intenção era que o caller que inicia a resposta obtivesse essa função.
Você já usou essa função escrita diretamente? Eu também não, e por boas razões: ela ultrapassa o processamento de middlewares, uma vez que diretamente vai para o fluxo de saída. Mas configura o caminho para o WSGI aceitá-la como realmente é, um caminho simples para scripts WSGI-ify CGI. Por exemplo, a interface mercurial hgweb era uma usuária proeminente dessa função.
Mas a start_response () não acaba aí. Há um terceiro parâmetro que as pessoas geralmente esquecem: exc_info. Ele é raramente usado porque o error handling é tipicamente usado em um nível mais alto na pilha, mas naturalmente a intenção é alertar o servidor quanto a erros. Deveria funcionar assim: a resposta é iniciada e está a ponto de enviar dados, mas acontece um erro. Você pode mudar de ideia e iniciar a resposta uma segunda vez com a informação de erro. Você poderia também não ter iniciado a resposta e informá-la diretamente a respeito dos erros. Por que isso? Porque isso vem em combinação com um outro fato: os headers não são enviados – eles são configurados.
O Async Nod
Como protocolo, o WSGI foi desenhado teoricamente para suportar também aplicativos assíncronos. Ao se iniciar a resposta, ela não está sendo realmente iniciada. O servidor está sendo informado a respeito dos headers que desejamos enviar, mas eles não são realmente enviados até ser fornecido uma string não-vazia. Isso permite que se mude de uma notificação já feita de 200 OK para 500 INTERNAL SERVER ERROR, até um outro ponto posterior. Por exemplo, o código abaixo é um código WSGI perfeitamente válido:
def weird_app(environ, start_response):
try:
start_response('200 OK', [('Content-Type', 'text/plain')])
yield ''
yield ''
raise Exception('Something went wrong late')
except Exception, e:
start_response('500 INTERNAL SERVER ERROR',
[('Content-Type', 'text/plain')],
sys.exc_info())
yield 'Application Failed'
Este é um exemplo extremo que não se verá na prática. O servidor deveria tentar mudar os headers se ainda não tiverem sido enviados ou se recuperar de qualquer forma possível dessa condição de erro. A regra “os headers são enviados quando a primeira string não-vazia é fornecida” nada mais é do que um nod bacana para sistemas assíncronos que podem usar esse truque para fornecer strings vazias para assinalar que elas ainda não estão prontas. Não sei se esse foi um comportamento intencional, mas o PEP é bastante elaborado ao mencionar que isso é para sistemas assíncronos, de forma que suponho que alguém pensou a respeito disso.
Você geralmente não encontrará esse uso na prática. É apenas algo que geralmente faz o processamento do código WSGI mais árduo do que o necessário.
Restabelecendo e proxying
O WSGI é chato no que diz respeito a restabelecer mensagens de uma app WSGI para outra. Vamos assumir por um momento que o WSGI não tem o start_response() chamável e a string vazia e um write chamável. O “Hello World” provavelmente será algo assim:
def hello_world(environ):
return '200 OK', [('Content-Type', 'text/plain')], \
['Hello World!']
Simples e direto, e seria incrivelmente fácil fazer um proxy dessas coisas. Tudo que você teria que fazer seria chamar essa função, passá-la por um ambiente dicionário e então pegar o valor de retorno, trabalhar com ele e passá-lo para frente.
O WSGI faz isso ser muito difícil por uma série de razões:
- O valor de retorno de um aplicativo. Quando você o tem, o start_response() já o chamou ou não? Se você retornar diretamente uma listagem de que ele foi chamado, se a função inteira tem um yield em algum lugar, a função start_response() não será chamada até a primeira iteração do valor de retorno.
- Alguém em qualquer ponto fez misturou write() e um valor de retorno iterável? Em caso positivo, como esse mix foi feito?
- É preciso ser cuidadoso com o fato que os headers podem mudar antes que uma string não-vazia retorne do iterável.
- O iterador poder ter um método close que seja preciso chamar.
Tudo isso faz do WSGI um protocolo terrível para o caso simples de chamar outro aplicativo, mastigá-lo com o valor de retorno e então passá-lo para frente.
Se isso é tão ruim, por que se decidiu que se trabalhasse dessa forma? Porque obviamente alguma coisa estaria sendo sacrificada se fosse mudado em um flat tuple como valor de retorno. Para iniciantes, você perde:
- A habilidade de mudar uma resposta bem sucedida em uma resposta de erro de servidor.
- Os sistemas assíncronos teriam que apresentar os resultados imediatamente quando for chamada a função ou o block (ruim) até que eles saibam o que retornar.
- Tudo teria que ser alimentado pelo gerador, e não haveria mais response.write(), a menos que greenlets fossem introduzidos.
Na última parte do artigo, veja algumas sugestões para melhorar o WSGI, entre outros assuntos.
?
Texto original disponível em http://lucumr.pocoo.org/2011/7/27/the-pluggable-pipedream/








