Ключевые слова:perl, cgi, web, (найти похожие документы)
From: Nab <http://kiev.pm.org/>
Date: Mon, 1 Oct 2007 18:21:07 +0000 (UTC)
Subject: Разработка web скриптов на Perl при помощи CGI::Application.
Оригинал: http://kiev.pm.org/?q=node/36http://kiev.pm.org/?q=node/59
CGI::Application. Часть 1. Введение.
CGI::Application (в дальнейшем CAP) это основа для создания веб
приложений, с возможностью управлять системой на любом этапе.
Основной порядок работы любого веб приложения приблизительно таков:
* получение запроса
* его анализ
* получение выходных данных
* генерация выходного потока
* завершение работы
Зачастую некоторые из этих пунктов не выделяют, а смешивают с
соседним, но мы продвинутые и будем строить все по правильному
см.(MVC).
CAP предоставляет для удобства разработки несколько ключевых понятий,
а именно:
режим работы
это параметр запроса, который можно изменять по своему усмотрению,
и который будет специфицировать какую подпрограмму вызывать. По
умолчанию он имеет имя `rm', но это тоже можно сменить. Его можно
даже определить как параметр в path_info. То есть можно будет
вызвать http://localhost/myapp.pl/test вместо стандартных
http://localhost/myapp.pl?rm=test.
четко выраженные этапы работы приложения
* init
* prerun
* run
* postrun
* teardown
* error в случае ошибки этапа run
хуки
этапы работы реализованы в виде хуков вызываемых внутри двух
функций new и run. Хук init вызываеться внутри метода new, для
начальной инициализации. Им часто пользуються плагины, ну и к
примеру подключение к базе данных, тоже лучше сделать в нем.
Остальные хуки вызываються в методе run и отображают этапы
обработки запроса. Хука run, как такового, нет, я так назвал этап
на котором выполняется реальная работа вашего приложения, в
соответствии с выбраным режимом. Имеено эту часть вы как правило и
реализовываете.
доступ к CGI
доступ к CGI окружению производиться посредством модуля CGI.pm,
объект которого доступен как метод query. Вы его можете сменить на
свой, к примеру CGI::Simple.
систему шаблонов
основная подключаемая по дефолту это HTML::Template, но так как CAP
предоставляет интерфейс к системе шаблонов, то подменить ее не
составляет труда, чем и можно воспользоваться подключив любой из
доступных плагинов.
Вот пример простейшего приложения
#!/usr/bin/perl
use CGI::Application;
CGI::Application->new->run;
так как там прописаны дефолтные значения оно просто выведет дамп CGI и
ENV, то есть начальную отладочную информацию :) Естественно, в таком
виде редко когда используется CAP, поэтому мы сделаем нормальное
приложение. Состоит оно из самого приложения - модуля-потомка CAP и
скрипта запуска.
файл запуска myapp.pl
#!/usr/bin/perl
use strict; use warnings;
use MyApp;
MyApp->new->run;
1;
модуль MyApp.pm
package MyApp;
use strict; use warnings;
use base 'CGI::Application';
# Метод setup не являеться хуком он предназначен для конфигурирования
# конретного приложения и вызываеться вслед за хуком init в методе new
sub setup {
my $self = shift;
# вот это описание режимов работы
# режим start дефолтный и именно он вызываеться если режим не указан
# и именно в нем выводиться по умолчанию dump
#
# режим AUTOLOAD по дефолту не определен, но если он определен то он
# будет вызван если указанный режим не найден
#
# в качестве параметра режима может быть указано имя функции ссылка на
# функцию или замыкание на функцию
$self->run_modes(
start => 'on_start',
test => \&on_test,
AUTOLOAD => sub { return 'Запрошенной страницы не существует' }
);
}
sub on_test {
return '<h1>Моя тестовая страничка</h1>'
}
sub on_start {
my $output = '<h1>Вы зашли на сайт Васи Пупкина</h1>';
$output .= '<a href="?rm=test">Тест</a>';
return $output
}
1;
Так как при разработке возможны ошибки, минимальный способ их отловить
тоже присутствует, это error_mode - указание режима, который будет
выполнен если произошла ошибка во время выполнения вашего кода. Так
как ваш код выполняться в защищенном окружении, с помощью eval, то
есть возможность это отловить. Напишем режим для отлова ошибок.
setup будет иметь вид:
sub setup {
my $self = shift;
$self->run_modes(
start => 'on_start',
test => \&on_test,
AUTOLOAD => sub { return 'Запрошенной страницы не существует' }
);
# добавляем режим для ошибок
$self->error_mode(\&on_error);
}
sub on_error {
# первым параметром в любой режим передаеться ссылка на сам объект CAP
my $self = shift;
# вторым параметром передаеться строка с ошибкой
my $error = shift;
# простой заголовок
my $output = "<h2>Internal error:</h2><br />\n";
# наша ошибка
$output .= "<pre><code>$error</code></pre>";
# и добавляем кое какую системную инфу
$output .= $self->dump_html;
return $output
}
Все теперь вы можете сменить свой on_test на такой:
sub on_test {
die "Специальная ошибка в on_test";
return '<h1>Моя тестовая страничка</h1>'
}
и попробовать обратиться к этой странице.
Далее, как вы заметили, во всех описанных режимах работы мы
генерировали только полезную информацию, оставляя за кадром
обязательные элементы html документа. Сейчас я опишу как это делается
в CAP. Чтобы не генерить все теги в каждом режиме, мы их вынесем в
обработчик хука postrun, то есть будем добавлять к уже готовому выводу
в самом конце. Мы не будем писать свой обработчик хука, а
воспользуемся одним из предопреденных в системе, а именно
cgiapp_postrun:
sub cgiapp_postrun {
# объект CAP
my $self = shift;
# вторым параметром ссылка на уже сгенерированный контент
my $body_ref = shift;
# добавляем в HTTP заголовок корректную кодировку и тип документа
# конкретно об этом и других методах я напишу в следующих материалах :)
$self->header_add(-Content_Type => 'text/html; charset=windows-1251');
# минимальный html заголовок
my $output = "<html lang=\"ru\"><head>\n";
$output .= "<title>Сайт Васи Пупкина</title>\n";
$output .= "</head><body>\n";
# теперь добавляем к выводу
# заметьте что мы не заменяем ссылку на новый контент, а изменяем
# содержимое переданной ссылки. Это позволит системе дальше работать
# с ней, и вызвать следующие хуки, для обработки контента если они
# определены у вас или в плагинах. Да и в противном случае, у вас
# то,что мы тут сгенерировани просто не выведеться :)
$$body_ref = $output.$$body_ref."</body></html>\n";
}
Ну вот и все, конечный вариант нашего модуля выглядит так:
package MyApp;
use strict; use warnings;
use base 'CGI::Application';
# Метод setup не являеться хуком он предназначен для конфигурирования
# конретного приложения и вызываеться вслед за хуком init в методе new
sub setup {
my $self = shift;
# вот это описание режимов работы
# режим start дефолтный и именно он вызываеться если режим не указан
# и именно в нем выводиться по умолчанию dump
#
# режим AUTOLOAD по дефолту не определен, но если он определен то он
# будет вызван если указанный режим не найден
#
# в качестве параметра режима может быть указано имя функции ссылка на
# функцию или замыкание на функцию
#
# я добавил тестовый режим с ошибкой
$self->run_modes(
start => 'on_start',
test => \&on_test,
error_test => sub { die "Страница с ошибкой" },
AUTOLOAD => sub { return 'Запрошенной страницы не существует' }
);
$self->error_mode(\&on_error);
}
sub on_test {
return '<h1>Моя тестовая страничка</h1>'
}
sub on_start {
my $output = '<h1>Вы зашли на сайт Васи Пупкина</h1>';
$output .= '<a href="?rm=test">Тест</a><br />';
$output .= '<a href="?rm=error_test">Тест с ошибкой</a>';
return $output
}
sub on_error {
# первым параметром в любой режим передаеться ссылка на сам объект CAP
my $self = shift;
# вторым параметром передаеться строка с ошибкой
my $error = shift;
# простой заголовок
my $output = "<h2>Internal error:</h2><br />\n";
# наша ошибка
$output .= "<pre><code>$error</code></pre>";
# и добавляем кое какую системную инфу
$output .= $self->dump_html;
return $output
}
sub cgiapp_postrun {
# объект CAP
my $self = shift;
# вторым параметром ссылка на уже сгенерированный контент
my $body_ref = shift;
$self->header_add(-Content_Type => 'text/html; charset=windows-1251');
# минимальный html заголовок
my $output = "<html><head>\n";
$output .= "<title>Сайт Васи Пупкина</title>\n";
$output .= "</head><body>\n";
# теперь добавляем к выводу
# заметьте что мы не заменяем ссылку на новый контент, а изменяем
# содержимое переданной ссылки. Это позволит системе дальше работать
# с ней, и вызвать следующие хуки, для обработки контента если они
# определены у вас или в плагинах. Да и в противном случае, у вас
# то,что мы тут сгенерировани просто не выведеться :)
$$body_ref = $output.$$body_ref."\n</body></html>\n";
}
1;
Конечно это приложение вполне себе корректный и рабочий вариант, но
оно не соответствует духу MVC, в нем есть контроллер (пока что
неявно), есть модель, но нет отдельно представления. Им то мы сейчас и
займемся. А именно системой шаблонов. Заодно используем дополнительные
возможности контроллера.
Система шаблонов в CAP реализована двумя следующими методами:
* tmpl_path
* load_tmpl
Первая задает каталог для поиска шаблонов, по умолчанию текущий.
Вторая загружает шаблон и возвращает объект HTML::Template, для
заполнения и вывода.
load_tmpl может быть вызвана без параметров вообще, тогда в качестве
имени файла с шаблоном будет взято имя текущего режима, и дописано
дефолтное расширение `.html'. Может быть вызвана с именем файла в
качестве параметра, или с ссылкой на хеш параметров, которые будут
переданы конструктору системы шаблонов. Также в качестве параметра
может быть передан уже открытый файловый дескриптор содержащий шаблон.
Мы воспользуемся самым простым вариантом, и перепишем наш on_test таким
образом:
sub on_test {
my $self = shift;
# загружаем файл 'test.html'
my $template = $self->load_tmpl;
# заполняем параметрами
$template->param(
headline => "Моя тестовая страничка",
message => "Приветствую вас на моей тестовой странице"
);
return $template->output;
}
Файл шаблона примет такой вид:
test.html
<h2><TMPL_VAR NAME="headline"></h2>
<p><TMPL_VAR NAME="message"></p>
Вот и вся красота :) Можно проверять работу.
Следующий шаг, это интерактивность, выбор действий на основе
результатов контроллера. Попросим посетителя представиться и выведем
именованное приветствие. Имя будет вводится в форме, а имя параметра
будет `guest_name'.
режим on_test примет такой вид:
sub on_test {
my $self = shift;
# получаем стандартным способом параметр cgi запроса через встроенный
# объект CGI
my $name = $self->query->param('guest_name') || '';
# дефолтное сообщение
my $message = "Представьтесь пожалуйста";
# если имя присутствует
$message = "Приветствую вас, $name, на моей тестовой странице" if $name;
# загружаем файл 'test.html'
my $template = $self->load_tmpl;
# заполняем параметрами
$template->param(
headline => "Моя тестовая страничка",
message => $message,
# сохраняем имя текущего режима чтобы попасть на нашу тестовую страничку
mode_name => $self->get_current_runmode
);
return $template->output;
}
теперь модифицируем шаблон
test.html
<h2><TMPL_VAR NAME="headline"></h2>
<p><TMPL_VAR NAME="message"></p>
<form>
<input type="hidden" name="rm" value="<TMPL_VAR NAME="MODE_NAME">">
<input type="text" name="guest_name">
<button type="submit">Представиться</button>
</form>
Ну вот и все, тестовая интерактивная страничка готова.
Теперь нужно переписать наш обработчик хука postrun для использования с
шаблоном:
sub cgiapp_postrun {
# объект CAP
my $self = shift;
# вторым параметром ссылка на уже сгенерированный контент
my $body_ref = shift;
$self->header_add(-Content_Type => 'text/html; charset=windows-1251');
# Так как имя режима в качестве имени шаблона нам не подходит
# используем такое
my $template = $self->load_tmpl('main_template.html');
# заполняем параметрами
$template->param(
# добавляем заголовок
title => "Сайт Васм Пупкина",
# рвзименовываем ссылку на тело
body => $$body_ref
);
# выводим, заметьте опять я заполняю контент ссылки а не меняю ссылку
$$body_ref = $template->output;
}
теперь наш шаблон main_template.html:
<html><head>
<title><TMPL_VAR NAME="title"></title>
</head><body>
<TMPL_VAR NAME="body">
</body></html>
Все, наше первое вполне полноценное MVC приложение на CAP готово.
В коде у нас нет ни единого елемента оформления, кроме генерирования
странички с ошибкой (но тут считаю оправданным независеть от каких
либо внешних сервисов в том числе и от шаблонов, хотя вас ничто не
ограничивает и здесь можно красивую информативную страничку повесить).
Мы же все оформление вынесли в шаблоны, которые сможет править человек
от перла вообще далекий, я уже не говорю про возможности CSS по
оформлению.
Многие моменты я не описал, поэтому рекомендую обратиться к
официальной документации на CAP и HTML::Template. Некоторые из них я
опишу в следующих материалах, но лучше если вы подготовитесь сами :)
тем более неизвестно когда меня вдохновение посетит снова :)
Полный код модуля таков:
MyApp.pm
package MyApp;
use strict; use warnings;
use base 'CGI::Application';
# Метод setup не являеться хуком он предназначен для конфигурирования
# конретного приложения и вызываеться вслед за хуком init в методе new
sub setup {
my $self = shift;
# вот это описание режимов работы
# режим start дефолтный и именно он вызываеться если режим не указан
# и именно в нем выводиться по умолчанию dump
#
# режим AUTOLOAD по дефолту не определен, но если он определен то он
# будет вызван если указанный режим не найден
#
# в качестве параметра режима может быть указано имя функции ссылка на
# функцию или замыкание на функцию
$self->run_modes(
start => 'on_start',
test => \&on_test,
error_test => sub { die "Страница с ошибкой" },
AUTOLOAD => sub { return 'Запрошенной страницы не существует' }
);
$self->error_mode(\&on_error);
}
sub on_test {
my $self = shift;
# получаем стандартным способом параметр cgi запроса через встроенный
# объект CGI
my $name = $self->query->param('guest_name') || '';
# дефолтное сообщение
my $message = "Представьтесь пожалуйста";
#если имя присутствует
$message = "Приветствую вас, $name, на моей тестовой странице" if $name;
# загружаем файл 'test.html'
my $template = $self->load_tmpl;
# заполняем параметрами
$template->param(
headline => "Моя тестовая страничка",
message => $message,
# сохраняем имя текущего режима чтобы попасть на нашу тестовую страничку
mode_name => $self->get_current_runmode
);
return $template->output;
}
sub on_start {
my $output = '<h1>Вы зашли на сайт Васи Пупкина</h1>';
$output .= '<a href="?rm=test">Тест</a>';
return $output
}
sub on_error {
# первым параметром в любой решим передаеться ссылка на сам объект CAP
my $self = shift;
# вторым параметром передаеться строка с ошибкой
my $error = shift;
# простой заголовок
my $output = "<h2>Internal error:</h2><br />\n";
# наша ошибка
$output .= "<pre><code>$error</code></pre>";
# и добавляем кое какую системную инфу
$output .= $self->dump_html;
return $output
}
sub cgiapp_postrun {
# объект CAP
my $self = shift;
# вторым параметром ссылка на уже сгенерированный контент
my $body_ref = shift;
$self->header_add(-Content_Type => 'text/html; charset=windows-1251');
# Так как имя режима в качестве имени шаблона нам не подходит
# используем такое
my $template = $self->load_tmpl('main_template.html');
# заполняем параметрами
$template->param(
# добавляем заголовок
title => "Сайт Васм Пупкина",
# рвзименовываем ссылку на тело
body => $$body_ref
);
# выводим, заметьте опять я заполняю контент ссылки а не меняю ссылку
$$body_ref = $template->output;
}
1;
CGI::Application. Часть 2. Плагины.
-----------------------------------
Плагины, это программы, библиотеки, модули, для расширения
функционала основного приложения. Они могут расширять функционал
основных свойств приложения, могут предоставлять какие-то
дополнительные сервисные функции, как приложению так и для других
плагинов.
Для CGI::Application, далее CAP, имеют смысл два функциональных типа
расширения. Это предоставление дополнительных сервисов девелоперу для
использования в разработке, и обработка данных, контента. Редко когда
плагин имеет четко выраженную функциональность, большинство из них
содержат оба типа.
Я расскажу о наиболее часто используемых, без которых редко какая
разработка обходиться... Все они доступны со CPAN.
Первые два, это плагины для смены рабочего окружения:
* CGI-Application-Plugin-Forward-1.06.tar.gz
* CGI-Application-Plugin-Redirect-0.1.tar.gz
Первый изменяет текущий run_mode внутри приложения, а второй делает
редирект средствами HTTP на другой URL.
Для большинства задач оптимальнее форвардинг, особенно он полезен
высоконагруженным приложениям, где нужна минимизация клиентских
запросов.
Редирект хорош, когда вам нужна полная смена окружения, переход на
другой узел или на другое приложение. Одна из отличительных черт его,
это то, что в строке адреса браузера клиента будет показан фактический
адрес страницы, тогда как при форвардинге, урл указывает на одно, а
отображается совсем другой запрос :)
Так, в типичных приложениях категории BREAD (Browse, Read, Edit, Add,
Delete), после изменения данных должна быть показана страница
просмотра. При использовании форвардинга мы можем выполнить изменение
и показать результирующую страницу. Но так как браузер клиента
кеширует запросы то при обновлении страницы, запрос на изменение будет
отправлен снова. И здесь можно поступить по разному. Или использовать
редирект, или внутри приложения отследить за повторной отправкой
данных. Что будет оптимальнее в конкретном случае, нужно определять по
контексту.
Следующие плагины, в разной мере конечно, но входят в группу
реализующую персонификацию рабочего окружения:
* CGI-Application-Plugin-Session-1.02.tar.gz
* CGI-Application-Plugin-Authentication-0.10.tar.gz
* CGI-Application-Plugin-Authorization-0.05.tar.gz
Session - основан на модуле CGI::Session, и реализует поддержку
рабочих сессий для CAP. Позволяет сохранять любые данные о
пользователе и его настройках в текущей сессии. Может использовать
различные способы идентификации, такие как куки или параметры запроса.
Использует различные хранилища, плоские файлы или базу данных. Также
можно написать свои драйвера для работы с необходимым хранилищем.
Authentication - это система аутентификации. Очень гибко
настраиваемая, с возможностью тонкой подгонки под нужды каждого
приложения. Также может работать с различными драйверами проверки
идентификации и хранения учетных записей.
Authorization - этот сильнейший модуль хоть и используется реже, но
зато в компании с аутентификацией, позволяет построить распределенную
систему прав доступа к различным частям приложения.
Наш следующий плагин предназначен для уменьшения исходящего трафика,
ну или входящего для клиентов :)
* CGI-Application-Plugin-CompressGzip-0.03.tar.gz
Он расширяет и замещает стандартный CGI.pm, позволяя сжимает выходной
поток данных методом gzip. Конечно если этот метод поддерживается
клиентом. Споры о таком сжатии ведутся постоянно, так как это
увеличивает нагрузку, как на сервер, так и на клиентскую часть. Но
уменьшение преобладающего текстового трафика весьма существенно.
Поэтому решение использовать его, или нет, нужно принимать в каждом
конкретном случае отдельно, в зависимости от специфики приложения.
Плагин
* CGI-Application-Plugin-AnyTemplate-0.17.tar.gz
позволяет использовать не только HTML::Template в качестве шаблона, у
него на данный момент, присутствуют драйвера для работы с
HTML::Template, HTML::Template::Expr, HTML::Template::Pluggable,
Template::Toolkit и Petal.
Самое важное что смена системы шаблонов при правильном использовании
может остаться прозрачной для используемого приложения. Он реализует
единый интерфейс для работы с ними. И если не используются специфичные
свойства каждой из систем, то перейти на альтернативу, не составит
труда для программиста (мы скромно умолчим о труде верстальщика :)).
Плагин
* CGI-Application-Plugin-Config-Context-0.16.tar.gz
предоставляет возможность работы с конфигурацией в зависимости от
контекста вызова. Достаточно популярен и очень гибок, может менять
конфигурацию в зависимости от настроек в httpd.conf вебсервера, и еще
много чего. Основан на Config::Context.
Также для работы с конфигурациями разного типа существуют еще модули:
* CGI-Application-Plugin-Config-Simple-1.00.tar.gz
* CGI-Application-Plugin-Config-YAML-0.01.tar.gz
и устаревший предшественник Context
* CGI-Application-Plugin-Config-General-0.07.tar.gz
Плагин
* CGI-Application-Plugin-DBH-4.00.tar.gz
реализует доступ к базам данных на основе DBI. Предоставляет единую
точку подключения к базе, для использования в различных частях
приложения.