Python: Веб-разработка без фреймворков (часть 1)
Каждый разработчик тщательно выбирает свой инструментарий и чем лучше он им владеет, тем эффективнее его работа и тем более востребованы его услуги. В то же время многие программисты гордятся своей ленью и ищут такие инструменты, которые делали бы за них как можно больше работы. При этом, на мой взгляд, забывают, что бесплатный сыр только в мышеловке и гоняются за иллюзорным «лучшим» фреймворком тщательно сверяя какой из них делает больше работы: Zope, ROR или Django? Мой опыт говорит о том, что время на изучение фреймворков и подстройка под их ограничения почти никогда не окупается, а пользуясь минимальным инструментарием, с которым я хочу вас ознакомить, можно добиться гораздо лучших результатов.
WSGI
Не так давно библиотек и фреймворков для веб-разработки на Python было очень много, и взаимодействовали они между собой не наилучшим образом. Выбрав для разработки одно решение приходилось придерживаться его и в дальнейшем. Разрешением этой проблемы многие считали выбрать один-два главных фреймворка и сконцентрироваться на их разработке, таким образом сделав остальные ненужными. Такая консолидация решила бы проблемы с взаимодействием разных частей, но и минусов у неё порядочно. Ведь так много библиотек возникло не зря, и причина этого именно в том что нет очевидно предпочтительного подхода к веб-разработке.
Решение подоспело в виде стандарта WSGI (PEP-333). Это сверх-компактная спецификация, которая решает как вопросы взаимодействия разных компонент веб-приложений, так и связки между этими приложениями и HTTP-сервером.
Вкратце, WSGI (Web Server Gateway Interface) требует от приложения предоставлять следующий интерфейс:
def app(environ, start_response):
"""
(dict, callable( status: str,
headers: list[(header_name: str, header_value: str)]))
-> body: str or iterable of strings
"""
Т.е. приложение будет вызвано с двумя аргументами. Первый — словарь environ, содержащий различные данные о запросе, второй — функция запускающая процесс ответа на запрос. В основном содержимое environ совпадает с содержимым переменных окружения CGI скрипта при аналогичном запросе, откуда и происходит имя этого аргументa, это же позволяет использовать уже имеющиеся средства для разбора строки запроса итп. Назначение start_response проще показать на примере, а возвращать WSGI приложение должно либо строку, либо итератор по строкам, т.е. функция может быть генератором и «скармливать» веб-серверу ответ по частям. Например:
import cgi
def hello_app(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
yield "Hello, "
form = cgi.FieldStorage(environ=environ)
name = form.getfirst('name', 'stranger')
yield name
Удобство использования WSGI заключается, конечно, не в том, что мы можем писать такой код, а в том, что предоставив такой интерфейс, мы получаем доступ к множеству совместимых библиотек. Например, мы элементарно можем запустить сервер с вышеуказанным приложением:
from paste.httpserver import serve
serve(hello_app)
Мы можем также предоставить его через FastCGI, SCGI или еще какой-нибудь диковинный протокол общения между HTTP-сервером и сервером приложений. Существует также mod_wsgi, модуль Apache, позволяющий в считанные минуты запустить ваше приложение через этот сервер, причем с отличной интеграцией с его инфраструктурой.
Ну и, конечно же, мы можем вызывать другие WSGI приложения и обрабатывать их результаты (о чем будет сказано в разговоре о middleware). Этот стандарт к тому же делает возможным библиотеки тестирования веб-приложений, не привязанные ни к каким фреймворкам. В результате библиотек для веб-разработки врядли стало меньше, ведь создавать новые теперь куда проще, но проблема совместимости была решена. Теперь любой фреймворк, вне зависимости от того каким образом он сообщается с вашим кодом, предоставляет WSGI. Различные приложения, не использующие сторонних фреймворков, также доступны через WSGI (например Trac).
PythonPaste
PythonPaste (paste — тесто) это замечательное собрание различной функциональности реализованной для WSGI и общей для разных фреймворков. Если вы заглянете своей любимой библиотеке под капот и не найдете там Paste, то выбор ваш возможно не так уж хорош. Впрочем, сам Paste настолько удобен, что ознакомившись с его возможностями, не должно возникнуть желания использовать какой-либо фреймворк вообще.
Например, в нем есть готовая поддержка самых разных способов аутентификации (Basic, Digest, form, signed cookie, auth_tkt), поддержка корректной и удобной генерации ответов и заголовков (к примеру редиректы, Cache-control, Expires, gzipper и прочие). Различные базовые средства комбинации приложений (URLMap, Cascade, Recursive), статических данных (с учетом Etag, If-Modified итп). Скажем так:
from paste.urlmap import URLMap
from paste.fileapp import FileApp
root_app = URLMap()
root_app['/static'] = FileApp('/var/www/static/')
from paste.auth.digest import digest_password, AuthDigestHandler
def private_app(environ, start_response):
remote_user = environ['REMOTE_USER']
app = FileApp('/home/%s/htdocs/' % remote_user)
return app(environ, start_response)
def authfunc(environ, realm, username):
return digest_password(realm, username, username) # password == username
private_wrapped = AuthDigestHandler(private_app, "Private area", authfunc)
root_app['/private'] = private_wrapped
Как видите, кода написано минимум, а часть сайта уже защищена паролем и соответствует разным папкам на диске в зависимости от имени пользователя. Представить себе, что нечто подобное можно записать короче и понятней, трудно.
Можно также предоставлять статические файлы прямо из архива (ArchiveStore), есть поддержка сессий, возможность отслеживать прогресс загрузки на сервер больших форм, и прочее и прочее.
PythonPaste для отладки
Особо радуют средства для отладки приложений. Тут и возможность приложения автоматически перезапускаться при изменении части исходных кодов, и профайлер для каждого запроса, и возможность слать отчеты об ошибках на почту (или сохранять их локально). Можно валидировать все [X]HTML ответы сервера, есть те самые средства тестирования WSGI приложений итд итп. Есть даже возможность интерактивной отладки прямо в браузере. На этом стоило бы остановиться особо, но это лучше один раз увидеть, чем сто раз прочитать, просто запустите этот код и откройте в браузере localhost:8080. (Также можно посмотреть скринкаст).
from paste.evalexception.middleware import EvalException
def error_app(environ, start_response):
raise ValueError
from paste.httpserver import serve
serve(EvalException(error_app))
Далее
В следующих частях я собираюсь рассказать про использование WebOb, написание middleware, возможности mod_wsgi и вопросы конфигурации WSGI приложений.
23 коментарі
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.