1 package Catalyst::Engine;
4 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5 use UNIVERSAL::require;
10 use Time::HiRes qw/gettimeofday tv_interval/;
12 use Catalyst::Request;
13 use Catalyst::Request::Upload;
14 use Catalyst::Response;
16 require Module::Pluggable::Fast;
18 $Data::Dumper::Terse = 1;
20 __PACKAGE__->mk_classdata('components');
21 __PACKAGE__->mk_accessors(qw/request response state/);
32 Catalyst::Engine - The Catalyst Engine
44 =item $c->benchmark($coderef)
46 Takes a coderef with arguments and returns elapsed time as float.
48 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
49 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
56 my $time = [gettimeofday];
57 my @return = &$code(@_);
58 my $elapsed = tv_interval $time;
59 return wantarray ? ( $elapsed, @return ) : $elapsed;
64 =item $c->component($name)
66 Get a component object by name.
68 $c->comp('MyApp::Model::MyModel')->do_stuff;
70 Regex search for a component.
72 $c->comp('mymodel')->do_stuff;
77 my ( $c, $name ) = @_;
78 if ( my $component = $c->components->{$name} ) {
82 for my $component ( keys %{ $c->components } ) {
83 return $c->components->{$component} if $component =~ /$name/i;
90 =item $c->error($error, ...)
92 =item $c->error($arrayref)
94 Returns an arrayref containing error messages.
96 my @error = @{ $c->error };
100 $c->error('Something bad happened');
106 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
107 push @{ $c->{error} }, @$error;
111 =item $c->execute($class, $coderef)
113 Execute a coderef in given class and catch exceptions.
114 Errors are available via $c->error.
119 my ( $c, $class, $code ) = @_;
120 $class = $c->comp($class) || $class;
122 my $callsub = ( caller(1) )[3];
126 my $action = $c->actions->{reverse}->{"$code"};
127 $action = "/$action" unless $action =~ /\-\>/;
128 $action = "-> $action" if $callsub =~ /forward$/;
129 my ( $elapsed, @state ) =
130 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
131 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
134 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
136 if ( my $error = $@ ) {
138 unless ( ref $error ) {
140 $error = qq/Caught exception "$error"/;
143 $c->log->error($error);
159 $c->finalize_cookies;
161 if ( my $location = $c->response->redirect ) {
162 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
163 $c->response->header( Location => $location );
164 $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
167 if ( $#{ $c->error } >= 0 ) {
171 if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
175 if ( $c->response->output && !$c->response->content_length ) {
176 use bytes; # play safe with a utf8 aware perl
177 $c->response->content_length( length $c->response->output );
180 my $status = $c->finalize_headers;
185 =item $c->finalize_cookies
191 sub finalize_cookies {
194 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
195 my $cookie = CGI::Cookie->new(
197 -value => $cookie->{value},
198 -expires => $cookie->{expires},
199 -domain => $cookie->{domain},
200 -path => $cookie->{path},
201 -secure => $cookie->{secure} || 0
204 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
208 =item $c->finalize_error
217 $c->res->headers->content_type('text/html');
218 my $name = $c->config->{name} || 'Catalyst Application';
220 my ( $title, $error, $infos );
222 $error = join '<br/>', @{ $c->error };
223 $error ||= 'No output';
224 $title = $name = "$name on Catalyst $Catalyst::VERSION";
225 my $req = encode_entities Dumper $c->req;
226 my $res = encode_entities Dumper $c->res;
227 my $stash = encode_entities Dumper $c->stash;
230 <b><u>Request</u></b><br/>
232 <b><u>Response</u></b><br/>
234 <b><u>Stash</u></b><br/>
243 (en) Please come back later
244 (de) Bitte versuchen sie es spaeter nocheinmal
245 (nl) Gelieve te komen later terug
246 (no) Vennligst prov igjen senere
247 (fr) Veuillez revenir plus tard
248 (es) Vuelto por favor mas adelante
249 (pt) Voltado por favor mais tarde
250 (it) Ritornato prego piĆ¹ successivamente
255 $c->res->output( <<"" );
258 <title>$title</title>
259 <style type="text/css">
261 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
262 Tahoma, Arial, helvetica, sans-serif;
264 background-color: #eee;
269 background-color: #ccc;
270 border: 1px solid #aaa;
273 -moz-border-radius: 10px;
276 background-color: #977;
277 border: 1px solid #755;
281 -moz-border-radius: 10px;
284 background-color: #797;
285 border: 1px solid #575;
289 -moz-border-radius: 10px;
292 background-color: #779;
293 border: 1px solid #557;
296 -moz-border-radius: 10px;
302 <div class="error">$error</div>
303 <div class="infos">$infos</div>
304 <div class="name">$name</div>
311 =item $c->finalize_headers
317 sub finalize_headers { }
319 =item $c->finalize_output
325 sub finalize_output { }
327 =item $c->handler( $class, $r )
334 my ( $class, $engine ) = @_;
336 # Always expect worst case!
341 my $c = $class->prepare($engine);
342 $c->{stats} = \@stats;
346 if ( $class->debug ) {
348 ( $elapsed, $status ) = $class->benchmark($handler);
349 $elapsed = sprintf '%f', $elapsed;
350 my $av = sprintf '%.3f', 1 / $elapsed;
351 my $t = Text::ASCIITable->new;
352 $t->setCols( 'Action', 'Time' );
353 $t->setColWidth( 'Action', 64, 1 );
354 $t->setColWidth( 'Time', 9, 1 );
356 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
357 $class->log->info( "Request took $elapsed" . "s ($av/s)",
360 else { $status = &$handler }
362 if ( my $error = $@ ) {
364 $class->log->error(qq/Caught exception in engine "$error"/);
370 =item $c->prepare($r)
372 Turns the engine-specific request( Apache, CGI ... )
373 into a Catalyst context .
378 my ( $class, $r ) = @_;
380 request => Catalyst::Request->new(
384 headers => HTTP::Headers->new,
390 response => Catalyst::Response->new(
391 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
397 my $secs = time - $START || 1;
398 my $av = sprintf '%.3f', $COUNT / $secs;
399 $c->log->debug('**********************************');
400 $c->log->debug("* Request $COUNT ($av/s) [$$]");
401 $c->log->debug('**********************************');
402 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
404 $c->prepare_request($r);
408 $c->prepare_connection;
409 my $method = $c->req->method || '';
410 my $path = $c->req->path || '';
411 my $hostname = $c->req->hostname || '';
412 my $address = $c->req->address || '';
413 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
416 $c->prepare_parameters;
418 if ( $c->debug && keys %{ $c->req->params } ) {
419 my $t = Text::ASCIITable->new;
420 $t->setCols( 'Key', 'Value' );
421 $t->setColWidth( 'Key', 37, 1 );
422 $t->setColWidth( 'Value', 36, 1 );
423 for my $key ( keys %{ $c->req->params } ) {
424 my $value = $c->req->params->{$key} || '';
425 $t->addRow( $key, $value );
427 $c->log->debug( 'Parameters are', $t->draw );
433 =item $c->prepare_action
441 my $path = $c->req->path;
442 my @path = split /\//, $c->req->path;
443 $c->req->args( \my @args );
445 $path = join '/', @path;
446 if ( my $result = ${ $c->get_action($path) }[0] ) {
450 my $match = $result->[1];
451 my @snippets = @{ $result->[2] };
453 qq/Requested action is "$path" and matched "$match"/)
456 'Snippets are "' . join( ' ', @snippets ) . '"' )
457 if ( $c->debug && @snippets );
458 $c->req->action($match);
459 $c->req->snippets( \@snippets );
462 $c->req->action($path);
463 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
465 $c->req->match($path);
468 unshift @args, pop @path;
470 unless ( $c->req->action ) {
471 $c->req->action('default');
474 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
475 if ( $c->debug && @args );
478 =item $c->prepare_connection
484 sub prepare_connection { }
486 =item $c->prepare_cookies
492 sub prepare_cookies {
495 if ( my $header = $c->request->header('Cookie') ) {
496 $c->req->cookies( { CGI::Cookie->parse($header) } );
500 =item $c->prepare_headers
506 sub prepare_headers { }
508 =item $c->prepare_parameters
514 sub prepare_parameters { }
516 =item $c->prepare_path
518 Prepare path and base.
524 =item $c->prepare_request
526 Prepare the engine request.
530 sub prepare_request { }
532 =item $c->prepare_uploads
538 sub prepare_uploads { }
552 Returns a C<Catalyst::Request> object.
560 Returns a C<Catalyst::Response> object.
574 $self->setup_components;
575 if ( $self->debug ) {
576 my $name = $self->config->{name} || 'Application';
577 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
581 =item $class->setup_components
587 sub setup_components {
591 my $class = ref $self || $self;
594 import Module::Pluggable::Fast
595 name => '_components',
597 '$class\::Controller', '$class\::C',
598 '$class\::Model', '$class\::M',
599 '$class\::View', '$class\::V'
602 if ( my $error = $@ ) {
605 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
607 $self->components( {} );
609 for my $comp ( $self->_components($self) ) {
610 $self->components->{ ref $comp } = $comp;
613 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
614 $t->setCols('Class');
615 $t->setColWidth( 'Class', 75, 1 );
616 $t->addRow($_) for keys %{ $self->components };
617 $self->log->debug( 'Loaded components', $t->draw )
618 if ( @{ $t->{tbl_rows} } && $self->debug );
619 $self->setup_actions( [ $self, @comps ] );
624 Contains the return value of the last executed action.
628 Returns a hashref containing all your data.
630 $c->stash->{foo} ||= 'yada';
631 print $c->stash->{foo};
638 my $stash = $_[1] ? {@_} : $_[0];
639 while ( my ( $key, $val ) = each %$stash ) {
640 $self->{stash}->{$key} = $val;
643 return $self->{stash};
650 Sebastian Riedel, C<sri@cpan.org>
654 This program is free software, you can redistribute it and/or modify it under
655 the same terms as Perl itself.