Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Конференция «Технологии управления данными 2018»
СУБД, платформы, инструменты, реальные проекты.
29 ноября 2018 г.
2006 г.

WSGI, введение

Юревич Юрий, http://gorod-omsk.ru/blog/pythy/

С аббревиатурой WSGI я столкнулся, когда возникла задача развертывания Django-приложения, а mod_python у меня что-то не захотел работать. И в то время для меня WSGI было неким buzz-word, туманным и далеким. Так или иначе, Django я "завел" при помощи flup и lighttpd, но "виски" засел у меня занозой в мозгу.

Вспомнилась эта заноза не так давно, когда я стал читать блог Бена Бангерта Groovie. Бен апологет WSGI и создатель веб-фреймворка Pylons (надеюсь, у меня будет время рассказать о нем). Отправной точкой в моем "погружении" стали статьи на XML.com. А мотивом к написанию этой статьи стал тот факт, что информации по WSGI на русском просто нет. Что ж, постараюсь восполнить этот пробел.

Теория

WSGI - стандарт обмена данными между веб-сервером (backend) и веб-приложением (frontend). Под это определение попадают многие вещи, тот же самый CGI. Так что поясню.

Во-первых, WSGI - Python-специфичный стандарт, его описывает PEP 333. Во-вторых, он еще не принят (статус Draft, черновик). Эти оговорки для того, чтобы не испытывать лишних иллюзий. Между тем, стандарт нужный и уже используемый. Для меня WSGI - это в первую очередь возможность комбинировать различные back- и frontend’ы.

Теперь, что касается самого стандарта. Он описывает интерфейсы веб-приложения и веб-сервера.

Приложение - принимает в качестве параметров переменные окружения (в виде словаря) и исполняемый объект выполнения запроса. Возвращает итератор.

Сервер - тут чуть сложнее. В переменных окружения, к стандартным переменным веб-сервера, добавляются WSGI-специфичные. Особо останавливаться я на этом не буду, сошлюсь лишь на все тот же PEP 333, где приведен пример. Просто-напросто реализация на стороне сервера меня интересует постольку-поскольку, поэтому на ней не задерживаюсь.

Прослойка, middleware - самое интересное. Middleware "работает" в обе стороны. Т.е. у нее входной и выходной интерфейс идентичны. Я бы провел аналогию с декоратором. Middleware добавляет некую функциональность в исходное веб-приложение, например live debug, или http auth. Причем, можно выстраивать цепочки middleware.

Теперь попробуем все это на практике…

Практика

Итак, погружаемся в 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.

Заключение

WSGI достаточно простое и эффективное решение проблемы взаимодействия веб-сервера и веб-приложения. Как любой компромисс, он не идеален "везде и всюду", однако для большинства случаев - это разумный выбор. Некоторые используют WSGI не только как стандарт взаимодействия веб-сервера и веб-приложения, но и обособленных библиотек между собой. Возможно, в чем то этот подход оправдан.

Новости мира IT:

Архив новостей

Последние комментарии:

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...