Chapter 4: Ядро

Ядро

Опции командной строки

Можно пропустить GUI и запустить web2py непосредственно из командной строки, набрав что-то вроде:

password
1
python web2py.py -a 'your password' -i 127.0.0.1 -p 8000

Когда web2py запускается, он создает файл под названием "parameters_8000.py", где он хранит зашифрованный пароль. Если вы используете "<ask>" в качестве пароля, web2py спросит его у вас.

Для обеспечения дополнительной безопасности, вы можете запустить web2py со следующими атрибутами:

1
python web2py.py -a '<recycle>' -i 127.0.0.1 -p 8000

В этом случае web2py повторно воспользуется предварительно сохраненным хешированным паролем. Если пароль не указан, или если файл "parameters_8000.py" удален, то веб-интерфейс администрирования будет отключен.

PAM

В некоторых Unix/Linux системах, если пароль является

1
<pam_user:some_user>

web2py использует PAM пароль аккаунта операционной системы some_user для аутентификации администратора, если он не заблокирован через PAM конфигурацию.

web2py обычно работает с CPython (реализация интерпретатора Python на языке C, созданная Гвидо ван Россум), но он также может работать с PyPy и Cython. Последняя возможность позволяет использовать web2py в контексте инфраструктуры Java EE. Чтобы использовать Jython, просто заменить "python web2py.py ..." на "jython web2py.py". Подробности об установке Jython, модулей zxJDBC, необходимых для доступа к базам данных, можно найти в Главе 14.

Скрипт "web2py.py" может принимать множество аргументов командной строки, задающие максимальное количество потоков, включая SSL и т.д. Ознакомиться с полным списком типов:

command line
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
>>> python web2py.py -h
Usage: python web2py.py

web2py Web Framework startup script. ATTENTION: unless a password
is specified (-a 'passwd'), web2py will attempt to run a GUI.
In this case command line options are ignored.

Options:
  --version             показать номер версии программы и выйти
  -h, --help            показать эту справку и выйти
  -i IP, --ip=IP        IP-адрес сервера (например, 127.0.0.1 или ::1);
                        Примечание: Это значение игнорируется при использовании
                        опции 'interfaces'.
  -p PORT, --port=PORT  порт сервера (8000)
  -a PASSWORD, --password=PASSWORD
                        пароль для администрирования (используйте -a
                        "<recycle>" чтобы повторно использовать последний пароль))
  -c SSL_CERTIFICATE, --ssl_certificate=SSL_CERTIFICATE
                        файл, содержащий сертификат SSL
  -k SSL_PRIVATE_KEY, --ssl_private_key=SSL_PRIVATE_KEY
                        файл, содержащий закрытый ключ SSL
  --ca-cert=SSL_CA_CERTIFICATE
                        Использовать этот файл, содержащий сертификат CA
                        для проверки X509 сертификатов от клиентов
  -d PID_FILENAME, --pid_filename=PID_FILENAME
                        файл для хранения pid сервера
  -l LOG_FILENAME, --log_filename=LOG_FILENAME
                        файл для log соединений
  -n NUMTHREADS, --numthreads=NUMTHREADS
                        число потоков (устаревшее)
  --minthreads=MINTHREADS
                        минимальное число потоков сервера
  --maxthreads=MAXTHREADS
                        максимальное число потоков сервера
  -s SERVER_NAME, --server_name=SERVER_NAME
                        имя сервера для веб-сервера
  -q REQUEST_QUEUE_SIZE, --request_queue_size=REQUEST_QUEUE_SIZE
                        Максимальное количество запросов в очереди, когда сервер недоступен
  -o TIMEOUT, --timeout=TIMEOUT
                        время ожидания отдельного запроса (10 секунд)
  -z SHUTDOWN_TIMEOUT, --shutdown_timeout=SHUTDOWN_TIMEOUT
                        время ожидания при завершении работы сервера (5 секунд)
  --socket-timeout=SOCKET_TIMEOUT
                        время ожидания для сокета (5 секунд)
  -f FOLDER, --folder=FOLDER
                        расположение папки приложений (также известной как каталог)
  -v, --verbose         увеличить --test многословие
  -Q, --quiet           отключить все выходные данные
  -D DEBUGLEVEL, --debug=DEBUGLEVEL
                        задать выходной уровень отладки (0-100, 0 означает все, 100 означает
                        ничего; по умолчанию 30)
  -S APPNAME, --shell=APPNAME
                        запустить web2py в интерактивной оболочке или IPython (если
                        установлен) с заданным appname (если app не
                        существует, то оно создается). APPNAME наподобие a/c/f (c,f
                        опциональные)
  -B, --bpython         запустить web2py в интерактивной оболочке или bpython (если
                        установлен) с заданным appname (если app не
                        существует, то оно создается). Использовать вместе с --shell
  -P, --plain           использовать только простую python оболочку; следует использовать с
                        --shell опцией
  -M, --import_models   автоимпорт файлов модели; по умолчанию False; следует
                        использовать с --shell опцией
  -R PYTHON_FILE, --run=PYTHON_FILE
                        запустить PYTHON_FILE в среде web2py; следует использовать
                        с --shell опцией
  -K SCHEDULER, --scheduler=SCHEDULER
                        запуск задач по расписанию для указанных приложений: ожидает
                        список имен приложений, как -K app1,app2,app3 или список
                        app:groups как -K app1:group1:group2,app2:group1 для
                        заданных group_names. (допускаются только строки, без
                        пробелов. Требуется планировщик, определенный в
                        моделях
  -X, --with-scheduler  запустить планировщики наряду с веб-сервером
  -T TEST_PATH, --test=TEST_PATH
                        запустить doctests в среде web2py; TEST_PATH наподобие
                        a/c/f (c,f опционально)
  -C, --cron            переключить cron на запуск вручную; обычно вызывается из
                        crontab системы
  --softcron            переключиться на использование softcron
  -Y, --run-cron        запустить в фоне cron процесс
  -J, --cronjob         выявить инициализировавшую cron команду
  -L CONFIG, --config=CONFIG
                        файл конфигурации
  -F PROFILER_DIR, --profiler=PROFILER_DIR
                        директория профайлера
  -t, --taskbar         использовать web2py gui и запустить в панели задач (системный трей)
  --nogui               только текст, без GUI
  -A ARGS, --args=ARGS  должно сопровождаться списком аргументов, передаваемых
                        скрипту, используется с -S, -A в качестве последней
                        опции
  --no-banner           Не печатать заголовок баннера
  --interfaces=INTERFACES
                        прослушивать несколько адресов: "ip1:port1:key1:cert1:ca
                        _cert1;ip2:port2:key2:cert2:ca_cert2;..."
                        (:key:cert:ca_cert опционально; без пробелов; IPv6 адресация
                        должна быть в квадратных [] скобках)
  --run_system_tests    запуск тестов web2py
Обратите внимание: - Опция -W, используемая для установки службы Windows, была удалена. Подробнее смотрите nssm в главе Рецепты развертывания. - Профайлер выхода может быть проанализирован с использованием runsnakerun инструмента

Опции в нижнем регистре используются для настройки веб-сервера. Опция -L говорит web2py прочесть параметры конфигурации из файла, в то время как -S, -P and -M являются опциями запуска интерактивной оболочки Python. Опция -T находит и запускает doctests контроллера в среде исполнения web2py. Например, в следующем примере запускается doctests из всех контроллеров в приложения "welcome":

1
python web2py.py -vT welcome

Рабочий процесс

Рабочий процесс web2py выглядит следующим образом:

  • HTTP-запросы прибывают на веб-сервер (встроенный сервер Rocket или другой сервер, подключенный к web2py через WSGI или другой адаптер). Веб-сервер обрабатывает каждый запрос в отдельном потоке, параллельно.
  • Заголовок HTTP-запроса обрабатывается и передается диспетчеру (объясняется далее в этой главе).
  • Диспетчер решает, какое из установленных приложений будет обрабатывать запрос и отображает PATH_INFO в URL в вызове функции. Каждый URL соответствует одному вызову функции.
  • Запросы на файлы в статической папке обрабатываются непосредственно, а большие файлы автоматически поточно передаются клиенту.
  • Запросы на что-либо, кроме статического файла перенаправляются в действие (т.е. на функцию в файле контроллера, в запрашиваемом приложении).
  • Прежде чем вызывать действие, происходит несколько вещей: если заголовок запроса содержит куки сессии для приложения, то извлекается объект сессии; если нет, то создается идентификатор сессии (но файл сессии не сохраняется до появления последней версии); создается среда исполнения для запроса; в этой среде исполняются модели.
  • В конечном итоге, действие контроллера выполняется в предварительно построенной среде.
  • Если действие возвращает строку, то она сразу же возвращается клиенту (либо, если действие возвращает вспомогательный объект HTML web2py, то он сериализуется и возвращается клиенту).
  • Если действие возвращает итератор, то он используется в цикле и поточно передает данные клиенту.
  • Если действие возвращает словарь, web2py пытается найти представление для визуализации словаря. Представление должно иметь такое же имя, что и действия (если не указано иное) и такое же расширение, как запрашиваемой страницы (по умолчанию .html); в случае неудачи, web2py может подобрать обобщенное представление (если таковое имеется и если включено). Представление видит каждую переменную, определенную в моделях, а также находящиеся в словаре, который возвращается действием, но не видит глобальные переменные, определенные в контроллере.
  • Весь пользовательский код выполняется в одной транзакции базы данных, если не указано иное.
  • Если код пользователя завершается успешно, то транзакция фиксируется.
  • Если код пользователя завершается неудачей, то трэйсбэк сохраняется в билет, а ID билета выдается клиенту. Только системный администратор может найти и прочитать трэйсбэки в билетах.

Есть некоторые предостережения, которые нужно иметь в виду:

  • Модели в той же папке/подпапке исполняются в алфавитном порядке.
  • Любая переменная, определенная в модели будет видна для других моделей, следующих по алфавиту, для контроллеров и представлений.
models_to_run
conditional models

Условные модели

  • Модели в подпапках выполняются условно на базе контроллера при использовании. Это позволяет избежать обработки всех определений таблиц при каждом запросе. Например, если пользователь запросил "/a/c/f", где "a" это приложение, "c" это контроллер, и "f" это функция (действие), то следующие модели будут выполнены:
applications/a/models/*.py
applications/a/models/c/*.py
applications/a/models/c/f/*.py

Такое поведение моделей обеспечивается по умолчанию. Переделав список регулярных выражений response.models_to_run, вы можете заставить модели вести себя как вы хотите. Посмотрите на ответ для получения дополнительной информации

  • Запрашиваемый контроллер выполняется и запрошенная функция вызывается. Это означает, что весь код верхнего уровня в контроллере также выполняется при каждом запросе для этого контроллера.
  • Представление вызывается только если действие возвращает словарь.
  • Если представление не найдено, то web2py попытается использовать общее представление. По умолчанию общие представления отключены, хотя "welcome" приложение включает в себя строку в /models/db.py, чтобы включить их только на локальном хосте. Они могут быть включены по типу расширения и по действию (с помощью response.generic_patterns). В основном, общие представления являются средством разработки и, как правило, не должны использоваться в производстве. Если вы хотите, чтобы какие-то действия использовали общее представление, то перечислите эти действия в response.generic_patterns (более подробно обсуждается в Главе о службах).

Возможными поведениями действия являются следующие:

Возвращает строку

def index(): return 'data'

Возвращает словарь для представления:

def index(): return dict(key='value')

Возвращает все локальные переменные:

def index(): return locals()

Переадресация пользователя на другую страницу:

def index(): redirect(URL('other_action'))

Возвращает HTTP страницу, кроме "200 OK":

def index(): raise HTTP(404)

Возвращает помощника (например, FORM):

def index(): return FORM(INPUT(_name='test'))

(это в основном используется для обратных вызовов Ajax и компонентов, смотрите Главу 12)

Когда действие возвращает словарь, то он может содержать код, сгенерированный помощниками, в том числе формы, основанные на таблицах базы данных или формы с фабрики, например:

def index(): return dict(form=SQLFORM.factory(Field('name')).process())

(все формы, генерируемые web2py используют постбэки, смотрите в главе 3)

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

url mapping
dispatching

web2py сопоставляет URL следующего вида:

1
http://127.0.0.1:8000/a/c/f.html

с функцией f() контроллера "c.py" и приложения "a". Если f отсутствует, то web2py по умолчанию использует функцию index контроллера. Если c отсутствует, то web2py по умолчанию использует контроллер "default.py", и если a отсутствует, то web2py по умолчанию использует приложение init. Если не существует init приложения, то web2py пытается запустить welcome приложение. Это схематически показано на изображении ниже:

image

По умолчанию любой новый запрос также создает новую сессию. В дополнении к этому, куки сессии возвращаются в браузер клиента, чтобы отслеживать сессии.

Расширение .html не является обязательным; расширение .html принято по умолчанию. Расширение определяет расширение файла представления, которое отображает выходные данные функции f() контроллера. Оно позволяет один и тот же контент преподнести в различных форматах (HTML, XML, JSON, RSS и т.д.).

Функции, которые принимают аргументы или начинаются с двойного подчеркивания публично не выставляются и могут быть вызваны только другими функциями.
static files

Существует исключение, сделанное для URL-адресов следующего вида:

1
http://127.0.0.1:8000/a/static/filename

Здесь отсутствует контроллер под названием "static". web2py интерпретирует это как запрос для файла с именем "filename" в подпапке "static" приложения "а".

PARTIAL CONTENT
IF_MODIFIED_SINCE
Когда статические файлы загружаются, то web2py не создает сессию, а также не выдает куки или выполняет модели. web2py всегда поточно передает статические файлы в блоках по 1Мб, и посылает PARTIAL CONTENT, когда клиент отправляет запрос RANGE для подмножества файла.

web2py также поддерживает протокол IF_MODIFIED_SINCE и не отправляет файл, если он уже хранится в кэше браузера, а также если файл не изменился с этой версии.

При создании ссылок на аудио или видео файл в статической папке, если вы хотите заставить браузер загрузить файл вместо передачи потокового аудио/видео через медиа-плеер, то добавьте ?attachment к URL-адресу. Это сообщает web2py установить Content-Disposition в заголовке ответа HTTP на "attachment". Например:

1
<a href="/app/static/my_audio_file.mp3?attachment">Download</a>

Если нажать на вышеуказанную ссылку, то браузер предложит пользователю загрузить файл MP3 вместо потоковой передачи аудио. (Как это обсуждается ниже, вы также можете установить заголовки ответа HTTP напрямую путем присвоения словарю dict заголовка имен и их значений на response.headers.)

request.application
request.controller
request.function
GET
POST
request.args
web2py сопоставляет GET/POST запросы следующего вида:

1
http://127.0.0.1:8000/a/c/f.html/x/y/z?p=1&q=2

с функцией f в контроллере "c.py" приложения a, и сохраняет параметры URL в объекте request следующим образом:

1
request.args = ['x', 'y', 'z']

и:

1
request.vars = {'p':1, 'q':2}

и:

1
2
3
request.application = 'a'
request.controller = 'c'
request.function = 'f'

В приведенном выше примере, оба способа request.args[i] и request.args(i) могут быть использованы для получения i-го элемента в атрибуте request.args, но прежде чем вызывать исключение, если список не имеет такой индекс, атрибут request.args возвращает None.

request.url

Атрибут request.url сохраняет полный URL текущего запроса (не включая GET переменные).

request.ajax
request.cid

Атрибут request.ajaxпо умолчанию равен False, но переводится в True, если web2py определяет, что действие было вызвано с помощью запроса Ajax.

Если запрос является запросом Ajax и он инициируется компонентом web2py, имя компонента можно найти в атрибуте request.cid

Компоненты описаны более подробно в главе 12.

request.get_vars
request.post_vars
request.vars
Если HTTP запрос является GET, то атрибут request.env.request_method установлен на "GET"; если это POST, то атрибут request.env.request_method установлен на "POST".

Атрибут request.get_vars хранит переменные URL запроса.

Атрибут request.post_vars содержит все параметры, передаваемые в тело запроса (как правило один POST, PUT или DELETE).

Атрибут request.vars хранит словарь, содержащий оба атрибута (get_vars и post_vars взятые вместе)

web2py сохраняет WSGI и web2py переменные среды в request.env, например:

1
request.env.path_info = 'a/c/f'

и HTTP-заголовки в переменные среды, например:

1
request.env.http_host = '127.0.0.1:8000'
Обратите внимание на то, что web2py проверяет все URL-адреса для предотвращения атак обхода каталогов.

URL-адреса могут содержать только буквенно-цифровые символы, знаки подчеркивания и слеши; args может содержать непоследовательные точки.Перед проверкой пробелы заменяются подчеркиванием. Если синтаксис URL является недействительным, то web2py возвращает сообщение об ошибке HTTP 400 [http-w] [http-o] .

Если URL соответствует запросу для статического файла, то web2py просто читает и возвращает (в потоке) запрашиваемый файл.

Если URL не запрашивает статический файл, то web2py обрабатывает запрос в следующем порядке:

  • Разбирает куки.
  • Создает среду, в которой выполняется функция.
  • Инициализирует request, response, cache.
  • Открывает существующую session или создает новую.
  • Выполняет модели, принадлежащие к запрошенному приложению.
  • Выполняет запрошенную функцию действия контроллера.
  • Если функция возвращает словарь, выполняет ассоциированное представление.
  • В случае успеха, фиксирует все открытые транзакции.
  • Сохраняет сессию.
  • Возвращает HTTP ответ.

Обратите внимание на то, что контроллер и представление выполнены в разных копий одной и той же среды; Таким образом, представление не видит контроллер, но он видит модели и видит переменные, возвращенные функцией действия контроллера.

Если поднимается исключение(кроме HTTP), то web2py выполняет следующие действия:

  • Сохраняет трейсбэк в файле ошибок и присваивает ему номер билета.
  • Откатывает все открытые транзакции базы данных.
  • Возвращает страницу ошибки с сообщением номер билета.

Если исключение является HTTP исключением, то это считается предполагаемым поведением (например, HTTP переадресация), и все открытые транзакции базы данных фиксируются. Поведение после этого задается самим HTTP исключением. Класс исключения HTTP не является стандартным исключением Python; оно определяется web2py.

Библиотеки

Библиотеки web2py используются в приложениях пользователя в качестве глобальных объектов. Например (request, response, session, cache), классы (helpers, validators, DAL API), и функции (T и redirect).

Эти объекты определены в следующих файлах ядра:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
web2py.py
gluon/__init__.py    gluon/highlight.py   gluon/restricted.py  gluon/streamer.py
gluon/admin.py       gluon/html.py        gluon/rewrite.py     gluon/template.py
gluon/cache.py       gluon/http.py        gluon/rocket.py      gluon/storage.py
gluon/cfs.py         gluon/import_all.py  gluon/sanitizer.py   gluon/tools.py
gluon/compileapp.py  gluon/languages.py   gluon/serializers.py gluon/utils.py
gluon/contenttype.py gluon/main.py        gluon/settings.py    gluon/validators.py
gluon/dal.py         gluon/myregex.py     gluon/shell.py       gluon/widget.py
gluon/decoder.py     gluon/newcron.py     gluon/sql.py         gluon/winservice.py
gluon/fileutils.py   gluon/portalocker.py gluon/sqlhtml.py     gluon/xmlrpc.py
gluon/globals.py     gluon/reserved_sql_keywords.py
Обратите внимание, что многие из этих модулей, в частности dal (Абстрактный Уровень Базы данных), template (язык шаблона), rocket (веб-сервер), и html (помощники) не имеют зависимостей и могут быть использованы за пределами web2py.

Скаффолдинговое приложение welcome.w2p в tar gzip архиве находится на борту вместе с web2py.

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

При первом запуске web2py, создаются две новые папки: deposit и applications. Папка deposit используется как временное хранилище для установки и удаления приложений. При первом запуске web2py и после обновления, приложение "welcome" архивируется в фацл "welcome.w2p" для использования в качестве скаффолдиногового приложения.

Когда web2py обновляется, то он поставляется вместе с файлом под названием "NEWINSTALL". Если web2py находит этот файл, то он понимает, что обновление было выполнено, следовательно, он удаляет и создает новый файл "welcome.w2p".

Текущая версия web2py хранится в поле "VERSION", нотация которой следует семантическому стандарту версионности, id сборки соответствует штампу времени сборки.

Блок-тесты web2py находятся в

1
gluon/tests/

Есть обработчики для подключения с различными веб-серверами:

1
2
3
4
5
6
cgihandler.py       # discouraged
gaehandler.py       # for Google App Engine
fcgihandler.py      # for FastCGI
wsgihandler.py      # for WSGI
isapiwsgihandler.py # for IIS
modpythonhandler.py # deprecated

("fcgihandler" вызывает "gluon/contrib/gateways/fcgi.py", разработанный Allan Saddi) и anyserver.py

который представляет собой скрипт для взаимодействия с различными веб-серверами, описанных в главе 13.

Есть три файла с примерами в каталоге "examples":

1
2
3
options_std.py
routes.parametric.example.py
routes.patterns.example.py

Все они предназначены для копирования в корневую директорию (где находятся web2py.py или web2py.exe) и редактирования в соответствии с вашими предпочтениями. Первый из них является необязательным конфигурационным файлом, который может быть передан web2py.py с -L опцией. Второй является примером файла сопоставления URL-адресов. Он загружается автоматически, когда будет переименован в "routes.py". Третий это альтернативный синтаксис для сопоставления URL, а также может быть переименован (или скопирован в) "routes.py".

Следующие файлы

1
2
app.example.yaml
queue.example.yaml

являются примерами конфигурационных файлов, используемых для развертывания на Google App Engine. Вы можете прочитать о них в главе Рецепты развертывания и на страницах документации Google.

Существуют также дополнительные библиотеки, некоторые разработаны третьей стороной:

Библиотека feedparser[feedparser] от Mark Pilgrim для чтения каналов RSS и Atom:

1
2
gluon/contrib/__init__.py
gluon/contrib/feedparser.py

Библиотека markdown2[markdown2] от Trent Mick для wiki разметки:

1
2
gluon/contrib/markdown/__init__.py
gluon/contrib/markdown/markdown2.py

Библиотека разметки markmin:

1
gluon/contrib/markmin

(смотрите подробнее MARKMIN синтаксис)

Библиотека fpdf, созданная Mariano Reingart для генерации PDF документов:

gluon/contrib/fpdf

Она не задокументирована в этой книге, но размещена и задокументирована здесь:

http://code.google.com/p/pyfpdf/

Библиотека pysimplesoap представляет облегченную серверную реализацию SOAP, созданную Mariano Reingart:

1
gluon/contrib/pysimplesoap/

Библиотека simplejsonrpc это облегченный клиент JSON-RPC также созданный Mariano Reingart:

jsonrpc

gluon/contrib/simplejsonrpc.py

Библиотека memcache[memcache] Python API от Evan Martin:

gluon/contrib/memcache/__init__.py
gluon/contrib/memcache/memcache.py

Библиотека redis_cache

redis
представляет собой модуль кэш хранилища в базе данных Redis:

gluon/contrib/redis_cache.py

Библиотека gql, порт абстрактного уровня DAL для Google App Engine:

1
gluon/contrib/gql.py

Библиотека memdb, порт из DAL поверх кэша памяти:

1
gluon/contrib/memdb.py

Библиотека gae_memcache является API для использования кэша памяти на Google App Engine:

1
gluon/contrib/gae_memcache.py

Библиотека pyrtf[pyrtf] для создания документов в Rich Text Format (RTF документы), разработанный Simon Cusack и пересмотренный Grant Edwards:

1
gluon/contrib/pyrtf/

Библиотека PyRSS2Gen[pyrss2gen] разработанная Dalke Scientific Software, для создания RSS каналов:

1
gluon/contrib/rss2.py

Библиотека simplejson[simplejson] от Bob Ippolito, стандартная библиотека для разбора и записи объектов JSON:

1
gluon/contrib/simplejson/

Библиотека Google Wallet [googlewallet] предоставляет кнопки "pay now", которые привязаны Google для обработки платежей:

1
gluon/contrib/google_wallet.py

Библиотека Stripe.com [stripe] предоставляет простой API для приема платежей по кредитным картам:

1
gluon/contrib/stripe.py

Библиотека AuthorizeNet [authorizenet] предоставляет API для приема платежей по кредитным картам через сеть Authorize.net

1
gluon/contrib/AuthorizeNet.py

Библиотека Dowcommerce [dowcommerce] API обработки кредитных карт:

1
gluon/contrib/DowCommerce.py

Библиотека PaymentTech API обработки кредитных карт:

1
gluon/contrib/paymentech.py

Библиотека PAM[PAM] API аутентификации созданная Chris AtLee:

1
gluon/contrib/pam.py

Байесовский классификатор для заполнения базы данных фиктивными данными для целей тестирования:

1
gluon/contrib/populate.py

Файл heroku.py с API для работы на Heroku.com:

heroku

1
gluon/contrib/heroku.py

Файл taskbar_widget.py, который обеспечивает взаимодействие с панелью задач в windows, когда web2py работает как сервис:

1
gluon/contrib/taskbar_widget.py

Дополнительные пакеты login_methods и login_forms, которые будут использоваться для проверки подлинности:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
gluon/contrib/login_methods/__init__.py
gluon/contrib/login_methods/basic_auth.py
gluon/contrib/login_methods/browserid_account.py
gluon/contrib/login_methods/cas_auth.py
gluon/contrib/login_methods/dropbox_account.py
gluon/contrib/login_methods/email_auth.py
gluon/contrib/login_methods/extended_login_form.py
gluon/contrib/login_methods/gae_google_account.py
gluon/contrib/login_methods/ldap_auth.py
gluon/contrib/login_methods/linkedin_account.py
gluon/contrib/login_methods/loginza.py
gluon/contrib/login_methods/oauth10a_account.py
gluon/contrib/login_methods/oauth20_account.py
gluon/contrib/login_methods/oneall_account.py
gluon/contrib/login_methods/openid_auth.py
gluon/contrib/login_methods/pam_auth.py
gluon/contrib/login_methods/rpx_account.py
gluon/contrib/login_methods/x509_auth.py

web2py также содержит папку с полезными скриптами, включающими в себя

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
scripts/setup-web2py-fedora.sh
scripts/setup-web2py-ubuntu.sh
scripts/setup-web2py-nginx-uwsgi-ubuntu.sh
scripts/setup-web2py-heroku.sh
scripts/update-web2py.sh
scripts/make_min_web2py.py
...
scripts/sessions2trash.py
scripts/sync_languages.py
scripts/tickets2db.py
scripts/tickets2email.py
...
scripts/extract_mysql_models.py
scripts/extract_pgsql_models.py
...
scripts/access.wsgi
scripts/cpdb.py

Скрипты setup-web2py-* особенно полезны, поскольку они попытаются завершить установку и настроить web2py в производственной среде с нуля. Некоторые из них рассматриваются в Главе 14, но все они содержат внутренние строки документации, которые объясняют их назначение и использование.

И в завершении, web2py включает нижеследующие файлы, необходимые для создания бинарных дистрибутивов.

1
2
3
Makefile
setup_exe.py
setup_app.py

Это установочные скрипты для py2exe и py2app, соответственно, и они требуются только для создания бинарных дистрибутивов web2py. НИ В КОЕМ СЛУЧАЕ ЗАПУСКАЙТЕ ИХ.

web2py приложения содержат дополнительные файлы, в частности, сторонние библиотеки JavaScript, такие как JQuery, календарь и CodeMirror. Их авторы упоминаются в самих файлах.

Приложения

Приложения, разработанные в web2py состоят из следующих частей:

  • models описывают представление данных в виде таблиц базы данных и связей между таблицами.
  • controllers описывают логику приложения и рабочий процесс.
  • views описывают, как данные должны быть представлены пользователю с помощью HTML и JavaScript.
  • languages описывают, как перевести строки в приложении на различные поддерживаемые языки.
  • static files не требующие обработки файлы (например, изображения, CSS таблицы стилей и т.д.).
  • ABOUT и README документы говорят сами за себя.
  • errors хранят отчеты об ошибках, генерируемых приложением.
  • sessions хранят информацию, связанную с каждым конкретным пользователем.
  • databases хранят SQLite базы данных и дополнительную таблицу с информацией.
  • cache хранят в кэше элементы приложения.
  • modules другие дополнительные модули Python.
  • private файлы, доступные через контроллеры, но не доступные напрямую через разработчика.
  • uploads файлы, доступные через модели, но не доступные напрямую через разработчика (например, файлы, загруженные пользователями приложения).
  • tests является каталогом для хранения тестовых скриптов, приспособлений (fixtures) и пародий(mocks).

Модели, представления, контроллеры, языки, и статические файлы доступны через административный интерфейс. ABOUT, README, и errors также доступны через интерфейс администрирования через соответствующие пункты меню. Sessions, cache, modules и private files доступны для приложений, но не через интерфейс администрирования.

Все аккуратно организовано в четкой структуре каталогов, которая повторяется для каждого установленного приложения web2py, несмотря на это, пользователь никогда не должен получить доступ к файловой системе напрямую:

about
license
cache
controllers
databases
errors
languages
models
modules
private
session
static
tests
uploads
views
__init__.py

1
2
3
__init__.py  ABOUT        LICENSE    models    views
controllers  modules      private    tests     cron
cache        errors       upload     sessions  static

"__init__.py" пустой файл, который требуется для того, чтобы позволить Python (и web2py) импортировать модули в директории modules.

Обратите внимание на то, что приложение admin просто предоставляет веб-интерфейс для web2py приложений на файловой системе сервера. web2py приложения также могут быть созданы и разработаны из командной строки или в текстовом редакторе / IDE; вам не обязательно использовать браузер для admin интерфейса. Новое приложение может быть создано вручную путем копирования вышеуказанной структуры каталогов, например, как в "applications/newapp/" (или просто распаковав welcome.w2p файл в новый каталог приложения). Файлы приложений также могут быть созданы и изменены из командной строки без использования веб-интерфейса администратора (admin).

API Интерфейс

Модели, контроллеры и представления выполняются в среде, где следующие объекты уже импортированы для нас:

Глобальные объекты:

request
response
session
cache

1
request, response, session, cache

Интернационализация:

T
internationalization

1
T

Навигация:

redirect
HTTP

1
redirect, HTTP

Помощники:

helpers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
XML, URL, BEAUTIFY

A, B, BODY, BR, CENTER, CODE, COL, COLGROUP,
DIV, EM, EMBED, FIELDSET, FORM, H1, H2, H3, H4, H5, H6,
HEAD, HR, HTML, I, IFRAME, IMG, INPUT, LABEL, LEGEND,
LI, LINK, OL, UL, META, OBJECT, OPTION, P, PRE,
SCRIPT, OPTGROUP, SELECT, SPAN, STYLE,
TABLE, TAG, TD, TEXTAREA, TH, THEAD, TBODY, TFOOT,
TITLE, TR, TT, URL, XHTML, xmlescape, embed64

CAT, MARKMIN, MENU, ON

Формы и таблицы

SQLFORM (SQLFORM.factory, SQLFORM.grid, SQLFORM.smartgrid)

Валидаторы:

validators

1
2
3
4
5
6
7
CLEANUP, CRYPT, IS_ALPHANUMERIC, IS_DATE_IN_RANGE, IS_DATE,
IS_DATETIME_IN_RANGE, IS_DATETIME, IS_DECIMAL_IN_RANGE,
IS_EMAIL, IS_EMPTY_OR, IS_EXPR, IS_FLOAT_IN_RANGE, IS_IMAGE,
IS_IN_DB, IS_IN_SET, IS_INT_IN_RANGE, IS_IPV4, IS_LENGTH,
IS_LIST_OF, IS_LOWER, IS_MATCH, IS_EQUAL_TO, IS_NOT_EMPTY,
IS_NOT_IN_DB, IS_NULL_OR, IS_SLUG, IS_STRONG, IS_TIME,
IS_UPLOAD_FILENAME, IS_UPPER, IS_URL

База данных:

DAL

1
DAL, Field

Для обратной совместимости SQLDB=DAL и SQLField=Field. Мы рекомендуем Вам использовать новый синтаксис DAL и Field, вместо старого синтаксиса.

Другие объекты и модули определяются в библиотеках, но они не будут автоматически импортированы, так как они используются не так часто.

Объектами ядра API в среде выполнения web2py являются request, response, session, cache, URL, HTTP, redirect и T и обсуждаются ниже.

Несколько объектов и функций, включая Auth, Crud и Service, определены в "gluon/tools.py" и они должны быть импортированы по мере необходимости:

1
from gluon.tools import Auth, Crud, Service

Они импортируются в db.py в скаффолдинг-приложении.

Доступ к API из модулей Python

Ваши модели или контроллеры могут импортировать Python модули. Как правило, модули являются Python файлами, которые вы храните в каталоге модулей вашего приложения. Также вам возможно придется воспользоваться некоторыми API web2py. Способом сделать это, является импортировать их:

from gluon import *

На самом деле, любой модуль Python, даже если они не импортируются приложением web2py, может импортирован через web2py API, при условии, что web2py находится в sys.path.

Использование объекта current для совместного использования модулями глобальной области видимости

Существует однако один нюанс. Web2py определяет некоторые глобальные объекты (request, response, session, cache, T), которые могут существовать только тогда, когда присутствует HTTP запрос (или подделан). Таким образом, модули могут получить доступ к ним только тогда, когда они вызываются из приложения. По этим причинам они помещены в контейнер под названием current, который представляет собой поток локального объекта. Здесь приведен пример.

Создайте модуль "/myapp/modules/mytest.py", который содержит:

from gluon import *
def ip(): return current.request.client

Теперь из контроллера в "myapp" вы можете сделать

import mytest
def index():
    return "Your ip is " + mytest.ip()

Обратите внимание на несколько вещей:

  • Строка import mytest ищет модуль сперва в папке модулей текущего приложения, а затем в папках, перечисленных в sys.path. Таким образом, модули уровня приложения всегда имеют приоритет над модулями Python. Это позволяет различным приложениям поставляться с различными версиями своих модулей, без конфликтов.
  • Различные пользователи могут вызывать одно и то же действие index одновременно, что вызывает функцию в модуле, и пока нет никакого конфликта, потому что current.request является другим объектом в разных потоках. Только будьте осторожны, чтобы не получить доступ current.request вне функций или классов (т.е. на верхнем уровне) в модуле.
  • import mytest это сокращение для from applications.appname.modules import mytest. Используя более длинный синтаксис, можно импортировать модули из других приложений.

Для унификации с нормальным поведением Python, по умолчанию web2py не перегружает модули при внесении изменений. Тем не менее, это может быть изменено. Чтобы включить функцию автоперезагрузки для модулей, используйте функция track_changes следующим образом (как правило, в файле модели, перед любым импортом):

1
from gluon.custom_import import track_changes; track_changes(True)

С этого момента, каждый раз, когда модуль импортируется, импортер будет проверять изменился ли исходный файл Python (.py). Если он изменилось, то модуль будет перезагружен.

Не вызывайте track_changes в самих модулях.

При отслеживании изменений, отслеживаются только изменения для модулей, которые хранятся в приложении. Через модули, которые импортирует current можно получить доступ к:

  • current.request
  • current.response
  • current.session
  • current.cache
  • current.T

и любой другой переменной вашего приложения, выбранной для хранения в current. Например, в модели можно сделать

auth = Auth(db)
from gluon import current
current.auth = auth
current.db = db #не требуется в данном случае, но полезно

и теперь ко всем импортируемым модулям можно получить доступ через current.auth.

current и import создать мощный механизм для создания расширяемых и повторно используемых модулей для приложений.

Внимание! Не используйте объект current в глобальной области видимости внутри модуля.

Осторожно! Учитывайте что если вы импортируете объект from gluon import current, то его атрибуты правильнее всего использовать как current.request, как и любой другой атрибут из других локальных объектов потока, но никогда не следует назначать атрибуты в глобальные переменные в модуле, например вот так:

request = current.request # Неправильно! Опасно!

не следует также использовать current для присвоения атрибутам класса:

class MyClass:
    request = current.request # Неправильно! Опасно!

Это происходит потому, что локальный объект потока должен быть извлечен во время выполнения программы. Глобальные переменные, напротив определяются только один раз, когда модель импортируется впервые.

Вместо этого, назначьте внутри функции.

1
2
3
4
5
import * from gluon
...
def a_module_function():
   db = current.db  #предполагается, что уже вы назначили current.db = db в модели db.py
   ...

Еще один нюанс имеет отношение к кэш-памяти. Вы не можете использовать cache объект для декорирования функций в модулях, потому что он не будет вести себя как положено. Для того, чтобы кэшировать функцию f в модуле вы должны использовать lazy_cache:

1
2
3
4
from gluon.cache import lazy_cache

@lazy_cache('key', time_expire=60, cache_model='ram')
def f(a,b,c,): ....

Заметьте, что ключ определяется пользователем, но должен быть однозначно связан с функцией. Если этот параметр опущен, то web2py задаст ключ автоматически.

Объект request

request
Storage
request.cookies
user_agent

Объект request является экземпляром класса gluon.storage.Storage вездесущего web2py, который расширяет Python класс dict. В общем он является словарем, но значения элементов также могут быть доступны как атрибуты, например:

1
request.vars

такой же как:

1
request['vars']

В отличие от словаря, если атрибут (или ключ) не существует, то он не вызывает исключение. Вместо этого он возвращает None.

Иногда полезно создавать свои собственные объекты Storage. Вы можете сделать это следующим образом:
1
2
3
from gluon.storage import Storage
my_storage = Storage() # пустой storage объект
my_other_storage = Storage(dict(a=1, b=2)) # преобразование словаря в Storage

Объект request имеет следующие элементы/атрибуты, некоторые из которых также являются экземпляром Storage класса:

  • request.cookies: Объект Cookie.SimpleCookie() содержит куки, передаваемые с запросом HTTP. Он действует как словарь куки. Каждое куки является объектом Morsel [morsel].
  • request.env: Объект Storage содержит переменные среды, передаваемые в контроллер, включая переменные заголовка HTTP из запроса HTTP и стандартные параметры WSGI. Переменные среды, преобразуются в нижний регистр, а точки преобразуются в подчеркивания для облегчения запоминания.
  • request.application: наименование запрашиваемого приложения.
  • request.controller: наименование запрашиваемого контроллера.
  • request.function: наименование запрашиваемой функции.
  • request.extension: расширение запрашиваемого действия. По умолчанию это "HTML". Если функция контроллера возвращает словарь и не задано представление, это используется для определения расширения файла представления, который будет визуализировать словарь (разобранный от request.env.path_info).
  • request.folder: каталог приложения. Например, для приложения "welcome", request.folder установлен на абсолютный путь "/path/to/welcome". В ваших программах, вы должны всегда использовать эту переменную и os.path.join функцию для построения пути к файлам, к которым вам необходимо получить доступ. Хотя web2py всегда использует абсолютные пути, но хорошим правилом будет это никогда явным образом не изменять текущий рабочий каталог (для всего что есть), так как это не является потокобезопасной практикой.
  • request.now: Объект класса datetime.datetime хранит время и дату текущего запроса.
  • request.utcnow: Объект класса datetime.datetime хранит UTC время и дату текущего запроса.
  • request.args: Список URL путей к компонентам, следующий за именем функции контроллера; что эквивалентно request.env.path_info.split('/')[3:]
  • request.vars: Объект класса gluon.storage.Storage содержит все параметры запроса.
  • request.get_vars: Объект класса gluon.storage.Storage содержит только параметры, передаваемые в строке запроса (запрос с /a/c/f?var1=1&var2=2 в конце, будет преобразован в {var1: "1", var2: "2"})
  • request.post_vars: Объект класса gluon.storage.Storage содержит только параметры, передаваемые в теле запроса (обычно в POST, PUT, DELETE запросах).
  • request.client: IP-адрес клиента, как это определено, если он присутствует, через request.env.http_x_forwarded_for или через request.env.remote_addr в противном случае. Несмотря на то это полезно не следует доверять, так как http_x_forwarded_for может быть подделан.
  • request.is_local: True если клиент локального хоста, False в противном случае. Должен работать за прокси-сервером, если прокси поддерживается http_x_forwarded_for.
  • request.is_https: True если запрос с использованием протокола HTTPS, False в противном случае.
  • request.body: только для чтения файловый поток, который содержит тело запроса HTTP. Он автоматически разбирается, чтобы получить request.post_vars и затем обратно сворачивается. Он может быть прочитан с request.body.read().
  • request.ajax равняется True, если функция вызывается с помощью Ajax запроса.
  • request.cid это id компонента, который генерируется запрос Ajax (если таковые имеются). Вы можете прочитать больше о компонентах в главе 12.
  • request.requires_https() предотвращает дальнейшее выполнение кода, если запрос не через HTTPS и перенаправляет посетителя на текущую страницу по HTTPS.
  • request.restful это новый и очень полезный декоратор, который может использоваться для изменения поведения по умолчанию web2py действий путем разделения GET/POST/PUSH/DELETE запросов. Он будет обсуждаться более подробно в главе 10.
  • request.user_agent() разбирает поле user_agent от клиента и возвращает информацию в виде словаря. Полезно для обнаружения мобильных устройств. Оно использует "gluon/contrib/user_agent_parser.py" созданный Ross Peoples. Для того, чтобы посмотреть, что он делает, попытайтесь внедрить следующий код в представление:
1
{{=BEAUTIFY(request.user_agent())}}
  • request.global_settings
    request.global_settings
    содержит широкие настройки web2py системы. Они устанавливаются автоматически и вам не следует изменять их. Например request.global_settings.gluon_parent содержит полный путь к папке web2py, request.global_settings.is_pypy определяет, является ли web2py запущенным на PyPy.
  • request.wsgi это крюк, который позволяет вызывать приложения сторонних разработчиков WSGI изнутри действий

Последний включает в себя:

  • request.wsgi.environ
  • request.wsgi.start_response
  • request.wsgi.middleware

их использование обсуждается в конце настоящей главы.

В качестве примера, рассмотрим следующий вызов на типичной системе:

1
http://127.0.0.1:8000/examples/default/status/x/y/z?p=1&q=2

В результате получается следующий request объект:

request
env

ПеременнаяЗначение
request.applicationexamples
request.controllerdefault
request.functionstatus
request.extensionhtml
request.viewstatus
request.folderapplications/examples/
request.args['x', 'y', 'z']
request.vars<Storage {'p': 1, 'q': 2}>
request.get_vars<Storage {'p': 1, 'q': 2}>
request.post_vars<Storage {}>
request.is_localFalse
request.is_httpsFalse
request.ajaxFalse
request.cidNone
request.wsgi<hook>
request.env.content_length0
request.env.content_type
request.env.http_accepttext/xml,text/html;
request.env.http_accept_encodinggzip, deflate
request.env.http_accept_languageen
request.env.http_cookiesession_id_examples=127.0.0.1.119725
request.env.http_host127.0.0.1:8000
request.env.http_refererhttp://web2py.com/
request.env.http_user_agentMozilla/5.0
request.env.path_info/examples/simple_examples/status
request.env.query_stringremote_addr:127.0.0.1
request.env.request_methodGET
request.env.script_name
request.env.server_name127.0.0.1
request.env.server_port8000
request.env.server_protocolHTTP/1.1
request.env.server_softwareRocket 1.2.6
request.env.web2py_path/Users/mdipierro/web2py
request.env.web2py_versionVersion 2.4.1
request.env.wsgi_errors<open file, mode 'w' at >
request.env.wsgi_input
request.env.wsgi_url_schemehttp

Какие переменные среды фактически определены, зависит от веб-сервера. Здесь мы предполагаем, что используется встроенный Rocket WSGI сервер. Набор переменных не сильно отличается при использовании веб-сервера Apache.

Переменные request.env.http_* получены а результате разбора заголовка HTTP запроса.

Переменные request.env.web2py_* не разбираются из среды веб-сервера, а создаются web2py в случае, если ваши приложения должны знать о местонахождении web2py и версии, а также будет ли оно работать на Google App Engine (поскольку может потребоваться специфичная оптимизация).

Также обратите внимание на переменные request.env.wsgi_*. Они специфичны для адаптера wsgi.

Объект response

response
response.body
response.cookies
response.download
response.files
response.flash
response.headers
response.meta
response.menu
response.postprocessing
response.render
response.static_version
response.status
response.stream
response.subtitle
response.title
response.toolbar
response.view
response.delimiters
response.js
response.write
response.include_files
response.include_meta
response.optimize_css
response.optimize_js
response._caller
response.models_to_run

response это еще один экземпляр класса Storage. В нём содержится следующее:

  • response.body: Объект класса StringIO, в который web2py записывает тело выводимой страницы. НИКОГДА НЕ ИЗМЕНЯЙТЕ ЭТУ ПЕРЕМЕННУЮ.
  • response.cookies: похож на request.cookies, но в то время как последний содержит куки, посылаемые от клиента к серверу, первый содержит куки, посылаемые от сервера к клиенту. Куки сессии обрабатывается автоматически.
  • response.download(request, db): метод, используемый для реализации функции контроллера, который позволяет скачивать загруженные файлы. response.download ожидает, чтобы в последнем arg в request.args было закодированное имя файла (то есть, имя файла генерируется во время загрузки и хранится в поле загрузки). Он извлекает имя поля загрузки и имя таблицы, так же как и исходное имя файла из кодируемого имени файла.response.download имеет два необязательных аргумента: chunk_size устанавливает размер в байтах для фрагментирования потоковой передачи (по умолчанию 64К), а также attachments определяет, следует ли загружаемый файл рассматривать в качестве вложения или нет (по умолчанию True). Обратите внимание, response.download специально для загрузки файлов связан с полями загрузки db. Используйте response.stream (смотрите ниже) для других типов загрузки файлов и потоковой передачи. Кроме того, обратите внимание, что нет необходимости использовать response.download чтобы получить доступ к файлам, загруженных в /static folder -- статические файлы могут (и, как правило, должны) быть доступны непосредственно через URL (например, /app/static/files/myfile.pdf).
  • response.files: список из .css, .js, .coffee, и .less файлов, требуемых страницей. Они будут автоматически связаны в заголовке стандартного "layout.html" через входящий в комплект "web2py_ajax.html". Чтобы включить новый CSS, JS, COFFEE или LESS файл, просто добавьте его в этот список. Список обрабатывает дубликаты. Порядок очень важен.
  • response.include_files() генерирует HTML-теги заголовка страницы, чтобы включить все response.files (используется в "views/web2py_ajax.html").
  • response.flash: необязательный параметр, который может быть включен в представлениях. Обычно используется для уведомления пользователя о том, что произошло.
  • response.headers: объект класса dict для заголовков ответа HTTP. Web2py устанавливает некоторые заголовки по умолчанию, в том числе "Content-Length", "Content-Type", и "X-Powered-By" (устанавливается равным web2py). Web2py также устанавливает "Cache-Control", "Expires", и "Pragma" заголовки, чтобы предотвратить кэширование на стороне клиента, кроме запросов статических файлов, для которых на стороне клиента включено кэширование. Заголовки, которые web2py устанавливает, могут быть перезаписаны или удалены, а также могут быть добавлены новые заголовки (например, response.headers['Cache-Control'] = 'private'). Вы можете удалить заголовок, удалив его ключ из словаря response.headers, например командой del response.headers['Custom-Header'], но заголовки web2py по умолчанию будут повторно добавлены перед возвратом ответа. Чтобы избежать этого, просто установите значение заголовка на None, например чтобы удалить заголовок по умолчанию Content-Type, выполните response.headers['Content-Type'] = None
  • response.menu: необязательный параметр, который может быть включен в представлениях, как правило, используется для передачи дерева меню навигации в представление. Он может быть визуализирован через помощника MENU.
  • response.meta: объект класса Storage, который содержит необязательную <meta> информацию, как response.meta.author, .description, и/или .keywords. Содержание каждой мета-переменной автоматически помещается в надлежащий META тег через код в "views/web2py_ajax.html", который включен по умолчанию в "views/layout.html".
  • response.include_meta() генерирует строку, которая включает в себя все response.meta сериализованные заголовки (используется в "views/web2py_ajax.html").
  • response.postprocessing: это список функций, по умолчанию пустой. Эти функции используются для фильтрации объекта response на выходе какого-либо действия, прежде чем выходные данные визуализируются через представление. Он может быть использован для реализации поддержки других языков шаблонов.
  • response.render(view, vars): метод, используемый для вызова представления в явном виде внутри контроллера. view является необязательным параметром, который является именем файла представления, vars представляет собой словарь именованных значений, передаваемых в представление.
  • response.session_file: файл потока, содержащий сессию.
  • response.session_file_name: имя файла, в котором будет сохранена сессия.
  • response.session_id: id текущей сессии. Она определяется автоматически. НИКОГДА НЕ ИЗМЕНЯЙТЕ ЭТУ ПЕРЕМЕННУЮ.
  • response.session_id_name: наименование куки сессии для данного приложения. НИКОГДА НЕ ИЗМЕНЯЙТЕ ЭТУ ПЕРЕМЕННУЮ.
  • response.static_version: номер версии для управления статическими ценностями.
  • response.status: HTTP-код состояния, целое число передаваемое в ответе. По умолчанию 200 (OK).
  • response.stream(file, chunk_size, request=request, attachment=False, filename=None): когда контроллер возвращает его, web2py поточно передает содержимое файла обратно клиенту в виде блоков размером chunk_size. Параметр request требует использовать начальный фрагмент в HTTP заголовке. file должен содержать путь к файлу (для обратной совместимости, он может также быть открытым объектом файла, но это не рекомендуется). Как указано выше, response.download следует использовать для извлечения файлов, сохраненных с помощью поля загрузки. response.stream может быть использован в других случаях, таких как возврат временного файла или объекта StringIO, созданного с помощью контроллера. Если attachment установлен на True, заголовок Content-Disposition будет установлен в "attachment", и если также предоставляется filename, то он тоже будет добавлен в заголовок Content-Disposition (но только тогда, когда attachment в значении True). Если уже не включены в response.headers, то следующие заголовки ответа будут установлены автоматически: Content-Type, Content-Length, Cache-Control, Pragma, и Last-Modified (последние три устанавливаются, чтобы позволить браузеру кэшировать файл). Чтобы изменить любую из этих автоматических настроек заголовка, просто установите их в response.headers перед вызовом response.stream.
  • response.subtitle: необязательный параметр, который может быть включен в представлениях. Он должен содержать подзаголовок страницы.
  • response.title: необязательный параметр, который может быть включен в представлениях. Он должен содержать наименование страницы и должен быть отображен внутри HTML-тега title в заголовке.
  • response.toolbar: функция, которая позволяет встроить панель инструментов в страницу для целей отладки {{=response.toolbar()}}. Панель инструментов отображает запрос, ответ, переменные сессии и время доступа к базе данных для каждого запроса.
  • response._vars: эта переменная доступна только в представлении, а не в действии. Она содержит значения, возвращаемые действием в представление.
  • response._caller: это функция, которая оборачивает все вызовы действия. По умолчанию она имеет значение тождественной функции, но она может быть изменена для того, чтобы поймать специальные типы исключений для выполнения записи дополнительных сведений; response._caller = lambda f: f()
  • response.optimize_css: может быть установлен в "concat,minify,inline" для конкатенации, преуменьшения и встраивания CSS файлов, включенных в web2py.
  • response.optimize_js: может быть установлен в "concat,minify,inline" для конкатенации, преуменьшения и встраивания JavaScript файлов, включенных в web2py.
  • response.view: наименование шаблона представления, который должен отобразить страницу. Он устанавливается по умолчанию в:
    1
    2
      "%s/%s.%s" % (request.controller, request.function, request.extension)
      
    
    или, если вышеуказанный файл невозможно найти, в
    1
    2
      "generic.%s" % (request.extension)
      
    
    Измените значение этой переменной, чтобы изменить файл представления, связанный с конкретным действием.
  • response.delimiters по умолчанию ('{{','}}'). Он позволяет изменить разделитель кода встроенного в представлениях.
  • response.xmlrpc(request, methods): когда контроллер возвращает его, эта функция предоставляет методы через XML-RPC[xmlrpc] . Данная функция не рекомендуется, так как более удачный механизм доступен и описан в главе 10.
  • response.write(text): метод для записи текста в тело выводимой страницы.
  • response.js может содержать Javascript код. Этот код будет выполняться тогда и только тогда, когда ответ получен компонентом web2py, как описано в главе 12.
  • response.models_to_run содержит список регулярных выражений, который выбирает, какие модели запускаются.
    • По умолчанию, здесь задается автоматическая загрузка /a/models/*.py, /a/models/c/*.py, и /a/models/c/f/*.py файлов когда /a/c/f запрашивается. Вы можете установить, например, response.models_to_run = ['myfolder/'], чтобы заставить выполнить только модели внутри подпапки models/myfolder вашего приложения.
    • Обратите особое внимание: response.models_to_run является списком регулярных выражений, а не списком путей к файлам. Регулярное выражение выполняется относительно моделей/папки, так что любая модель, относительный путь к файлу которой соответствует одному из регулярных выражений, будет выполняться. Отметим также, что это не может повлиять на любые модели, которые ранее были проанализированы, так как они были заранее отсортированы в алфавитном порядке. То есть, если условная модель для контроллера orange была orange/orange_model.py и ей установлено регулярное выражение [.*], то это изменение не повлияет на какие-либо модели, предварительно отвергнутых для загрузки, таких как модель apple/apple_model.py ; это шаблон нового регулярного выражения, но оно было проанализировано и отклонено, прежде чем модель orange/orange_model.py изменила регулярное выражение.
    • Это означает, что если вы хотите использовать models_to_run, чтобы условно поделиться моделями между контроллерами, то положите модели в подкаталоге, который будет отсортирован последним, например ZZZ, а затем используйте в регулярном выражении 'ZZZ'.

Поскольку response является объектом класса gluon.storage.Storage, то он может быть использован для хранения других атрибутов, которые вы можете передать в представление. Хотя нет никаких технических ограничений, но мы рекомендуем хранить только те переменные, которые должны быть отображены на всех страницах в общем макете ("layout.html").

Во всяком случае, мы настоятельно рекомендуем придерживаться переменных, перечисленных здесь:

1
2
3
4
5
6
7
8
response.title
response.subtitle
response.flash
response.menu
response.meta.author
response.meta.description
response.meta.keywords
response.meta.*

потому что это облегчит для вас замену стандартного файла "layout.html", который поставляется с web2py на другой файл макета, который использует тот же набор переменных.

Старые версии web2py используют response.author вместо response.meta.author и аналогичные для других мета-атрибутов.

Объект session

session
session.connect
session.forget
session.secure
Объект session это еще один экземпляр класса Storage. Что угодно может хранится в session, например:

1
session.myvariable = "hello"

которая может быть получена в более позднее время:

1
a = session.myvariable

до тех пор, пока код выполняется в пределах одной сессии и тем же пользователем (при условии, что пользователь не удалил куки сессии, а сессия не просрочена). Поскольку session является объектом класса Storage, то попытка получить доступ к несуществующему атрибуту/ключу не вызовет исключение; объект возвращает None вместо этого.

Объект сессии имеет три важных метода. Одним из них является forget:

1
session.forget(response)

Он говорит web2py не сохранять сессию. Этот метод следует использовать в тех контроллерах, чьи действия вызываются часто и не нужно отслеживать активность пользователя. session.forget() препятствует записи в файл сеанса, независимо от того, был ли он изменен. session.forget(response) дополнительно разблокирует и закрывает файл сеанса. Вам редко понадобится вызов этого метода, так как сессии не сохраняются, когда они не изменяются. Тем не менее, если страница делает несколько одновременных запросов Ajax, то это хорошая идея для действий вызываемых с помощью Ajax вызова session.forget(response) (предполагается, что сессия не нуждается в действии). В противном случае, каждому действию Ajax придется ждать, пока завершится предыдущее (и разблокирует файл сессии), прежде чем продолжить, что замедляет загрузку страницы. Обратите внимание на то, что сессии не заблокированы, когда хранятся в базе данных.

Другой метод:

1
session.secure()

который говорит web2py установить куки сессии, чтобы обезопасить куки. Это должно быть установлено, если приложение работает через протокол HTTPS. Посредством задания настроек безопасности куки сессии, сервер запрашивает браузер не посылать куки сессии обратно на сервер, если через соединение выполняется по протоколу HTTPS.

Другой метод это connect. По умолчанию сессии сохраняются в файловой системе и куки сессии используется для хранения и извлечения session.id. Используя метод connect можно сказать web2y хранить сессии в базе данных или в куки, таким образом, устраняя необходимость доступа к файловой системе для управления сеансами.

Вот пример хранения сессий в базе данных:

1
session.connect(request, response, db, masterapp=None)

где db это имя соединения для открытой базы данных (возвращенное самой DAL). Оно говорит web2py, что вы хотите хранить сессии в базе данных, а не в файловой системе. Строка session.connect в коде должна следовать после строки db=DAL(...), но перед какой-либо другой логикой, которая требуется сессией, например, настройка Auth.

web2py создает таблицу:

1
2
3
4
5
6
7
db.define_table('web2py_session',
                 Field('locked', 'boolean', default=False),
                 Field('client_ip'),
                 Field('created_datetime', 'datetime', default=now),
                 Field('modified_datetime', 'datetime'),
                 Field('unique_key'),
                 Field('session_data', 'text'))

и хранит сериализованные через cPickled сессии в поле session_data.

Опция masterapp=None, по умолчанию, говорит web2pyпопробовать восстановить существующую сессию для приложения с именем в request.application, в запущенном приложении.

Если вы хотите поделиться сессиями с двумя или несколькими приложениями, то задайте в опции masterapp имя мастер-приложения.

Для хранения сессий в куки, вместо этого вы можете сделать:

1
session.connect(request,response,cookie_key='yoursecret',compression_level=None)

Здесь cookie_key симметричный ключ шифрования. compression_level является необязательным zlib уровнем шифрования.

Так как хранить сеансы в куки часто рекомендуется для масштабируемости, поэтому они ограничены в размерах. Большие сессии будут приводить к сломаным куки.

Вы можете проверить состояние вашего приложения в любое время с помощью печати request, session и response системных переменных. Один из способов сделать это, чтобы создать специальное действие:

1
2
def status():
    return dict(request=request, session=session, response=response)

В представлении "generic.html" это делается с помощью {{=response.toolbar()}}.

Не храните определенные пользователем классы в сессии

Переменные, хранящиеся в сессии, сохраняются между запросами с помощью сериализации.

Сессии извлекаются перед тем, как код модуля будет выполнен, и, следовательно, перед тем, как классы будут определены. Поэтому пользовательские классы не могут быть законсервированы.

Классы, определенные в модулях также являются серой областью, и их не следует помещать в хранилище. Большую часть времени они работают, но они могут сломаться. Это происходит потому, что, например, если вы перезапустите веб-сервер и пользователь извлечет сеанс, то это может произойти до того, как модуль будет импортирован. Такая же проблема возникает, когда веб-сервер начинает новый рабочий процесс. Такая же проблема возникает и в распределенной среде.

Разделение сессий

Если вы храните сессии в файловой системе и у вас их накопилось огромное количество, то доступ к файловой системе может стать узким местом. Одним из решений является следующее:

1
session.connect(request, response, separate=True)

Путем установки separate=True web2py будет хранить сессии не в папке "sessions/", а в подпапках внутри папки "sessions/". Подпапка будет создана автоматически. Сессии с тем же префиксом будут находиться в той же подпапке. Опять же, обратите внимание, что вышеуказанная строка кода должна быть вызвана перед любой логикой, которой может потребоваться сессия.

Объект cache

cache
cache.ram
cache.disk
Глобальный объект cache также доступен в среде выполнения web2py. Он имеет два атрибута:

  • cache.ram: кэш приложения в оперативной памяти.
  • cache.disk: кэш приложения на диске.

cache является вызываемым, это позволяет использовать его в качестве декоратора для кэширования действий и представлений.

Следующий пример кэширует функция time.ctime() в RAM:

1
2
3
4
def cache_in_ram():
    import time
    t = cache.ram('time', lambda: time.ctime(), time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

Выходные данные функции lambda: time.ctime() кэшируются в оперативной памяти в течение 5 секунд. Строка 'time' используется в качестве ключа кэша.

Следующий пример кэширует time.ctime() функцию на диск:

1
2
3
4
def cache_on_disk():
    import time
    t = cache.disk('time', lambda: time.ctime(), time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

Выходные данные функции lambda: time.ctime() кэшируются на диск (с помощью модуля shelve) в течение 5 секунд.

Обратите внимание, что второй аргумент cache.ram и cache.disk должен быть функцией или вызываемым объектом. Если вы хотите кэшировать существующий объект, а не выходные данные функции, вы можете просто вернуть его с помощью лямбда-функции:

1
cache.ram('myobject', lambda: myobject, time_expire=60*60*24)

Следующий пример кэширует функцию time.ctime() на RAM и диск:

1
2
3
4
5
6
def cache_in_ram_and_disk():
    import time
    t = cache.ram('time', lambda: cache.disk('time',
                       lambda: time.ctime(), time_expire=5),
                       time_expire=5)
    return dict(time=t, link=A('click me', _href=request.url))

Выходные данные функции lambda: time.ctime() кэшируются на диск (с помощью модуля shelve), а затем в оперативной памяти в течение 5 секунд. web2py сперва смотрит в оперативную память и, если не обнаруживает там данные, то смотрит данные уже на диске. Если данных нет в оперативной памяти или на диске, то выполняется функция lambda:time.ctime() и кэш обновляется. Этот метод полезен в многопроцессорной среде. Оба значения времени не должны быть одинаковыми.

В следующем примере кэшируются в оперативную память выходные данные функции контроллера (а не представления):

cache controller
1
2
3
4
5
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_in_ram():
    import time
    t = time.ctime()
    return dict(time=t, link=A('click me', _href=request.url))

Словарь, возвращаемый cache_controller_in_ram кэшируется в оперативной памяти в течение 5 секунд. Обратите внимание, что результат выборки из базы данных не может быть кэширован без предварительной сериализации. Лучший способ состоит в кешировании выборки из базы данных напрямую используя аргумент cache метода select.

Следующий пример кэширует выходные данные функции контроллера на диске (но не представления):

1
2
3
4
5
6
@cache(request.env.path_info, time_expire=5, cache_model=cache.disk)
def cache_controller_on_disk():
    import time
    t = time.ctime()
    return dict(time=t, link=A('click to reload',
                              _href=request.url))

Словарь, возвращаемый cache_controller_on_disk кэшируется на диске в течение 5 секунд. Помните, что web2py не сможет кэшировать словарь, который содержит неконсервируемые объекты.

Кроме того, можно кэшировать представления. Хитрость заключается в том, чтобы преобразовать представление в функцию контроллера, так чтобы контроллер возвращал строку. Это делается путем возврата response.render(d), где d это словарь, который мы намеревались передать в представление. В следующем примере кэшируются в оперативную память выходные данные функции контроллера (включая визуализированное представление):

cache view
1
2
3
4
5
6
@cache(request.env.path_info, time_expire=5, cache_model=cache.ram)
def cache_controller_and_view():
    import time
    t = time.ctime()
    d = dict(time=t, link=A('click to reload', _href=request.url))
    return response.render(d)

response.render(d) возвращает результат визуализации в виде строки, которая теперь кэшируется в течение 5 секунд. Это самый лучший и самый быстрый способ кэширования.

Мы рекомендуем @cache.action начиная с версии web2py > 2.4.6

Обратите внимание на то, что time_expire используется для сравнения текущего времени с временем последнего сохранения запрашиваемого объекта в кэше. Это не влияет на будущие запросы. Это дает возможность устанавливать динамически time_expire, когда объект запрашивается, а не фиксируется при сохранении объекта. Например:

1
message = cache.ram('message', lambda: 'Hello', time_expire=5)

Теперь предположим, что следующий вызов делается через 10 секунд после вышеуказанного вызова:

1
message = cache.ram('message', lambda: 'Goodbye', time_expire=20)

Поскольку time_expire устанавливается на 20 секунд во втором вызове, и так как прошло только 10 секунд с тех пор, когда сообщение было впервые сохранено, то значение "Hello" будет извлечено из кэша, и оно не будет обновлено на "Goodbye". Установка значения time_expire в 5 секунд при первом обращении не оказывает никакого влияния на второй вызов.

Установка time_expire=0 (или отрицательное значение) заставляет обновляться кэшированный элемент (потому что время, прошедшее с момента последнего сохранения всегда будет > 0), и установка time_expire=None заставляет восстанавливать кэшированное значение, независимо от времени, прошедшего с момента, когда он был сохранен (если time_expire всегда стоит в None, то кэшированный элемент будет эффективным и время хранения никогда не истечет).

Вы можете очистить одну или более переменных кэша с

cache clear
1
cache.ram.clear(regex='...')

где regex является регулярным выражением, соответствующее всем ключам, которые Вы хотите удалить из кэша. Можно также очистить один элемент с:

1
cache.ram(key, None)

где key является ключом кэшированного элемента.

Кроме того, можно определить другие механизмы кэширования, таких как кэш памяти. Кэш памяти доступен через gluon.contrib.memcache и обсуждается более подробно в Главе 14.

Будьте осторожны при кэшировании, запомните, что кэширование обычно выполняется на уровне приложения, а не на уровне пользователя. Если вам необходимо, например, кэширование конкретного контента пользователя, то выберите ключ, который включает в себя идентификатор пользователя.
Приложение администратора (admin) для приложения позволяет вам просмотреть ключи кэша (и очистить кэш). Доступ к ним выполняется с панели администратора на экране управления базами данных.

Декоратор cache.action

Web2py по умолчанию предполагает, что возвращаемое содержимое не будет кэшироваться, так как это уменьшает недостатки ненадлежащего кэширования страницы на стороне клиента.

Например, когда вы показываете пользователю форму, или список записей, то веб-страница не должна быть помещена в кэш, так как другие пользователи могут вставить новые записи в отображаемую вам таблицу.

И наоборот, если вы показываете пользователю вики страницу, содержание которой никогда не изменится (или оно меняется раз в неделю), то полезно сохранить эту страницу, но еще полезнее сказать клиенту, что эта страница не подлежит изменению.

Это достигается отправкой некоторых специфических заголовков наряду со страницей: когда клиентский браузер получает содержимое, то содержимое сохраняется в кэше браузера, и оно уже не будет запрошено снова с вашего сайта. Конечно же, это значительно увеличивает скорость для общедоступных сайтов.

Web2py > 2.4.6 внедрен новый cache.action декоратор для обеспечения более умной обработки этой ситуации. Декоратор cache.action может быть использован:

  • для установки смарт-заголовков кэша
  • для кэширования результатов соответствующим образом
Внимание: он сделает первое или второе, или оба вышеуказанных действия.

Основная проблема с кэшированием представления при использовании @cache(request.env.path_info, time_expire=300, cache_model=cache.ram) состоит в том, что использование request.env.path_info в качестве ключа приводит к ряду проблем, например.

  1. URL vars не рассматриваются
    • Вы кэшировали результат /app/default/index?search=foo : в течение следующих 300 секунд /app/default/index?search=bar будет возвращать ту же самую вещь /app/default/index?search=foo
  2. Пользователь не рассматривается
    • Ваш пользователь получает доступ к странице часто и вы решили кэшировать ее. Однако, когда вы кэшируете результат /app/default/index используя request.env.path_info как ключ, то другой пользователь увидит страницу, которая была предназначена не для него
    • Вы кэшируете страницу для "Bill", но "Bill" имеет доступ к странице с персонального компьютера. Сейчас он пытается получить к ней доступ со своего телефона: если вы подготовили шаблон для мобильных пользователей, который отличается от стандартного, то "Joe" не увидит его
  3. Язык не рассматривается
    • Когда вы кэшируете страницу, и если вы используете T() для некоторых элементов, то страница будет сохранена с фиксированным переводом
  4. Метод не рассматривается
    • Когда вы кэшируете страницу, то вам следует кэшировать её только тогда, когда она является результатом операции GET
  5. Код состояния не рассматривается
    • Когда вы кэшируете страницу в первый раз, и если что-то пошло не так, то вам возвращается славная страница 404. Вы же не хотите кэшировать ошибки? ^_^

Вместо того, чтобы позволить пользователям писать большое количество шаблонного кода, чтобы заботиться обо всех этих проблемах, был создан декоратор cache.action. Он будет по умолчанию использовать смарт-кэширование заголовков, чтобы позволить браузеру кэшировать результат: если вы передадите ему модель кэша, то он он также автоматически определить наилучший ключ, поэтому разные версии одной и той же страницы могут быть сохранены и извлечены соответствующим образом (например, одна для английских пользователей и одна для испанских пользователей).

Он принимает несколько параметров, с помощью смарт-значений по умолчанию:

  • time_expire : по умолчанию 300 секунд
  • cache_model : по умолчанию None. Это значит, что @cache.action будет изменять только заголовки по умолчанию, чтобы позволить браузеру клиента кэшировать содержимое
    • если вы передаете, например, cache.ram, результат будет сохранен в кэше как указано
  • prefix : если вы хотите авто-сгенерированный ключ префикса (в дальнейшем полезно для очистки с его помощью, например, cache.ram.clear(prefix*))
  • session : если вы хотите учитывать сессии, по умолчанию False
  • vars : если вы хотите учитывать URL vars, по умолчанию True
  • lang : если вы хотите учитывать язык, по умолчанию True
  • user_agent : если вы хотите учитывать агент пользователя, по умолчанию False
  • public : если вы хотите использовать одну и ту же страницу для всех пользователей, которые будут иметь когда-либо доступ к ней, по умолчанию True
  • valid_statuses : по умолчанию None. cache.client будет кэшировать только те страницы, которые запрашиваются с помощью метода GET и код состояния которых начинается с 1,2 или 3. Вы можете передать список кодов состояния (если вы хотите кэшировать страницы с этими состояниями, например, status_codes=[200] будет кэшировать только те страницы, результирующий код состояния которых равен 200)
  • quick : по умолчанию None, но вы можете передать список инициалов, чтобы задать конкретную особенность:
    • Session, Vars, Lang, User_agent, Public например выражение @cache.action(time_expire=300, cache_model=cache.ram, quick='SVP') такое же как и @cache.action(time_expire=300, cache_model=cache.ram, session=True, vars=True, public=True)

"Рассмотрим" что означает параметр, например vars, при помощи которого вы хотите кэшировать различные страницы, если vars отличаются, то /app/default/index?search=foo не будет равняться /app/default/index?search=bar Некоторые параметры переопределяют другие, так что, например, если вы установите session=True, public=True последний будет отброшен. Используйте их с умом!

Функция URL

URL

Функция URL является одной из наиболее важных функций в web2py. Она генерирует внутренние пути URL для действий и статических файлов.

Функция 'f' в нижеследующем примере:

1
URL('f')

сопоставляется с адресом

1
/[application]/[controller]/f

Обратите внимание на то, что выходные данные функции URL зависят от имени текущего приложения, вызываемого контроллера и других параметров. web2py поддерживает сопоставление URL и обратное сопоставление URL. Сопоставление URL позволяет переопределить формат внешних URL-адресов. Если вы используете функцию URL для генерации всех внутренних URL-адресов, то при внесении дополнений или изменений в URL-сопоставления пресекаются битые ссылки внутри приложения web2py.

Вы можете передать дополнительные параметры в функцию URL, т.е. дополнительные слагаемые в URL путь (args) и переменные URL запроса (vars):

1
URL('f', args=['x', 'y'], vars=dict(z='t'))

сопоставляется с адресом

1
/[application]/[controller]/f/x/y?z=t

Атрибуты args автоматически разбираются, декодируются, и в конечном итоге сохраняются в request.args средствами web2py. Аналогичным образом, vars разбираются, декодируются, а затем сохраняются в request.vars. Атрибуты args и vars обеспечивают основной механизм, посредством которого web2py обменивается информацией с браузером клиента.

Если args содержит только один элемент, то нет необходимости передавать его в списке.

Вы также можете использовать функцию URL для генерации URL-адресов для действий в других контроллерах и других приложениях:

1
URL('a', 'c', 'f', args=['x', 'y'], vars=dict(z='t'))

сопоставляется с адресом

/a/c/f/x/y?z=t

Можно также указать приложение, контроллер и функцию с помощью именованных аргументов:

1
URL(a='a', c='c', f='f')

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

1
URL('c', 'f')

Если имя контроллера с отсутствует, то предполагается текущий контроллер.

1
URL('f')

Вместо того чтобы передавать имя функции контроллера можно также передать саму функцию

1
URL(f)

По причинам, указанным выше, вы всегда должны использовать функцию URL для создания URL-адреса на статические файлы для ваших приложений. Статические файлы хранятся в подпапке static приложения (там, куда они идут, когда загружаются с помощью административного интерфейса). web2py предоставляет виртуальный 'static' контроллер, работа которого заключается в извлечении файлов из подпапки static, определения их типа содержимого, и поточной передаче файла клиенту. Следующий пример генерирует URL для статического файла "image.png":

1
URL('static', 'image.png')

сопоставляется с адресом

1
/[application]/static/image.png

Если статический файл находится в подпапке внутри папки static, вы можете включить вложенную папку(и) как часть имени файла. Например, для генерации:

/[application]/static/images/icons/arrow.png

следует использовать:

1
URL('static', 'images/icons/arrow.png')

Вам не нужно кодировать/избавляться от аргументов args и vars; это делается за вас автоматически.

По умолчанию расширение, соответствующее текущему запросу (который можно найти в request.extension) добавляется к функции, если по умолчанию request.extension не равно html. Это может быть переопределено в явном виде, путем включения расширения в часть имени функции URL (f='name.ext') или передачи аргумента с расширением:

1
URL(..., extension='css')

Текущее расширение может быть явным образом подавлено:

1
URL(..., extension=False)

Абсолютная URL-адресация

По умолчанию, URL генерирует относительные URL-адреса. Тем не менее, вы можете также генерировать абсолютные URL-адреса, указав аргументы scheme и host (это полезно, например, при вставке URL-адреса в сообщениях электронной почты):

1
URL(..., scheme='http', host='www.mysite.com')

Вы можете автоматически включить scheme и host текущего запроса, просто установив аргументы на True.

1
URL(..., scheme=True, host=True)

Функция URL также принимает аргумент port, чтобы указать порт сервера, если это необходимо.

Цифровая подпись URL-адресов

digitally signed URL

При генерации URL-адреса, у вас есть возможность установить для него цифровую подпись. При этом добавляется GET переменная _signature, которая может быть проверена сервером. Это можно сделать двумя способами.

Вы можете передать функции URL следующие аргументы:

  • hmac_key: ключ для подписи URL (строка)
  • salt: необязательная строка, чтобы "посолить" данные перед подписью
  • hash_vars: Необязательный список имен переменных из строки URL запроса (т.е. GET переменные), которые будут включены в подпись. Он также может быть установлен в True (по умолчанию), чтобы включить все переменные, или False, чтобы не включать ни одну из переменных.

Ниже приведен пример использования:

1
2
3
4
5
6
7
8
9
KEY = 'mykey'

def one():
    return dict(link=URL('two', vars=dict(a=123), hmac_key=KEY))

def two():
    if not URL.verify(request, hmac_key=KEY): raise HTTP(403)
    # здесь что-нибудь делается
    return locals()

Здесь создается действие two, доступное только через цифровую подпись URL. URL-адрес с цифровой подписью выглядит следующим образом:

'/welcome/default/two?a=123&_signature=4981bc70e13866bb60e52a09073560ae822224e9'

Обратите внимание, что цифровая подпись проверяется с помощью функции URL.verify. URL.verify также принимает hmac_key, salt, и описанные выше аргументы hash_vars, и их значения должны соответствовать значениям, которые были переданы в функции URL когда цифровая подпись была создана для того, чтобы проверить URL.

Второй и более сложный, но более распространенный пример использования цифровой подписи URL-адресов в сочетании с аутентификацией. Это лучше всего объяснить на примере:

1
2
3
4
5
6
7
8
@auth.requires_login()
def one():
    return dict(link=URL('two', vars=dict(a=123), user_signature=True)

@auth.requires_signature()
def two():
    # здесь что-нибудь делается
    return locals()

В этом случае hmac_key автоматически генерируется и совместно используется в рамках сессии. Это позволяет действию two делегировать любой контроль доступа к действию one. Если ссылка сгенерирована и подписана, то она действительная; в противном случае недействительная. Если ссылка украдена другим пользователем, то ссылка будет недействительной.

Это хорошая практика, чтобы всегда цифровую подпись для обратных вызовов Ajax. Если вы используете web2py функцию LOAD, то она имеет аргумент user_signature который также может быть использован для этой цели:

{{=LOAD('default', 'two', vars=dict(a=123), ajax=True, user_signature=True)}}

Исключение HTTP и функцияredirect

HTTP
redirect

web2py определяет только одно новое исключение под названием HTTP. Это исключение может быть поднято в любом месте в модели, контроллере, или представлении с помощью команды:

1
raise HTTP(400, "my message")

Это вызывает поток управления, чтобы перейти от кода пользователя, обратно к web2py, и возвращает ответ HTTP наподобие:

1
2
3
4
5
6
7
8
9
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked

my message

Первым аргументом HTTP является код состояния HTTP. Вторым аргументом является строка, которая будет возвращена как тело ответа. Дополнительные необязательные именованные аргументы используются для создания заголовка ответа HTTP. Например:

1
raise HTTP(400, 'my message', test='hello')

generates:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 400 BAD REQUEST
Date: Sat, 05 Jul 2008 19:36:22 GMT
Server: Rocket WSGI Server
Content-Type: text/html
Via: 1.1 127.0.0.1:8000
Connection: close
Transfer-Encoding: chunked
test: hello

my message

Если вы не хотите зафиксировать открытую транзакцию базы данных, то выполните откат до подъема исключения.

Любое исключение, кроме HTTP исключения вызывает в web2py откат любой открытой транзакции базы данных, протоколирует трэйсбэк ошибки, выпускает билет посетителю и возвращает стандартную страницу ошибки.

Это означает, что только исключение HTTP может быть использовано для межстраничного управления потоком. Другие исключения должны быть пойманы приложением, в противном случае они билетируются посредством web2py. Команда:

1
redirect('http://www.web2py.com')

это просто сокращение для:

1
2
3
raise HTTP(303,
           'You are being redirected <a href="%s">here</a>' % location,
           Location='http://www.web2py.com')

Именные аргументы инициализатора метода HTTP переводятся в директивы HTTP-заголовка, в этом случае, местоположение цели перенаправления. redirect принимает необязательный второй аргумент, которым является код состояния HTTP для перенаправления (303 по умолчанию). Измените этот номер на 307 для временного перенаправления или на 301 для постоянного перенаправления.

Самый распространенный способ использования redirect состоит в перенаправлении на другие страницы в том же самом приложении и (по желанию) передачи параметров:

1
redirect(URL('index', args=(1,2,3), vars=dict(a='b')))

В Главе 12 мы обсудим web2py компоненты. Они делают запросы Ajax для web2py действий. Если вызываемое действие выполняет перенаправление, вы можете сделать Ajax запрос, чтобы последовать за перенаправлением или вы можете для всей страницы целиком выполнить Ajax запрос на перенаправление. В этом последнем случае вы можете установить:

1
redirect(...,client_side=True)

Интернационализация и плюрализация с объектом T

T
internationalization

Объект T является переводчиком языка. Он представляет собой единый глобальный экземпляр web2py класса gluon.language.translator. Все строковые константы (и только строковые константы) должны быть отмечены T, например:

1
a = T("hello world")

Строки, помеченные T обозначаются web2py как нуждающиеся в языковом переводе, и они будут переведены, когда код (в модели, контроллере или в представлении) выполняется. Если строка для перевода не является постоянной величиной, а является переменной, то она будет добавлена в файл перевода во время выполнения (за исключением GAE) и будет переведена позже.

Объект T может также содержать интерполированные переменные и поддерживает несколько эквивалентных синтаксисов:

1
2
3
4
a = T("hello %s", ('Tim',))
a = T("hello %(name)s", dict(name='Tim'))
a = T("hello %s") % ('Tim',)
a = T("hello %(name)s") % dict(name='Tim')

Последний синтаксис рекомендуется, так как это делает перевод проще. Первая строка переводится в соответствии с запрошенным языковым файлом и переменная name заменяется независимо от языка.

Вы можете сцепить переведенные строки и обычные строки:

1
T("blah ") + name + T(" blah")

Следующий код также допускается и зачастую предпочтительнее:

1
T("blah %(name)s blah", dict(name='Tim'))

или альтернативный синтаксис

1
T("blah %(name)s blah") % dict(name='Tim')

В обоих случаях перевод происходит до того, как имя переменной заменяется в слоте "%(name)s". Следующая альтернатива НЕ ДОЛЖНА ИСПОЛЬЗОВАТЬСЯ:

1
T("blah %(name)s blah" % dict(name='Tim'))

потому что перевод будет происходить после подстановки.

Определение языка

Запрашиваемый язык определяется полем "Accept-Language" в заголовке HTTP, но этот выбор может быть перезаписан программным путем запроса конкретного файла, например:

1
T.force('it-it')

который читает "languages/it-it.py" языковой файл. Языковые файлы могут быть созданы и отредактированы через административный интерфейс.

Вы также можете принудительно установить язык для каждой строки:

1
T("Hello World", language="it-it")
В случае запроса нескольких языков, например "it-it, fr-ft", web2py пытается найти "it-it.py" и "fr-fr.py" файлы переводов. Если ни один из запрошенных файлов не существует, то уже он пытается найти "it.py" и "fr.py". Если этих файлов нет, то используется значение по умолчанию "default.py". Если и этого файла нет, то он по умолчанию оставляет без перевода. Более общее правило заключается в том, что web2py пытается найти "xx-xy-yy.py", "xx-xy.py", "xx.py", "default.py" для каждого из "хх-ху-уу" принятых языков, и находит ближайшее совпадение с предпочтениями посетителя.

Вы можете отключить переводы полностью с помощью

1
T.force(None)

Как правило, перевод строк вычисляется лениво во время визуализации представления; следовательно, метод force переводчика не должен вызываться внутри представления.

Существует возможность отключить ленивые вычисления с помощью

1
T.lazy = False

В этом случае, строки переводятся непосредственно через оператор T на основе принятого в данный момент или принудительно заданного языка.

Также возможно отключить ленивые вычисления для отдельных строк:

1
T("Hello World", lazy=False)

Общая проблема заключается в следующем. Оригинальное приложение на английском языке. Предположим, что есть перевод файла (например, на итальянский, "it-it.py"), и клиент HTTP объявляет, что он принимает оба языка английской (en) и итальянский (it-it) в таком порядке. Получается следующая нежелательная ситуация: web2py не знает, что приложение по умолчанию написано на английском языке (en). Таким образом, он предпочитает все переводить на итальянский язык (it-it), потому что он нашел только итальянский файл перевода. Если бы не нашел "it-it.py" файл, то он использовал бы строки языка по умолчанию (английский).

Есть два варианта решения этой проблемы: создать файл перевода на английский язык, который был бы излишним и ненужным, или лучше, сообщить web2py, какие языки должны использоваться по умолчанию для языковых строк(строк, находящихся внутри кода приложения). Это можно сделать с помощью:

1
T.set_current_languages('en', 'en-en')

Он хранит в T.current_language список языков, которые не требуют перевода и принудительной перезагрузки языковых файлов.

Обратите внимание на то, что "it" и "it-it" разные языки с точки зрения web2py. Чтобы поддерживать их обоих, понадобиться два файла перевода, всегда в нижнем регистре. То же самое верно и для всех других языков.

Принятый в настоящее время язык хранится в

1
T.accepted_language

Перевод переменных

T(...) не только переводит строки, но он также может перевести значения, хранящиеся в переменных:

1
2
>>> a="test"
>>> print T(a)

В этом случае слово "test" является переводимым, но если слово не найдено и файловая система доступна для записи, то переводчик добавит его в список слов для перевода в языковом файле.

Обратите внимание на то, что это может привести к большому количеству операций ввода-вывода файла, и вы можете отключить его:

1
T.is_writable = False

предотвращает T от динамического обновления языковых файлов.

Комментарии и несколько переводов

Вполне возможно, что одна и та же строка появляется в различных контекстах в приложении и требует различных переводов в зависимости от контекста. Для того, чтобы сделать это, можно добавлять комментарии к исходной строке. Комментарии не будут отрисовываться, но будут использоваться web2py, чтобы определить наиболее подходящий вариант перевода. Например:

1
2
T("hello world ## first occurrence")
T("hello world ## second occurrence")

Текст после ##, в том числе удвоенная решетка ##, является комментариями.

Двигатель плюрализации

Начиная с версии 2.0, web2py включает в себя мощную систему плюрализации (PS). Это означает, что если текст, выделенный для перевода, зависит от числовой переменной, то он может быть переведен по-разному на основании числового значения. Например, в английском языке мы можем написать:

x book(s)

как

a book (x==1)
5 books (x==5)

Английский имеет одну единственную форму и одну множественную форму. множественная форма строится путем добавления "-s" или "-es" или используя исключительную форму. web2py предоставляет возможность определить правила плюрализация для каждого языка, а также исключения из правил по умолчанию. На самом деле web2py уже знает правила плюрализация для многих языков. Он знает, что, например, Словенский язык имеет одну особую форму и 3 формы множественного числа (для х==2,х==3 или х==4 и х>4). Эти правила закодированы в "gluon/contrib/plural_rules/*.py" файлах и новые файлы могут быть созданы. Явные формы для слов во множественном числе создаются путем редактирования файлов плюрализации с помощью административного интерфейса.

По умолчанию PS не активирован. Она вызывается через аргумент symbol функции T. Например:

1
T("You have %s %%{book}", symbols=10)

Теперь PS активируется для слова "book" и для числа 10. Результат на английском языке будет: "You have 10 books". Обратите внимание что "book" переведено во множественное число как "books".

PS состоит из 3-х частей:

  • местозаполнители %%{} чтобы отметить слова на входе в T
  • правило, выдающее решение о том, какую словоформу использовать ("rules/plural_rules/*.py")
  • словарь с формами слова во множественном числе ("app/languages/plural-*.py")

Значение из символов может быть одной переменной, списком/кортежем переменных или словарем.

Местозаполнитель %%{} состоит из 3-х частей:

%%{[<modifier>]<word>[<parameter>]},

где:

<modifier>::= ! | !! | !!!
<word> ::= любое слово или словосочетание в единственном числе в нижнем регистре (!)
<parameter> ::= [index] | (key) | (number)

Например:

  • %%{word} равносильно %%{word[0]} (если не используются модификаторы).
  • %%{word[index]} используется, когда символы это кортеж. symbols[index] дает нам номер, используемый для принятия решения по выбору словоформы.
  • %%{word(key)} используется для получения числового параметра из symbols[key]
  • %%{word(number)} позволяет задать number напрямую (например: %%{word(%i)})
  • %%{?word?number} возвращает "word" если number==1, в противном случае возвращает number
  • %%{?number} или %%{??number} возвращает number если number!=1, в противном случае возвращает nothing
T("blabla %s %%{word}", symbols=var)

%%{word} по умолчанию означает %%{word[0]}, где [0] является индексом элемента в символьном кортеже.

T("blabla %s %s %%{word[1]}", (var1, var2))

PS использует "word" и "var2" соответственно.

Вы можете использовать несколько %%{} местозаполнителей с одним индексом:

T("%%{this} %%{is} %s %%{book}", var)

или

T("%%{this[0]} %%{is[0]} %s %%{book[0]}", var)

Они генерируют:

var  output
------------------
 1   this is 1 book
 2   these are 2 books
 3   these are 2 books

Точно так же вы можете передать словарь к символам:

T("blabla %(var1)s %(wordcnt)s %%{word(wordcnt)}",
  dict(var1="tututu", wordcnt=20))

которая производит

blabla tututu 20 words

Вы можете заменить "1" любым словом, которое Вы пожелает посредством этого местозаполнителя %%{?word?number}. Например

T("%%{this} %%{is} %%{?a?%s} %%{book}", var)

производит:

var  output
------------------
 1   this is a book
 2   these are 2 books
 3   these are 3 books
 ...

Внутри %%{...} вы конечно же можете использовать следующие модификаторы:

  • ! Капитализировать(начать с заглавной) текст (эквивалентно string.capitalize)
  • !! Капитализировать каждое слово (эквивалентно string.title)
  • !!! Капитализировать каждый символ (эквивалентно string.upper)

Обратите внимание, вы можете воспользоваться\избежать ! и ?.

Переводы, плюрализация, и MARKMIN

Вы можете также использовать мощный MARKMIN синтаксис внутри переводимых строк путем замены

1
T("hello world")

на

T.M("hello world")

Теперь строка принимает MARKMIN разметку, как описано в Глава 5

Файлы cookie

cookies

web2py использует модули cookies Python для обработки куки.

Куки от браузера находятся в request.cookies, а куки от сервера находятся в response.cookies.

Вы можете установить куки следующим образом:

1
2
3
response.cookies['mycookie'] = 'somevalue'
response.cookies['mycookie']['expires'] = 24 * 3600
response.cookies['mycookie']['path'] = '/'

Вторая строка сообщает браузеру хранить куки в течении 24 часов. Третья строка сообщает браузеру послать куки обратно к любому приложению (URL путь) в текущем домене. Обратите внимание, что если вы не указываете путь к куки, то браузер будет считать что путь к URL это запрошенный путь, поэтому куки будут возвращены только на сервер, когда такой же URL-путь будет запрашиваться.

Для куки можно обеспечить безопасность:

1
response.cookies['mycookie']['secure'] = True

Эта строка сообщает браузеру отправлять куки обратно только через HTTPS, а не через HTTP.

Куки могут быть извлечены с помощью:

1
2
if request.cookies.has_key('mycookie'):
    value = request.cookies['mycookie'].value

Если сессии не будут отключены, то web2py, прямо под капотом, задает следующие куки и использует их для обработки сессий:

1
2
response.cookies[response.session_id_name] = response.session_id
response.cookies[response.session_id_name]['path'] = "/"

Обратите внимание, что если одно приложение включает в себя несколько поддоменов, и вы хотите поделиться сессией через эти поддомены (например, sub1.yourdomain.com, sub2.yourdomain.com и т.д.), то вы должны явно указать домен куки сессии как указано ниже:

1
2
if not request.env.remote_addr in ['127.0.0.1', 'localhost']:
    response.cookies[response.session_id_name]['domain'] = ".yourdomain.com"

Вышеуказанное, может быть полезно, например, если вы хотите позволить пользователю оставаться в системе через поддомены.

Приложение init

init

При развертывании web2py, вам требуется задать приложение по умолчанию, то есть приложение, которое запускается, когда относительный URL-адрес пустой, например:

1
http://127.0.0.1:8000

По умолчанию, при столкновении с пустым путем, web2py ищет приложение под названием init. Если нет приложения init, то он ищет приложение под названием welcome.

default_application

Имя приложения по умолчанию может быть изменено с init на другое имя, путем установки default_application в файле routes.py:

1
default_application = "myapp"

Примечание: default_application впервые появилось в web2py версии 1.83.

Вот четыре способа установки приложения по умолчанию:

  • Назвать ваше приложение "init" как приложение по умолчанию.
  • Установить default_application с именем вашего приложения в routes.py
  • Сделать символьную ссылку из "applications/init" в папку вашего приложения.
  • Переписать используемый URL, как описано в следующем разделе.

Перезапись URL

url rewrite
routes_in
routes_out

web2py имеет возможность перезаписи URL-пути входящих запросов перед вызовом действия контроллера (сопоставление URL), и, наоборот, web2py может переписать URL-путь, генерируемый функцией URL (обратное сопоставление URL). Одной из причин, для чего это делается, является обработка устаревших URL-адресов, другой причиной является возможность упростить пути и сделать их короче.

web2py включает в себя две различные системы перезаписи URL: проста в использовании система на основе параметров для большинства случаев использования, а также гибкая система на основе шаблонов для более сложных случаев. Чтобы указать правила URL перезаписи, создайте новый файл в папке "web2py" под названием routes.py (содержимое routes.py будет зависеть от того, какую из двух систем перезаписи вы выбираете, как описано в следующих двух разделах). Эти две системы не могут быть смешаны.

Обратите внимание, что при редактировании routes.py, вы должны перезагрузить его. Это можно сделать двумя способами: путем перезагрузки веб-сервера или нажав на кнопку перезагрузки маршрутов на панели администратора. При наличии ошибок в маршрутах, они не перезагружаются.

Система на основе параметров

Основанный на параметрах (параметрический) маршрутизатор обеспечивает легкий доступ к нескольким "законсервированным" методам URL-перезаписи. Его возможности включают в себя:

  • Пропуск приложения по умолчанию, контроллера и имен функций из внешне видимых URL (те, которые создаются с помощью функции URL())
  • Сопоставление доменов (и/или портов) для приложений или контроллеров
  • Встраивание селектора языка в URL
  • Удаление фиксированного префикса из входящих URL-адресов и добавление его обратно к исходящим URL-адресам
  • Сопоставление корневых файлов, такие как /robots.txt со статическим каталогом приложений.

Параметрический маршрутизатор также обеспечивает несколько более гибкую проверку входящих URL-адресов.

Предположим, вы написали приложение под названием myapp и желаете сделать его приложением по умолчанию, так чтобы имя приложения больше не являлось частью URL, которое видит пользователь. Ваш контроллер по умолчанию по-прежнему default, и вы конечно хотите удалить свое имя из видимых пользователю URL-адресов. Вот то, что вы должны положить в routes.py:

1
2
3
routers = dict(
  BASE  = dict(default_application='myapp'),
)

Вот и все. Параметрический маршрутизатор достаточно умен, чтобы знать, как сделать правильную вещь с URL-адресами, таких как:

1
http://domain.com/myapp/default/myapp

или

1
http://domain.com/myapp/myapp/index

где нормальное сокращение будет неоднозначным. Если у вас есть два приложения, myapp и myapp2, вы получите тот же эффект, и дополнительно контроллер по умолчанию myapp2 будет удален из URL, когда это безопасно (что по большей части все время).

Вот еще один случай: предположим, что вы хотите поддержку URL на основе языков, где ваши URL-адреса выглядят следующим образом:

1
http://myapp/en/some/path

или (переписанный)

1
http://en/some/path

Здесь показано как это сделать:

1
2
3
4
routers = dict(
  BASE  = dict(default_application='myapp'),
  myapp = dict(languages=['en', 'it', 'jp'], default_language='en'),
)

Теперь входящий URL в виде:

1
http:/domain.com/it/some/path

будет направляться на /myapp/some/path, и request.uri_language будет установлен в положение 'it', таким образом вы можете принудительно выполнить перевод. Вы также можете иметь зависящие от языка статические файлы.

1
http://domain.com/it/static/filename

будет сопоставлен с:

1
applications/myapp/static/it/filename

если этот файл существует. Если это не так, то URL-адрес вроде:

1
http://domain.com/it/static/base.css

все равно будет сопоставлен с:

1
applications/myapp/static/base.css

(потому что нет файла static/it/base.css).

Так что теперь вы можете иметь зависящие от языка статические файлы, включая изображения, если вам нужно. Сопоставление домена также поддерживается:

1
2
3
4
5
6
7
8
routers = dict(
  BASE  = dict(
      domains = {
          'domain1.com' : 'app1',
          'domain2.com' : 'app2',
      }
  ),
)

делает то, что вы ожидаете.

1
2
3
4
5
6
7
8
routers = dict(
  BASE  = dict(
      domains = {
          'domain.com:80'  : 'app/insecure',
          'domain.com:443' : 'app/secure',
      }
  ),
)

сопоставление http://domain.com получает доступ к контроллеру с именем insecure, в то время как HTTPS получает доступ перехода к контроллеру secure. В качестве альтернативы, можно сопоставить различные порты для различных приложений, очевидным образом.

Для получения дополнительной информации, пожалуйста, обратитесь к файлу "routes.parametric.example.py", который предоставляется в папке "examples" стандартного дистрибутива web2py.

Примечание: Система на основе параметров впервые появилась в web2py версии 1.92.1.

Система на основе шаблонов

Несмотря на то, что системы на основе параметров, которая только что была описана, должно быть достаточно для большинства случаев использования, альтернативная система на основе шаблонов обеспечивает некоторую дополнительную гибкость для более сложных случаев. При использовании системы на основе шаблонов, вместо определения маршрутизаторов в качестве словарей с параметрами маршрутизации, вы определяете два списка (или кортежа) из 2-кортежей, routes_in и routes_out. Каждый кортеж содержит два элемента: шаблон, который должен быть заменен, и строка, которая заменяет его. Например:

1
2
3
4
5
6
routes_in = (
  ('/testme', '/examples/default/index'),
)
routes_out = (
  ('/examples/default/index', '/testme'),
)

С помощью этих маршрутов, URL-адрес:

1
http://127.0.0.1:8000/testme

сопоставляется с:

1
http://127.0.0.1:8000/examples/default/index

Для посетителей, все ссылки на страницу с URL вроде /testme.

Шаблоны имеют тот же синтаксис, что и регулярные выражения Python. Например:

1
  ('.*.php', '/init/default/index'),

привяжет все URL-адреса, заканчивающиеся на ".php" к странице index.

Второе слагаемое правило также может быть перенаправлением на другую страницу:

1
  ('.*.php', '303->http://example.com/newpage'),

Здесь 303 является HTTP кодом для перенаправления ответа.

Иногда вы хотите избавиться от префикса приложения из URL-адреса, потому что вы собираетесь выставить только одно приложение. Это может быть достигнуто с помощью:

1
2
3
4
5
6
routes_in = (
  ('/(?P<any>.*)', '/init/\g<any>'),
)
routes_out = (
  ('/init/(?P<any>.*)', '/\g<any>'),
)

Существует также альтернативный синтаксис, который может быть смешан с вышеуказанным регулярным выражением. Он заключается в использовании $name вместо (?P<name>\w+) или \g<name>. Например:

1
2
3
4
5
6
7
routes_in = (
  ('/$c/$f', '/init/$c/$f'),
)

routes_out = (
  ('/init/$c/$f', '/$c/$f'),
)

будет также устранять префикс приложения "/example" во всех URL-адресах.

Используя $name обозначение, вы можете автоматически сопоставить routes_in с routes_out, если вы не используете регулярные выражения. Например:

1
2
3
4
5
routes_in = (
  ('/$c/$f', '/init/$c/$f'),
)

routes_out = [(x, y) for (y, x) in routes_in]

Если есть несколько маршрутов, то выполняется первое соответствие URL. Если ни один шаблон не соответствует, то путь остается без изменений.

Вы можете использовать $anything, что соответствует (.*) вплоть до конца строки.

Вот минимальный "routes.py" для обработки favicon и robots запросов:

favicon
robots

1
2
3
4
5
routes_in = (
  ('/favicon.ico', '/examples/static/favicon.ico'),
  ('/robots.txt', '/examples/static/robots.txt'),
)
routes_out = ()

Вот более сложный пример, который выставляет одно приложение "myapp" без лишних префиксов, но также выставляет admin, appadmin и static:

1
2
3
4
5
6
7
8
routes_in = (
  ('/admin/$anything', '/admin/$anything'),
  ('/static/$anything', '/myapp/static/$anything'),
  ('/appadmin/$anything', '/myapp/appadmin/$anything'),
  ('/favicon.ico', '/myapp/static/favicon.ico'),
  ('/robots.txt', '/myapp/static/robots.txt'),
)
routes_out = [(x, y) for (y, x) in routes_in[:-2]]

Общий синтаксис для маршрутов является более сложным, чем простые примеры, которые мы видели до сих пор. Вот более общий и представительный пример:

1
2
3
4
routes_in = (
 ('140.191.\d+.\d+:https?://www.web2py.com:post /(?P<any>.*).php',
  '/test/default/index?vars=\g<any>'),
)

Он сопоставляет http или https POST запросы (обратите внимание, нижний регистр "post") с хостом www.web2py.com с удаленного IP, соответствующего регулярному выражению

1
'140.191.\d+.\d+'

запрашивающий страницу, соответствующую регулярному выражению

1
'/(?P<any>.*).php'

в

1
'/test/default/index?vars=\g<any>'

где \g<any> заменяется соответствующим регулярным выражением.

Общий синтаксис

1
'[remote address]:[protocol]://[host]:[method] [path]'

Если первая часть шаблона (все кроме [path]) отсутствует, то web2py предоставляет по умолчанию:

1
'.*?:https?://[^:/]+:[a-z]+'

Все выражение соответствует регулярному выражению, поэтому точки "." должны быть экранированы и любые соответствия подвыражения могут быть захвачены с помощью (?P<...>...) используя синтаксис регулярных выражений Python. Методы запроса (как правило GET, или POST) должны быть в нижнем регистре. URL будет соответствовать любому %xx, избегая не котирующиеся.

Это позволяет перенаправлять запросы на основе IP-адреса клиента или домена, в зависимости от типа запроса, метода и пути. Он также позволяет web2py сопоставить различные виртуальные хосты в различных приложениях. Любое соответствие подвыражению может быть использовано для создания целевого URL и, в конечном итоге, передачи в качестве переменной GET.

Все основные веб-серверы, такие как Apache и Lighttpd, также имеют возможность перезаписывать URL. Что может быть вариантом использования в производственной среде вместо routes.py. Что бы вы ни решили сделать, мы настоятельно рекомендуем вам не задавать жестко внутренние URL-адреса в вашем приложении и использовать функцию URL для их создания. Это сделает ваше приложение более переносимым в случае необходимости изменения маршрутов.

Специфичная для приложения перезапись URL
routes_app

При использовании системы на основе шаблонов, приложение может устанавливать специфичные для приложения свои собственные маршруты в файле routes.py, расположенного в базовой папке приложения. Это обеспечивается путем конфигурирования routes_app в базовом routes.py, чтобы выделить во входящем URL-адресе имя приложения, которое можно выбрать. Когда это произойдет, то специфичный для приложения файл routes.py будет использоваться вместо базового routes.py.

Формат routes_app идентичен routes_in, за исключением того, что вместо шаблона замены используется просто имя приложения. Если применение routes_app к входящему URL не приводит к имени приложения, или файл routes.py конкретного приложения не найден, то базовый файл routes.py используется как обычно.

Примечание: routes_app впервые появился в web2py версии 1.83.

Приложение, контроллер и функция по умолчанию
default_application
default_controller
default_function

При использовании системы на основе шаблонов, имена по умолчанию для приложения, контроллера и функции могут быть изменены с init, default, и index соответственно на другие имена путем задания соответствующего значения в routes.py:

1
2
3
default_application = "myapp"
default_controller = "admin"
default_function = "start"

Примечание: Эти элементы впервые появились в web2py версии 1.83.

Маршрутизация ошибок

routes_onerror

Вы также можете воспользоваться routes.py, чтобы перенаправить запросы к специальным действиям при возникновении ошибки на сервере. Вы можете указать эти сопоставления глобально, для каждого приложения, для каждого кода ошибки, или для каждого приложения и кода ошибки. Вот пример:

1
2
3
4
5
6
routes_onerror = [
  ('init/400', '/init/default/login'),
  ('init/*', '/init/static/fail.html'),
  ('*/404', '/init/static/cantfind.html'),
  ('*/*', '/init/error/index')
]

Для каждого кортежа, первая строка сравнивается с "[app name]/[error code]". Если совпадение найдено, то неисправный запрос перенаправляется на URL во второй строке кортежа совпадений. Если URL, вызвавший ошибку, не явялется ссылкой на статический файл, то следующие GET переменные будут переданы к действию ошибки:

  • code: код состояния HTTP (например, 404, 500)
  • ticket: в форме "[app name]/[ticket number]" (или "None" если нет билета)
  • requested_uri: что эквивалентно request.env.request_uri
  • request_url: что эквивалентно request.url

Эти переменные будут доступны в действии обработки ошибок через request.vars и могут быть использованы при создании ответа об ошибке. В частности, это хорошая идея для действия ошибки, чтобы вернуть исходный код ошибки HTTP вместо кода состояния 200 (OK) по умолчанию. Это может быть сделано путем установки response.status = request.vars.code. Кроме того, через действие имеется возможность послать ошибку (или очередь) по электронной почте администратору, в том числе и ссылку на билет в admin.

Несопоставленные ошибки отображают страницу ошибки по умолчанию. Эта страница ошибки по умолчанию также может быть настроена здесь (см "routes.parametric.example.py" и "routes.patterns.example.py" в папке "examples"):

1
2
3
4
error_message = '<html><body><h1>%s</h1></body></html>'
error_message_ticket = '''<html><body><h1>Internal error</h1>
     Ticket issued: <a href="/admin/default/ticket/%(ticket)s"
     target="_blank">%(ticket)s</a></body></html>'''

Первая переменная содержит сообщение об ошибке, когда запрашивается недопустимое приложение или функция. Вторая переменная содержит сообщение об ошибке, когда выдается билет.

routes_onerror work with both routing mechanisms.

error_handler

В "routes.py" вы можете также указать действие, отвечающее за обработку ошибок:

1
2
3
error_handler = dict(application='error',
                      controller='default',
                      function='index')

Если error_handler задан, то действие вызывается без перенаправления пользователя и действие-обработчик будет нести ответственность за работу над ошибкой. В случае, если страница обработки ошибок сама возвращает ошибку, то web2py вернется к своим старым статическим ответам.

Управление статическим имуществом

Начиная с версии 2.1.0, web2py имеет возможность управлять статическим имуществом.

Когда приложение находится в стадии разработки, статический файл может часто меняться, поэтому web2py посылает статические файлы без кэширования заголовков. Это имеет побочный эффект "заставляя" браузер запрашивать статические файлы при каждом запросе. Это приводит к низкой производительности при загрузке страницы.

На сайте "производства", вы можете захотеть подавать статические файлы с cache заголовками, чтобы предотвратить ненужные загрузки, поскольку статические файлы не изменяются.

cache заголовки позволяют браузеру получать каждый файл только один раз, тем самым экономя пропускную способность и уменьшая время загрузки.

Тем не менее, существует проблема: Что должно объявить кэширование заголовков? Когда должен истечь срок файлов? Когда файлы впервые подаются, то сервер не может предсказать, когда они будут изменены.

Ручной подход состоит в создании подпапок для разных версий статических файлов. Например для ранней версии "layout.css" может быть сделан доступ по URL "/myapp/static/css/1.2.3/layout.css". При изменении файла, вы создаете новую подпапку и ссылаетесь на нее как "/myapp/static/css/1.2.4/layout.css".

Эта процедура работает, но она является педантичной, так как каждый раз, когда вы обновляете файл CSS, вам нужно не забыть переместить его в другую папку, изменить URL файла в вашем layout.html и развернуть.

Управление статическим имуществом решает эту проблему, позволяя разработчику объявить версию для группы статических файлов, и они будут запрошены снова только тогда, когда меняется номер версии. Номер версии имущества включается в часть URL файла, как и в предыдущем примере. Отличие от предыдущего подхода состоит в том, что номер версии появляется только в URL, а не в файловой системе.

Если вы хотите подать "/myapp/static/layout.css" с заголовками кэша, вам нужно просто включить файл с измененным URL, который включает в себя номер версии:

/myapp/static/_1.2.3/layout.css

(обратите внимание, что в URL-адресе определяется номер версии, он не появляется больше нигде).

Обратите внимание на то, что URL-адрес начинается с "/myapp/static/", далее следует номер версии, состоящий из символа подчеркивания и 3-х целых чисел, разделенных точкой (как описано в SemVer), затем следует имя файла. Также заметьте, что вам не нужно создавать папку "_1.2.3 /".

Каждый раз, когда статический файл с версией в URL будет запрошен, он будет подан с заголовком кэширования на "далекое будущее", а именно:

Cache-Control : max-age=315360000
Expires: Thu, 31 Dec 2037 23:59:59 GMT

Это означает, что браузер получит эти файлы только один раз, и они сохраняются "навсегда" в кэше браузера.

Каждый раз, когда запрашивается файл "_1.2.3/filename", web2py удаляет часть пути с версией и подает ваш файл "на далекое будущее" в заголовках, таким образом, файл будет кэширован навсегда. Если вы измените номер версии в URL, этот трюк заставит браузер думать, что запрашивается другой файл, и файл забирается снова.

Вы можете использовать "_1.2.3", "_0.0.0", "_999.888.888", до тех пор пока версия начинается с подчеркивания за которым следуют три цифры, разделенные точкой.

При разработке, вы можете использовать response.files.append(...) для привязки статических URL-адресов на статические файлы. В этом случае вы можете вручную включить часть "_1.2.3 /" или воспользоваться новым параметром объекта response: response.static_version. Просто включите файлы так, как вы привыкли, например

{{response.files.append(URL('static','layout.css'))}}

и в моделях установите

1
response.static_version = '1.2.3'

Это перезапишет автоматически каждый "/myapp/static/layout.css" url-адрес как "/myapp/static/_1.2.3/layout.css", для каждого файла, включенного в response.files.

Часто в производстве вы позволяете веб-серверу (Apache, Nginx и т.д.) подавать статические файлы. Вам необходимо настроить конфигурацию таким образом, что он будет "пропускать" часть "_1.2.3 /".

Например, в Apache, измените это:

AliasMatch ^/([^/]+)/static/(.*)    /home/www-data/web2py/applications/$1/static/$2

на следующее:

AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*)    /home/www-data/web2py/applications/$1/static/$2

Аналогичным образом, в Nginx измените это:

location ~* /(\w+)/static/ {
    root /home/www-data/web2py/applications/;
    expires max;
}

на следующее:

location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
   alias /home/www-data/web2py/applications/$1/static/$2;
   expires max;
}

Запуск задач в фоновом режиме

В web2py, каждый HTTP запрос подается в отдельном потоке. Потоки перерабатываются для повышения эффективности и управления веб-сервером. Для обеспечения безопасности, веб-сервер устанавливает перерыв для каждого запроса. Это означает, что действия не должны выполнять задачи, которые занимают слишком много времени, не должны создавать новые потоки, и не разветвлять процессы (это возможно, но не рекомендуется).

Правильным способом запуска трудоемких задач является сделать их в фоновом режиме. Существует не один способ сделать это, но здесь мы опишем три механизма, которые встроены в web2py: хрон (cron), самодельные очереди задач (homemade task queues) и планировщик (scheduler).

Под cron мы имеем в виду функциональность web2py, а не механизм Unix Cron. Web2py cron работает также и в Windows.

web2py cron является путем по которому следует идти, если вам нужны задачи в фоновом режиме по расписанию, и эти задачи требуют сравнительно короткий промежуток времени по сравнению с временным интервалом между двумя вызовами. Каждая задача выполняется в собственном процессе, и несколько задач могут выполняться одновременно, но вы не имеете никакого контроля над тем, как много задач выполняются. Если случайно одна задача пересекается с самой собой, то это может вызвать блокировку базы данных и всплеск использования памяти.

web2py планировщик использует другой подход. Количество запущенных процессов фиксируется, и они могут работать на разных машинах. Каждый процесс называется работником. Каждый работник, когда доступен, выбирает себе задачу и выполняет ее как можно скорее после времени, когда он запланирован для запуска, но не обязательно точное в это время. Запущенных процессов не может быть больше, чем число запланированных задач и, следовательно, нет всплесков памяти. Планировщик задач может быть определен в моделях и хранится в базе данных. Web2py планировщик не реализует распределенной очереди, так как он предполагает, что время распределения задач пренебрежимо мало по сравнению с временем запуска задач. Работники забирают задачу из базы данных.

Самодельные очереди задач могут быть более простой альтернативой web2py планировщику в некоторых случаях.

Механизм Cron

cron

Web2py Cron предоставляет возможность приложениям выполнять задачи в заданное время, в независимой от платформы способом.

Для каждого приложения, функциональность Cron определяется файлом crontab:

app/cron/crontab

Он следует синтаксису, определенному по ссылке. [cron] (с некоторыми расширениями, которые специфичны для web2py).

До web2py 2.1.1, Cron был включен по умолчанию и мог быть отключен опцией -N командной строки. Начиная с версии 2.1.1, cron отключен по умолчанию и может быть включен опцией -Y. Это изменение было вызвано стремлением подтолкнуть пользователей к использованию нового планировщика (который превосходит механизм Cron), а также потому, что Cron может повлиять на производительность.

Это означает, что каждое приложение может иметь отдельную конфигурацию Cron и конфигурация Cron может быть изменена изнутри web2py, без влияния на саму операционную систему хоста.

Здесь приведен пример:

1
2
3
4
5
0-59/1  *  *  *  *  root python /path/to/python/script.py
30      3  *  *  *  root *applications/admin/cron/db_vacuum.py
*/30    *  *  *  *  root **applications/admin/cron/something.py
@reboot root    *mycontroller/myfunction
@hourly root    *applications/admin/cron/expire_sessions.py

Последние две строки в этом примере используют расширения для регулярного синтаксиса Cron, чтобы обеспечить дополнительные функциональные возможности web2py.

Файл "applications/admin/cron/expire_sessions.py" на самом деле существует и поставляется с приложением admin. Он проверяет истекшие сессии и удаляет их. "applications/admin/cron/crontab" запускает эту задачу каждый час.

Если task/script предваряется звездочкой (*) и оканчивается .py, то он будет выполнен в web2py среде. Это означает, что вы будете иметь все контроллеры и модели в вашем распоряжении. Если вы используете две звездочки (**), то модели не будут выполнены. Это рекомендуемый способ вызова, так как он имеет меньше накладных расходов и позволяет избежать потенциальных проблем блокировок.

Заметить, что scripts/functions выполненная в web2py среде требуют ручного db.commit() в конце функции или транзакция будет отменена.

web2py не генерирует билеты или значимые трэйсбэки в режиме оболочки, которая запускается Cron, поэтому убедитесь, что ваш код web2py запускается без ошибок, прежде чем установить его в качестве задачи Cron, так как вы, вероятно, не сможете увидеть эти ошибки, когда запустите код из cron. Кроме того, будьте внимательны, когда вы используете модели: несмотря на то, что выполнение происходит в отдельном процессе, блокировки базы данных должны быть приняты во внимание, чтобы избежать страниц ожидания завершения Cron задач, которые могут блокировать базу данных. Используйте ** в синтаксисе, если вам не нужно использовать базу данных в Cron задаче.

Вы также можете вызвать функцию контроллера, в этом случае нет никакой необходимости, чтобы указать путь. Контроллер и функция будут в том же вызывающем приложении. Соблюдайте особую осторожность, относительно предостережений, перечисленных выше. Например:

1
*/30  *  *  *  *  root *mycontroller/myfunction

Если вы укажете @reboot в первом поле в файле crontab, то данная задача будет выполняться только один раз, при запуске web2py. Вы можете использовать эту функцию, если вы хотите предварительно кэшировать, проверить, или инициализировать данные для приложения при запуске web2py. Обратите внимание, что Cron задачи выполняются параллельно с приложением --- если приложение не готово обслуживать запросы до тех пор, пока задача Cron не будет завершена, вы должны внедрить проверку, чтобы отразить это. Например:

1
@reboot  root *mycontroller/myfunction

В зависимости от того, как вы призываете web2py, есть четыре режима работы web2py Cron.

  • soft cron: доступен при всех режимах выполнения
  • hard cron: доступен при использовании встроенного веб-сервера (либо напрямую или через Apache mod_proxy)
  • external cron: доступен, если у вас есть доступ к собственной системной службе Cron
  • No cron

По умолчанию hard Cron, если вы используете встроенный веб-сервер; во всех остальных случаях, по умолчанию является soft Cron. Soft cron метод используется по умолчанию, если вы используете CGI, FASTCGI или WSGI (но обратите внимание, что soft cron не enabled по умолчанию в стандартном файле wsgihandler.py, предоставленном web2py).

Ваши задачи будут выполняться при первом вызове (загрузке страницы) до истечении времени, указанного в кронтаб web2py; но только после обработки страницы, так что задержка не будет наблюдаться пользователем. Очевидно, что существует некоторая неопределенность в отношении того, когда именно задача будет выполнена, в зависимости от получаемого сайтом трафика. Кроме того, задача Cron может получить прерывание, если веб-сервер имеет заданное время ожидания загрузки страницы. Если эти ограничения являются не приемлемыми, то смотрите external cron. Soft cron является разумным выбором в качестве последнего средства, но если ваш веб-сервер позволяет другие методы Cron, то они более предпочтительнее, чем soft cron.

Hard cron используется по умолчанию, если вы используете встроенный веб-сервер (либо напрямую или через Apache mod_proxy). Hard Cron выполняется в параллельном потоке, так что в отличие от soft cron, не существует никаких ограничений в отношении времени запуска или точности времени выполнения.

External Cron не является по умолчанию в любом сценарии, но требует, чтобы вы имели доступ к системным Cron возможностям. Он работает в параллельном процессе, поэтому ни одно из ограничений soft Cron не применяется. Это рекомендуемый способ использования Cron под WSGI или FastCGI.

Пример строки, чтобы добавить к системным crontab (как правило /etc/crontab):

1
0-59/1 * * * * web2py cd /var/www/web2py/ && python web2py.py -J -C -D 1 >> /tmp/cron.output 2>&1

C external cron, убедитесь в добавлении -J (или --cronjob, что то же самое), как указано выше, с тем, чтобы web2py знал, что задача выполняется через Cron. Web2py устанавливает это внутренне с soft и hard cron.

Самодельные очереди задач

Несмотря на то, что Cron полезен для выполнения задач через равные промежутки времени, это не всегда является лучшим решением для запуска задачи в фоне. Для этих целей web2py предоставляет возможность запуска любого скрипта python, как если бы он был внутри контроллера:

1
python web2py.py -S app -M -R applications/app/private/myscript.py -A a b c

где -S app говорит web2py запустить "myscript.py" как "app", -M говорит web2py выполнить модели, и -A a b c это опциональные аргументы командной строки sys.argv=['applications/app/private/myscript.py','a','b','c'] передаваемые в "myscript.py".

Этот тип фонового процесса не должен выполняться через cron (за исключением, пожалуй cron @reboot), так как вы должны быть уверены, что у вас одновременно работает не более одного экземпляра. С Cron вполне возможно, что процесс начинается при Cron итерации 1 и не завершается к Cron итерации 2, так что Cron запускает его снова, и снова, и снова - таким образом, заглушая почтовый сервер.

В главе 8 мы приведем пример того, как использовать метод, описанный выше, чтобы отправить электронную почту.

Планировщик web2py

Основным решением web2py для запуска задач в фоновом режиме (и, следовательно, ухода от процесса веб-сервера) является встроенный планировщик.

Стабильный API состоит из следующих функций:

  • disable()
  • resume()
  • terminate()
  • kill()
  • queue_task(),
  • task_status()
  • stop_task()

Работа web2py планировщика очень напоминает очереди задач, описанные в предыдущем подразделе с некоторыми различиями:

  • Он предоставляет стандартный механизм для создания, планирования и мониторинга задач.
  • Существует не один фоновый процесс, а совокупность рабочих процессов.
  • Выполнение задач узловых работников может отслеживаться, так как их состояние, а также состояние задач, хранится в базе данных.
  • Он работает без web2py, но это не документировано здесь.

Планировщик не использует Cron, хотя можно использовать Cron @reboot для запуска узловых работников.

Более подробная информация о развертывании планировщика под Linux и Windows, находится в главе, посвященной рецептам развертывания.

В планировщике, задача является простой функцией, определенной в модели (или в модуле и импортируемой в модель). Например:

1
2
def task_add(a,b):
    return a+b

Задачи всегда будут вызываться в той среде, которая видна контроллерами и поэтому им видны все глобальные переменные, определенные в моделях, в том числе подключения к базе данных (db). Задачи отличаются от действия контроллера, потому что они не связаны с запросом HTTP и, следовательно, нет имеют request.env. Кроме того, задачам может быть доступна другая переменная среды, которая отсутствует в нормальных запросах: W2P_TASK. W2P_TASK.id содержит поле scheduler_task.id и W2P_TASK.uuid содержит поле scheduler_task.uuid для запущенной задачи.

Не забудьте вызвать db.commit() в конце каждой задачи, если она связана со вставкой/обновлением в базе данных. web2py по умолчанию фиксирует транзакции в конце успешного действия, но задачи планировщика не являются действиями.

Чтобы включить планировщик необходимо создать экземпляр класса Scheduler в модели. Рекомендуемым способом включения планировщика для вашего приложения является создание файла модели с именем scheduler.py и определение вашей функции там. После функций, вы можете поместить следующий код в модель:

1
2
from gluon.scheduler import Scheduler
scheduler = Scheduler(db)

Если ваши задачи определены в модуле (в отличие от моделей) может потребоваться перезапуск работников.

Задача ставится в очередь следующим образом:

1
scheduler.queue_task(task_add,pvars=dict(a=1,b=2))

Параметры

Первым аргументом класса Scheduler должна быть база данных, которая будет использоваться планировщиком для взаимодействия с работниками. Это может быть db приложения или другая связанная db, возможно одна общая для нескольких приложений. Если вы используете SQLite, то рекомендуется использовать базу данных, отделенную от той, которая используется вашим приложением для того, чтобы сохранить отзывчивость приложения. После того, как задачи определены и создан экземпляр класса Scheduler, все, что нужно сделать, это запустить работников. Вы можете сделать это несколькими способами:

python web2py.py -K myapp

запускает работника для приложения myapp. Если вы хотите запустить несколько работников для одного приложения, вы можете сделать это простой передачей myapp,myapp. Вы можете передать также group_names (переопределяя один набор в вашей модели) с помощью:

python web2py.py -K myapp:group1:group2,myotherapp:group1

Если у вас есть модель под названием scheduler.py, то вы можете запустить/остановить работников из окна web2py по умолчанию (тот, который вы используете для установки IP-адреса и порта).

Развертывание планировщика

И последнее приятное дополнение: если вы используете встроенный веб-сервер, то вы можете запустить веб-сервер и планировщик с помощью всего одной строки кода (здесь предполагается, что вы не хотите, чтобы выскакивало окно web2py, в противном случае вы можете использовать меню "Schedulers")

python web2py.py -a yourpass -K myapp -X

Вы можете передать обычные параметры (-i, -p, здесь -a предотвращает появление окна), передать любое приложение в -K параметре и добавить в -X. Планировщик будет работать вместе с веб-сервером!

Пользователям Windows необходимо создать службу, о том как сделать это показано в главе Рецепты развертывания.

Описание класса Scheduler

Атрибутами класса Scheduler являются:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Scheduler(
    db,
    tasks=None,
    migrate=True,
    worker_name=None,
    group_names=None,
    heartbeat=HEARTBEAT,
    max_empty_runs=0,
    discard_results=False,
    utc_time=False
)

Давайте посмотрим их по порядку:

  • db является экземпляром класса DAL базы данных, куда вы хотите поместить таблицы планировщика.
  • tasks это словарь, который сопоставляет имена задач с функциями. Если Вы не передадите этот параметр, то функция будет искаться в среде приложения.
  • worker_name имеет None по умолчанию. Как только работник запускается, имя работника генерируется в виде hostname-uuid. Если вы хотите указать имя работника, то убедитесь что оно уникально.
  • group_names по умолчанию [main]. Все задачи имеют параметр group_name, установленный на main по умолчанию. Работники могут забирать задачи только из своей назначенной группы.
Внимание: Это полезно, если у вас есть различные экземпляры работников (например, на разных машинах), и вы хотите назначать задачи конкретному работнику. Особое примечание 2: Существует возможность назначать работнику несколько групп задач, и они могут быть также все одинаковыми, например ['mygroup','mygroup']. Задачи будут распределены с учетом того, что работник с group_names ['mygroup','mygroup'] в состоянии обработать двойные задачи работника с group_names ['mygroup'].
  • heartbeat по умолчанию задано 3 секунды. Этот параметр контролирует, как часто планировщик будет проверять свой статус в таблице scheduler_worker и смотрит есть ли какие нибудь НАЗНАЧЕННЫЕ (ASSIGNED) для себя задачи на обработку.
  • max_empty_runs установлен 0 по умолчанию, это означает, что работник будет продолжать обрабатывать задачи, как только они будут назначены (ASSIGNED). Если вы установите это значение, скажем, на 10, то работник умрет автоматически, если он АКТИВНЫЙ (ACTIVE) и нет НАЗНАЧЕННЫХ (ASSIGNED) задач для него в течении 10 циклов. Цикл - это период, когда работник выполняет поиск заданий, по умолчанию каждые 3 секунды (или заданное время в heartbeat)
  • discard_results по умолчанию False. Если установить True, то записи в поле scheduler_run не будут создаваться.
Внимание: записи в поле scheduler_run будут создаваться как и прежде для таких статусов задач, как FAILED, TIMEOUT и STOPPED.
  • utc_time по умолчанию False. Если вам необходима координация работников, живущих в разных часовых поясах, или не иметь проблем с солнечным/DST временем, поставляемыми DateTimes из разных стран, и т.д., вы можете установить на True. Планировщик будет приветствовать время UTC и работать, оставляя местное время в сторону. Предостережение: вам нужно планировать задачи с временем UTC (для start_time, stop_time, и так далее.)

Теперь у нас есть инфраструктура на месте: определены задачи, планировщику рассказано о них, запущен работник(и). Остается фактически запланировать задачи.

Задачи

Задачи могут быть запланированы программно или с помощью appadmin. На самом деле, задача запланирована просто путем добавления записи в таблицу "scheduler_task", к которой вы можете получить доступ через appadmin:

http://127.0.0.1:8000/myapp/appadmin/insert/db/scheduler_task

Смысл полей в этой таблице очевиден. Поля "args" и "vars"" содержат значения, передаваемые задаче в формате JSON. В случае с "task_add" в приведенном примере выше, "args" и "vars" могут быть:

1
2
args = [3, 4]
vars = {}

или

1
2
args = []
vars = {'a':3, 'b':4}

Таблица scheduler_task является тем местом, где организованы задачи.

Для добавления задачи через API, воспользуйтесь

scheduler.queue_task('mytask',...)

который документируется в дальнейшем .

Жизненный цикл задачи

Все задачи следуют жизненному циклу

планировщик задач

По умолчанию, когда вы отправляете задание в планировщик, оно находится в статусе QUEUED (в очереди). Если вам необходимо выполнение задания позже, то используйте параметр start_time (по умолчанию = now). Если по некоторым причинам вам необходимо убедиться в том, что задача не выполнится через определенный момент времени (это может быть запрос на веб-сервис, который закрывается в 1:00, или письмо, которое должно быть отправлено до окончания рабочего дня, и т.д...) вы можете установить для задачи параметр stop_time (по умолчанию = None). Если вашу задачу НЕ подобрал работник перед stop_time, то она будет помечена как ОТСРОЧЕНАЯ (EXPIRED). Задачи без установки stop_time или подобранные ПОСЛЕ stop_time являются НАЗНАЧЕННЫМИ (ASSIGNED) работнику. Когда работники подбирают задачи, то статус задач изменяется на **ЗАПУЩЕННЫЙ* (RUNNING).

**ЗАПУЩЕННЫЕ* (RUNNING) задачи могут завершится со следующими статусами:

  • TIMEOUT Когда прошло более чем n секунд времени ожидания, переданного в параметре timeout (по умолчанию = 60 секунд).
  • FAILED при обнаружении исключения.
  • COMPLETED при успешном завершении.

Значения для start_time и stop_time должны быть объектом datetime. Чтобы запланировать, например, для задачи "mytask" запуск через 30 секунд от текущего времени, вы должны сделать следующее:

1
2
3
from datetime import timedelta as timed
scheduler.queue_task('mytask',
    start_time=request.now + timed(seconds=30))

Дополнительно вы можете контролировать, сколько раз задача должна быть повторена (т.е. вам необходимо объединить некоторые данные через заданные интервалы времени). Для этого установите параметр repeats (по умолчанию = 1 раз только, 0 = безлимитно). Вы можете повлиять на то, какое количество секунд должно пройти между выполнениями с параметром period (по умолчанию = 60 секунд).

Поведение по умолчанию: Период рассчитывается не как время между ОКОНЧАНИЕМ первого раунда и НАЧАЛОМ следующего, а с момента НАЧАЛА первого раунда до момента НАЧАЛА следующего цикла). Это может привести к накоплению 'дрейфа' в момент начала работы. В версии 2.8.2, добавлен новый параметр prevent_drift, по умолчанию False. Если задать True, то в очереди задач, параметр start_time будет иметь приоритет в течение всего периода, предотвращая дрейф.

Вы также можете установить, сколько раз функция может вызвать исключение (т.е. запрашивая данные из медленной веб-службы) и заново поставлена в очередь вместо прекращения в статусе FAILED с помощью параметра retry_failed (по умолчанию = 0, -1 = безлимитно).

task repeats

Подведем итоги: вы имеете

  • period и repeats чтобы получить автоматическую перепланировку функции
  • timeout чтобы убедиться в не превышении заданного времени выполнения функции
  • retry_failed чтобы контролировать, сколько раз задача может "глючить"
  • start_time и stop_time чтобы запланировать функцию в ограниченных временных рамках

Метод queue_task

Метод:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
scheduler.queue_task(
    function,
    pargs=[],
    pvars={},
    start_time=now, 		#datetime
    stop_time = None,		#datetime
    timeout = 60,               #seconds
    prevent_drift=False,
    period=60,                  #seconds
    immediate=False,
    repeats = 1
)

позволяет вам поставить в очередь задачи, которые должны выполняться работниками. Он возвращает строку(смотри здесь), и он принимает следующие параметры:

  • function (обязательный): Это может быть имя задачи или ссылка на саму функцию.
  • pargs: аргументы, передаваемые задаче, которые хранятся в виде списка Python.
  • pvars : именованные аргументы, передаваемые задаче, хранящиеся в качестве словаря Python.
  • все остальные столбцы scheduler_task могут быть переданы как ключевое слово аргументам; наиболее важные показываются.

Например:

scheduler.queue_task('demo1', [1,2])

делает точно такую же вещь, как

1
scheduler.queue_task('demo1', pvars={'a':1, 'b':2})

как

1
st.validate_and_insert(function_name='demo1', args=json.dumps([1,2]))

и как:

1
st.validate_and_insert(function_name='demo1', vars=json.dumps({'a':1,'b':2}))

Вот более сложный полный пример:

1
2
3
4
5
6
7
def task_add(a,b):
    return a+b

scheduler = Scheduler(db, tasks=dict(demo1=task_add))

scheduler.queue_task('demo1', pvars=dict(a=1,b=2),
                     repeats = 0, period = 180)

Начиная с версии 2.4.1, если вы передадите дополнительный параметр immediate=True, то это заставит главного работника переназначить задачи. До версии 2.4.1, работник проверял наличие новых задач каждые 5 циклов (получаетя, 5 * heartbeats секунд). Если бы у вас было приложение, которому необходима частая проверка наличия новых задач, то чтобы получить шустрое поведение вы были бы вынуждены опустить heartbeat параметр, поставив db под давлением без всякой причины. С параметром immediate=True вы можете принудительно проверить наличие новых задач: это произойдет самое большее по прошествии heartbeat секунд.

Вызов scheduler.queue_task возвращает id и uuid задачи, которую вы поставили в очередь (может быть переданным вами или автоматически сгенерированным), и возможно errors:

<Row {'errors': {}, 'id': 1, 'uuid': '08e6433a-cf07-4cea-a4cb-01f16ae5f414'}>

Если есть ошибки (как правило, синтаксические ошибки или ошибки проверки ввода), то вы получите результат проверки, а также id и uuid будет None

<Row {'errors': {'period': 'enter an integer greater than or equal to 0'}, 'id': None, 'uuid': None}>

Статус задачи task_status

Для запроса у планировщика статуса задач, используется task_status

1
scheduler.task_status(ref, output=False)

Аргумент ref может быть

  • integer --> поиск будет сделан через scheduler_task.id
  • string --> поиск будет сделан через scheduler_task.uuid
  • query --> поиск, как вы хотите (как в db.scheduler_task.task_name == 'test1')

output=True извлечет запись scheduler_run

Он возвращает один объект Row, для самой последней задачи в очереди, отвечающей критерию.

Запись scheduler_run извлекается с помощью соединения слева, так что он может иметь все поля == None

Пример: Получение данных о статусе задачи планировщика, результатов и трэйсбэков

Здесь экземпляр класса планировщика scheduler это mysched

1
2
3
4
5
task = mysched.queue_task(f, ....)
task_status = mysched.task_status(task.id, output=True)
traceback = task_status.scheduler_run.traceback
result = task_status.scheduler_run.run_result #or
result = task_status.result

Результаты и вывод данных

В таблице "scheduler_run" хранится статус всех запущенных задач. Каждая запись ссылается на задачу, которая была подхвачена работником. Одна задача может иметь несколько запусков. Например, если задачу планируется повторять 10 раз в час, то вероятно имеется 10 запусков (если кто-то не дает сбой или они занимают больше времени, чем 1 час). Имейте ввиду, что если задача не имеет возвращаемых значений, то она удаляется из таблицы scheduler_run, как только она будет закончена.

Возможные статусы запуска:

RUNNING, COMPLETED, FAILED, TIMEOUT

Если запуск завершен, исключений не возникло и не истекло время ожидания задачи, то запуск помечается как COMPLETED и задача помечается как QUEUED или COMPLETED в зависимости от того, предполагается ли повторный запуск задачи в более позднее время. Выход задачи сериализуется в формате JSON и хранится в записи запуска.

когда RUNNING задачи вызывают исключение, запуск помечается как FAILED и задача помечается как FAILED. Трэйсбэк сохраняется в записи запуска.

Аналогичным образом, когда запуск превышает время ожидания, он останавливается и помечается как TIMEOUT, и задача помечается как TIMEOUT.

В любом случае, стандартный вывод захватывается, и также входит в запись запуска.

Из-за многопроцессорных ограничений, остерегайтесь использования огромных возвращаемых значений или огромных операторов печати для находящихся в очереди функций. Поскольку выходные данные буферизуются, то ваша задача может потерпеть неудачу только потому, что родительский процесс зависает на чтении значений. Кроме того, сократите до минимума операторов печати, а при необходимости используйте надлежащую библиотеку протоколирования, которая не загромождает стандартный вывод. Что же касается огромных возвращаемых значений, то лучшим вариантом может быть использование таблицы, где функция сохраняет результат: вы можете вернуть только ссылку на конкретную строку результатов, не препятствуя работе мастер-процесса планировщика.

Используя appadmin, можно проверить все запущенные RUNNING задачи, выходные данные завершенных COMPLETED задач, ошибки сбойных FAILED задач, и т.д.

Планировщик также создает еще одну таблицу под названием "scheduler_worker", в которой хранится сердцебиение (heartbeat) работников и их статус.

Управление процессами

Управление работником является трудным. Этот модуль старается не оставить позади любую платформу (Mac, Win, Linux) .

Когда вы запускаете работника, вы можете в дальнейшем захотеть:

  • убить его "независимо от того, что он делает"
  • убить его, только если он не обрабатывает задачи
  • положить его спать

Может быть, у вас есть еще некоторые задачи в очереди, и вы хотите сохранить некоторые ресурсы. Вы знаете, что вам нужно, чтобы они обрабатывались каждый час, таким образом, вы будете хотеть, чтобы:

  • все стоящие в очереди задачи обрабатывались и умирали автоматически

Все эти вещи являются возможными, путем управления параметрами Scheduler или таблицей scheduler_worker. Если быть более точным, то для запущенных работников вы можете изменить значение status любого работника и повлиять на его поведение. Что касается задач, работники могут находиться в одном из следующих статусов: ACTIVE, DISABLED, TERMINATE или KILLED.

Статусы ACTIVE и DISABLED являются "неизменными", в то время как TERMINATE или KILL, как можно предположить из названия статусов, больше "команды", чем реальные статусы. Нажатие Ctrl + C равносильно установке работнику KILL

workers statuses

Есть несколько удобных функций, начиная с версии 2.4.1 (говорящих сами за себя)

1
2
3
4
scheduler.disable()
scheduler.resume()
scheduler.terminate()
scheduler.kill()

каждая функция принимает необязательный параметр, который может быть строкой или списком, чтобы управлять работниками на основе их group_names. По умолчанию используется параметр group_names, определенный в созданном экземпляре класса планировщика.

Пример может быть лучше, чем тысячи слов: scheduler.terminate('high_prio') ПРЕКРАТИТ работу всех работников, которые занимаются обработкой high_prio задач, в то время какscheduler.terminate(['high_prio', 'low_prio']) прекратит все задачи high_prio и low_prio работников.

Берегитесь: если у вас есть работник, обрабатывающий задачи high_prio и low_prio, scheduler.terminate('high_prio') прекращает все задачи работника, даже если вы не хотите завершать задачу low_prio.

Все, что можно сделать с помощью appadmin можно сделать программно, путем вставки и обновления записей в этих таблицах.

Во всяком случае, не следует обновлять записи относительно RUNNING задач, так как это может создать непредсказуемое поведение. Лучшая практика состоит в использовании очереди задач с помощью метода "queue_task".

Например:

1
2
3
4
5
6
7
8
scheduler.queue_task(
    function_name='task_add',
    pargs=[],
    pvars={'a':3,'b':4},
    repeats = 10, # запустить 10 раз
    period = 3600, # каждый 1 час
    timeout = 120, # должна занимать менее 120 секунд
    )

Обратите внимание, что поля "times_run", "last_run_time" и "assigned_worker_name" не предусмотрены в назначенное время, но заполняются автоматически рабочими.

Можно также получить выходные данные завершенных задач:

1
completed_runs = db(db.scheduler_run.run_status='COMPLETED').select()
Планировщик считается экспериментальным, поскольку он нуждается в более обширном тестировании и потому, что структура таблицы может изменяться по мере добавления более широких возможностей.

Отчетность о ходе работ в процентах

Специальное "слово" встречающееся в операторе print ваших функций очищает все предыдущие выведенные данные. Это слово !clear!. Это слово, в сочетании с sync_output параметром, позволяет сообщать о процентах.

Здесь приведен пример:

def reporting_percentages():
    time.sleep(5)
    print '50%'
    time.sleep(5)
    print '!clear!100%'
    return 1

Функция reporting_percentages спит в течение 5 секунд, выводит 50% . Затем, она спит еще 5 секунд и выводит 100%. Обратите внимание, что выходные данные в таблице scheduler_run синхронизируются каждые 2 секунды, и что второй оператор print, который содержит !clear!100%, получает от предыдущего вывод 50%, который очищается и заменяется на 100%.

1
2
scheduler.queue_task(reporting_percentages,
                     sync_output=2)

Модули сторонних разработчиков

import

web2py написан на Python, поэтому он может импортировать и использовать любой модуль Python, включая модули сторонних производителей. Он просто должен быть в состоянии найти их. Как и с любым приложением Python, модули могут быть установлены в официальном Python каталоге "site-packages", а затем они могут быть импортированы из любой точки внутри вашего кода.

Модули в каталоге "site-packages" являются, как следует из названия, пакетами на уровне сайта. Приложения, требующие site-packages не являются переносимыми, если эти модули не будут установлены отдельно. Преимуществом обладания модулями в "site-packages" является то, что различные приложения могут обмениваться ими. Рассмотрим, например, пакет отрисовки под названием "Matplotlib". Вы можете установить его из командной строки с помощью команды easy_install[easy-install] (или его современного заменителя pip [PIP] ):

1
easy_install py-matplotlib

а затем вы можете импортировать его в любой модели/контроллере/представлении:

1
import matplotlib

Исходный дистрибутив web2py и бинарный дистрибутив Windows, имеют папку site-packages в папке верхнего уровня. Бинарный дистрибутив Mac имеет папку site-packages в папке:

1
web2py.app/Contents/Resources/site-packages

Проблемой с использованием site-packages является то, что становится трудно использовать различные версии одного модуля в одно и то же время, например, у вас могут быть два приложения, но каждое из них использует различные версии одного и того же модуля. В этом примере, sys.path не может быть изменен, так как это будет влиять на оба приложения.

Для такого рода ситуаций, web2py предоставляет еще один способ импорта модулей таким образом, что глобальный sys.path не изменяется: путем размещения их в папку "modules" приложения. Преимущество состоит в том, что модуль будет автоматически копироваться и распространяться вместе с приложением.

После того, как модуль "mymodule.py" помещается в папку "modules /" приложения, он может быть импортирован из любой точки кода внутри приложения web2py (без необходимости изменять sys.path) при помощи строчки кода:
import mymodule

Среда исполнения

exec_environment
В то время как все, что обсуждалось здесь работает отлично, мы рекомендуем вместо этого строить свое приложение с использованием компонентов, как описано в главе 12.

Файлы моделей и контроллеров web2py не являются модулями Python, потому что они не могут быть импортированы с помощью оператора Python import. Причина этого заключается в том, что модели и контроллеры предназначены для выполнения в заранее подготовленной среде, которая предварительно заполняется глобальными объектами web2py (request, response, session, cache и T) и вспомогательными функциями. Это необходимо потому, что Python представляет собой статическую (лексическую) область действия языка, в то время как среда web2py создается динамически.

web2py предоставляет функцию exec_environment, чтобы позволить вам иметь доступ к моделям и контроллерам напрямую. exec_environment создает среду исполнения web2py, загружает файл в нее, а затем возвращает объект Storage, содержащий среду. Объект Storage служит также в качестве механизма пространства имен. Любой файл Python, предназначенный для выполнения в среде выполнения, может быть загружен с помощью exec_environment. Используемая среда exec_environment включает в себя:

  • Доступ к данным (модели) из других приложений.
  • Доступ к глобальным объектам из других моделей или контроллеров.
  • Выполнение функций контроллера из других контроллеров.
  • Загрузка вспомогательных библиотек для всего сайта.

Этот пример читает строки из таблицы user в приложении cas:

1
2
3
from gluon.shell import exec_environment
cas = exec_environment('applications/cas/models/db.py')
rows = cas.db().select(cas.db.user.ALL)

Другой пример: предположим, у вас есть контроллер "other.py", который содержит:

1
2
def some_action():
    return dict(remote_addr=request.env.remote_addr)

Здесь показано, как вы можете вызвать это действие от другого контроллера (или из web2py оболочки):

1
2
3
from gluon.shell import exec_environment
other = exec_environment('applications/app/controllers/other.py', request=request)
result = other.some_action()

В строке 2, request=request является необязательным. Он имеет эффект прохождения текущего запроса до "другой" окружающей среде. Без этого аргумента, среда будет содержать новый и пустой (кроме request.folder) объект запроса. Можно также передать объект response и session в exec_environment. Будьте осторожны при передаче объектов request, response и session --- модификация при помощи вызываемого действия или кодированием зависимостей в вызываемом действии может привести к неожиданным побочным эффектам.

Вызов функции в строке 3 не выполняет представление; он просто возвращает словарь, если только response.render не вызывается в явном виде через "some_action".

Одно последнее предостережение: не используйте exec_environment без необходимости. Если вы хотите использовать результаты действий в другом приложении, то вам вероятно, следует реализовать XML-RPC API (реализации XML-RPC API с web2py почти тривиальная). Не используйте exec_environment в качестве механизма перенаправления; используйте redirect помощник.

Сотрудничество

cooperation

Есть множество способов, при помощи которых приложения могут сотрудничать:

  • Приложения могут подключаться к одной базе данных и тем самым совместно используя таблицы. Не обязательно, чтобы все таблицы в базе данных были определены для всех приложений, но они должны быть определены в тех приложениях, которые их используют. Всем приложениям, которые используют одну и ту же таблицу, но единственную, необходимо определить таблицу с параметром migrate=False.
  • Приложения могут внедрять компоненты других приложений с помощью помощника LOAD (описанного в главе 12).
  • Приложения могут совместно использовать сессии.
  • Приложения могут вызывать действия друг друга удаленно с помощью XML-RPC.
  • Приложения могут получать доступ к файлам друг друга через файловую систему (предполагается, что они совместно используют одну и ту же файловую систему).
  • Приложения могут вызывать действия друг друга локально с помощью exec_environment, как описано выше.
  • Приложения могут импортировать модули друг друга, используя синтаксис:
1
from applications.otherapp.modules import mymodule

или

1
import applications.otherapp.modules.othermodule
  • Приложения могут импортировать любой модуль в PYTHONPATH путь поиска, sys.path.

Одно приложение может загрузить сессию другого приложения с помощью команды:

1
session.connect(request, response, masterapp='appname', db=db)

Здесь "appname" это имя мастер приложения, того самого, который задает первоначальный session_id в куки. db является соединением с базой данных, которая содержит таблицу сессии (web2py_session). Все приложения, которые совместно используют сессии должны использовать одну и ту же базу данных для хранения сессии.

Логирование

Python предоставляет API интерфейс логирования. Web2py предоставляет механизм настройки, который может использоваться приложениями.

В вашем приложении вы можете создать логгер, например, в модели:

1
2
3
import logging
logger = logging.getLogger("web2py.app.myapp")
logger.setLevel(logging.DEBUG)

и вы можете использовать его для записи сообщений различной важности

1
2
3
4
logger.debug("Just checking that %s" % details)
logger.info("You ought to know that %s" % details)
logger.warn("Mind that %s" % details)
logger.error("Oops, something bad happened %s" % details)

logging это стандартный модуль Python, описанный здесь:

http://docs.python.org/library/logging.html

Строка "web2py.app.myapp" определяет логгер уровня приложения.

Для того чтобы это работало должным образом, вам нужен файл конфигурации для логгера. Один такой файл "logging.example.conf" предоставляется web2py в папке "examples". Вам необходимо только скопировать файл в директорию web2py, переименовав файл в "logging.conf", и настроить его по мере необходимости.

Этот файл самодокументирован, поэтому вам следует только открыть и прочитать его.

Чтобы создать настраиваемый логгер для приложения "myapp", вы должны добавить к myapp [loggers] список ключей:

1
2
[loggers]
keys=root,rocket,markdown,web2py,rewrite,app,welcome,myapp

и вы должны добавить [logger_myapp] секцию, используя [logger_welcome] в качестве отправной точки.

1
2
3
4
5
[logger_myapp]
level=WARNING
qualname=web2py.app.myapp
handlers=consoleHandler
propagate=0

Директива "handlers" задает тип логирования, и здесь логирование приложения "myapp" выполняется на консоль.

Интерфейс шлюза веб-сервера WSGI

WSGI

Между web2py и WSGI имеются отношения любви-ненависти. Наша перспектива в том, что WSGI был разработан в качестве протокола для подключения веб-серверов к веб-приложениям переносимым способом, и мы используем его для этой цели. web2py по своей сути является приложением WSGI: gluon.main.wsgibase. Некоторые разработчики подтолкнули WSGI к своим пределам в качестве протокола для мидлваре коммуникаций и разработки веб-приложений, который как луковица с множеством слоев (каждый слой будучи WSGI мидлваре разработан независимо от всего фреймворка). web2py не принимает эту структуру внутренне. Так как мы чувствуем, что основные функциональные возможности фреймворков (обработка куки, сессии, ошибки, операции, диспетчеризация) могут быть лучше оптимизированы для повышения скорости и безопасности, если они обрабатываются через единый всеобъемлющий слой.

Тем не менее, web2py позволяет использовать WSGI приложения сторонних разработчиков и мидлваре тремя способами (и их комбинацией):

  • Вы можете отредактировать файл "wsgihandler.py" и включить любое стороннее WSGI мидлваре.
  • Вы можете подключить стороннее WSGI мидлваре к какому-либо конкретному действию в ваших приложениях.
  • Вы можете вызвать стороннее WSGI приложение из ваших действий.

Единственным ограничением является то, что вы не можете использовать стороннее мидлваре для замены основных функций web2py.

Внешнее мидлваре

Рассмотрим файл "wsgibase.py":

1
2
3
4
5
6
7
8
9
#...
LOGGING = False
#...
if LOGGING:
    application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
                                        logfilename='httpserver.log',
                                        profilerfilename=None)
else:
    application = gluon.main.wsgibase

Когда LOGGING установлен на True, тогда gluon.main.wsgibase оборачивается мидлваре функцией gluon.main.appfactory. Она обеспечивает логирование в файл "httpserver.log". Подобным же образом можно добавить любое стороннее мидлваре. Мы рекомендуем обратиться к официальной документации по WSGI для получения более подробной информации.

Внутреннее мидлваре

Учитывая любое действие в контроллерах (например index) и любое стороннее мидлваре приложение (например MyMiddleware, который преобразует выход в верхний регистр), вы можете использовать web2py декоратор, чтобы применить мидлваре к этому действию. Здесь приведен пример:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MyMiddleware:
    """converts output to upper case"""
    def __init__(self,app):
        self.app = app
    def __call__(self, environ, start_response):
        items = self.app(environ, start_response)
        return [item.upper() for item in items]

@request.wsgi.middleware(MyMiddleware)
def index():
    return 'hello world'

Мы не можем обещать, что всё стороннее мидлваре будет работать с этим механизмом.

Вызов WSGI приложений

Вызывать WSGI приложение из действия web2py очень легко. Здесь приведен пример:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def test_wsgi_app(environ, start_response):
    """this is a test WSGI app"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain'),
                        ('Content-Length','13')]
    start_response(status, response_headers)
    return ['hello world!\n']

def index():
    """a test action that calls the previous app and escapes output"""
    items = test_wsgi_app(request.wsgi.environ,
                          request.wsgi.start_response)
    for item in items:
        response.write(item,escape=False)
    return response.body.getvalue()

В этом случае, index действие вызывает test_wsgi_app и убегает от возвращаемого значения, прежде чем вернуть его. Обратите внимание на то, что index сам не является WSGI приложением, и оно должно использовать обычный web2py API такой как response.write для записи в сокет).

 top