Python: Веб-разработка без фреймворков (часть 4)
В предыдущих статьях мы разобрались, как можно создавать веб-приложения на Python используя лишь необходимые средства. Следующим этапом будет развертывание приложения на сервере и связанная с этим задача конфигурации его компонент (deployment).
Сама задача WSGI стандарта — установить интерфейс, через который HTTP сервер будет общаться с веб-приложением, так что не приходится беспокоиться поддерживается ли нравящийся нам вариант связки или сервер. Выбор большой:
- Реализация HTTP сервера непосредственно на Python
- wsgiref (однопотоковый)
- paste.httpserver
- cherrypy.wsgiserver
- Twisted (пример использования)
- В дополнение нередко применяются reverse proxy (Apache, lighttpd, Squid)
- CGI
- FastCGI
- SCGI
- AJP
- Apache + mod_python
- Apache + mod_wsgi
В зависимости от вашего выбора связка выполняется или просто или очень просто, так что выбирать следует исходя из того в каком окружении будет работать ваше приложение и с каким сервером вы наиболее знакомы. Я перепробовал много вариантов, но когда появился mod_wsgi опробовал и перешел на его использование во всех случаях когда только возможно.
mod_wsgi
Как ясно из названия, это модуль Apache написанный специально для связки непосредственно с WSGI приложениями. Я думаю для многих важно, что в нем есть базовая поддержка хостинга пользовательских скриптов в shared-hosting окружениях. На самом деле одной из ведущих мотиваций при написании этого замечательного модуля было сделать хостинг веб-приложений на Python доступнее, а сами приложения в таких условиях быстрее. mod_wsgi показал себя отлично в реальных условиях, работает стабильно, быстро и безошибочно, потому можно рассчитывать, что он получит распространение среди хостеров. Также он на пути к включению в основные репозитории Debian.
Приблизительная модель работы этого модуля такова:
- Есть один или более именованных пулов python-процессов обрабатывающих запросы. Обратите внимание, что, среди прочего, благодаря этому снимаются какие-либо вопросы к поддержке параллельных вычислений в Python
- Конфигурация сервера позволяет определять политику наполнения пулов процессами (минимальное и максимальное их количество, количество потоков в каждом, таймауты и прочее)
- У каждого пула может быть свой python-path. Таким образом, можно создавать независимые окружения (при помощи virtualenv) не привязанные к версиям библиотек установленных на системе глобально или в других окружениях. Такие окружения также могут принадлежать непривилигерованым пользователям
- Есть довольно гибкий механизм для определения какой группой процессов будет обработан тот или иной запрос. На это могут влиять довольно высокоуровневые переменные вроде имени пользователя (при условии, что аутентификация была проведена самим Apache)
- Для крайне нагруженных серверов можно использовать менее гибкий, зато еще более быстрый вариант связки — встраивание интерпретатора и исполнение приложения непосредственно в процессе Apache.
Каким же именно образом в конфигурации сервера указывается WSGI приложения и их конфигурация? Пожалуй самым простым из возможных — указанием Python-скрипта по исполнению которого в его пространстве имен окажется переменная application значение которой и будет использовано как WSGI-приложение. Скрипт может иметь любое имя, но принято давать таким файлам расширение .wsgi. Как правило, такой скрипт будет импортировать все необходимые компоненты и конфигурировать их для данного размещения. Может показаться, что это какой-то недостаточно мощный или непродуманный подход, но опыт показывает, что это оптимальное решение. Для того чтобы стало понятней почему я так считаю, давайте кратко рассмотрим альтернативный подход.
PasteDeploy
PasteDeploy — это, в первую очередь, средство конфигурации и компоновки WSGI приложений, а также выбора и конфигурации связки с веб-сервером. Предназначено оно, среди прочего, для конечного пользователя / администратора не обязательно знакомого с Python, поэтому конфигурация хранится в .ini файлах. Эту конфигурацию можно запускать, мониторить и в целом использовать как UNIX-демон благодаря команде paster из PasteScript. На первый взгляд это хорошее решение, но практическое его использование раскрывает ряд недостатков.
Я не буду вдаваться в подробности его применения, так как моя цель раскритиковать такой подход, а не научить им пользоваться. Вкратце конфигурация содержит отдельные секции для каждого используемого приложения, фильтра (middleware) и сервера. Используемые приложения указываются в виде URI в специальных схемах. Наиболее используемая URI-схема egg: указывает на setuptools entry point, который является заводом (factory) по производству искомых приложений из передаваемой конфигурации. Также есть схема для использования других конфигурационных файлов (config:).
Я не буду перечислять преимущества системы, сосредоточившись на недостатках, потому что они не так очевидны без опыта использования.
- Для того чтобы сделать свое приложение доступным необходимо:
- определить функцию или класс производящие приложения из конфигурации. Тут открываются разные детали:
- предусмотренных переменных конфигурации всегда оказывается мало и со временем factory нужно подправлять;
- конфигурация это всегда словарь где и ключи и значения — строки, таким образом возникают неопределенности если вам нужны юникод-значения;
- если в конфигурации неплохо бы применить список или еще какое значение типом посложнее, то придется изобретать кодирование такого значения для ini формата;
- затем нужно создать entry point в setup.py для вашего пакета,
- задокументировать это,
- сослаться на него в конфигурации.
- определить функцию или класс производящие приложения из конфигурации. Тут открываются разные детали:
- Для использования приложения надо знать имя его entry point, как именно его конфигурировать итп. Таким образом возникает необходимость в еще одном наборе документации к приложению. Впрочем, чаще всё равно приходится читать исходники.
- Также есть сложности с повторным использованием блоков конфигурации, например, если нужно использовать одну и ту же базу данных в различных приложениях. Минимальная поддержка есть, но нельзя скажем параметризовать включаемый конфигурационный файл. Также, например, не смотря на то, что мы можем использовать одну и ту же БД в разных приложениях, они будут использовать раздельные пулы соединений.
Хотя часть этих проблем может быть решена конфигурацией в другом виде, например XML, большая часть проблем неизбежна при использовании отдельного языка для конфигурации приложений. Если же для конфигурации использовать Python-скрипты, то все упомянутые проблемы решаются сами собой — повторное использование параметризованных блоков конфигурации превращается ни во что иное как определение и вызовы функций. Не нужны никакие уловки для построения более сложных структур данных. Использование точек входа становится необязательным. И именно так работают wsgi-скрипты для mod_wsgi. Такое решение, кажущееся на первый взгляд кустарным, оказывается гораздо более мощным и удобным в использовании, чем любая возможная альтернатива. Мы теряем возможности PasteScript по управлению демонами сервера приложений, но это нас не беспокоит, так как теперь они управляются совместно с Apache, для их перезапуска применяется привычное sudo service apache2 force-reload.
Что до конфигурации приложений непрограммистами, то править файл с синтаксисом Python ничуть не сложнее чем аналогичный INI.
Запуск WSGI-скриптов без Apache
Что ж, с развертыванием на сервере более-менее ясно, а как быть с разработкой? Будем поднимать локальный сервер, настраивать его, перезапускать всякий раз когда вносим в код изменения? Я считаю что не стоит, и потому привожу свой модуль для исполнения wsgi-скриптов (это сокращенный, но рабочий вариант):
import sys
import os
from time import sleep
from traceback import print_exc
from paste.httpserver import serve
from paste.evalexception import EvalException
from paste.debug.prints import PrintDebugMiddleware
from paste import reloader
def run_script(script):
if not os.path.isfile(script):
print script, "does not exist"
sys.exit(1)
reloader.install()
reloader.watch_file(script)
script_locals = {}
execfile(script, {'__file__': script}, script_locals)
app = script_locals['application']
app = EvalException(app)
app = PrintDebugMiddleware(app)
serve(app)
if __name__ == '__main__':
try:
run_script(sys.argv[0])
except SystemExit, exc:
raise exc
except:
print_exc()
print '-' * 20, 'Restarting in 5 secs..', '-' * 20
sleep(5)
sys.exit(3)
Сохраним этот файл как run_wsgiscript.py там, где он будет доступен импортированию. Лучше оформить его как пакет, но самым простым вариантом будет разместить его в site-packages.
Такой скрипт загружает WSGI приложение образом аналогичным mod_wsgi, но в дополнение он оборачивает его парой отладочных middleware и запускает HTTP-сервер (по умолчанию на
Последняя недостающая часть нашей системы — это внешний скрипт который перезапукает наш модуль. Я разрабатываю под Windows и пользуюсь таким (файл run-wsgi.bat):
@echo off
:repeat
python -m run_wsgiscript %1
if %errorlevel% == 3 goto repeat
Если всё еще сохранилась неясность почему и как это сделано, просто посмотрите краткую документацию paste.reloader — мы просто применили эту систему к загрузке wsgi скриптов.
Если кто захочет оформить это всё в виде пакета — сообщите, я тоже поучаствую.
SciTE
Тем, кто, как и я, использует в работе SciTE, будет удобно добавить в его конфигурацию следующие строки, которые добавят wsgi -скриптам корректную подсветку и позволят запускать их по F5:
file.patterns.py=*.py;*.pyw;*.wsgi
command.go.*.wsgi=run-wsgi.bat "$(FilePath)"
command.go.subsystem.*.wsgi=2
WSGI-скрипт
На всякий случай, упреждая возможные вопросы, приведу пример скрипта. Поскольку wsgi-скрипт это полноценный питоновский файл, то мы можем определить WSGI приложение прямо в нем. Т.е. любой из примеров приведенных в предыдущих статьях можно использовать таким образом. Только теперь там, где мы вызывали paste.httpserver.serve(APP) нужно писать application = APP. Например вот наш самый первый пример в виде 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
application = hello_app
В Apache тоже ничего мудрить не надо, для начала достаточно такого:
WSGIScriptAlias /path-to-app /home/user/public/hello.wsgi
Не думаю что статья отвечает на все вопросы, но моей задачей было, в первую очередь, показать что в этом вопросе нет ничего страшного и отослать к документации тех компонент которые могут пригодиться.
Следующая запланированная статья будет о создании WSGI middleware.
Все про українське ІТ в телеграмі — підписуйтеся на канал DOU
20 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.