WSGI, Paste

May 07, 2007 22:40


Originally published at Pythy. You can comment here or there.

Продолжим работать с WSGI. Сегодня поговорим о Paste.

Когда заходит разговор о Paste, возникает проблема определения. Достаточно сложно одним-двумя словами сказать что же такое Paste. Набор WSGI-компонент и middleware… Если человек не особо плотно работает с WSGI, то это ему ничего не скажет. Бен Бангерт в своем выступлении на Google Tech Talks называет Paste "те компоненты, функционал которых обычно ожидают от фреймворка", "набор компонент, слишком мелких для выделения в отдельные пакеты".

Давайте на простых примерах разберемся что такое Paste, что умеет и зачем нужен. Paste состоит из трех частей: собственно Paste, PasteDeploy и PasteScript. Сегодня будем разговаривать о "просто Paste".
Необходимое предисловие

Для демонстрации Paste мы будем использовать
простейшее WSGI-приложение, либо небольшие его вариации:

def sample_app(environ, start_response): start_response('200 OK', [('Content-type', 'text/html')]) sorted_keys = sorted(environ.keys()) content = ['TrivialWSGIApp in action
'] + \ ['
Sample WSGI application. Just show your environment.

    '] + \ ['
  • %s : %s
  • ' % (str(k), str(environ[k])) for k in sorted_keys] + \ ['

'] return content
Ну и прежде чем приступить, установим Paste (он активно использует возможности setuptools и сам распространяется в виде egg, так что лучше если вы заранее настроите easy_install)

$ easy_install Paste Сервер

Во-первых, Paste предоставляет сервер для работы с WSGI-приложениями. Естественно, может использоваться как на месте разработчика (чтобы не "тащить" за собой Apache, Lighttpd или Nginx), так и при развертывании (он достаточно быстр).

Что ж, давайте попробуем.

>>> from paste import httpserver >>> httpserver.serve(sample_app, host='127.0.0.1', port=5000) serving on http://127.0.0.1:5000
Вуаля…

Сервер Paste сделан на основе BaseHTTPServer и если у вас есть "своя" реализация сервера, наследованная от BaseHTTPServer, то Paste дает возможность более-менее просто сделать свой сервер WSGI-совместимым. Для этого следует воспользоваться WSGIHandlerMixIn.

Диспетчеризация
Каскад

Каскад приложений. Из каскада возвращается первый не-ошибочный ответ.

Например, есть некое приложение, которое будет возвращать ошибку 404, если в пути
не будет присутствовать foo.

def foo_app(environ, start_response): if 'foo' in environ['PATH_INFO']: start_response('200 OK', [('Content-type', 'text/html')]) content = ['Foo app
'] + \ ['
We've found foo in path
'] else: start_response('404 Not found', [('Content-type', 'text/html')]) content = ['404: Not found
'] + \ ['
Foo not found in path
'] return content
И наше sample_app. Если мы составим такой каскад:

from paste import cascade cascaded = cascade.Cascade([foo_app, sample_app], catch=(404,500))
То если foo_app вернет 404, то запрос будет обслужен sample_app. По умолчанию Cascade перехватывает именно 404, однако можно указать и другие, например 500. Это позволяет организовывать различного рода "умные" обработчики ошибок.
Префиксы

Если каскад это аналог списка или кортежа, то префиксы - аналог словаря. В зависимости от префикса запрос обслуживается либо одним, либо другим приложением.

from paste import urlmap mapping = urlmap.URLMap() mapping['/foo'] = foo_app mapping['/bar'] = sample_app
Теперь URLы, начинающиеся с ‘foo’ будет обслуживать foo_app, а начинающиеся с ‘bar’ - приложение sample_app
Заметьте одну особенность: если вы пойдете по URL http://127.0.0.1:5000/foo, то приложение foo_app ругнется, что foo нет в пути. И действительно, Paste "вырезает" префикс из пути (PATH_INFO). И это правильно.

Префиксы полезны, когда вы в один сайт вставляете несколько WSGI-приложений. Например, форум и блог.
Обработчики ошибок

Если не нужно "хитрых" обработчиков, а нужно просто делать редиректы на страницы с ошибками, то в этом вам поможет errordocument.

from paste import fileapp, urlmap, errordocument def err404_app(environ, start_response): start_response('200 OK', [('Content-type', 'text/html')]) return ["Err 404: Object not found in %s
" % environ['paste.recursive.old_path_info']] mapping = urlmap.URLMap() mapping['/foo'] = foo_app mapping['/err404'] = err404_app error_handled = errordocument.forward(mapping, codes={404:'/err404'})
Отметим, что обработчик должен возвращать ответ 200 OK, а иначе будет зацикливание обработчиков. Стоит обратить внимание, что в переменных окружения фиксируются операции, проводимые paste. В частности, можно получить значение переменной окружения PATH_INFO до редиректа. Упомянем еще одну особенность: при запросе /foo, PATH_INFO будет пустым, поскольку paste имитирует корень для приложения foo_app. Это сделано затем, чтобы приложение обрабатывало только свой "кусок" URL’а.
Инструменты
Запросы и ответы (request/response)

Удобные обертки для запросов и ответов.

from paste import wsgiwrappers def advanced_app(environ, start_response): request = wsgiwrappers.WSGIRequest(environ) response = wsgiwrappers.WSGIResponse("Advanced WSGI app in action
") response.write("
Your preferred languages are: %s
" % request.languages) response.write("
Params of your query are: %s
" % request.params) response.write("") return response(environ, start_response)
Код говорит сам за себя. С такими объектами гораздо удобней работать, чем с "голым WSGI".
Тестирование

Paste предоставляет простой интерфейс для тестирования WSGI-приложений. Используя paste.fixture, делаем тест-обертку для приложения, и работаем уже с этой оберткой:

from paste import fixture import unittest def testing_app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain'), ('X-Powered-By', 'Python Paste')]) return ["Paste is a collection of useful tools for WSGI"] class WSGIAppTestCase(unittest.TestCase): def setUp(self): self.test_app = fixture.TestApp(testing_app) def test_headers(self): response = self.test_app.get('/path/to/url') self.assertEquals(response.header('X-Powered-By'), 'Python Paste') def test_content(self): response = self.test_app.get('/path/to/url') # similar to response.mustcontain('WSGI'), but more pythonic self.assert_('WSGI' in response)
Тут же упомяну и paste.lint.middleware - middleware, проверяющая корректность реализации WSGI.
Отладка

Paste дает возможность вылавливать ошибки самыми различными способами
paste.exceptions

Отлов ошибок и показ traceback либо в браузере (при отладке), либо письмом.

from paste.exceptions import errormiddleware def erroneous_app(environ, start_response): if environ['PATH_INFO'] == '/error': raise Exception("Some error occured here") start_response('200 OK', [('Content-Type', 'text/html')]) return ['Erroreous application
'] + \ ['
Go to /error to see exception
'] + \ [''] app = errormiddleware.ErrorMiddleware(erroneous_app, debug=True) paste.cgitb_catcher

Любители фиолетовых страниц ошибок в стиле cgitb могут получать их для WSGI-приложений средствами paste.cgitb_catcher.

from paste import cgitb_catcher app = cgitb_catcher.CgitbMiddleware(erroneous_app, display=True) paste.evalexception

Результат работы этого middleware сильно похож на paste.exceptions, но с одним большим плюсом: в нем есть интерактивная Python-консоль, так что вы можете более подробно изучить причины ошибки.

from paste import evalexception app = evalexception.EvalException(erroneous_app) Профилирование

При помощи paste.debug.profile мы можем "на лету" отпрофилировать приложение и сразу же посмотреть результаты в веб-браузере.

from paste.debug import profile app = profile.ProfileMiddleware(sample_app, None) Показ print’ов на странице

Интересный middleware - "отлавливает" print’ы и показывает их на странице. Примерно так:

from paste.debug import prints def sample_app_prints(environ, start_response): print "start response with 200 OK status" start_response('200 OK', [('Content-type', 'text/html')]) print "retrieve environ keys" sorted_keys = environ.keys() print "sort keys" sorted_keys.sort() print "construct content" content = ['Trivial WSGI app in action
'] + \ ['
Sample WSGI application. Just show your environment.

    '] + \ ['
  • %s : %s
  • ' % (str(k), str(environ[k])) for k in sorted_keys] + \ ['

'] print "return content" return content app = prints.PrintDebugMiddleware(sample_app_prints) Еще…

В Paste помимо всего вышеописанного есть:
  • Работа с WSGI-переменными из environ в paste.request
  • Представление cgi-приложений как WSGI в paste.cgiapp
  • Представление любого HTTP-приложения как WSGI в paste.proxy
  • Обработка исключений (напр. HTTPNotFound) в правильные HTTP-ответы в paste.httpexceptions
  • Управление сессиями в paste.session и paste.flup_session
  • Сжатие на лету трафика средствами paste.gzipper
  • Журнал обращений в стиле Apache в paste.translogger
  • Управление глобальными переменными в paste.registry, thread-safe
  • Различного рода аутентификации (в том числе и с поддержкой OpenID) в paste.auth
  • Монитор изменений (перезагружает сервер при изменении контролируемого кода) в paste.reloader
  • … и кое-что еще
Итог

Paste - это "кирпичики", из которых можно строить свое приложение или фреймворк (Pylons, RhubarbTart, CleverHarold поступают именно так). Ряд интересных middleware (то же самое профилирование) вы можете использовать не только с этими фреймворками (где все компоненты - WSGI-совместимы) или с собственным WSGI-приложенем, но и с Django, где WSGI - это внешний интерфейс.

Полный код всех примеров, как всегда, можно найти на code.google.com.

python, web, wsgi, Документация, Инструменты

Previous post Next post
Up