В этом документе кратко описаны основные возможности системы
Pager. Система пока еще находится в стадии разработки, но конец уже
близок.
Результаты тестирования системы на машине Pentium100/Windows98/32M
при обработке активной (по большей части) блочной страницы с несколькими
рисунками и другими макротэгами:
- компиляция во внутреннее представление (исключая запуск Perl):
1.2 с
время сокращено за счет отказа от HTML::Parser
- исполнение откомпилированной страницы (исключая время компиляции модулей)
0.0125 с (80 стр/c)
без кэширования вывода - 0.03 с (33 стр/с)
Таким образом, получаем, что открытие страницы, поддерживаемой
системой Pager, очень незначительно (около 30% при использовании mod_perl,
что теоретически исключает время компиляции модулей) отличается от
открытия статической страницы! Конечно, результаты несколько хуже на
обычном Perl - сказывается замедление, вызванное запуском Perl и
компиляцией немногочисленных модулей...
Возможности системы
-------------------
Здесь кратко перечислены особенности и возможности системы Scriptor&Pager.
- работает в качестве CGI на Perl 5.002 и старше
- предельно простая установка и настройка
- низкая загрузка сервера
- иерархическая схема построения страницы из блоков, наследование
блоков от родительских директорий
- использование предварительной компиляции страниц и трехуровневого кэша
страниц и блоков
- автоматическое отслеживание изменений в файлах и перекомпиляция
в случае необходимости
- кэширование заданных участков страниц; различные алгоритмы
обновления кэша;
поддержка множества кэшей для одного и того же блока в зависимости
от данных, на которых он базируется (незаменим при проектировании
новостных и др. систем с высокой нагрузкой и частым обновлением)
- поддержка автоматического генерирования фреймов для страницы -
"вставка страницы во фреймы"; поддержка "умного" кэша фреймов,
серьезно снижающего нагрузку на браузер пользователя и трафик
- автоматическая генерация названия страницы на основе имен наддиректорий
- возможность ссылаться на другие блоки - как прямые, так и "поздние";
возможность ссылаться на блоки и других файлов и использовать
относительные URL; исключение "зацикливаний" при обращении блока к
себе - удобные средства для включения одних файлов из других
- мощная макроподстановка с использованием регулярных выражений; как
пример - автоматическое построение карты (содержания) страницы
- возможность внедрения Perl-кода в страницы, подстановка $-переменных
непосредственно, прямо в страницу
- различные дополнительные server-side "псевдотэги", такие как <if>..</if>,
<foreach>...</foreach> и т.д.
- "прозрачные" и "умные" form-тэги (такие как все input-ы, select и
textarea) - между вызовами одной и той же формы СОХРАНЯЮТ свои
значения. У тэга <select> введен параметр buttonize (и tail),
позволяющий представлять элемент не в виде списка, а в виде
кнопок - radios при не-multiple и checkboxes при multiple-select-е.
Можно также задать не набор options-ов, а имя массива-списка значений.
Дополнительный параметр confirm у submit-кнопки - позволяет задавать
вопрос о подтверждении при нажатии. ВСЕ остальные функции сохранены
без изменения, и их можно также использовать
- возможность надстраивания существующих моделей страниц
- поддержка идеологии трехэшелонного проектирования обработчиков форм
(дизайн и html-кодирование <-> интерфейс <-> реализация); тэг <datasrc>
- гарантированное избавление от 500-й Ошибки - вывод ошибок в браузер
- в случае ошибки при выполнении любого блока показывается имя файла и строка,
где она произошла, даже если блок получился в результате макроподстановки
- простая расширяемость в будущем; простой код
- API разработчика, в том числе поддержка GET-POST-Cookies-Upload
Этот список не полон, кроме того, в будущем новые возможности будут
постоянно добавляться без ущерба совместимости.
Несколько слов об организации файлов Pager
-------------------------------------------------------------------
Все файлы имеют ОДИН И ТОТ ЖЕ формат. Расширение различно
только для удобства. Для примера, это распространяется на следующие
файлы:
- файлы *.blk
- файлы *.htm
- файлы *.html (документы)
- файлы .htaccess (да-да, те самые!)
Далее все такие файлы мы будем называть "файлами блоков".
Файлы, содержащие блок Output (косвенно или непосредственно), назовем
"моделями страницы". Можно сказать, что модель страницы задает ее
внешний вид.
Формат файлов
-------------
Каждый файл состоит из одного или нескольких именованных блоков.
Новый блок объявляется в виде конструкции:
##ИмяБлока = Содержимое_Блока_На_Одной_Строке
или
##ИмяБлока
Содержимое
блока
на нескольких
строках
Первое объявление задает однострочный блок, последнее -
многострочный. Содержимое однострочного блока может быть заключено в
кавычки - в этом случае из него не удаляются начальные и концевые
пробелы. Иначе - удаляются, ровно как и из многострочного. Имени блока
обязательно должны предшествовать символы "##", идущие с начала строки.
Имена блоков ЧУВСТВИТЕЛЬНЫ к регистру символов!
Использование "##" мотивировано тем, что это позволяет легко
задавать блоки в файле .htaccess, которые сервер воспримет просто как
комментарии.
Концом содержимого блока считается либо конец файла, либо
начало определения следующего блока. Исключение - однострочный блок:
его конец определяется концом строки. Начальные и концевые пробельные
символы отрезаются. Этого можно избежать, заключив ВСЕ тело блока в
двойные кавычки.
Любой текст, расположенный вне тел блоков, рассматривается как
комментарий и игнорируется. Часто для большей читабельности мы предваряем
такие комментарии символом "#", который не играет никакой роли.
При обработке какого-либо файла сначала собираются вместе все
блоки, которые ему соответствуют (все внешние .htaccess-ы плюс блоки
в самом файле и блоки, на которые он ссылается).
Прагмы
------
Помимо блоков, файлы могут содержать также управляющие
конструкции, выглядящие, как блоки. Вот они:
##@INCLUDE = URL_файла
включает В ДАННОЕ МЕСТО (!) все блоки, содержащиеся в
файле URL_файла
##@USE = URL_файла
почти то же самое, что и @INCLUDE, за одним очень важным исключением:
добавляемые блоки из файла помещаются В САМЫЙ КОНЕЦ "стопки" блоков,
а не в то место, где встретилось @USE. Обычно эта директива используется
для загрузки файлов-моделей в главном .htaccess-файле, чтобы можно
было делать надстройки одной модели над другой - например, создать
модель A, полностью копирующую модель B, но вставляющую в блок Текст
локальную карту страницы.
##@INC = URL_с_файлами
задает очередной URL, в котором будут искаться все файлы,
включаемые посредством @INCLUDE или @USE
##@AUTORUN = код_для_автообработки
как только управление доходит до этого блока, текст,
указанный как "код_для_автообработки", обрабатывается,
и то, что получилось, выводится прямо в браузер, минуя
другие блоки. Этот блок имеет смысл, если в нем присутствуют
участки кода на Perl, заключенные в <? и ?>, либо же расширенные тэги
от WiseTags. Например, так мы загружаем код, реализующий интерфейс с
системой новостей:
##@AUTORUN = <DATASRC src=News::List>
где News::List есть указание использовать модуль News/List.pm
из стандартной директории модулей. См. описание тэга <DATASRC>.
##@DEFAULTGLUE = разделитель_по_умолчанию
задает разделитель по умолчанию, который будет использован для
склейки названий, если "клеевой" блок не указан. Чтобы следующее
название "приклеивалось" к предыдущему, нужно в начале его тела
поставить символы [имя_блока_клея], например:
##Название = [Glue]Документ
В этом примере блок "Название" будет составлен как содержимое
предыдущего блока "Название" и текста "Документ", склеенное
содержимым блока "Glue". Если указаны пустые скобки,
подразумевается блок @DEFAULTGLUE.
##@CACHE = имя_блока; выражение [; кэш_код_зависимостей]
говорит системе о том, что результат работы блока имя_блока
должен кэшироваться. Как часто обновлять кэш - говорит выражение.
Если оно представляет собой десятичное число - например, 20, -
то это означает, что кэш блока должен обновляться КАЖДЫЕ 20 секунд
- то есть, по истечение этого промежутка времени блок будет исполнен
при запросе к нему. Если выражение - любая непустая строка
(рекомендуем применять строку "!" или "update"), то кэш считается
недействительным сейчас и обновляется. Если же выражение равно
пустой строке (""), то кэш используется всегда, и его перевычисление
не производится. Тело прагмы @CACHE, как и любой другой прагмы или
блока, вычисляется при КАЖДОМ открытии страницы - в этом она
похожа на прагму @AUTORUN. Это означает, что мы можем использовать в
качестве тела не статическую строку, а результат работы некоторого
кода, который как раз и будет генерировать "!" в случае
недействительности кэша (например, когда получено сообщение о том, что
база данных обновлена) и "" в случае его "истинности". Отдельно
рассмотрим, что такое необязательный параметр кэш_код_зависимостей.
Дело в том, что тело блока может кэшироваться не в "единственном"
экземпляре, а, например, в зависимости от того, с какими параматрами была
вызвана страница. Например, совершенно очевидно, что для страницы
новостной системы, которая принимает в параметре дату, за которую
выводятся новости, будет недостаточно одного-единственного кэша -
нужно, чтобы для каждой даты был свой собственный кэш. Добиться этого
можно, указав в третьем параметре дату, для которой затребована страница
- вообще говоря, для любых двух разных строк, стоящих на месте третьего
параметра, используются два разных и независимых кэша. Итак, для нашей
гипотетической новостной системы прагма @CACHE будет выглядеть так:
##@CACHE = НовостнойБлок; 300; $IN{date}
что говорит системе использовать разные кэши бля блока НовостнойБлок,
каждый из которых соответствует разным датам $IN{date}.
##@CACHEDIE = время
задает время (в секундах), спустя которое будут удаляться давно не
используемые кэши блоков. Если эта прагма нигде не задана, по умолчанию
используется 3 часа.
##@FILIZATOR = URL_директории; тайм-аут
задает параметры настройки Filizator-а (см. ниже).
URL_директории - это URL той директории, где будет храниться
кэш фреймов, а тайм-аут - время, через которое этот кэш очищается
(то есть, фреймы, к которым давно не обращались, автоматически
удаляются из кэша после любого запроса). Если эта прагма нигде не указана,
используется директория /frames и время 10 минут.
##/выражение/
макротэг, задаваемый регулярным выражением. Макротеги -
это специальные блоки, имя которых является регулярным
выражением. После того, как страница полностью обработана,
но до того, как она будет откомпилирована (!), эти выражения в
ней заменяются на содержимое соответствующих блоков (при этом, если
эти блоки содержат Perl-код, то он выполняется ТОЛЬКО ОДИН РАЗ,
а именно, перед компиляцией, но не при каждом открытии страницы.
Например, так мы можем сделать, чтобы во всей странице
тэги <h3>...</h3> превратились в <b><h3>...</h3></b>:
##/<h3>(.*?)</h3>/ = <b><h3>$1</h3></b>
Нужно заметить, что во избежание проблем с администрированием
лучше использовать только макротэги, заключенные в "<" и ">".
Причина - в элементах формы <textarea> и др. символы "<"
и ">" заменяются на "<" и ">" соотв., и впоследствии
уже не обрабатываются процессором макротэгов, что нам и нужно
(в противном случае мы бы получили расширения этих тэгов
прямо во время администрирования в форме, что, конечно,
нежелательно).
Ссылка на блоки
---------------
В любом блоке можно сослаться на содержимое другого блока.
То есть, мы можем вставить в блок A содержимое блока B при помощи
конструкции:
##A
текст блока A
{{B}}
кажется, вставили то, что было в B...
Те блоки, на которые в документе нет ссылок (за исключением
прагм), НЕ ОБРАБАТЫВАЮТСЯ, а значит, не тормозят процесс.
Существует и другой способ ссылаться на блок. Делается это при помощи
предопределенного хэша %Blocks (или %Blk, что то же самое). Например:
##A
текст блока A
$Blk{B}
кажется, вставили то, что было в B...
Отличие от {{B}} заключается в том, что в первом случае макропроцессор
пытается подставить тело затребованного блока на место {{B}} как можно
раньше - а именно, ДО компиляции, тогда как в последнем случае тело блока
B вставляется в нужное место ВО ВРЕМЯ ИСПОЛНЕНИЯ кода страницы. Кроме того,
допустима такая конструкция:
##A
<?
$a=$Blk{B}; # так предпочтительнее!
$a=RunBlock("B"); # ...но можно и так
?>
но не допустима следующая:
##A
<?
$a={{B}}
?>
Возможно, в будущих версиях будут работать оба способа, но пока -
только первый (это связано с трудностью определения, находится ли некоторая
позиция в строке внутри <? и ?> или вне их).
С точки зрения быстродействия обе конструкции работают почти
одинаково. Функциональные их различия можно понять и на примере макротэга,
строящего локальную карту страницы, включая все блоки, которые ей используются
- в этом случае нужно обязательно будет использовать синтаксис {{B}}, но не
$Blk{B}, потому что первая разворачивается до того, как будут обработаны
макротэги, а вторая - после.
При ссылке на блок можно также задавать, из какого файла он будет
браться (естественно, этот файл должен быть сначала подгружен страницей с
помощью @INCLUDE или @USE). Делается это путем задания префикса перед
именем блока - абсолютного либо относительного URL. Например:
{{B}} - вставляет "ближайший" блок B
{{../B}} - B из файла .htaccess, находящегося на уровень выше
{{/B}} - вставляет из главного .htaccess-файла
{{/dir/file.html/B}} - вставляет блок из файла /dir/file.html
Все ссылки представляют собой URL, а не абсолютный пути файловой системы.
Имена ссылок могут также формироваться динамически, во время исполнения
соответствующего блока. То есть, если имя в {{...}} не содержит
Perl-переменные или код, то ссылка расширяется в момент препроцессирования,
иначе - в момент исполнения. Можно форсировать позднее расширение ссылки
(например, для уменьшения размера кэш-файла) путем использования синтаксиса
{{late:имя_блока}}.
Рассмотренные только что ссылки назовем "прямыми". Существуют также и
"косвенные" ссылки, связанные с понятием Filizator (см. ниже). Их синтаксис
почти такой же, как и у прямых ссылок, и выглядит так:
{{filize:имя_блока}}. Косвенные ссылки - всегда "поздние", в отличие
от прямых.
Блок Output
-----------
Этот блок несет специальный смысл. А именно, его содержимое
выводится в браузер. Обычно блок Output содержат файлы-модели страниц.
Вообще, выбор имени Output совершенно произволен - просто Scriptor
настроен именно на него.
Встраивание Perl-кода в блоки
-----------------------------
В текст любого блока можно встроить код на Perl. В этом случае
он выполняется, и то, что он вывел, и вставляется в текст блока.
Код должен обрамляться специальными тэгами <? и ?>.
Рекомендуется КАК МОЖНО МЕНЬШЕ использовать эту возможность.
В качестве альтернативы предлагаются расширенные тэги управления от
WiseTags, которые действительно способны удовлетворить все потребности,
которые могут возникнуть при использовании Pager.
Расширенные html-тэги ветвления и циклов (WiseTags)
---------------------------------------------------
<input type=...>
преобразуется в эквивалент из Tags.pm
<textarea...>...</textarea>
преобразуется в эквивалент из Tags.pm
<select...>...</select>
преобразуется в эквивалент из Tags.pm. Кроме того, существуют дополнительные
параметры к тэгу <select>:
buttonize - использует не список, а набор кнопок
tail=... - текст, которым будет завершена каждая кнопка (см. buttonize)
multiple - формирует список с множественным выбором
Если вместо ВСЕХ <option> задана ОДНА СТРОКА из букв и цифр, то она
трактуется как имя переменной, содержащей массив или хэш элементов.
<foreach var=имя_счетчика src=имя_переменной_списка>...</foreach>
заключает блок текста между тэгами в цикл foreach
<if expr=логическое_выражение>...</if> или <elsif...> или <else>
заключает блок в оператор if
<elsif expr=логическое_выражение>
то же, только в оператор elsif
<else>
то же, только в оператор else
<ifarray var=имя_переменной>
то же, что <if>, только проверяется, является ли переменная массивом
<ifhash var=имя_переменной>
то же, но проверяется на хэш
<ifscalar var=имя_переменной>
то же, но проверяется, что переменная - не массив и не хэш
<dump[src=имя_объекта]>
печатает полное содержимое объекта независимо от его структуры. Если
параметр не задан, то печатается содержимое %IN.
<require src=имя_модуля_скрипта>
выполняет команду use для модуля, а затем делает доступными в пакете
вызвавшего контекста переменные, взятые из хэша %IN.
<datasrc src=имя_модуля_скрипта>
то же самое
В параметре src у этих тэгов должна быть указана переменная-список
(за исключением <dump> - у него там может быть любой объект, и <datasrc> с
<require>, где должна быть указана константа), причем БЕЗ предваряющего
символа "@". Вообще, единственное место, где иногда нужно употреблять $, %
или @ - это в параметрах expr и некоторых других местах. Везде, где требуется
лишь имя переменной, предварять ее $, % или % НЕЛЬЗЯ!
Функции API
-----------
При запуске конкретного блока ему доступны некоторые переменные и
функции API. Все блоки запускаются внутри пакета Pager, поэтому все
глобальные переменные, которые они создают, расположены именно в этом
пакете. Кроме того, доступны следующие предопределенные переменные:
$CurPagerFile
содержит указатель на объект текущего файла.
$CurBlock
содержит указатель на данные текущего блока
$CurKey
содержит ключ (название) текущего исполняемого блока. При обработке
макротэга - ключ того блока, который обрабатывается в данный момент.
$CurBody
только во время расширения макротэгов - содержит еще не откомпилированное
тело блока, который обрабатывается в данный момент.
%Blocks или %Blk
псевдохэш, при обращении к ключам которого на самом деле происходит
исполнение указанного блока.
%Filize
псевдохэш, при обращении к ключам которого вызывается функция
FilizeBlock() (см. ниже).
Доступны также следующие функции:
string RunBlock(string $BlkName)
исполняет и возвращает результат для блока $BlkName. Исполнение блока
происходит только при первом обращении к нему. Если такого блока не
существует, возвращает undef. Вместо этой функции предпочтительнее
использовать оператор {{...}} или псевдохэш %Blocks.
string FilizeBlock(string $BlkName)
запускает указанный блок, а результат работы записывает в специальный
фрейм-файл filizator-а (см. описание filizator-а). Возвращает полный
URL полученного файла. Вместо вызова этой функции предпочтительнее
использовать оператор {{filize:...}} или псевдохэш %Filize.
void Depends(string $url)
рекомендуется применять только в макротэгах, то есть в коде: выполняемом в
момент расширения и компиляции блока. Сигнализирует кэшу о том, что файл
зависит от указанного $url, и в случае обновления этого $url необходимо
перекомпилировать весь файл. Пример такого макротэга - тэг вставки
csv-таблицы.
Блок может в любой момент установить Cookie или сделать Redirect при помощи
соответствующих функций. Гарантируется, что все то, что он печатает с
помощью print или echo, не попадет напрямую в браузер, а будет вставлено
в шаблон.
Возможности Filizator-а
-----------------------
Filizator - набор специальных функций, позволяющих строить шаблоны на
основе фреймовой структуры. Главная из них - FilizeBlock() (вызывается
оператором {{filize:...}}, рассматриваемым ниже), которая делает
следующее: запускает блок и сохраняет его вывод в специальной директории,
"видимой" снаружи - например, в директории /frames. Файлу
дается уникальное имя, основанное на его контрольной сумме, поэтому,
если блок не меняется, не меняется и имя файла. Функция возвращает
полный URL к этому файлу, который может быть вставлен на место параметра
src тэга <frame>, например:
##Output
<html>
<head><title>{{Название}}</title></head>
<frameset rows="29%,*">
<frame name=FTop src={{filize:Верх}} frameborder=1>
<frame name=FBody src={{filize:Текст}} frameborder=1>
</frameset>
</html>
Как видим, все довольно просто. Главное, что при неизменности блока
браузер пользователя возьмет соответствующий фрейм из кэша, а не будет его
перезагружать каждый раз, поэтому эта схема может быть даже оптимальнее с
точки зрения трафика, чем компоновка одной страницы из многих блоков.
Единственный минус - то, что кэш фреймов все же нужно время от времени
очищать, поэтому URL конкретного фрейма, вообще говоря, не существует.
Возможные направления модернизации
----------------------------------
1) Не расширять макротэги и {{...}} внутри блоков <? ... ?>, потому что
это, вообще говоря, делать действительно нельзя.
2) Сделать защиту для Filize-блоков, подобную авторизации, чтобы можно было
безопасно использовать Filizator даже и в скриптах администрирования,
не опасаясь, что кто-то что-то подсмотрит. Вообще говоря, как это сделать
- неясно.
3) Ввести поддержку mod_perl.