1 package Catalyst::Engine;
4 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
6 use UNIVERSAL::require;
11 use Time::HiRes qw/gettimeofday tv_interval/;
13 use Catalyst::Request;
14 use Catalyst::Request::Upload;
15 use Catalyst::Response;
18 require Module::Pluggable::Fast;
21 $Data::Dumper::Terse = 1;
23 __PACKAGE__->mk_classdata('components');
24 __PACKAGE__->mk_accessors(qw/counter depth request response state/);
30 # For backwards compatibility
31 *finalize_output = \&finalize_body;
36 our $RECURSION = 1000;
37 our $DETACH = "catalyst_detach\n";
41 Catalyst::Engine - The Catalyst Engine
53 =item $c->benchmark($coderef)
55 Takes a coderef with arguments and returns elapsed time as float.
57 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
58 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
65 my $time = [gettimeofday];
66 my @return = &$code(@_);
67 my $elapsed = tv_interval $time;
68 return wantarray ? ( $elapsed, @return ) : $elapsed;
73 =item $c->component($name)
75 Get a component object by name.
77 $c->comp('MyApp::Model::MyModel')->do_stuff;
79 Regex search for a component.
81 $c->comp('mymodel')->do_stuff;
92 if ( my $component = $c->components->{$name} ) {
97 for my $component ( keys %{ $c->components } ) {
98 return $c->components->{$component} if $component =~ /$name/i;
103 return sort keys %{ $c->components };
108 Returns a hashref containing coderefs and execution counts.
109 (Needed for deep recursion detection)
113 Returns the actual forward depth.
117 =item $c->error($error, ...)
119 =item $c->error($arrayref)
121 Returns an arrayref containing error messages.
123 my @error = @{ $c->error };
127 $c->error('Something bad happened');
133 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
134 push @{ $c->{error} }, @$error;
138 =item $c->execute($class, $coderef)
140 Execute a coderef in given class and catch exceptions.
141 Errors are available via $c->error.
146 my ( $c, $class, $code ) = @_;
147 $class = $c->components->{$class} || $class;
149 my $callsub = ( caller(1) )[3];
153 $action = $c->actions->{reverse}->{"$code"};
154 $action = "/$action" unless $action =~ /\-\>/;
155 $c->counter->{"$code"}++;
157 if ( $c->counter->{"$code"} > $RECURSION ) {
158 my $error = qq/Deep recursion detected in "$action"/;
159 $c->log->error($error);
165 $action = "-> $action" if $callsub =~ /forward$/;
172 my ( $elapsed, @state ) =
173 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
174 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
177 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }
181 if ( my $error = $@ ) {
183 if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
185 unless ( ref $error ) {
187 $error = qq/Caught exception "$error"/;
190 $c->log->error($error);
207 $c->finalize_cookies;
209 if ( my $location = $c->response->redirect ) {
210 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
211 $c->response->header( Location => $location );
212 $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
215 if ( $#{ $c->error } >= 0 ) {
219 if ( !$c->response->body && $c->response->status == 200 ) {
223 if ( $c->response->body && !$c->response->content_length ) {
224 $c->response->content_length( bytes::length( $c->response->body ) );
227 if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
228 $c->response->headers->remove_header("Content-Length");
229 $c->response->body('');
232 if ( $c->request->method eq 'HEAD' ) {
233 $c->response->body('');
236 my $status = $c->finalize_headers;
241 =item $c->finalize_output
243 <obsolete>, see finalize_body
245 =item $c->finalize_body
251 sub finalize_body { }
253 =item $c->finalize_cookies
259 sub finalize_cookies {
262 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
263 my $cookie = CGI::Cookie->new(
265 -value => $cookie->{value},
266 -expires => $cookie->{expires},
267 -domain => $cookie->{domain},
268 -path => $cookie->{path},
269 -secure => $cookie->{secure} || 0
272 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
276 =item $c->finalize_error
285 $c->res->headers->content_type('text/html');
286 my $name = $c->config->{name} || 'Catalyst Application';
288 my ( $title, $error, $infos );
290 $error = join '<br/>', @{ $c->error };
291 $error ||= 'No output';
292 $title = $name = "$name on Catalyst $Catalyst::VERSION";
293 my $req = encode_entities Dumper $c->req;
294 my $res = encode_entities Dumper $c->res;
295 my $stash = encode_entities Dumper $c->stash;
298 <b><u>Request</u></b><br/>
300 <b><u>Response</u></b><br/>
302 <b><u>Stash</u></b><br/>
311 (en) Please come back later
312 (de) Bitte versuchen sie es spaeter nocheinmal
313 (nl) Gelieve te komen later terug
314 (no) Vennligst prov igjen senere
315 (fr) Veuillez revenir plus tard
316 (es) Vuelto por favor mas adelante
317 (pt) Voltado por favor mais tarde
318 (it) Ritornato prego piĆ¹ successivamente
323 $c->res->body( <<"" );
326 <title>$title</title>
327 <style type="text/css">
329 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
330 Tahoma, Arial, helvetica, sans-serif;
332 background-color: #eee;
337 background-color: #ccc;
338 border: 1px solid #aaa;
341 -moz-border-radius: 10px;
344 background-color: #977;
345 border: 1px solid #755;
349 -moz-border-radius: 10px;
352 background-color: #797;
353 border: 1px solid #575;
357 -moz-border-radius: 10px;
360 background-color: #779;
361 border: 1px solid #557;
364 -moz-border-radius: 10px;
370 <div class="error">$error</div>
371 <div class="infos">$infos</div>
372 <div class="name">$name</div>
379 =item $c->finalize_headers
385 sub finalize_headers { }
387 =item $c->handler( $class, @arguments )
394 my ( $class, @arguments ) = @_;
396 # Always expect worst case!
402 my $c = $class->prepare(@arguments);
403 $c->{stats} = \@stats;
408 if ( $class->debug ) {
410 ( $elapsed, $status ) = $class->benchmark($handler);
411 $elapsed = sprintf '%f', $elapsed;
412 my $av = sprintf '%.3f',
413 ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
414 my $t = Text::ASCIITable->new;
415 $t->setCols( 'Action', 'Time' );
416 $t->setColWidth( 'Action', 64, 1 );
417 $t->setColWidth( 'Time', 9, 1 );
419 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
420 $class->log->info( "Request took $elapsed" . "s ($av/s)",
423 else { $status = &$handler }
427 if ( my $error = $@ ) {
429 $class->log->error(qq/Caught exception in engine "$error"/);
436 =item $c->prepare(@arguments)
438 Turns the engine-specific request( Apache, CGI ... )
439 into a Catalyst context .
444 my ( $class, @arguments ) = @_;
449 request => Catalyst::Request->new(
453 headers => HTTP::Headers->new,
460 response => Catalyst::Response->new(
464 headers => HTTP::Headers->new( 'Content-Length' => 0 ),
473 my $secs = time - $START || 1;
474 my $av = sprintf '%.3f', $COUNT / $secs;
475 $c->log->debug('**********************************');
476 $c->log->debug("* Request $COUNT ($av/s) [$$]");
477 $c->log->debug('**********************************');
478 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
481 $c->prepare_request(@arguments);
482 $c->prepare_connection;
488 my $method = $c->req->method || '';
489 my $path = $c->req->path || '';
490 my $address = $c->req->address || '';
492 $c->log->debug(qq/"$method" request for "$path" from $address/)
495 if ( $c->request->method eq 'POST' and $c->request->content_length ) {
497 if ( $c->req->content_type eq 'application/x-www-form-urlencoded' ) {
498 $c->prepare_parameters;
500 elsif ( $c->req->content_type eq 'multipart/form-data' ) {
501 $c->prepare_parameters;
509 if ( $c->request->method eq 'GET' ) {
510 $c->prepare_parameters;
513 if ( $c->debug && keys %{ $c->req->params } ) {
514 my $t = Text::ASCIITable->new;
515 $t->setCols( 'Key', 'Value' );
516 $t->setColWidth( 'Key', 37, 1 );
517 $t->setColWidth( 'Value', 36, 1 );
518 for my $key ( sort keys %{ $c->req->params } ) {
519 my $param = $c->req->params->{$key};
520 my $value = defined($param) ? $param : '';
521 $t->addRow( $key, $value );
523 $c->log->debug( 'Parameters are', $t->draw );
529 =item $c->prepare_action
537 my $path = $c->req->path;
538 my @path = split /\//, $c->req->path;
539 $c->req->args( \my @args );
542 $path = join '/', @path;
543 if ( my $result = ${ $c->get_action($path) }[0] ) {
547 my $match = $result->[1];
548 my @snippets = @{ $result->[2] };
550 qq/Requested action is "$path" and matched "$match"/)
553 'Snippets are "' . join( ' ', @snippets ) . '"' )
554 if ( $c->debug && @snippets );
555 $c->req->action($match);
556 $c->req->snippets( \@snippets );
560 $c->req->action($path);
561 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
564 $c->req->match($path);
567 unshift @args, pop @path;
570 unless ( $c->req->action ) {
571 $c->req->action('default');
575 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
576 if ( $c->debug && @args );
579 =item $c->prepare_body
581 Prepare message body.
587 =item $c->prepare_connection
593 sub prepare_connection { }
595 =item $c->prepare_cookies
601 sub prepare_cookies {
604 if ( my $header = $c->request->header('Cookie') ) {
605 $c->req->cookies( { CGI::Cookie->parse($header) } );
609 =item $c->prepare_headers
615 sub prepare_headers { }
617 =item $c->prepare_parameters
623 sub prepare_parameters { }
625 =item $c->prepare_path
627 Prepare path and base.
633 =item $c->prepare_request
635 Prepare the engine request.
639 sub prepare_request { }
641 =item $c->prepare_uploads
647 sub prepare_uploads { }
661 Returns a C<Catalyst::Request> object.
669 Returns a C<Catalyst::Response> object.
684 # Initialize our data structure
685 $self->components( {} );
687 $self->setup_components;
689 if ( $self->debug ) {
690 my $t = Text::ASCIITable->new;
691 $t->setOptions( 'hide_HeadRow', 1 );
692 $t->setOptions( 'hide_HeadLine', 1 );
693 $t->setCols('Class');
694 $t->setColWidth( 'Class', 75, 1 );
695 $t->addRow($_) for sort keys %{ $self->components };
696 $self->log->debug( 'Loaded components', $t->draw )
697 if ( @{ $t->{tbl_rows} } );
700 # Add our self to components, since we are also a component
701 $self->components->{$self} = $self;
703 $self->setup_actions;
705 if ( $self->debug ) {
706 my $name = $self->config->{name} || 'Application';
707 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
711 =item $class->setup_components
717 sub setup_components {
721 my ( $component, $context ) = @_;
723 unless ( $component->isa('Catalyst::Base') ) {
727 my $suffix = Catalyst::Utils::class2classsuffix($component);
728 my $config = $self->config->{$suffix} || {};
732 eval { $instance = $component->new( $context, $config ); };
734 if ( my $error = $@ ) {
736 die qq/Couldn't instantiate component "$component", "$error"/;
743 Module::Pluggable::Fast->import(
744 name => '_components',
746 "$self\::Controller", "$self\::C",
747 "$self\::Model", "$self\::M",
748 "$self\::View", "$self\::V"
750 callback => $callback
754 if ( my $error = $@ ) {
756 die qq/Couldn't load components "$error"/;
759 for my $component ( $self->_components($self) ) {
760 $self->components->{ ref $component || $component } = $component;
766 Contains the return value of the last executed action.
770 Returns a hashref containing all your data.
772 $c->stash->{foo} ||= 'yada';
773 print $c->stash->{foo};
780 my $stash = @_ > 1 ? {@_} : $_[0];
781 while ( my ( $key, $val ) = each %$stash ) {
782 $self->{stash}->{$key} = $val;
785 return $self->{stash};
792 Sebastian Riedel, C<sri@cpan.org>
796 This program is free software, you can redistribute it and/or modify it under
797 the same terms as Perl itself.