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.