Mercurial > pub > Impl
comparison Lib/IMPL/Web/Application.pm @ 229:47f77e6409f7
heavily reworked the resource model of the web application:
*some ResourcesContraact functionality moved to Resource
+Added CustomResource
*Corrected action handlers
author | sergey |
---|---|
date | Sat, 29 Sep 2012 02:34:47 +0400 |
parents | d6e2ea24af08 |
children | 6d8092d8ce1b |
comparison
equal
deleted
inserted
replaced
228:431db7034a88 | 229:47f77e6409f7 |
---|---|
6 use CGI; | 6 use CGI; |
7 use Carp qw(carp); | 7 use Carp qw(carp); |
8 | 8 |
9 use IMPL::declare { | 9 use IMPL::declare { |
10 require => { | 10 require => { |
11 TAction => 'IMPL::Web::Application::Action', | 11 TAction => 'IMPL::Web::Application::Action', |
12 TResponse => 'IMPL::Web::Application::Response', | 12 HttpResponse => 'IMPL::Web::HttpResponse', |
13 TFactory => '-IMPL::Object::Factory' | 13 TFactory => '-IMPL::Object::Factory', |
14 }, | 14 Exception => 'IMPL::Exception', |
15 base => { | 15 InvalidOperationException => 'IMPL::InvalidOperationException', |
16 'IMPL::Config' => '@_', | 16 Loader => 'IMPL::Code::Loader' |
17 }, | |
18 base => [ | |
19 'IMPL::Config' => '@_', | |
17 'IMPL::Object::Singleton' => '@_' | 20 'IMPL::Object::Singleton' => '@_' |
18 } | 21 ], |
22 props => [ | |
23 actionFactory => PROP_ALL, | |
24 handlers => PROP_ALL | PROP_LIST, | |
25 security => PROP_ALL, | |
26 options => PROP_ALL, | |
27 fetchRequestMethod => PROP_ALL, | |
28 output => PROP_ALL | |
29 ] | |
19 }; | 30 }; |
20 | 31 |
21 BEGIN { | |
22 public property errorHandler => PROP_ALL; | |
23 public property actionFactory => PROP_ALL; | |
24 public property handlers => PROP_ALL | PROP_LIST; | |
25 public property responseCharset => PROP_ALL; | |
26 public property security => PROP_ALL; | |
27 public property options => PROP_ALL; | |
28 public property fetchRequestMethod => PROP_ALL; | |
29 } | |
30 | |
31 | |
32 #TODO: remove | |
33 sub handlersQuery { | |
34 carp "handlersQuery is obsolete use handlers instead"; | |
35 goto &handlers; | |
36 } | |
37 | |
38 | |
39 sub CTOR { | 32 sub CTOR { |
40 my ($this) = @_; | 33 my ($this) = @_; |
41 | 34 |
42 die IMPL::InvalidArgumentException->new("handlers","At least one handler should be supplied") unless $this->handlers->Count; | 35 die IMPL::InvalidArgumentException->new( "handlers", |
43 | 36 "At least one handler should be supplied" ) |
44 $this->actionFactory(TAction) unless $this->actionFactory; | 37 unless $this->handlers->Count; |
45 $this->responseCharset('utf-8') unless $this->responseCharset; | 38 |
46 $this->fetchRequestMethod(\&defaultFetchRequest) unless $this->fetchRequestMethod; | 39 $this->actionFactory(TAction) unless $this->actionFactory; |
47 $this->errorHandler(\&defaultErrorHandler) unless $this->errorHandler; | 40 $this->fetchRequestMethod( \&defaultFetchRequest ) |
41 unless $this->fetchRequestMethod; | |
48 } | 42 } |
49 | 43 |
50 sub Run { | 44 sub Run { |
51 my ($this) = @_; | 45 my ($this) = @_; |
52 | 46 |
53 my $handler; | 47 my $handler; |
54 | 48 |
55 $handler = _ChainHandler($_,$handler) foreach $this->handlers; | 49 $handler = _ChainHandler( $_, $handler ) foreach $this->handlers; |
56 | 50 |
57 while (my $query = $this->FetchRequest()) { | 51 while ( my $query = $this->FetchRequest() ) { |
58 | 52 |
59 my $action = $this->actionFactory->new( | 53 my $action = $this->actionFactory->new( |
60 query => $query, | 54 query => $query, |
61 application => $this, | 55 application => $this, |
62 ); | 56 ); |
63 | 57 |
64 eval { | 58 eval { |
65 $action->response->charset($this->responseCharset); | 59 my $result = $handler->($action); |
66 | 60 |
67 $handler->($action); | 61 die InvalidOperationException->new( |
68 | 62 "Invalid handlers result. A reference to IMPL::Web::HttpResponse is expexted." |
69 $action->response->Complete; | 63 ) unless eval { $result->isa(HttpResponse) }; |
70 }; | 64 |
71 if ($@) { | 65 $result->PrintResponse( $this->output ); |
72 my $e = $@; | 66 }; |
73 # we are expecting this method to be safe otherwise we can trust nothing in this wolrd | 67 if ($@) { |
74 $this->errorHandler()->($this,$action,$e); | 68 my $e = $@; |
75 } | 69 |
76 } | 70 HttpResponse->InternalError( |
71 type => 'text/plain', | |
72 charset => 'utf-8', | |
73 body => $e | |
74 )->PrintResponse( $this->output ); | |
75 | |
76 } | |
77 } | |
77 } | 78 } |
78 | 79 |
79 sub _ChainHandler { | 80 sub _ChainHandler { |
80 my ($handler,$next) = @_; | 81 my ( $handler, $next ) = @_; |
81 | 82 |
82 if (ref $handler eq 'CODE') { | 83 if ( ref $handler eq 'CODE' ) { |
83 return sub { | 84 return sub { |
84 my ($action) = @_; | 85 my ($action) = @_; |
85 return $handler->($action,$next); | 86 return $handler->( $action, $next ); |
86 }; | 87 }; |
87 } elsif (eval { $handler->can('Invoke') } ) { | 88 } |
88 return sub { | 89 elsif ( eval { $handler->can('Invoke') } ) { |
89 my ($action) = @_; | 90 return sub { |
90 return $handler->Invoke($action,$next); | 91 my ($action) = @_; |
91 }; | 92 return $handler->Invoke( $action, $next ); |
92 } elsif (eval{ $handler->isa(TFactory) }) { | 93 }; |
94 } | |
95 elsif ( eval { $handler->isa(TFactory) } ) { | |
93 return sub { | 96 return sub { |
94 my ($action) = @_; | 97 my ($action) = @_; |
95 my $inst = $handler->new(); | 98 my $inst = $handler->new(); |
96 return $inst->Invoke($action,$next); | 99 return $inst->Invoke( $action, $next ); |
100 } | |
101 } | |
102 elsif ( $handler | |
103 and not ref $handler | |
104 and $handler =~ m/^(-)?(\w+(?:::\w+)*)$/ ) | |
105 { | |
106 my $class = $2; | |
107 if ( not $1 ) { | |
108 Loader->safe->Require($class); | |
109 die IMPL::InvalidArgumentException->( | |
110 "An invalid handler supplied", $handler | |
111 ) unless $class->can('Invoke'); | |
97 } | 112 } |
98 } elsif ($handler and not ref $handler and $handler =~ m/^(-)?(\w+(?:::\w+)*)$/) { | 113 |
99 my $class = $2; | |
100 if (not $1) { | |
101 my $mod = $class; | |
102 $mod =~ s/::/\//g; | |
103 require "$mod.pm"; | |
104 | |
105 die IMPL::InvalidArgumentException->("An invalid handler supplied",$handler) unless $class->can('Invoke'); | |
106 } | |
107 | |
108 return sub { | 114 return sub { |
109 my ($action) = @_; | 115 my ($action) = @_; |
110 my $inst = $class->new(); | 116 my $inst = $class->new(); |
111 return $inst->Invoke($action,$next); | 117 return $inst->Invoke( $action, $next ); |
112 }; | 118 }; |
113 } else { | 119 } |
114 die new IMPL::InvalidArgumentException("An invalid handler supplied",$handler); | 120 else { |
121 die new IMPL::InvalidArgumentException( "An invalid handler supplied", | |
122 $handler ); | |
115 } | 123 } |
116 } | 124 } |
117 | 125 |
118 sub FetchRequest { | 126 sub FetchRequest { |
119 my ($this) = @_; | 127 my ($this) = @_; |
120 | 128 |
121 if( ref $this->fetchRequestMethod eq 'CODE' ) { | 129 if ( ref $this->fetchRequestMethod eq 'CODE' ) { |
122 return $this->fetchRequestMethod->($this); | 130 return $this->fetchRequestMethod->($this); |
123 } else { | 131 } |
124 die new IMPL::Exception("Unknown fetchRequestMethod type",ref $this->fetchRequestMethod); | 132 else { |
125 } | 133 die new IMPL::Exception( |
134 "Unknown fetchRequestMethod type", | |
135 ref $this->fetchRequestMethod | |
136 ); | |
137 } | |
126 } | 138 } |
127 | 139 |
128 { | 140 { |
129 my $hasFetched = 0; | 141 my $hasFetched = 0; |
130 | 142 |
131 sub defaultFetchRequest { | 143 sub defaultFetchRequest { |
132 my ($this) = @_; | 144 my ($this) = @_; |
133 return undef if $hasFetched; | 145 return undef if $hasFetched; |
134 $hasFetched = 1; | 146 $hasFetched = 1; |
135 my $query = CGIWrapper->new(); | 147 $this->output(*STDOUT); |
136 $query->charset($this->responseCharset); | 148 my $query = CGIWrapper->new(); |
137 return $query; | 149 return $query; |
138 } | 150 } |
139 } | 151 } |
140 | 152 |
141 sub defaultErrorHandler { | 153 sub defaultErrorHandler { |
142 my ($this,$action,$e) = @_; | 154 my ( $this, $action, $e ) = @_; |
143 warn $e; | 155 warn $e; |
144 if ( eval { $action->ReinitResponse(); 1; } ) { | 156 if ( eval { $action->ReinitResponse(); 1; } ) { |
145 $action->response->contentType('text/plain'); | 157 $action->response->contentType('text/plain'); |
146 $action->response->charset($this->responseCharset); | 158 $action->response->charset( $this->responseCharset ); |
147 $action->response->status(500); | 159 $action->response->status(500); |
148 my $hout = $action->response->streamBody; | 160 my $hout = $action->response->streamBody; |
149 print $hout $e; | 161 print $hout $e; |
150 $action->response->Complete(); | 162 $action->response->Complete(); |
151 } | 163 } |
152 } | 164 } |
153 | 165 |
154 package CGIWrapper; | 166 package CGIWrapper; |
155 use parent qw(CGI); | 167 use parent qw(CGI); |
156 | 168 |
157 use Encode; | 169 use Encode; |
158 | 170 |
159 our $NO_DECODE = 0; | 171 our $NO_DECODE = 0; |
160 | 172 |
161 sub param { | 173 sub param { |
162 my $this = shift; | 174 my $this = shift; |
163 | 175 |
164 return $this->SUPER::param(@_) if $NO_DECODE; | 176 return $this->SUPER::param(@_) if $NO_DECODE; |
165 | 177 |
166 if (wantarray) { | 178 if (wantarray) { |
167 my @result = $this->SUPER::param(@_); | 179 my @result = $this->SUPER::param(@_); |
168 | 180 |
169 return map Encode::is_utf8($_) ? $_ : Encode::decode($this->charset,$_,Encode::LEAVE_SRC), @result; | 181 return map Encode::is_utf8($_) |
170 } else { | 182 ? $_ |
171 my $result = $this->SUPER::param(@_); | 183 : Encode::decode( $this->charset, $_, Encode::LEAVE_SRC ), @result; |
172 | 184 } |
173 return Encode::is_utf8($result) ? $result : Encode::decode($this->charset,$result,Encode::LEAVE_SRC); | 185 else { |
174 } | 186 my $result = $this->SUPER::param(@_); |
187 | |
188 return Encode::is_utf8($result) | |
189 ? $result | |
190 : Encode::decode( $this->charset, $result, Encode::LEAVE_SRC ); | |
191 } | |
175 | 192 |
176 } | 193 } |
177 | 194 |
178 sub upload { | 195 sub upload { |
179 my $this = shift; | 196 my $this = shift; |
180 | 197 |
181 local $NO_DECODE = 1; | 198 local $NO_DECODE = 1; |
182 my $oldCharset = $this->charset(); | 199 my $oldCharset = $this->charset(); |
183 $this->charset('ISO-8859-1'); | 200 $this->charset('ISO-8859-1'); |
184 | 201 |
185 my $fh = $this->SUPER::upload(@_); | 202 my $fh = $this->SUPER::upload(@_); |
186 | 203 |
187 $this->charset($oldCharset); | 204 $this->charset($oldCharset); |
188 return $fh; | 205 return $fh; |
189 } | 206 } |
190 | 207 |
191 1; | 208 1; |
192 | 209 |
193 __END__ | 210 __END__ |
194 | 211 |
195 =pod | 212 =pod |
196 | 213 |
214 =head1 NAME | |
215 | |
216 C<IMPL::Web::Application> Класс для создания экземпляров приложения | |
217 | |
197 =head1 SYNOPSIS | 218 =head1 SYNOPSIS |
198 | 219 |
199 =begin code | 220 =begin code |
200 | 221 |
201 require MyApp; | 222 use IMPL::require { |
202 | 223 App => 'IMPL::Web::Application' |
203 my $instance = spawn MyApp('app.config'); | 224 }; |
204 | 225 |
205 $instance->Run(); | 226 my $instance = App->spawn(); # will use ./IMPL/Web/Application.xml as configuration |
227 | |
228 $instance->Run; | |
206 | 229 |
207 =end code | 230 =end code |
208 | 231 |
209 =head1 DESCRIPTION | 232 =head1 DESCRIPTION |
210 | 233 |
211 C< inherits IMPL::Config, IMPL::Object::Singleton > | 234 Создает экземпляр объекта, который получает и обрабатывает C<HTTP> запрос. |
212 | 235 Приложение можно загрузить из C<xml> файла в котором описано состояние свойств, |
213 Зкземпляр приложения содержит в себе глобальные настройки, реализует контроллер запросов, | 236 для этого используется механизм C<IMPL::Serialization>. |
214 в качестве источника запросов используется CGI или иной совместимый модуль. | 237 |
215 | 238 Приложение представлет собой модульную конструкцию, которая состоит из цепочки |
216 Процесс обработки запроса состоит из следующих частей | 239 обработчиков. Цепочка обработчиков вызывается снизу вверх, при этом каждый |
217 | 240 обработчик самостоятельно рекурсивно вызывает следующий (более высокого уровня). |
218 =over | |
219 | |
220 =item 1 | |
221 | |
222 Получение cgi запроса | |
223 | |
224 =item 2 | |
225 | |
226 Создание объекта C<IMPL::Web::Application::Action> | |
227 | |
228 =item 3 | |
229 | |
230 Формирование цепочки вызовов при помощи C<< IMPL::Web::Application::Action->ChainHandler >> | |
231 | |
232 =item 4 | |
233 | |
234 Выполнение запроса C<< IMPL::Web::Application::Action->Invoke >> | |
235 | 241 |
236 =cut | 242 =cut |
237 | |
238 Также приложение поддерживает отложенное создание объектов, которые по первому обращению | |
239 к свойствам. Это реализовано в базовом классе C< IMPL::Configuration >. Для настройки | |
240 активаторов можно использовать свойство C<options>, в которое должен быть помещен хеш | |
241 со ссылками на активаторы, см. пример ниже C<CONFIGURATION>. | |
242 | |
243 =head2 CONFIGURATION | |
244 | |
245 Ниже приведен пример конфигурации приложения | |
246 | |
247 =begin code xml | |
248 | |
249 <?xml version="1.0" encoding="UTF-8"?> | |
250 <Application id='app' type="Test::Web::Application::Instance"> | |
251 | |
252 <!-- Begin custom properties --> | |
253 <name>Sample application</name> | |
254 <dataSource type='IMPL::Config::Activator' id='ds'> | |
255 <factory>IMPL::Object</factory> | |
256 <parameters type='HASH'> | |
257 <db>data</db> | |
258 <user>nobody</user> | |
259 </parameters> | |
260 </dataSource> | |
261 <securityMod type='IMPL::Config::Activator'> | |
262 <factory>IMPL::Object</factory> | |
263 <parameters type='HASH'> | |
264 <ds refid='ds'/> | |
265 </parameters> | |
266 </securityMod> | |
267 <!-- End custom properties --> | |
268 | |
269 <!-- direct access to the activators --> | |
270 <options type="HASH"> | |
271 <dataSource refid='ds'/> | |
272 </options> | |
273 | |
274 <!-- Set default output encoding, can be changed due query handling --> | |
275 <responseCharset>utf-8</responseCharset> | |
276 | |
277 <!-- Actions creation configuration --> | |
278 <actionFactory type="IMPL::Object::Factory"> | |
279 | |
280 <!-- Construct actions --> | |
281 <factory>IMPL::Web::Application::Action</factory> | |
282 <parameters type='HASH'> | |
283 | |
284 <!-- with special responseFactory --> | |
285 <responseFactory type='IMPL::Object::Factory'> | |
286 | |
287 <!-- Where resopnses have a special streamOut --> | |
288 <factory>IMPL::Web::Application::Response</factory> | |
289 <parameters type='HASH'> | |
290 | |
291 <!-- in memory dummy output instead of STDOUT --> | |
292 <streamOut>memory</streamOut> | |
293 | |
294 </parameters> | |
295 </responseFactory> | |
296 </parameters> | |
297 </actionFactory> | |
298 | |
299 <!-- Query processing chain --> | |
300 <handlersQuery type="IMPL::Object::List"> | |
301 <item type="IMPL::Web::QueryHandler::PageFormat"> | |
302 <templatesCharset>cp1251</templatesCharset> | |
303 </item> | |
304 </handlersQuery> | |
305 </Application> | |
306 | |
307 =end code xml | |
308 | |
309 =head1 MEMBERS | |
310 | |
311 =over | |
312 | |
313 =item C<[get,set] errorHandler> | |
314 | |
315 Обработчик который будет вызван в случае возникновения необработанной ошибки | |
316 в процессе работы приложения. После чего приложение корректно завершается. | |
317 | |
318 =item C<[get,set] actionFactory> | |
319 | |
320 Фабрика объектов, которая используется приложением, для создания объектов | |
321 типа C<IMPL::Web::Application::Action> при обработки C<CGI> запросов. | |
322 | |
323 =begin code | |
324 | |
325 my $action = $this->actionFactory->new( | |
326 query => $query, | |
327 application => $this, | |
328 ); | |
329 | |
330 =end code | |
331 | |
332 =item C< [get,set] fetchRequestMethod > | |
333 | |
334 Метод получения CGI запроса. Возвращает C<CGI> объект следующего запроса, если | |
335 запросов больше нет, то возвращает C<undef>. По-умолчанию использует C<defaultFetchRequest>. | |
336 | |
337 Может быть как ссылкой на функцию, так и объектом типа C<IMPL::Web::Application::RequestFetcher>. | |
338 | |
339 =item C< [get,set,list] handlersQuery > | |
340 | |
341 Список обработчиков запросов, которые будут переданы созданному объекту-действию. | |
342 | |
343 =item C< [get,set] responseCharset> | |
344 | |
345 Кодировка ответа клиенту. | |
346 | |
347 =item C< [get,set] security > | |
348 | |
349 Объект C<IMPL::Web::Security>, для работы с инфраструктурой безопасности. | |
350 | |
351 =item C< [get,set] options > | |
352 | |
353 Обычно ссылка на хеш с настраиваемыми объектами, используется для возможности | |
354 програмной настройки активаторов, т.к. напрямую через свойства приложения получить | |
355 к ним доступ не получится. | |
356 | |
357 =back | |
358 | |
359 =cut |