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
50 This is the core of catalyst. The various drivers are subclasses
57 =item $c->benchmark($coderef)
59 Takes a coderef with arguments and returns elapsed time as float.
61 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
62 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
69 my $time = [gettimeofday];
70 my @return = &$code(@_);
71 my $elapsed = tv_interval $time;
72 return wantarray ? ( $elapsed, @return ) : $elapsed;
77 Shortcut for $c->component
79 =item $c->component($name)
81 Get a component object by name.
83 $c->comp('MyApp::Model::MyModel')->do_stuff;
85 Regex search for a component.
87 $c->comp('mymodel')->do_stuff;
92 my ( $c, $name ) = @_;
93 if ( my $component = $c->components->{$name} ) {
97 for my $component ( keys %{ $c->components } ) {
98 return $c->components->{$component} if $component =~ /$name/i;
105 Dispatch request to actions.
111 my $action = $c->req->action;
113 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
114 if $action eq 'default';
115 unless ($namespace) {
116 if ( my $result = $c->get_action($action) ) {
117 $namespace = _class2prefix( $result->[0]->[0]->[0] );
120 my $default = $action eq 'default' ? $namespace : undef;
121 my $results = $c->get_action( $action, $default );
127 if ( my $begin = @{ $c->get_action( 'begin', $namespace ) }[-1] ) {
128 $c->execute( @{ $begin->[0] } );
129 return if scalar @{ $c->error };
132 # Execute the auto chain
133 for my $auto ( @{ $c->get_action( 'auto', $namespace ) } ) {
134 $c->execute( @{ $auto->[0] } );
135 return if scalar @{ $c->error };
136 last unless $c->state;
139 # Execute the action or last default
140 if ( ( my $action = $c->req->action ) && $c->state ) {
141 if ( my $result = @{ $c->get_action( $action, $default ) }[-1] ) {
142 $c->execute( @{ $result->[0] } );
147 if ( my $end = @{ $c->get_action( 'end', $namespace ) }[-1] ) {
148 $c->execute( @{ $end->[0] } );
149 return if scalar @{ $c->error };
153 my $path = $c->req->path;
155 ? qq/Unknown resource "$path"/
156 : "No default action defined";
157 $c->log->error($error) if $c->debug;
164 =item $c->error($error, ...)
166 =item $c->error($arrayref)
168 Returns an arrayref containing error messages.
170 my @error = @{ $c->error };
174 $c->error('Something bad happened');
180 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
181 push @{ $c->{error} }, @$error;
185 =item $c->execute($class, $coderef)
187 Execute a coderef in given class and catch exceptions.
188 Errors are available via $c->error.
193 my ( $c, $class, $code ) = @_;
194 $class = $c->comp($class) || $class;
196 my $callsub = ( caller(1) )[3];
200 my $action = $c->actions->{reverse}->{"$code"};
201 $action = "/$action" unless $action =~ /\-\>/;
202 $action = "-> $action" if $callsub =~ /forward$/;
203 my ( $elapsed, @state ) =
204 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
205 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
208 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
210 if ( my $error = $@ ) {
212 unless ( ref $error ) {
214 $error = qq/Caught exception "$error"/;
217 $c->log->error($error);
226 Finalize request. This function can typically be overloaded with
227 NEXT by plugins that need to do something at the end of the request.
234 $c->finalize_cookies;
236 if ( my $location = $c->response->redirect ) {
237 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
238 $c->response->header( Location => $location );
239 $c->response->status(302) if $c->response->status !~ /3\d\d$/;
242 if ( $#{ $c->error } >= 0 ) {
246 if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
250 if ( $c->response->output && !$c->response->content_length ) {
251 use bytes; # play safe with a utf8 aware perl
252 $c->response->content_length( length $c->response->output );
255 my $status = $c->finalize_headers;
260 =item $c->finalize_cookies
266 sub finalize_cookies {
269 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
270 my $cookie = CGI::Cookie->new(
272 -value => $cookie->{value},
273 -expires => $cookie->{expires},
274 -domain => $cookie->{domain},
275 -path => $cookie->{path},
276 -secure => $cookie->{secure} || 0
279 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
283 =item $c->finalize_error
285 This is the default error screen displayed from finalize. Override
286 with your own output if you need something special.
293 $c->res->headers->content_type('text/html');
294 my $name = $c->config->{name} || 'Catalyst Application';
296 my ( $title, $error, $infos );
298 $error = join '<br/>', @{ $c->error };
299 $error ||= 'No output';
300 $title = $name = "$name on Catalyst $Catalyst::VERSION";
301 my $req = encode_entities Dumper $c->req;
302 my $res = encode_entities Dumper $c->res;
303 my $stash = encode_entities Dumper $c->stash;
306 <b><u>Request</u></b><br/>
308 <b><u>Response</u></b><br/>
310 <b><u>Stash</u></b><br/>
319 (en) Please come back later
320 (de) Bitte versuchen sie es spaeter nocheinmal
321 (nl) Gelieve te komen later terug
322 (no) Vennligst prov igjen senere
323 (fr) Veuillez revenir plus tard
324 (es) Vuelto por favor mas adelante
325 (pt) Voltado por favor mais tarde
326 (it) Ritornato prego piĆ¹ successivamente
331 $c->res->output( <<"" );
334 <title>$title</title>
335 <style type="text/css">
337 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
338 Tahoma, Arial, helvetica, sans-serif;
340 background-color: #eee;
345 background-color: #ccc;
346 border: 1px solid #aaa;
349 -moz-border-radius: 10px;
352 background-color: #977;
353 border: 1px solid #755;
357 -moz-border-radius: 10px;
360 background-color: #797;
361 border: 1px solid #575;
365 -moz-border-radius: 10px;
368 background-color: #779;
369 border: 1px solid #557;
372 -moz-border-radius: 10px;
378 <div class="error">$error</div>
379 <div class="infos">$infos</div>
380 <div class="name">$name</div>
387 =item $c->finalize_headers
389 Finalize headers. Null action by default.
393 sub finalize_headers { }
395 =item $c->finalize_output
397 Finalize output. Null action by default
401 sub finalize_output { }
403 =item $c->forward($command)
405 Forward processing to a private action or a method from a class.
406 If you define a class without method it will default to process().
409 $c->forward('/controller/action');
410 $c->forward('index');
411 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
412 $c->forward('MyApp::View::TT');
420 $c->log->debug('Nothing to forward to') if $c->debug;
423 my $caller = caller(0);
424 my $global = $command =~ /^\// ? 0 : 1;
426 if ( $command =~ /^\// ) {
427 $command =~ /^(.*)\/(\w+)$/;
428 $namespace = $1 || '/';
431 else { $namespace = _class2prefix($caller) || '/' }
432 my $results = $c->get_action( $command, $namespace, $global );
433 unless ( @{$results} ) {
434 my $class = $command;
435 if ( $class =~ /[^\w\:]/ ) {
436 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
439 my $method = shift || 'process';
440 if ( my $code = $class->can($method) ) {
441 $c->actions->{reverse}->{"$code"} = "$class->$method";
442 $results = [ [ [ $class, $code ] ] ];
445 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
450 for my $result ( @{$results} ) {
451 $c->execute( @{ $result->[0] } );
452 return if scalar @{ $c->error };
453 last unless $c->state;
458 =item $c->get_action( $action, $namespace, $global )
460 Get an action in a given namespace.
465 my ( $c, $action, $namespace, $global ) = @_;
466 return [] unless $action;
471 for my $uid ( keys %{ $c->actions->{private} } ) {
472 if ( my $result = $c->actions->{private}->{$uid}->{$action} ) {
473 push @results, [$result];
479 $namespace = '' if $namespace eq '/';
480 my $parent = $c->tree;
482 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
483 push @results, [$result] if $result;
484 my $visitor = Tree::Simple::Visitor::FindByPath->new;
485 for my $part ( split '/', $namespace ) {
486 $visitor->setSearchPath($part);
487 $parent->accept($visitor);
488 my $child = $visitor->getResult;
489 my $uid = $child->getUID if $child;
490 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
491 push @results, [$match] if $match;
492 $parent = $child if $child;
497 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
498 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
500 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
501 my $name = $c->actions->{compiled}->[$i]->[0];
502 my $regex = $c->actions->{compiled}->[$i]->[1];
503 if ( $action =~ $regex ) {
505 for my $i ( 1 .. 9 ) {
508 push @snippets, ${$i};
510 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
517 =item $c->handler( $class, $r )
519 The main request handler.
524 my ( $class, $engine ) = @_;
526 # Always expect worst case!
531 my $c = $class->prepare($engine);
532 $c->{stats} = \@stats;
536 if ( $class->debug ) {
538 ( $elapsed, $status ) = $class->benchmark($handler);
539 $elapsed = sprintf '%f', $elapsed;
540 my $av = sprintf '%.3f', 1 / $elapsed;
541 my $t = Text::ASCIITable->new;
542 $t->setCols( 'Action', 'Time' );
543 $t->setColWidth( 'Action', 64, 1 );
544 $t->setColWidth( 'Time', 9, 1 );
546 for my $stat (@stats) {
547 $t->addRow( wrap( $stat->[0], 64 ), wrap( $stat->[1], 9 ) );
549 $class->log->info( "Request took $elapsed" . "s ($av/s)",
552 else { $status = &$handler }
554 if ( my $error = $@ ) {
556 $class->log->error(qq/Caught exception in engine "$error"/);
562 =item $c->prepare($r)
564 Turns the engine-specific request( Apache, CGI ... )
565 into a Catalyst context .
570 my ( $class, $r ) = @_;
572 request => Catalyst::Request->new(
576 headers => HTTP::Headers->new,
582 response => Catalyst::Response->new(
583 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
589 my $secs = time - $START || 1;
590 my $av = sprintf '%.3f', $COUNT / $secs;
591 $c->log->debug('**********************************');
592 $c->log->debug("* Request $COUNT ($av/s) [$$]");
593 $c->log->debug('**********************************');
594 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
596 $c->prepare_request($r);
600 $c->prepare_connection;
601 my $method = $c->req->method || '';
602 my $path = $c->req->path || '';
603 my $hostname = $c->req->hostname || '';
604 my $address = $c->req->address || '';
605 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
608 $c->prepare_parameters;
610 if ( $c->debug && keys %{ $c->req->params } ) {
611 my $t = Text::ASCIITable->new;
612 $t->setCols( 'Key', 'Value' );
613 $t->setColWidth( 'Key', 37, 1 );
614 $t->setColWidth( 'Value', 36, 1 );
615 for my $key ( keys %{ $c->req->params } ) {
616 my $value = $c->req->params->{$key} || '';
617 $t->addRow( wrap( $key, 37 ), wrap( $value, 36 ) );
619 $c->log->debug( 'Parameters are', $t->draw );
625 =item $c->prepare_action
627 Prepare action for processing.
633 my $path = $c->req->path;
634 my @path = split /\//, $c->req->path;
635 $c->req->args( \my @args );
637 $path = join '/', @path;
638 if ( my $result = ${ $c->get_action($path) }[0] ) {
642 my $match = $result->[1];
643 my @snippets = @{ $result->[2] };
645 qq/Requested action is "$path" and matched "$match"/)
648 'Snippets are "' . join( ' ', @snippets ) . '"' )
649 if ( $c->debug && @snippets );
650 $c->req->action($match);
651 $c->req->snippets( \@snippets );
654 $c->req->action($path);
655 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
657 $c->req->match($path);
660 unshift @args, pop @path;
662 unless ( $c->req->action ) {
663 $c->req->action('default');
666 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
667 if ( $c->debug && @args );
670 =item $c->prepare_connection
672 Prepare connection. Null action by default
676 sub prepare_connection { }
678 =item $c->prepare_cookies
684 sub prepare_cookies {
687 if ( my $header = $c->request->header('Cookie') ) {
688 $c->req->cookies( { CGI::Cookie->parse($header) } );
692 =item $c->prepare_headers
694 Prepare headers. Null action by default
698 sub prepare_headers { }
700 =item $c->prepare_parameters
702 Prepare parameters. Null action by default
706 sub prepare_parameters { }
708 =item $c->prepare_path
710 Prepare path and base. Null action by default
716 =item $c->prepare_request
718 Prepare the engine request. Null action by default
722 sub prepare_request { }
724 =item $c->prepare_uploads
726 Prepare uploads. Null action by default
730 sub prepare_uploads { }
734 Starts the engine. Null action by default
742 Shortcut for $c->request
746 Returns a C<Catalyst::Request> object.
748 my $req = $c->request;
752 Shortcut for $c->response
756 Returns a C<Catalyst::Response> object.
760 =item $c->set_action( $action, $code, $namespace, $attrs )
762 Set an action in a given namespace. Used to defined the actions
763 in the attribute handlers.
768 my ( $c, $method, $code, $namespace, $attrs ) = @_;
770 my $prefix = _class2prefix($namespace) || '';
773 for my $attr ( @{$attrs} ) {
774 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
775 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
776 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
777 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
778 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
781 return unless keys %flags;
783 my $parent = $c->tree;
784 my $visitor = Tree::Simple::Visitor::FindByPath->new;
785 for my $part ( split '/', $prefix ) {
786 $visitor->setSearchPath($part);
787 $parent->accept($visitor);
788 my $child = $visitor->getResult;
790 $child = $parent->addChild( Tree::Simple->new($part) );
791 $visitor->setSearchPath($part);
792 $parent->accept($visitor);
793 $child = $visitor->getResult;
797 my $uid = $parent->getUID;
798 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
799 my $forward = $prefix ? "$prefix/$method" : $method;
801 if ( $flags{path} ) {
802 $flags{path} =~ s/^\w+//;
803 $flags{path} =~ s/\w+$//;
804 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
805 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
807 if ( $flags{regex} ) {
808 $flags{regex} =~ s/^\w+//;
809 $flags{regex} =~ s/\w+$//;
810 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
811 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
814 my $reverse = $prefix ? "$prefix/$method" : $method;
816 if ( $flags{local} || $flags{global} || $flags{path} ) {
817 my $path = $flags{path} || $method;
819 if ( $path =~ /^\/(.+)/ ) {
823 $absolute = 1 if $flags{global};
824 my $name = $absolute ? $path : $prefix ? "$prefix/$path" : $path;
825 $c->actions->{plain}->{$name} = [ $namespace, $code ];
827 if ( my $regex = $flags{regex} ) {
828 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
829 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
832 $c->actions->{reverse}->{"$code"} = $reverse;
837 Setup the application. required to initialize actions.
845 $self->setup_components;
846 if ( $self->debug ) {
847 my $name = $self->config->{name} || 'Application';
848 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
852 =item $class->setup_actions($component)
854 Setup actions for a component.
859 my ( $self, $comp ) = @_;
860 $comp = ref $comp || $comp;
861 for my $action ( @{ $comp->_cache } ) {
862 my ( $code, $attrs ) = @{$action};
865 my @cache = ( $comp, @{"$comp\::ISA"} );
867 while ( my $namespace = shift @cache ) {
868 $namespaces{$namespace}++;
869 for my $isa ( @{"$comp\::ISA"} ) {
870 next if $namespaces{$isa};
875 for my $namespace ( keys %namespaces ) {
876 for my $sym ( values %{ $namespace . '::' } ) {
877 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
878 $name = *{$sym}{NAME};
879 $self->set_action( $name, $code, $comp, $attrs );
887 =item $class->setup_components
889 Setup all the components in YourApp::(M|V|C|Model|View|Controller)::*
893 sub setup_components {
897 my $class = ref $self || $self;
900 import Module::Pluggable::Fast
901 name => '_components',
903 '$class\::Controller', '$class\::C',
904 '$class\::Model', '$class\::M',
905 '$class\::View', '$class\::V'
908 if ( my $error = $@ ) {
911 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
913 $self->setup_actions($self);
914 $self->components( {} );
915 for my $comp ( $self->_components($self) ) {
916 $self->components->{ ref $comp } = $comp;
917 $self->setup_actions($comp);
919 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
920 $t->setCols('Class');
921 $t->setColWidth( 'Class', 75, 1 );
922 $t->addRow( wrap( $_, 75 ) ) for keys %{ $self->components };
923 $self->log->debug( 'Loaded components', $t->draw )
924 if ( @{ $t->{tbl_rows} } && $self->debug );
925 my $actions = $self->actions;
926 my $privates = Text::ASCIITable->new;
927 $privates->setCols( 'Private', 'Class', 'Code' );
928 $privates->setColWidth( 'Private', 28, 1 );
929 $privates->setColWidth( 'Class', 28, 1 );
930 $privates->setColWidth( 'Code', 14, 1 );
932 my ( $walker, $parent, $prefix ) = @_;
933 $prefix .= $parent->getNodeValue || '';
934 $prefix .= '/' unless $prefix =~ /\/$/;
935 my $uid = $parent->getUID;
936 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
937 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
939 wrap( "$prefix$action", 28 ),
944 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
946 $walker->( $walker, $self->tree, '' );
947 $self->log->debug( 'Loaded private actions', $privates->draw )
948 if ( @{ $privates->{tbl_rows} } && $self->debug );
949 my $publics = Text::ASCIITable->new;
950 $publics->setCols( 'Public', 'Private' );
951 $publics->setColWidth( 'Public', 37, 1 );
952 $publics->setColWidth( 'Private', 36, 1 );
954 for my $plain ( sort keys %{ $actions->{plain} } ) {
955 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
956 $publics->addRow( wrap( "/$plain", 37 ),
957 wrap( $self->actions->{reverse}->{$code} || $code, 36 ) );
959 $self->log->debug( 'Loaded public actions', $publics->draw )
960 if ( @{ $publics->{tbl_rows} } && $self->debug );
961 my $regexes = Text::ASCIITable->new;
962 $regexes->setCols( 'Regex', 'Private' );
963 $regexes->setColWidth( 'Regex', 37, 1 );
964 $regexes->setColWidth( 'Private', 36, 1 );
965 for my $regex ( sort keys %{ $actions->{regex} } ) {
966 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
967 $regexes->addRow( wrap( $regex, 37 ),
968 wrap( $self->actions->{reverse}->{$class} || $class, 36 ) );
970 $self->log->debug( 'Loaded regex actions', $regexes->draw )
971 if ( @{ $regexes->{tbl_rows} } && $self->debug );
976 Contains the return value of the last executed action.
980 The stash is a global hash which can be used to pass around data
981 between your components.
983 $c->stash->{foo} ||= 'yada';
984 print $c->stash->{foo};
991 my $stash = $_[1] ? {@_} : $_[0];
992 while ( my ( $key, $val ) = each %$stash ) {
993 $self->{stash}->{$key} = $val;
996 return $self->{stash};
1000 my ( $class, $name ) = @_;
1001 my $prefix = _class2prefix($class);
1002 $name = "$prefix/$name" if $prefix;
1007 my $class = shift || '';
1009 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
1011 $prefix =~ s/\:\:/\//g;
1022 =item L<Catalyst::Engine::Apache> - Apache Engines for MP1/2
1023 =item L<Catalyst::Engine::CGI> - CGI Engine
1024 =item L<Catalyst::Engine::FCGI> - FastCGI Engine
1025 =item L<Catalyst::Engine::HTTP> - Standalone Catalyst Server
1026 =item L<Catalyst::Engine::Test> - Engine for testing
1032 Sebastian Riedel, C<sri@cpan.org>
1033 Marcus Ramberg, C<mramberg@cpan.org>
1037 This program is free software, you can redistribute it and/or modify it under
1038 the same terms as Perl itself.