1 package Catalyst::Engine;
4 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5 use UNIVERSAL::require;
11 use Time::HiRes qw/gettimeofday tv_interval/;
13 use Text::ASCIITable::Wrap 'wrap';
15 use Tree::Simple::Visitor::FindByPath;
16 use Catalyst::Request;
17 use Catalyst::Response;
19 require Module::Pluggable::Fast;
21 $Data::Dumper::Terse = 1;
23 __PACKAGE__->mk_classdata($_) for qw/actions components tree/;
24 __PACKAGE__->mk_accessors(qw/request response state/);
27 { plain => {}, private => {}, regex => {}, compiled => [], reverse => {} }
29 __PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
38 memoize('_class2prefix');
42 Catalyst::Engine - The Catalyst Engine
54 =item $c->benchmark($coderef)
56 Takes a coderef with arguments and returns elapsed time as float.
58 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
59 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
66 my $time = [gettimeofday];
67 my @return = &$code(@_);
68 my $elapsed = tv_interval $time;
69 return wantarray ? ( $elapsed, @return ) : $elapsed;
74 =item $c->component($name)
76 Get a component object by name.
78 $c->comp('MyApp::Model::MyModel')->do_stuff;
80 Regex search for a component.
82 $c->comp('mymodel')->do_stuff;
87 my ( $c, $name ) = @_;
88 if ( my $component = $c->components->{$name} ) {
92 for my $component ( keys %{ $c->components } ) {
93 return $c->components->{$component} if $component =~ /$name/i;
100 Dispatch request to actions.
106 my $action = $c->req->action;
108 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
109 if $action eq 'default';
110 unless ($namespace) {
111 if ( my $result = $c->get_action($action) ) {
112 $namespace = _class2prefix( $result->[0]->[0]->[0] );
115 my $default = $action eq 'default' ? $namespace : undef;
116 my $results = $c->get_action( $action, $default );
122 if ( my $begin = @{ $c->get_action( 'begin', $namespace ) }[-1] ) {
123 $c->execute( @{ $begin->[0] } );
124 return if scalar @{$c->error};
127 # Execute the auto chain
128 for my $auto ( @{ $c->get_action( 'auto', $namespace ) } ) {
129 $c->execute( @{ $auto->[0] } );
130 return if scalar @{$c->error};
131 last unless $c->state;
134 # Execute the action or last default
135 if ( ( my $action = $c->req->action ) && $c->state ) {
136 if ( my $result = @{ $c->get_action( $action, $default ) }[-1] ) {
137 $c->execute( @{ $result->[0] } );
142 if ( my $end = @{ $c->get_action( 'end', $namespace ) }[-1] ) {
143 $c->execute( @{ $end->[0] } );
144 return if scalar @{$c->error};
148 my $path = $c->req->path;
150 ? qq/Unknown resource "$path"/
151 : "No default action defined";
152 $c->log->error($error) if $c->debug;
159 =item $c->error($error, ...)
161 =item $c->error($arrayref)
163 Returns an arrayref containing error messages.
165 my @error = @{ $c->error };
169 $c->error('Something bad happened');
175 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
176 push @{ $c->{error} }, @$error;
180 =item $c->execute($class, $coderef)
182 Execute a coderef in given class and catch exceptions.
183 Errors are available via $c->error.
188 my ( $c, $class, $code ) = @_;
189 $class = $c->comp($class) || $class;
191 my $callsub = ( caller(1) )[3];
195 my $action = $c->actions->{reverse}->{"$code"};
196 $action = "/$action" unless $action =~ /\-\>/;
197 $action = "-> $action" if $callsub =~ /forward$/;
198 my ( $elapsed, @state ) =
199 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
200 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
203 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
205 if ( my $error = $@ ) {
207 unless ( ref $error ) {
209 $error = qq/Caught exception "$error"/;
212 $c->log->error($error);
228 $c->finalize_cookies;
230 if ( my $location = $c->response->redirect ) {
231 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
232 $c->response->header( Location => $location );
233 $c->response->status(302) if $c->response->status !~ /3\d\d$/;
236 if ( $#{ $c->error } >= 0 ) {
240 if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
244 if ( $c->response->output && !$c->response->content_length ) {
245 use bytes; # play safe with a utf8 aware perl
246 $c->response->content_length( length $c->response->output );
249 my $status = $c->finalize_headers;
254 =item $c->finalize_cookies
260 sub finalize_cookies {
263 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
264 my $cookie = CGI::Cookie->new(
266 -value => $cookie->{value},
267 -expires => $cookie->{expires},
268 -domain => $cookie->{domain},
269 -path => $cookie->{path},
270 -secure => $cookie->{secure} || 0
273 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
277 =item $c->finalize_error
286 $c->res->headers->content_type('text/html');
287 my $name = $c->config->{name} || 'Catalyst Application';
289 my ( $title, $error, $infos );
291 $error = join '<br/>', @{ $c->error };
292 $error ||= 'No output';
293 $title = $name = "$name on Catalyst $Catalyst::VERSION";
294 my $req = encode_entities Dumper $c->req;
295 my $res = encode_entities Dumper $c->res;
296 my $stash = encode_entities Dumper $c->stash;
299 <b><u>Request</u></b><br/>
301 <b><u>Response</u></b><br/>
303 <b><u>Stash</u></b><br/>
312 (en) Please come back later
313 (de) Bitte versuchen sie es spaeter nocheinmal
314 (nl) Gelieve te komen later terug
315 (no) Vennligst prov igjen senere
316 (fr) Veuillez revenir plus tard
317 (es) Vuelto por favor mas adelante
318 (pt) Voltado por favor mais tarde
319 (it) Ritornato prego piĆ¹ successivamente
324 $c->res->output( <<"" );
327 <title>$title</title>
328 <style type="text/css">
330 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
331 Tahoma, Arial, helvetica, sans-serif;
333 background-color: #eee;
338 background-color: #ccc;
339 border: 1px solid #aaa;
342 -moz-border-radius: 10px;
345 background-color: #977;
346 border: 1px solid #755;
350 -moz-border-radius: 10px;
353 background-color: #797;
354 border: 1px solid #575;
358 -moz-border-radius: 10px;
361 background-color: #779;
362 border: 1px solid #557;
365 -moz-border-radius: 10px;
371 <div class="error">$error</div>
372 <div class="infos">$infos</div>
373 <div class="name">$name</div>
380 =item $c->finalize_headers
386 sub finalize_headers { }
388 =item $c->finalize_output
394 sub finalize_output { }
396 =item $c->forward($command)
398 Forward processing to a private action or a method from a class.
399 If you define a class without method it will default to process().
402 $c->forward('index');
403 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
404 $c->forward('MyApp::View::TT');
412 $c->log->debug('Nothing to forward to') if $c->debug;
415 my $caller = caller(0);
417 if ( $command =~ /^\// ) {
418 $command =~ /^(.*)\/(\w+)$/;
419 $namespace = $1 || '/';
422 else { $namespace = _class2prefix($caller) || '/' }
423 my $results = $c->get_action( $command, $namespace );
424 unless ( @{$results} ) {
425 my $class = $command;
426 if ( $class =~ /[^\w\:]/ ) {
427 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
430 my $method = shift || 'process';
431 if ( my $code = $class->can($method) ) {
432 $c->actions->{reverse}->{"$code"} = "$class->$method";
433 $results = [ [ [ $class, $code ] ] ];
436 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
441 for my $result ( @{$results} ) {
442 $c->execute( @{ $result->[0] } );
447 =item $c->get_action( $action, $namespace )
449 Get an action in a given namespace.
454 my ( $c, $action, $namespace ) = @_;
455 return [] unless $action;
458 $namespace = '' if $namespace eq '/';
459 my $parent = $c->tree;
461 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
462 push @results, [$result] if $result;
463 my $visitor = Tree::Simple::Visitor::FindByPath->new;
464 for my $part ( split '/', $namespace ) {
465 $visitor->setSearchPath($part);
466 $parent->accept($visitor);
467 my $child = $visitor->getResult;
468 my $uid = $child->getUID if $child;
469 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
470 push @results, [$match] if $match;
471 $parent = $child if $child;
475 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
476 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
478 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
479 my $name = $c->actions->{compiled}->[$i]->[0];
480 my $regex = $c->actions->{compiled}->[$i]->[1];
481 if ( $action =~ $regex ) {
483 for my $i ( 1 .. 9 ) {
486 push @snippets, ${$i};
488 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
495 =item $c->handler( $class, $r )
502 my ( $class, $engine ) = @_;
504 # Always expect worst case!
509 my $c = $class->prepare($engine);
510 $c->{stats} = \@stats;
514 if ( $class->debug ) {
516 ( $elapsed, $status ) = $class->benchmark($handler);
517 $elapsed = sprintf '%f', $elapsed;
518 my $av = sprintf '%.3f', 1 / $elapsed;
519 my $t = Text::ASCIITable->new;
520 $t->setCols( 'Action', 'Time' );
521 $t->setColWidth( 'Action', 64, 1 );
522 $t->setColWidth( 'Time', 9, 1 );
524 for my $stat (@stats) {
525 $t->addRow( wrap( $stat->[0], 64 ), wrap( $stat->[1], 9 ) );
527 $class->log->info( "Request took $elapsed" . "s ($av/s)",
530 else { $status = &$handler }
532 if ( my $error = $@ ) {
534 $class->log->error(qq/Caught exception in engine "$error"/);
540 =item $c->prepare($r)
542 Turns the engine-specific request( Apache, CGI ... )
543 into a Catalyst context .
548 my ( $class, $r ) = @_;
550 request => Catalyst::Request->new(
554 headers => HTTP::Headers->new,
560 response => Catalyst::Response->new(
561 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
567 my $secs = time - $START || 1;
568 my $av = sprintf '%.3f', $COUNT / $secs;
569 $c->log->debug('**********************************');
570 $c->log->debug("* Request $COUNT ($av/s) [$$]");
571 $c->log->debug('**********************************');
572 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
574 $c->prepare_request($r);
578 $c->prepare_connection;
579 my $method = $c->req->method || '';
580 my $path = $c->req->path || '';
581 my $hostname = $c->req->hostname || '';
582 my $address = $c->req->address || '';
583 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
586 $c->prepare_parameters;
588 if ( $c->debug && keys %{ $c->req->params } ) {
589 my $t = Text::ASCIITable->new;
590 $t->setCols( 'Key', 'Value' );
591 $t->setColWidth( 'Key', 37, 1 );
592 $t->setColWidth( 'Value', 36, 1 );
593 for my $key ( keys %{ $c->req->params } ) {
594 my $value = $c->req->params->{$key} || '';
595 $t->addRow( wrap( $key, 37 ), wrap( $value, 36 ) );
597 $c->log->debug( 'Parameters are', $t->draw );
603 =item $c->prepare_action
611 my $path = $c->req->path;
612 my @path = split /\//, $c->req->path;
613 $c->req->args( \my @args );
615 $path = join '/', @path;
616 if ( my $result = ${ $c->get_action($path) }[0] ) {
620 my $match = $result->[1];
621 my @snippets = @{ $result->[2] };
623 qq/Requested action is "$path" and matched "$match"/)
626 'Snippets are "' . join( ' ', @snippets ) . '"' )
627 if ( $c->debug && @snippets );
628 $c->req->action($match);
629 $c->req->snippets( \@snippets );
632 $c->req->action($path);
633 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
635 $c->req->match($path);
638 unshift @args, pop @path;
640 unless ( $c->req->action ) {
641 $c->req->action('default');
644 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
645 if ( $c->debug && @args );
648 =item $c->prepare_connection
654 sub prepare_connection { }
656 =item $c->prepare_cookies
662 sub prepare_cookies {
665 if ( my $header = $c->request->header('Cookie') ) {
666 $c->req->cookies( { CGI::Cookie->parse($header) } );
670 =item $c->prepare_headers
676 sub prepare_headers { }
678 =item $c->prepare_parameters
684 sub prepare_parameters { }
686 =item $c->prepare_path
688 Prepare path and base.
694 =item $c->prepare_request
696 Prepare the engine request.
700 sub prepare_request { }
702 =item $c->prepare_uploads
708 sub prepare_uploads { }
722 Returns a C<Catalyst::Request> object.
730 Returns a C<Catalyst::Response> object.
734 =item $c->set_action( $action, $code, $namespace, $attrs )
736 Set an action in a given namespace.
741 my ( $c, $method, $code, $namespace, $attrs ) = @_;
743 my $prefix = _class2prefix($namespace) || '';
746 for my $attr ( @{$attrs} ) {
747 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
748 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
749 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
750 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
751 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
754 return unless keys %flags;
756 my $parent = $c->tree;
757 my $visitor = Tree::Simple::Visitor::FindByPath->new;
758 for my $part ( split '/', $prefix ) {
759 $visitor->setSearchPath($part);
760 $parent->accept($visitor);
761 my $child = $visitor->getResult;
763 $child = $parent->addChild( Tree::Simple->new($part) );
764 $visitor->setSearchPath($part);
765 $parent->accept($visitor);
766 $child = $visitor->getResult;
770 my $uid = $parent->getUID;
771 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
772 my $forward = $prefix ? "$prefix/$method" : $method;
774 if ( $flags{path} ) {
775 $flags{path} =~ s/^\w+//;
776 $flags{path} =~ s/\w+$//;
777 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
778 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
780 if ( $flags{regex} ) {
781 $flags{regex} =~ s/^\w+//;
782 $flags{regex} =~ s/\w+$//;
783 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
784 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
787 my $reverse = $prefix ? "$prefix/$method" : $method;
789 if ( $flags{local} || $flags{global} || $flags{path} ) {
790 my $path = $flags{path} || $method;
792 if ( $path =~ /^\/(.+)/ ) {
796 $absolute = 1 if $flags{global};
797 my $name = $absolute ? $path : "$prefix/$path";
798 $c->actions->{plain}->{$name} = [ $namespace, $code ];
800 if ( my $regex = $flags{regex} ) {
801 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
802 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
805 $c->actions->{reverse}->{"$code"} = $reverse;
818 $self->setup_components;
819 if ( $self->debug ) {
820 my $name = $self->config->{name} || 'Application';
821 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
825 =item $class->setup_actions($component)
827 Setup actions for a component.
832 my ( $self, $comp ) = @_;
833 $comp = ref $comp || $comp;
834 for my $action ( @{ $comp->_cache } ) {
835 my ( $code, $attrs ) = @{$action};
838 my @cache = ( $comp, @{"$comp\::ISA"} );
840 while ( my $namespace = shift @cache ) {
841 $namespaces{$namespace}++;
842 for my $isa ( @{"$comp\::ISA"} ) {
843 next if $namespaces{$isa};
848 for my $namespace ( keys %namespaces ) {
849 for my $sym ( values %{ $namespace . '::' } ) {
850 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
851 $name = *{$sym}{NAME};
852 $self->set_action( $name, $code, $comp, $attrs );
860 =item $class->setup_components
866 sub setup_components {
870 my $class = ref $self || $self;
873 import Module::Pluggable::Fast
874 name => '_components',
876 '$class\::Controller', '$class\::C',
877 '$class\::Model', '$class\::M',
878 '$class\::View', '$class\::V'
881 if ( my $error = $@ ) {
884 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
886 $self->setup_actions($self);
887 $self->components( {} );
888 for my $comp ( $self->_components($self) ) {
889 $self->components->{ ref $comp } = $comp;
890 $self->setup_actions($comp);
892 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
893 $t->setCols('Class');
894 $t->setColWidth( 'Class', 75, 1 );
895 $t->addRow( wrap( $_, 75 ) ) for keys %{ $self->components };
896 $self->log->debug( 'Loaded components', $t->draw )
897 if ( @{ $t->{tbl_rows} } && $self->debug );
898 my $actions = $self->actions;
899 my $privates = Text::ASCIITable->new;
900 $privates->setCols( 'Private', 'Class', 'Code' );
901 $privates->setColWidth( 'Private', 28, 1 );
902 $privates->setColWidth( 'Class', 28, 1 );
903 $privates->setColWidth( 'Code', 14, 1 );
905 my ( $walker, $parent, $prefix ) = @_;
906 $prefix .= $parent->getNodeValue || '';
907 $prefix .= '/' unless $prefix =~ /\/$/;
908 my $uid = $parent->getUID;
909 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
910 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
912 wrap( "$prefix$action", 28 ),
917 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
919 $walker->( $walker, $self->tree, '' );
920 $self->log->debug( 'Loaded private actions', $privates->draw )
921 if ( @{ $privates->{tbl_rows} } && $self->debug );
922 my $publics = Text::ASCIITable->new;
923 $publics->setCols( 'Public', 'Private' );
924 $publics->setColWidth( 'Public', 37, 1 );
925 $publics->setColWidth( 'Private', 36, 1 );
927 for my $plain ( sort keys %{ $actions->{plain} } ) {
928 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
929 $publics->addRow( wrap( "/$plain", 37 ),
930 wrap( $self->actions->{reverse}->{$code} || $code, 36 ) );
932 $self->log->debug( 'Loaded public actions', $publics->draw )
933 if ( @{ $publics->{tbl_rows} } && $self->debug );
934 my $regexes = Text::ASCIITable->new;
935 $regexes->setCols( 'Regex', 'Private' );
936 $regexes->setColWidth( 'Regex', 37, 1 );
937 $regexes->setColWidth( 'Private', 36, 1 );
938 for my $regex ( sort keys %{ $actions->{regex} } ) {
939 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
940 $regexes->addRow( wrap( $regex, 37 ),
941 wrap( $self->actions->{reverse}->{$class} || $class, 36 ) );
943 $self->log->debug( 'Loaded regex actions', $regexes->draw )
944 if ( @{ $regexes->{tbl_rows} } && $self->debug );
949 Contains the return value of the last executed action.
953 Returns a hashref containing all your data.
955 $c->stash->{foo} ||= 'yada';
956 print $c->stash->{foo};
963 my $stash = $_[1] ? {@_} : $_[0];
964 while ( my ( $key, $val ) = each %$stash ) {
965 $self->{stash}->{$key} = $val;
968 return $self->{stash};
972 my ( $class, $name ) = @_;
973 my $prefix = _class2prefix($class);
974 $name = "$prefix/$name" if $prefix;
979 my $class = shift || '';
981 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
983 $prefix =~ s/\:\:/\//g;
992 Sebastian Riedel, C<sri@cpan.org>
996 This program is free software, you can redistribute it and/or modify it under
997 the same terms as Perl itself.