Mercurial > pub > Impl
view Lib/IMPL/Web/Application.pm @ 218:358f867712b4
sync
author | sergey |
---|---|
date | Mon, 20 Aug 2012 17:24:48 +0400 |
parents | d6e2ea24af08 |
children | 47f77e6409f7 |
line wrap: on
line source
package IMPL::Web::Application; use strict; use warnings; use IMPL::lang qw(:declare :constants); use CGI; use Carp qw(carp); use IMPL::declare { require => { TAction => 'IMPL::Web::Application::Action', TResponse => 'IMPL::Web::Application::Response', TFactory => '-IMPL::Object::Factory' }, base => { 'IMPL::Config' => '@_', 'IMPL::Object::Singleton' => '@_' } }; BEGIN { public property errorHandler => PROP_ALL; public property actionFactory => PROP_ALL; public property handlers => PROP_ALL | PROP_LIST; public property responseCharset => PROP_ALL; public property security => PROP_ALL; public property options => PROP_ALL; public property fetchRequestMethod => PROP_ALL; } #TODO: remove sub handlersQuery { carp "handlersQuery is obsolete use handlers instead"; goto &handlers; } sub CTOR { my ($this) = @_; die IMPL::InvalidArgumentException->new("handlers","At least one handler should be supplied") unless $this->handlers->Count; $this->actionFactory(TAction) unless $this->actionFactory; $this->responseCharset('utf-8') unless $this->responseCharset; $this->fetchRequestMethod(\&defaultFetchRequest) unless $this->fetchRequestMethod; $this->errorHandler(\&defaultErrorHandler) unless $this->errorHandler; } sub Run { my ($this) = @_; my $handler; $handler = _ChainHandler($_,$handler) foreach $this->handlers; while (my $query = $this->FetchRequest()) { my $action = $this->actionFactory->new( query => $query, application => $this, ); eval { $action->response->charset($this->responseCharset); $handler->($action); $action->response->Complete; }; if ($@) { my $e = $@; # we are expecting this method to be safe otherwise we can trust nothing in this wolrd $this->errorHandler()->($this,$action,$e); } } } sub _ChainHandler { my ($handler,$next) = @_; if (ref $handler eq 'CODE') { return sub { my ($action) = @_; return $handler->($action,$next); }; } elsif (eval { $handler->can('Invoke') } ) { return sub { my ($action) = @_; return $handler->Invoke($action,$next); }; } elsif (eval{ $handler->isa(TFactory) }) { return sub { my ($action) = @_; my $inst = $handler->new(); return $inst->Invoke($action,$next); } } elsif ($handler and not ref $handler and $handler =~ m/^(-)?(\w+(?:::\w+)*)$/) { my $class = $2; if (not $1) { my $mod = $class; $mod =~ s/::/\//g; require "$mod.pm"; die IMPL::InvalidArgumentException->("An invalid handler supplied",$handler) unless $class->can('Invoke'); } return sub { my ($action) = @_; my $inst = $class->new(); return $inst->Invoke($action,$next); }; } else { die new IMPL::InvalidArgumentException("An invalid handler supplied",$handler); } } sub FetchRequest { my ($this) = @_; if( ref $this->fetchRequestMethod eq 'CODE' ) { return $this->fetchRequestMethod->($this); } else { die new IMPL::Exception("Unknown fetchRequestMethod type",ref $this->fetchRequestMethod); } } { my $hasFetched = 0; sub defaultFetchRequest { my ($this) = @_; return undef if $hasFetched; $hasFetched = 1; my $query = CGIWrapper->new(); $query->charset($this->responseCharset); return $query; } } sub defaultErrorHandler { my ($this,$action,$e) = @_; warn $e; if ( eval { $action->ReinitResponse(); 1; } ) { $action->response->contentType('text/plain'); $action->response->charset($this->responseCharset); $action->response->status(500); my $hout = $action->response->streamBody; print $hout $e; $action->response->Complete(); } } package CGIWrapper; use parent qw(CGI); use Encode; our $NO_DECODE = 0; sub param { my $this = shift; return $this->SUPER::param(@_) if $NO_DECODE; if (wantarray) { my @result = $this->SUPER::param(@_); return map Encode::is_utf8($_) ? $_ : Encode::decode($this->charset,$_,Encode::LEAVE_SRC), @result; } else { my $result = $this->SUPER::param(@_); return Encode::is_utf8($result) ? $result : Encode::decode($this->charset,$result,Encode::LEAVE_SRC); } } sub upload { my $this = shift; local $NO_DECODE = 1; my $oldCharset = $this->charset(); $this->charset('ISO-8859-1'); my $fh = $this->SUPER::upload(@_); $this->charset($oldCharset); return $fh; } 1; __END__ =pod =head1 SYNOPSIS =begin code require MyApp; my $instance = spawn MyApp('app.config'); $instance->Run(); =end code =head1 DESCRIPTION C< inherits IMPL::Config, IMPL::Object::Singleton > Зкземпляр приложения содержит в себе глобальные настройки, реализует контроллер запросов, в качестве источника запросов используется CGI или иной совместимый модуль. Процесс обработки запроса состоит из следующих частей =over =item 1 Получение cgi запроса =item 2 Создание объекта C<IMPL::Web::Application::Action> =item 3 Формирование цепочки вызовов при помощи C<< IMPL::Web::Application::Action->ChainHandler >> =item 4 Выполнение запроса C<< IMPL::Web::Application::Action->Invoke >> =cut Также приложение поддерживает отложенное создание объектов, которые по первому обращению к свойствам. Это реализовано в базовом классе C< IMPL::Configuration >. Для настройки активаторов можно использовать свойство C<options>, в которое должен быть помещен хеш со ссылками на активаторы, см. пример ниже C<CONFIGURATION>. =head2 CONFIGURATION Ниже приведен пример конфигурации приложения =begin code xml <?xml version="1.0" encoding="UTF-8"?> <Application id='app' type="Test::Web::Application::Instance"> <!-- Begin custom properties --> <name>Sample application</name> <dataSource type='IMPL::Config::Activator' id='ds'> <factory>IMPL::Object</factory> <parameters type='HASH'> <db>data</db> <user>nobody</user> </parameters> </dataSource> <securityMod type='IMPL::Config::Activator'> <factory>IMPL::Object</factory> <parameters type='HASH'> <ds refid='ds'/> </parameters> </securityMod> <!-- End custom properties --> <!-- direct access to the activators --> <options type="HASH"> <dataSource refid='ds'/> </options> <!-- Set default output encoding, can be changed due query handling --> <responseCharset>utf-8</responseCharset> <!-- Actions creation configuration --> <actionFactory type="IMPL::Object::Factory"> <!-- Construct actions --> <factory>IMPL::Web::Application::Action</factory> <parameters type='HASH'> <!-- with special responseFactory --> <responseFactory type='IMPL::Object::Factory'> <!-- Where resopnses have a special streamOut --> <factory>IMPL::Web::Application::Response</factory> <parameters type='HASH'> <!-- in memory dummy output instead of STDOUT --> <streamOut>memory</streamOut> </parameters> </responseFactory> </parameters> </actionFactory> <!-- Query processing chain --> <handlersQuery type="IMPL::Object::List"> <item type="IMPL::Web::QueryHandler::PageFormat"> <templatesCharset>cp1251</templatesCharset> </item> </handlersQuery> </Application> =end code xml =head1 MEMBERS =over =item C<[get,set] errorHandler> Обработчик который будет вызван в случае возникновения необработанной ошибки в процессе работы приложения. После чего приложение корректно завершается. =item C<[get,set] actionFactory> Фабрика объектов, которая используется приложением, для создания объектов типа C<IMPL::Web::Application::Action> при обработки C<CGI> запросов. =begin code my $action = $this->actionFactory->new( query => $query, application => $this, ); =end code =item C< [get,set] fetchRequestMethod > Метод получения CGI запроса. Возвращает C<CGI> объект следующего запроса, если запросов больше нет, то возвращает C<undef>. По-умолчанию использует C<defaultFetchRequest>. Может быть как ссылкой на функцию, так и объектом типа C<IMPL::Web::Application::RequestFetcher>. =item C< [get,set,list] handlersQuery > Список обработчиков запросов, которые будут переданы созданному объекту-действию. =item C< [get,set] responseCharset> Кодировка ответа клиенту. =item C< [get,set] security > Объект C<IMPL::Web::Security>, для работы с инфраструктурой безопасности. =item C< [get,set] options > Обычно ссылка на хеш с настраиваемыми объектами, используется для возможности програмной настройки активаторов, т.к. напрямую через свойства приложения получить к ним доступ не получится. =back =cut