Biggest DevOps Conference in Ukraine! Kubernetes, TensorFlow, KubFlow, Cloud solutions, Ballerina and much more. Register until August, 22!

Python: Веб-разработка без фреймворков (часть 4)

В предыдущих статьях мы разобрались, как можно создавать веб-приложения на Python используя лишь необходимые средства. Следующим этапом будет развертывание приложения на сервере и связанная с этим задача конфигурации его компонент (deployment).

Сама задача WSGI стандарта — установить интерфейс, через который HTTP сервер будет общаться с веб-приложением, так что не приходится беспокоиться поддерживается ли нравящийся нам вариант связки или сервер. Выбор большой:

В зависимости от вашего выбора связка выполняется или просто или очень просто, так что выбирать следует исходя из того в каком окружении будет работать ваше приложение и с каким сервером вы наиболее знакомы. Я перепробовал много вариантов, но когда появился 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-сервер (по умолчанию на 8080-м порту). Также, благодаря paste.reloader, раз в секунду будет проверяться наличие изменений в самом скрипте и загруженных им модулях. Если такие изменения появятся, то скрипт выйдет с кодом ошибки 3; подразумевается, что внешний, вызывающий его скрипт воспринимает это как сигнал к перезапуску, о чем чуть дальше. Если при загрузке скрипта произошла ошибка (обычно это происходит если во внесенных изменениях есть синтаксическая ошибка), то на консоль будет выведен трейсбек и наш модуль выдержит пятисекундную паузу перед перезапуском (чтобы дать спокойно прочитать где была ошибка).

Последняя недостающая часть нашей системы — это внешний скрипт который перезапукает наш модуль. Я разрабатываю под Windows и пользуюсь таким (файл run-wsgi.bat):

@echo off
:repeat
    python -m run_wsgiscript %1
if %errorlevel% == 3 goto repeat

Если всё еще сохранилась неясность почему и как это сделано, просто посмотрите краткую документацию paste.reloader — мы просто применили эту систему к загрузке wsgi скриптов.

Если кто захочет оформить это всё в виде пакета — сообщите, я тоже поучаствую.

SciTE

Тем, кто, как и я, использует в работе SciTE, будет удобно добавить в его конфигурацию следующие строки, которые добавят wsgi -скриптам корректную подсветку и позволят запускать их по F5:

<a href="http://file.patterns.py" target="_blank">file.patterns.py</a>=*.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.

LinkedIn

20 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

@Kixirosys.argv [1] — да, действительно.Насчет отладки, сейчас точно не скажу, попробуйте заменить paste.evalexception на weberror или pasteob.debug.eval.

Интересная обертка для запуска своих wsgi скриптов, только вот наверное в ней sys.argv [1] указать, чтобы она не в себе искала application.А в linux можно запускать её так: err=3while test «$err» -eq 3; do python run_wsgiscript.py $1 err="$? «doneС именем испытуемого скрипта.Вот только одно понять не могу. Почему-то у меня при попадании в отладочный режим в браузере не разворачиваются «крестики» над ошибкой... может что-то не хватает?

Я кстати забыл упомянуть вариант деплоймента на Google AppEngine, там достаточно сделать вот так: if name == ’main’: from wsgiref.handlers import CGIHandler CGIHandler ().run (application)

Здесь неуместно задавать вопросы по Pylons.

а ещё такой вопрос, бывают какие-то глюки если не писать такой код в контроллерах pylons:

def after(self): Session.flush() Session.close()

Глюки типа запись то есть то не ту и так мелькает или ещё какая нить фигня вылазит.

Ога, через годик через два, родила она сынка)) Из детского стишка)

Я думаю в sqlalchemy-devel всё и решится)

В первой ссылке сообщение UTF error

Да и для будущих статей о алхимии. Встретился вот с такой проблемой ссылка 1 и ссылка 2. Не ясно как вразумительно это решить.

Можно в wsgi скрипте через

URLMap

, и можно наверное в конфигурации через

Location

, именно такого делать пока не нужно было, наверняка не скажу.

А как в апаче указать два приложения? Что бы одно открывалось в корне сайта, а другое в нижележащей папке.

Мне кажется, что часто пытаются оптимизировать уж слишком рано, например если статический контент и медленные клиенты становятся проблемой для апача, то можно вынести этот контент на другой IP и возможно новый сервер, на котором уже запускать мудреные веб-сервера. А сразу проксить nginx -> Apache + mod_wsgi это как-то диковато.Вообще пока речь идет об одном сервере выгодней добавить мощности чем даже просто исследовать варианты разных деплойментов, я так считаю.

тоже, но с «особенностями». обсуждение.

Так это ж тоже проксение или как иначе? В их группах ничего не нашел.

В Pylons активно обсуждают использование nginx вместо Apache+mod_wsgi. Все собираюсь попробовать для ДОУ, да никак руки не доходят.

Более внятные примеры на которых очевидно преимущество конфигурации прямо в скрипте надеюсь получатся в следующих статьях, когда подтянутся middleware, шаблоны, БД итд. Там должно быть видно, что это действительно единственно верный подход. А пока можно примерить на себя «как бы конфигурировались мои приложения если бы можно было это делать прямо в скриптах», думаю идея станет проясняться.

P.S.: Спасибо за статейку, полезные инструкции. Еще очень понравилось то, что присутствует много полезных ссылок.

Можно, но вот если по честному привести тут всё что для этого надо, то станет видно что даже единоразово это вообще не проще.

Сорри за анонимность: -Р

Запуск WSGI-скриптов без Apache

Мне кажется, можно это сделать немного проще... либо я не полностью проникся идеей:) Можно локально сервить paster-ом с похожим смыслом: paster serve —reload —monitor-restart development.ini

Подписаться на комментарии