fixed pod coverage
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5use UNIVERSAL::require;
fc7ec1d9 6use Data::Dumper;
7use HTML::Entities;
8use HTTP::Headers;
9use Time::HiRes qw/gettimeofday tv_interval/;
10use Catalyst::Request;
11use Catalyst::Response;
12
13require Module::Pluggable::Fast;
14
15$Data::Dumper::Terse = 1;
16
17__PACKAGE__->mk_classdata($_) for qw/actions components/;
18__PACKAGE__->mk_accessors(qw/request response/);
19
20__PACKAGE__->actions(
21 { plain => {}, regex => {}, compiled => {}, reverse => {} } );
22
23*comp = \&component;
24*req = \&request;
25*res = \&response;
26
27our $COUNT = 1;
28our $START = time;
29
30=head1 NAME
31
32Catalyst::Engine - The Catalyst Engine
33
34=head1 SYNOPSIS
35
36See L<Catalyst>.
37
38=head1 DESCRIPTION
39
40=head2 METHODS
41
42=head3 action
43
44Add one or more actions.
45
46 $c->action( '!foo' => sub { $_[1]->res->output('Foo!') } );
47
48Get an action's class and coderef.
49
50 my ($class, $code) = @{ $c->action('foo') };
51
52Get a list of available actions.
53
54 my @actions = $c->action;
55
56It also automatically calls setup() if needed.
57
58See L<Catalyst::Manual::Intro> for more informations about actions.
59
60=cut
61
62sub action {
63 my $self = shift;
64 $self->setup unless $self->components;
65 $self->actions( {} ) unless $self->actions;
66 my $action;
67 $_[1] ? ( $action = {@_} ) : ( $action = shift );
68 if ( ref $action eq 'HASH' ) {
69 while ( my ( $name, $code ) = each %$action ) {
7833fdfc 70 my $class = caller(0);
fc7ec1d9 71 if ( $name =~ /^\/(.*)\/$/ ) {
72 my $regex = $1;
73 $self->actions->{compiled}->{qr/$regex/} = $name;
74 $self->actions->{regex}->{$name} = [ $class, $code ];
75 }
76 elsif ( $name =~ /^\?(.*)$/ ) {
77 $name = $1;
7833fdfc 78 $name = _prefix( $class, $name );
fc7ec1d9 79 $self->actions->{plain}->{$name} = [ $class, $code ];
80 }
81 elsif ( $name =~ /^\!\?(.*)$/ ) {
82 $name = $1;
7833fdfc 83 $name = _prefix( $class, $name );
fc7ec1d9 84 $name = "\!$name";
85 $self->actions->{plain}->{$name} = [ $class, $code ];
86 }
87 else { $self->actions->{plain}->{$name} = [ $class, $code ] }
88 $self->actions->{reverse}->{"$code"} = $name;
7833fdfc 89 $self->log->debug(qq/"$class" defined "$name" as "$code"/)
fc7ec1d9 90 if $self->debug;
91 }
92 }
93 elsif ($action) {
94 if ( my $p = $self->actions->{plain}->{$action} ) { return [$p] }
95 elsif ( my $r = $self->actions->{regex}->{$action} ) { return [$r] }
96 else {
5783a9a5 97 for my $regex ( keys %{ $self->actions->{compiled} } ) {
98 my $name = $self->actions->{compiled}->{$regex};
fc7ec1d9 99 if ( $action =~ $regex ) {
100 my @snippets;
101 for my $i ( 1 .. 9 ) {
102 no strict 'refs';
103 last unless ${$i};
104 push @snippets, ${$i};
105 }
7e5adedd 106 return [ $self->actions->{regex}->{$name},
107 $name, \@snippets ];
fc7ec1d9 108 }
109 }
110 }
111 return 0;
112 }
113 else {
114 return (
115 keys %{ $self->actions->{plain} },
116 keys %{ $self->actions->{regex} }
117 );
118 }
119}
120
121=head3 benchmark
122
123Takes a coderef with arguments and returns elapsed time as float.
124
125 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
126 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
127
128=cut
129
130sub benchmark {
131 my $c = shift;
132 my $code = shift;
133 my $time = [gettimeofday];
134 my @return = &$code(@_);
135 my $elapsed = tv_interval $time;
136 return wantarray ? ( $elapsed, @return ) : $elapsed;
137}
138
748161c4 139=head3 component / comp
fc7ec1d9 140
141Get a component object by name.
142
143 $c->comp('MyApp::Model::MyModel')->do_stuff;
144
145Regex search for a component.
146
147 $c->comp('mymodel')->do_stuff;
148
149=cut
150
151sub component {
152 my ( $c, $name ) = @_;
153 if ( my $component = $c->components->{$name} ) {
154 return $component;
155 }
156 else {
157 for my $component ( keys %{ $c->components } ) {
158 return $c->components->{$component} if $component =~ /$name/i;
159 }
160 }
161}
162
163=head3 errors
164
165Returns an arrayref containing errors messages.
166
167 my @errors = @{ $c->errors };
168
169Add a new error.
170
171 $c->errors('Something bad happened');
172
173=cut
174
175sub errors {
176 my $c = shift;
177 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
178 push @{ $c->{errors} }, @$errors;
179 return $c->{errors};
180}
181
182=head3 finalize
183
184Finalize request.
185
186=cut
187
188sub finalize {
189 my $c = shift;
190 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
191 $c->res->headers->content_type('text/html');
192 my $name = $c->config->{name} || 'Catalyst Application';
193 my ( $title, $errors, $infos );
194 if ( $c->debug ) {
195 $errors = join '<br/>', @{ $c->errors };
196 $errors ||= 'No output';
197 $title = $name = "$name on Catalyst $Catalyst::VERSION";
198 my $req = encode_entities Dumper $c->req;
199 my $res = encode_entities Dumper $c->res;
200 my $stash = encode_entities Dumper $c->stash;
201 $infos = <<"";
202<br/>
203<b><u>Request</u></b><br/>
204<pre>$req</pre>
205<b><u>Response</u></b><br/>
206<pre>$res</pre>
207<b><u>Stash</u></b><br/>
208<pre>$stash</pre>
209
210 }
211 else {
212 $title = $name;
213 $errors = '';
214 $infos = <<"";
215<pre>
216(en) Please come back later
217(de) Bitte versuchen sie es spaeter nocheinmal
218(nl) Gelieve te komen later terug
219(no) Vennligst prov igjen senere
220(fr) Veuillez revenir plus tard
221(es) Vuelto por favor mas adelante
222(pt) Voltado por favor mais tarde
223(it) Ritornato prego più successivamente
224</pre>
225
226 $name = '';
227 }
228 $c->res->{output} = <<"";
229<html>
230 <head>
231 <title>$title</title>
232 <style type="text/css">
233 body {
234 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
235 Tahoma, Arial, helvetica, sans-serif;
236 color: #ddd;
237 background-color: #eee;
238 margin: 0px;
239 padding: 0px;
240 }
241 div.box {
242 background-color: #ccc;
243 border: 1px solid #aaa;
244 padding: 4px;
245 margin: 10px;
246 -moz-border-radius: 10px;
247 }
248 div.errors {
249 background-color: #977;
250 border: 1px solid #755;
251 padding: 8px;
252 margin: 4px;
253 margin-bottom: 10px;
254 -moz-border-radius: 10px;
255 }
256 div.infos {
257 background-color: #797;
258 border: 1px solid #575;
259 padding: 8px;
260 margin: 4px;
261 margin-bottom: 10px;
262 -moz-border-radius: 10px;
263 }
264 div.name {
265 background-color: #779;
266 border: 1px solid #557;
267 padding: 8px;
268 margin: 4px;
269 -moz-border-radius: 10px;
270 }
271 </style>
272 </head>
273 <body>
274 <div class="box">
275 <div class="errors">$errors</div>
276 <div class="infos">$infos</div>
277 <div class="name">$name</div>
278 </div>
279 </body>
280</html>
281
282 }
283 if ( my $location = $c->res->redirect ) {
284 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
285 $c->res->headers->header( Location => $location );
286 $c->res->status(302);
287 }
288 $c->res->headers->content_length( length $c->res->output );
289 my $status = $c->finalize_headers;
290 $c->finalize_output;
291 return $status;
292}
293
294=head3 finalize_headers
295
296Finalize headers.
297
298=cut
299
300sub finalize_headers { }
301
302=head3 finalize_output
303
304Finalize output.
305
306=cut
307
308sub finalize_output { }
309
310=head3 forward
311
312Forward processing to a private/public action or a method from a class.
313If you define a class without method it will default to process().
314
315 $c->forward('!foo');
316 $c->forward('index.html');
317 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
318 $c->forward('MyApp::View::TT');
319
320=cut
321
322sub forward {
323 my $c = shift;
324 my $command = shift;
325 unless ($command) {
326 $c->log->debug('Nothing to forward to') if $c->debug;
327 return 0;
328 }
329 if ( $command =~ /^\?(.*)$/ ) {
330 $command = $1;
331 my $caller = caller(0);
332 $command = _prefix( $caller, $command );
333 }
334 elsif ( $command =~ /^\!\?(.*)$/ ) {
335 $command = $1;
336 my $caller = caller(0);
337 $command = _prefix( $caller, $command );
338 $command = "\!$command";
339 }
7242d068 340 elsif ( $command =~ /^\!(.*)$/ ) {
341 my $try = $1;
342 my $caller = caller(0);
343 my $prefix = _class2prefix($caller);
344 $try = "!$prefix/$command";
345 $command = $try if $c->actions->{plain}->{$try};
346 }
fc7ec1d9 347 my ( $class, $code );
348 if ( my $action = $c->action($command) ) {
7e5adedd 349 if ( $action->[2] ) {
350 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
351 if $c->debug;
352 return 0;
353 }
fc7ec1d9 354 ( $class, $code ) = @{ $action->[0] };
355 }
356 else {
357 $class = $command;
358 if ( $class =~ /[^\w\:]/ ) {
359 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
360 return 0;
361 }
362 my $method = shift || 'process';
363 if ( $code = $class->can($method) ) {
364 $c->actions->{reverse}->{"$code"} = "$class->$method";
365 }
366 else {
367 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
368 if $c->debug;
369 return 0;
370 }
371 }
372 $class = $c->components->{$class} || $class;
373 return $c->process( $class, $code );
374}
375
376=head3 handler
377
378Handles the request.
379
380=cut
381
382sub handler {
383 my ( $class, $r ) = @_;
384
385 # Always expect worst case!
386 my $status = -1;
387 eval {
388 my $handler = sub {
389 my $c = $class->prepare($r);
7833fdfc 390 if ( my $action = $c->action( $c->req->action ) ) {
fc7ec1d9 391 my ( $begin, $end );
7833fdfc 392 my $class = ${ $action->[0] }[0];
393 my $prefix = _class2prefix($class);
394 if ($prefix) {
fc7ec1d9 395 if ( $c->actions->{plain}->{"\!$prefix/begin"} ) {
396 $begin = "\!$prefix/begin";
397 }
398 elsif ( $c->actions->{plain}->{'!begin'} ) {
399 $begin = '!begin';
400 }
401 if ( $c->actions->{plain}->{"\!$prefix/end"} ) {
402 $end = "\!$prefix/end";
403 }
404 elsif ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
405 }
7833fdfc 406 else {
407 if ( $c->actions->{plain}->{'!begin'} ) {
408 $begin = '!begin';
409 }
410 if ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
411 }
fc7ec1d9 412 $c->forward($begin) if $begin;
413 $c->forward( $c->req->action ) if $c->req->action;
414 $c->forward($end) if $end;
415 }
416 else {
417 my $action = $c->req->path;
418 my $error = $action
419 ? qq/Unknown resource "$action"/
7833fdfc 420 : "No default action defined";
fc7ec1d9 421 $c->log->error($error) if $c->debug;
422 $c->errors($error);
423 }
424 return $c->finalize;
425 };
426 if ( $class->debug ) {
427 my $elapsed;
428 ( $elapsed, $status ) = $class->benchmark($handler);
429 $elapsed = sprintf '%f', $elapsed;
430 my $av = sprintf '%.3f', 1 / $elapsed;
431 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
432 }
433 else { $status = &$handler }
434 };
435 if ( my $error = $@ ) {
436 chomp $error;
437 $class->log->error(qq/Caught exception in engine "$error"/);
438 }
439 $COUNT++;
440 return $status;
441}
442
443=head3 prepare
444
445Turns the request (Apache, CGI...) into a Catalyst context.
446
447=cut
448
449sub prepare {
450 my ( $class, $r ) = @_;
451 my $c = bless {
452 request => Catalyst::Request->new(
453 {
454 arguments => [],
455 cookies => {},
456 headers => HTTP::Headers->new,
457 parameters => {},
458 snippets => [],
459 uploads => {}
460 }
461 ),
462 response => Catalyst::Response->new(
463 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
464 ),
465 stash => {}
466 }, $class;
467 if ( $c->debug ) {
468 my $secs = time - $START || 1;
469 my $av = sprintf '%.3f', $COUNT / $secs;
470 $c->log->debug('********************************');
471 $c->log->debug("* Request $COUNT ($av/s) [$$]");
472 $c->log->debug('********************************');
473 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
474 }
475 $c->prepare_request($r);
476 $c->prepare_path;
fc7ec1d9 477 $c->prepare_cookies;
478 $c->prepare_headers;
5783a9a5 479 my $method = $c->req->method;
480 my $path = $c->req->path;
481 $c->log->debug(qq/"$method" request for "$path"/) if $c->debug;
fc7ec1d9 482 $c->prepare_action;
483 $c->prepare_parameters;
484 $c->prepare_uploads;
485 return $c;
486}
487
488=head3 prepare_action
489
490Prepare action.
491
492=cut
493
494sub prepare_action {
495 my $c = shift;
496 my $path = $c->req->path;
497 my @path = split /\//, $c->req->path;
498 $c->req->args( \my @args );
499 while (@path) {
7833fdfc 500 $path = join '/', @path;
fc7ec1d9 501 if ( my $result = $c->action($path) ) {
502
503 # It's a regex
504 if ($#$result) {
7e5adedd 505 my $match = $result->[1];
506 my @snippets = @{ $result->[2] };
fc7ec1d9 507 $c->log->debug(qq/Requested action "$path" matched "$match"/)
508 if $c->debug;
509 $c->log->debug(
510 'Snippets are "' . join( ' ', @snippets ) . '"' )
511 if ( $c->debug && @snippets );
512 $c->req->action($match);
513 $c->req->snippets( \@snippets );
514 }
515 else {
516 $c->req->action($path);
517 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
518 }
519 $c->req->match($path);
fc7ec1d9 520 last;
521 }
522 unshift @args, pop @path;
523 }
524 unless ( $c->req->action ) {
525 my $prefix = $c->req->args->[0];
526 if ( $prefix && $c->actions->{plain}->{"\!$prefix/default"} ) {
527 $c->req->match('');
528 $c->req->action("\!$prefix/default");
529 $c->log->debug('Using prefixed default action') if $c->debug;
530 }
531 elsif ( $c->actions->{plain}->{'!default'} ) {
532 $c->req->match('');
533 $c->req->action('!default');
534 $c->log->debug('Using default action') if $c->debug;
535 }
536 }
5783a9a5 537 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
538 if ( $c->debug && @args );
fc7ec1d9 539}
540
541=head3 prepare_cookies;
542
543Prepare cookies.
544
545=cut
546
547sub prepare_cookies { }
548
549=head3 prepare_headers
550
551Prepare headers.
552
553=cut
554
555sub prepare_headers { }
556
557=head3 prepare_parameters
558
559Prepare parameters.
560
561=cut
562
563sub prepare_parameters { }
564
565=head3 prepare_path
566
567Prepare path and base.
568
569=cut
570
571sub prepare_path { }
572
573=head3 prepare_request
574
575Prepare the engine request.
576
577=cut
578
579sub prepare_request { }
580
581=head3 prepare_uploads
582
583Prepare uploads.
584
585=cut
586
587sub prepare_uploads { }
588
589=head3 process
590
591Process a coderef in given class and catch exceptions.
592Errors are available via $c->errors.
593
594=cut
595
596sub process {
597 my ( $c, $class, $code ) = @_;
598 my $status;
599 eval {
600 if ( $c->debug )
601 {
602 my $action = $c->actions->{reverse}->{"$code"} || "$code";
603 my $elapsed;
604 ( $elapsed, $status ) =
605 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
606 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
607 if $c->debug;
608 }
609 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
610 };
611 if ( my $error = $@ ) {
612 chomp $error;
613 $error = qq/Caught exception "$error"/;
614 $c->log->error($error);
615 $c->errors($error) if $c->debug;
616 return 0;
617 }
618 return $status;
619}
620
621=head3 remove_action
622
623Remove an action.
624
625 $c->remove_action('!foo');
626
627=cut
628
629sub remove_action {
630 my ( $self, $action ) = @_;
631 if ( delete $self->actions->{regex}->{$action} ) {
632 while ( my ( $regex, $name ) = each %{ $self->actions->{compiled} } ) {
633 if ( $name eq $action ) {
634 delete $self->actions->{compiled}->{$regex};
635 last;
636 }
637 }
638 }
639 else {
640 delete $self->actions->{plain}->{$action};
641 }
642}
643
644=head3 request (req)
645
646Returns a C<Catalyst::Request> object.
647
648 my $req = $c->req;
649
650=head3 response (res)
651
652Returns a C<Catalyst::Response> object.
653
654 my $res = $c->res;
655
656=head3 setup
657
658Setup.
659
660 MyApp->setup;
661
662=cut
663
664sub setup {
665 my $self = shift;
666 $self->setup_components;
667 if ( $self->debug ) {
668 my $name = $self->config->{name} || 'Application';
669 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
670 }
671}
672
673=head3 setup_components
674
675Setup components.
676
677=cut
678
679sub setup_components {
680 my $self = shift;
681
682 # Components
683 my $class = ref $self || $self;
684 eval <<"";
685 package $class;
686 import Module::Pluggable::Fast
687 name => '_components',
688 search => [
689 '$class\::Controller', '$class\::C',
690 '$class\::Model', '$class\::M',
691 '$class\::View', '$class\::V'
692 ];
693
694 if ( my $error = $@ ) {
695 chomp $error;
696 $self->log->error(
697 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
698 }
699 $self->components( {} );
700 for my $component ( $self->_components($self) ) {
701 $self->components->{ ref $component } = $component;
702 }
703 $self->log->debug( 'Initialized components "'
704 . join( ' ', keys %{ $self->components } )
705 . '"' )
706 if $self->debug;
707}
708
709=head3 stash
710
711Returns a hashref containing all your data.
712
713 $c->stash->{foo} ||= 'yada';
714 print $c->stash->{foo};
715
716=cut
717
718sub stash {
719 my $self = shift;
720 if ( $_[0] ) {
721 my $stash = $_[1] ? {@_} : $_[0];
722 while ( my ( $key, $val ) = each %$stash ) {
723 $self->{stash}->{$key} = $val;
724 }
725 }
726 return $self->{stash};
727}
728
729sub _prefix {
730 my ( $class, $name ) = @_;
7833fdfc 731 my $prefix = _class2prefix($class);
732 $name = "$prefix/$name" if $prefix;
733 return $name;
734}
735
736sub _class2prefix {
737 my $class = shift;
fc7ec1d9 738 $class =~ /^.*::[(M)(Model)(V)(View)(C)(Controller)]+::(.*)$/;
739 my $prefix = lc $1 || '';
740 $prefix =~ s/\:\:/_/g;
7833fdfc 741 return $prefix;
fc7ec1d9 742}
743
744=head1 AUTHOR
745
746Sebastian Riedel, C<sri@cpan.org>
747
748=head1 COPYRIGHT
749
750This program is free software, you can redistribute it and/or modify it under
751the same terms as Perl itself.
752
753=cut
754
7551;