Питон - статьи
773123a3

Практика


Итак, погружаемся в WSGI. Пишем простенькое WSGI-приложение:

def app(environ, start_response): start_response('200 OK', [('Content-type', 'text/plain')]) return ['Hello here']

Все достаточно просто - как и говорилось выше, приложение принимает в качестве аргументов словарь переменных окружения (environ) и исполняемый объект выполнения запроса (start_response). Далее, посылаем начало ответа серверу и возвращаем сам ответ в виде итератора (в данном случае - в виде обычного списка).

Теперь встает вопрос о запуске нашего приложения. Для этого воспользуемся библиотекой wsgiref. Счастливчики с Python 2.5 в этом месте широко улыбаются, потому что у них wsgiref уже есть. Запускаем так:

from wsgiref import simple_server server = simple_server.WSGIServer( ('', 8080), simple_server.WSGIRequestHandler, ) server.set_app(app) server.serve_forever()

Тоже все достаточно просто - создаем объект сервера со стандартным обработчиком, задаем ему порт 8080 для ожидания соединений, указываем какое WSGI-приложение выполнять и запускаем сервер.

Пока что преимущества WSGI не ощущаются.

Теперь усложним задачу. Попробуем написать такой сервер, который бы работал с произвольным WSGI-приложением, и приложение, которое бы работало с произвольным WSGI-сервером. Что ж, приступим.

В начале определю, что значит "произвольный": скрипту, который реализует тот или иной компонент, передается в качестве аргумента "путь" к другому, парному, компоненту. И пусть они взаимодействуют. Чтобы не усложнять код, я написал маленький модуль, helper, который и делает всю "машинерию" по преобразованию полного имени компонента (пакет.модуль.объект) в компонент-объект. Итак, наше "тривиальное приложение" стало выглядеть так:

def app(environ, start_response): start_response('200 OK', [('Content-type', 'text/html')]) sorted_keys = environ.keys() sorted_keys.sort() result = ['<html><body><h1>TrivialWSGIApp in action</h1>'] + ['<p>Sample WSGI application.
Just show your environment.</p><p><ul>'] + ['<li> %s => %s</li>' % (str(k), str(environ[k])) for k in sorted_keys] + ['</ul></p></body></html>'] return result

if __name__ == '__main__': import sys import helper

server = helper.get_arg(sys.argv, "Usage: trivial_wsgi_app.py package.wsgi.server_callable") server(app)

Немного "усложнили" приложение - теперь оно показывает доступные переменные окружения, ну и плюс код, запускающий парный компонент - WSGI-сервер, переданный как параметр.



А "тривиальный сервер" стал выглядеть так:

from wsgiref import simple_server, validate

class TrivialWSGIServer(object): def __init__(self, app): self.app = app self.server = simple_server.WSGIServer( ('', 8080), simple_server.WSGIRequestHandler, ) self.server.set_app(validate.validator(self.app))

def serve(self): self.server.serve_forever()

def runner(app): TrivialWSGIServer(app).serve()

if __name__ == '__main__': import sys import helper

app = helper.get_arg(sys.argv, "Usage: trivial_wsgi_server.py package.wsgi.app") runner(app)

У него добавились: "исполнитель" runner, чтобы в один шаг запускать приложение на запуск и код для запуска парного компонента - WSGI-приложения. Отмечу одну из "прослоек" (middleware), которая здесь используется - validator - проверяет, что "диалог" между сервером и приложением идет в рамках стандарта.

Запуск осуществляется следующим образом:

trivial_wsgi_app.py trivial_wsgi_server.runner

или так:

trivial_wsgi_server.py trivial_wsgi_app.app

Усложняем задачу. Теперь напишем WSGI-сервер средствами Twisted, но с таким же "интерфейсом запуска"

from twisted.internet import reactor from twisted.web2 import wsgi, channel, server

class TwistedWSGIServer(object): def __init__(self, app): self.app = app self.wsgi_res = wsgi.WSGIResource(app) self.site = server.Site(self.wsgi_res) self.factory = channel.HTTPFactory(self.site)



def serve(self): reactor.listenTCP(8080, self.factory) reactor.run()

def runner(app): TwistedWSGIServer(app).serve()

if __name__ == '__main__': import sys import helper

app = helper.get_arg(sys.argv, "Usage: twisted_wsgi_server.py package.wsgi.app") runner(app)

Здесь мы воспользовались WSGI-сервером, встроенным в Twisted Web2, ну а процедура старта Twisted-приложения описана здесь.

Теперь пробуем запустить с нашим приложением:

twisted_wsgi_server.py trivial_wsgi_app.app

Работает. Еще больше усложним задачу и напишем Nevow-приложение с WSGI-интерфейсом (правда, с некоторыми оговорками):

from nevow import rend, loaders, wsgi, tags, inevow

class NevowPage(rend.Page):

addSlash = True

docFactory = loaders.stan( tags.html[ tags.head[tags.title['Nevow WSGI hello app']], tags.body[ tags.h1(id='title')['Nevow WSGI hello app'], tags.p(id='welcome')['Welcome to the Nevow (WSGI powered). Just show your environment.'], tags.p(id='environment')[tags.invisible(render=tags.directive('environ'))] ] ] )

def render_environ(self, context, data): environ = inevow.IRequest(context).environ sorted_keys = environ.keys() sorted_keys.sort() inner = [tags.li[k, " => ", str(environ[k])] for k in sorted_keys] return tags.ul[inner]

app = wsgi.createWSGIApplication(NevowPage())

if __name__ == '__main__': import sys import helper

server = helper.get_arg(sys.argv, "Usage: nevow_wsgi_app.py package.wsgi.server_callable") server(get_wsgi_app())

Особо углубляться в код не буду, тем более, что есть желание сделать Nevow одной из тем разговора.

Теперь можно комбинировать WSGI-сервера и WSGI-приложения в любых сочетаниях - результат будем идентичным. Естественно, что часть возможностей, которые не укладываются в WSGI, будут недоступны. Напримерб в Twisted Web2, WSGI-приложение выполняется в отдельном потоке, так что воспользоваться асинхронными "фишками" Twisted не получится. Поэтому использовать Nevow с Twisted через WSGI - нонсенс. Об использовании Twisted в веб-приложениях, я думаю, расскажу в ближайшее время.А приведенный код можно получить с code.google.com.


Содержание раздела