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 Tree::Simple::Visitor::FindByPath;
14 use Catalyst::Request;
15 use Catalyst::Response;
17 require Module::Pluggable::Fast;
19 $Data::Dumper::Terse = 1;
21 __PACKAGE__->mk_classdata($_) for qw/actions components tree/;
22 __PACKAGE__->mk_accessors(qw/request response state/);
25 { plain => {}, private => {}, regex => {}, compiled => [], reverse => {} }
27 __PACKAGE__->tree( Tree::Simple->new( 0, Tree::Simple->ROOT ) );
36 memoize('_class2prefix');
40 Catalyst::Engine - The Catalyst Engine
52 =item $c->benchmark($coderef)
54 Takes a coderef with arguments and returns elapsed time as float.
56 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
57 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
64 my $time = [gettimeofday];
65 my @return = &$code(@_);
66 my $elapsed = tv_interval $time;
67 return wantarray ? ( $elapsed, @return ) : $elapsed;
72 =item $c->component($name)
74 Get a component object by name.
76 $c->comp('MyApp::Model::MyModel')->do_stuff;
78 Regex search for a component.
80 $c->comp('mymodel')->do_stuff;
85 my ( $c, $name ) = @_;
86 if ( my $component = $c->components->{$name} ) {
90 for my $component ( keys %{ $c->components } ) {
91 return $c->components->{$component} if $component =~ /$name/i;
98 =item $c->error($error, ...)
100 =item $c->error($arrayref)
102 Returns an arrayref containing error messages.
104 my @error = @{ $c->error };
108 $c->error('Something bad happened');
114 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
115 push @{ $c->{error} }, @$error;
119 =item $c->execute($class, $coderef)
121 Execute a coderef in given class and catch exceptions.
122 Errors are available via $c->error.
127 my ( $c, $class, $code ) = @_;
128 $class = $c->comp($class) || $class;
133 my $action = $c->actions->{reverse}->{"$code"};
134 $action = "/$action" unless $action =~ /\-\>/;
135 my ( $elapsed, @state ) =
136 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
137 push @{ $c->{stats} },
138 _prettify( $action, sprintf( '%fs', $elapsed ), '' );
141 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
143 if ( my $error = $@ ) {
145 $error = qq/Caught exception "$error"/;
146 $c->log->error($error);
147 $c->error($error) if $c->debug;
162 $c->finalize_cookies;
164 if ( my $location = $c->res->redirect ) {
165 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
166 $c->response->header( Location => $location );
167 $c->response->status(302);
170 if ( $c->res->status =~ /^(1\d\d|[23]04)$/ ) {
171 $c->response->headers->remove_content_headers;
172 return $c->finalize_headers;
175 if ( !$c->res->output || $#{ $c->error } >= 0 ) {
176 $c->res->headers->content_type('text/html');
177 my $name = $c->config->{name} || 'Catalyst Application';
178 my ( $title, $error, $infos );
180 $error = join '<br/>', @{ $c->error };
181 $error ||= 'No output';
182 $title = $name = "$name on Catalyst $Catalyst::VERSION";
183 my $req = encode_entities Dumper $c->req;
184 my $res = encode_entities Dumper $c->res;
185 my $stash = encode_entities Dumper $c->stash;
188 <b><u>Request</u></b><br/>
190 <b><u>Response</u></b><br/>
192 <b><u>Stash</u></b><br/>
201 (en) Please come back later
202 (de) Bitte versuchen sie es spaeter nocheinmal
203 (nl) Gelieve te komen later terug
204 (no) Vennligst prov igjen senere
205 (fr) Veuillez revenir plus tard
206 (es) Vuelto por favor mas adelante
207 (pt) Voltado por favor mais tarde
208 (it) Ritornato prego piĆ¹ successivamente
213 $c->res->{output} = <<"";
216 <title>$title</title>
217 <style type="text/css">
219 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
220 Tahoma, Arial, helvetica, sans-serif;
222 background-color: #eee;
227 background-color: #ccc;
228 border: 1px solid #aaa;
231 -moz-border-radius: 10px;
234 background-color: #977;
235 border: 1px solid #755;
239 -moz-border-radius: 10px;
242 background-color: #797;
243 border: 1px solid #575;
247 -moz-border-radius: 10px;
250 background-color: #779;
251 border: 1px solid #557;
254 -moz-border-radius: 10px;
260 <div class="error">$error</div>
261 <div class="infos">$infos</div>
262 <div class="name">$name</div>
268 $c->res->headers->content_length( length $c->res->output );
269 my $status = $c->finalize_headers;
274 =item $c->finalize_cookies
280 sub finalize_cookies {
283 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
284 my $cookie = CGI::Cookie->new(
286 -value => $cookie->{value},
287 -expires => $cookie->{expires},
288 -domain => $cookie->{domain},
289 -path => $cookie->{path},
290 -secure => $cookie->{secure} || 0
293 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
297 =item $c->finalize_headers
303 sub finalize_headers { }
305 =item $c->finalize_output
311 sub finalize_output { }
313 =item $c->forward($command)
315 Forward processing to a private action or a method from a class.
316 If you define a class without method it will default to process().
319 $c->forward('index');
320 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
321 $c->forward('MyApp::View::TT');
329 $c->log->debug('Nothing to forward to') if $c->debug;
332 my $caller = caller(0);
334 if ( $command =~ /^\// ) {
335 $command =~ /^(.*)\/(\w+)$/;
336 $namespace = $1 || '/';
339 else { $namespace = _class2prefix($caller) || '/' }
340 my $results = $c->get_action( $command, $namespace );
341 unless ( @{$results} ) {
342 my $class = $command;
343 if ( $class =~ /[^\w\:]/ ) {
344 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
347 my $method = shift || 'process';
348 if ( my $code = $class->can($method) ) {
349 $c->actions->{reverse}->{"$code"} = "$class->$method";
350 $results = [ [ [ $class, $code ] ] ];
353 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
358 for my $result ( @{$results} ) {
359 $c->state( $c->execute( @{ $result->[0] } ) );
364 =item $c->get_action( $action, $namespace )
366 Get an action in a given namespace.
371 my ( $c, $action, $namespace ) = @_;
374 $namespace = '' if $namespace eq '/';
375 my $parent = $c->tree;
377 my $result = $c->actions->{private}->{ $parent->getUID }->{$action};
378 push @results, [$result] if $result;
379 my $visitor = Tree::Simple::Visitor::FindByPath->new;
380 for my $part ( split '/', $namespace ) {
381 $visitor->setSearchPath($part);
382 $parent->accept($visitor);
383 my $child = $visitor->getResult;
384 my $uid = $child->getUID if $child;
385 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;
386 push @results, [$match] if $match;
387 $parent = $child if $child;
391 elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }
392 elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }
394 for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {
395 my $name = $c->actions->{compiled}->[$i]->[0];
396 my $regex = $c->actions->{compiled}->[$i]->[1];
397 if ( $action =~ $regex ) {
399 for my $i ( 1 .. 9 ) {
402 push @snippets, ${$i};
404 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];
411 =item $c->handler( $class, $r )
418 my ( $class, $engine ) = @_;
420 # Always expect worst case!
425 my $c = $class->prepare($engine);
426 $c->{stats} = \@stats;
427 my $action = $c->req->action;
429 $namespace = ( join( '/', @{ $c->req->args } ) || '/' )
430 if $action eq 'default';
431 unless ($namespace) {
432 if ( my $result = $c->get_action($action) ) {
433 $namespace = _class2prefix( $result->[0]->[0]->[0] );
436 my $default = $action eq 'default' ? $namespace : undef;
437 my $results = $c->get_action( $action, $default );
440 for my $begin ( @{ $c->get_action( 'begin', $namespace ) } ) {
441 $c->state( $c->execute( @{ $begin->[0] } ) );
443 for my $result ( @{ $c->get_action( $action, $default ) }[-1] )
445 $c->state( $c->execute( @{ $result->[0] } ) );
446 last unless $default;
448 for my $end ( reverse @{ $c->get_action( 'end', $namespace ) } )
450 $c->state( $c->execute( @{ $end->[0] } ) );
454 my $path = $c->req->path;
456 ? qq/Unknown resource "$path"/
457 : "No default action defined";
458 $c->log->error($error) if $c->debug;
463 if ( $class->debug ) {
465 ( $elapsed, $status ) = $class->benchmark($handler);
466 $elapsed = sprintf '%f', $elapsed;
467 my $av = sprintf '%.3f', 1 / $elapsed;
468 $class->log->info( "Request took $elapsed" . "s ($av/s)", @stats );
470 else { $status = &$handler }
472 if ( my $error = $@ ) {
474 $class->log->error(qq/Caught exception in engine "$error"/);
480 =item $c->prepare($r)
482 Turns the engine-specific request( Apache, CGI ... )
483 into a Catalyst context .
488 my ( $class, $r ) = @_;
490 request => Catalyst::Request->new(
494 headers => HTTP::Headers->new,
500 response => Catalyst::Response->new(
501 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
507 my $secs = time - $START || 1;
508 my $av = sprintf '%.3f', $COUNT / $secs;
509 $c->log->debug('********************************');
510 $c->log->debug("* Request $COUNT ($av/s) [$$]");
511 $c->log->debug('********************************');
512 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
514 $c->prepare_request($r);
518 $c->prepare_connection;
519 my $method = $c->req->method || '';
520 my $path = $c->req->path || '';
521 my $hostname = $c->req->hostname || '';
522 my $address = $c->req->address || '';
523 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
526 $c->prepare_parameters;
528 if ( $c->debug && keys %{ $c->req->params } ) {
530 for my $key ( keys %{ $c->req->params } ) {
531 my $value = $c->req->params->{$key} || '';
532 push @params, " $key=$value";
534 $c->log->debug( 'Parameters are', @params );
540 =item $c->prepare_action
548 my $path = $c->req->path;
549 my @path = split /\//, $c->req->path;
550 $c->req->args( \my @args );
552 $path = join '/', @path;
553 if ( my $result = ${ $c->get_action($path) }[0] ) {
557 my $match = $result->[1];
558 my @snippets = @{ $result->[2] };
559 $c->log->debug(qq/Requested action "$path" matched "$match"/)
562 'Snippets are "' . join( ' ', @snippets ) . '"' )
563 if ( $c->debug && @snippets );
564 $c->req->action($match);
565 $c->req->snippets( \@snippets );
568 $c->req->action($path);
569 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
571 $c->req->match($path);
574 unshift @args, pop @path;
576 unless ( $c->req->action ) {
577 $c->req->action('default');
580 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
581 if ( $c->debug && @args );
584 =item $c->prepare_connection
590 sub prepare_connection { }
592 =item $c->prepare_cookies
598 sub prepare_cookies {
601 if ( my $header = $c->request->header('Cookie') ) {
602 $c->req->cookies( { CGI::Cookie->parse($header) } );
606 =item $c->prepare_headers
612 sub prepare_headers { }
614 =item $c->prepare_parameters
620 sub prepare_parameters { }
622 =item $c->prepare_path
624 Prepare path and base.
630 =item $c->prepare_request
632 Prepare the engine request.
636 sub prepare_request { }
638 =item $c->prepare_uploads
644 sub prepare_uploads { }
658 Returns a C<Catalyst::Request> object.
666 Returns a C<Catalyst::Response> object.
670 =item $c->set_action( $action, $code, $namespace, $attrs )
672 Set an action in a given namespace.
677 my ( $c, $method, $code, $namespace, $attrs ) = @_;
679 my $prefix = _class2prefix($namespace) || '';
682 for my $attr ( @{$attrs} ) {
683 if ( $attr =~ /^(Local|Relative)$/ ) { $flags{local}++ }
684 elsif ( $attr =~ /^(Global|Absolute)$/ ) { $flags{global}++ }
685 elsif ( $attr =~ /^Path\((.+)\)$/i ) { $flags{path} = $1 }
686 elsif ( $attr =~ /^Private$/i ) { $flags{private}++ }
687 elsif ( $attr =~ /^(Regex|Regexp)\((.+)\)$/i ) { $flags{regex} = $2 }
690 return unless keys %flags;
692 my $parent = $c->tree;
693 my $visitor = Tree::Simple::Visitor::FindByPath->new;
694 for my $part ( split '/', $prefix ) {
695 $visitor->setSearchPath($part);
696 $parent->accept($visitor);
697 my $child = $visitor->getResult;
699 $child = $parent->addChild( Tree::Simple->new($part) );
700 $visitor->setSearchPath($part);
701 $parent->accept($visitor);
702 $child = $visitor->getResult;
706 my $uid = $parent->getUID;
707 $c->actions->{private}->{$uid}->{$method} = [ $namespace, $code ];
708 my $forward = $prefix ? "$prefix/$method" : $method;
710 if ( $flags{path} ) {
711 $flags{path} =~ s/^\w+//;
712 $flags{path} =~ s/\w+$//;
713 if ( $flags{path} =~ /^'(.*)'$/ ) { $flags{path} = $1 }
714 if ( $flags{path} =~ /^"(.*)"$/ ) { $flags{path} = $1 }
716 if ( $flags{regex} ) {
717 $flags{regex} =~ s/^\w+//;
718 $flags{regex} =~ s/\w+$//;
719 if ( $flags{regex} =~ /^'(.*)'$/ ) { $flags{regex} = $1 }
720 if ( $flags{regex} =~ /^"(.*)"$/ ) { $flags{regex} = $1 }
723 my $reverse = $prefix ? "$prefix/$method" : $method;
725 if ( $flags{local} || $flags{global} || $flags{path} ) {
726 my $path = $flags{path} || $method;
728 if ( $path =~ /^\/(.+)/ ) {
732 $absolute = 1 if $flags{global};
733 my $name = $absolute ? $path : "$prefix/$path";
734 $c->actions->{plain}->{$name} = [ $namespace, $code ];
736 if ( my $regex = $flags{regex} ) {
737 push @{ $c->actions->{compiled} }, [ $regex, qr#$regex# ];
738 $c->actions->{regex}->{$regex} = [ $namespace, $code ];
741 $c->actions->{reverse}->{"$code"} = $reverse;
754 $self->setup_components;
755 if ( $self->debug ) {
756 my $name = $self->config->{name} || 'Application';
757 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
761 =item $class->setup_actions($component)
763 Setup actions for a component.
768 my ( $self, $comp ) = @_;
769 $comp = ref $comp || $comp;
770 for my $action ( @{ $comp->_cache } ) {
771 my ( $code, $attrs ) = @{$action};
774 my @cache = ( $comp, @{"$comp\::ISA"} );
776 while ( my $namespace = shift @cache ) {
777 $namespaces{$namespace}++;
778 for my $isa ( @{"$comp\::ISA"} ) {
779 next if $namespaces{$isa};
784 for my $namespace ( keys %namespaces ) {
785 for my $sym ( values %{ $namespace . '::' } ) {
786 if ( *{$sym}{CODE} && *{$sym}{CODE} == $code ) {
787 $name = *{$sym}{NAME};
788 $self->set_action( $name, $code, $comp, $attrs );
796 =item $class->setup_components
802 sub setup_components {
806 my $class = ref $self || $self;
809 import Module::Pluggable::Fast
810 name => '_components',
812 '$class\::Controller', '$class\::C',
813 '$class\::Model', '$class\::M',
814 '$class\::View', '$class\::V'
817 if ( my $error = $@ ) {
820 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
822 $self->setup_actions($self);
823 $self->components( {} );
824 for my $comp ( $self->_components($self) ) {
825 $self->components->{ ref $comp } = $comp;
826 $self->setup_actions($comp);
829 push @comps, " $_" for keys %{ $self->components };
830 $self->log->debug( 'Loaded components', @comps )
831 if ( @comps && $self->debug );
832 my $actions = $self->actions;
833 my @messages = ('Loaded private actions');
835 my ( $walker, $parent, $messages, $prefix ) = @_;
836 $prefix .= $parent->getNodeValue || '';
837 $prefix .= '/' unless $prefix =~ /\/$/;
838 my $uid = $parent->getUID;
839 for my $action ( keys %{ $actions->{private}->{$uid} } ) {
840 my ( $class, $code ) = @{ $actions->{private}->{$uid}->{$action} };
841 push @$messages, _prettify( "$prefix$action", $class, $code );
843 $walker->( $walker, $_, $messages, $prefix )
844 for $parent->getAllChildren;
846 $walker->( $walker, $self->tree, \@messages, '' );
847 $self->log->debug(@messages) if ( $#messages && $self->debug );
848 @messages = ('Loaded plain actions');
849 for my $plain ( sort keys %{ $actions->{plain} } ) {
850 my ( $class, $code ) = @{ $actions->{plain}->{$plain} };
851 push @messages, _prettify( "/$plain", $class, $code );
853 $self->log->debug(@messages) if ( $#messages && $self->debug );
854 @messages = ('Loaded regex actions');
855 for my $regex ( sort keys %{ $actions->{regex} } ) {
856 my ( $class, $code ) = @{ $actions->{regex}->{$regex} };
857 push @messages, _prettify( $regex, $class, $code );
859 $self->log->debug(@messages) if ( $#messages && $self->debug );
864 Returns a hashref containing all your data.
866 $c->stash->{foo} ||= 'yada';
867 print $c->stash->{foo};
874 my $stash = $_[1] ? {@_} : $_[0];
875 while ( my ( $key, $val ) = each %$stash ) {
876 $self->{stash}->{$key} = $val;
879 return $self->{stash};
883 my ( $class, $name ) = @_;
884 my $prefix = _class2prefix($class);
885 $name = "$prefix/$name" if $prefix;
890 my $class = shift || '';
892 if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) {
894 $prefix =~ s/\:\:/\//g;
900 my ( $val1, $val2, $val3 ) = @_;
902 ' @<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<< @>>>>>>>>>>>>>> ',
913 Sebastian Riedel, C<sri@cpan.org>
917 This program is free software, you can redistribute it and/or modify it under
918 the same terms as Perl itself.