Fixed: builtin actions are private too (Tutorial Docs)
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
424b2705 4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
d70195d8 5use attributes ();
fc7ec1d9 6use UNIVERSAL::require;
6dc87a0f 7use CGI::Cookie;
fc7ec1d9 8use Data::Dumper;
9use HTML::Entities;
10use HTTP::Headers;
11use Time::HiRes qw/gettimeofday tv_interval/;
0f7ecc53 12use Text::ASCIITable;
fc7ec1d9 13use Catalyst::Request;
146554c5 14use Catalyst::Request::Upload;
fc7ec1d9 15use Catalyst::Response;
16
17require Module::Pluggable::Fast;
18
99fe1710 19# For pretty dumps
fc7ec1d9 20$Data::Dumper::Terse = 1;
21
1abd6db7 22__PACKAGE__->mk_classdata('components');
b768faa3 23__PACKAGE__->mk_accessors(qw/request response state/);
fc7ec1d9 24
fc7ec1d9 25*comp = \&component;
26*req = \&request;
27*res = \&response;
28
99fe1710 29# For statistics
fc7ec1d9 30our $COUNT = 1;
31our $START = time;
32
33=head1 NAME
34
35Catalyst::Engine - The Catalyst Engine
36
37=head1 SYNOPSIS
38
39See L<Catalyst>.
40
41=head1 DESCRIPTION
42
23f9d934 43=head1 METHODS
fc7ec1d9 44
23f9d934 45=over 4
46
23f9d934 47=item $c->benchmark($coderef)
fc7ec1d9 48
49Takes a coderef with arguments and returns elapsed time as float.
50
51 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
52 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
53
54=cut
55
56sub benchmark {
57 my $c = shift;
58 my $code = shift;
59 my $time = [gettimeofday];
60 my @return = &$code(@_);
61 my $elapsed = tv_interval $time;
62 return wantarray ? ( $elapsed, @return ) : $elapsed;
63}
64
23f9d934 65=item $c->comp($name)
66
67=item $c->component($name)
fc7ec1d9 68
69Get a component object by name.
70
71 $c->comp('MyApp::Model::MyModel')->do_stuff;
72
73Regex search for a component.
74
75 $c->comp('mymodel')->do_stuff;
76
77=cut
78
79sub component {
80 my ( $c, $name ) = @_;
99fe1710 81
fc7ec1d9 82 if ( my $component = $c->components->{$name} ) {
83 return $component;
84 }
99fe1710 85
fc7ec1d9 86 else {
87 for my $component ( keys %{ $c->components } ) {
88 return $c->components->{$component} if $component =~ /$name/i;
89 }
90 }
99fe1710 91
fc7ec1d9 92}
93
a554cc3b 94=item $c->error
23f9d934 95
a554cc3b 96=item $c->error($error, ...)
23f9d934 97
a554cc3b 98=item $c->error($arrayref)
fc7ec1d9 99
a554cc3b 100Returns an arrayref containing error messages.
fc7ec1d9 101
a554cc3b 102 my @error = @{ $c->error };
fc7ec1d9 103
104Add a new error.
105
a554cc3b 106 $c->error('Something bad happened');
fc7ec1d9 107
108=cut
109
a554cc3b 110sub error {
fc7ec1d9 111 my $c = shift;
a554cc3b 112 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
113 push @{ $c->{error} }, @$error;
114 return $c->{error};
fc7ec1d9 115}
116
6dc87a0f 117=item $c->execute($class, $coderef)
118
119Execute a coderef in given class and catch exceptions.
120Errors are available via $c->error.
121
122=cut
123
124sub execute {
125 my ( $c, $class, $code ) = @_;
126 $class = $c->comp($class) || $class;
127 $c->state(0);
39de91b0 128 my $callsub = ( caller(1) )[3];
99fe1710 129
6dc87a0f 130 eval {
131 if ( $c->debug )
132 {
133 my $action = $c->actions->{reverse}->{"$code"};
134 $action = "/$action" unless $action =~ /\-\>/;
fb13403c 135 $action = "-> $action" if $callsub =~ /forward$/;
6dc87a0f 136 my ( $elapsed, @state ) =
137 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
0f7ecc53 138 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
6dc87a0f 139 $c->state(@state);
140 }
141 else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
142 };
99fe1710 143
6dc87a0f 144 if ( my $error = $@ ) {
b9ffe28b 145
146 unless ( ref $error ) {
147 chomp $error;
148 $error = qq/Caught exception "$error"/;
149 }
150
6dc87a0f 151 $c->log->error($error);
b9ffe28b 152 $c->error($error);
6dc87a0f 153 $c->state(0);
154 }
155 return $c->state;
156}
157
23f9d934 158=item $c->finalize
fc7ec1d9 159
ca39d576 160Finalize request.
fc7ec1d9 161
162=cut
163
164sub finalize {
165 my $c = shift;
23f9d934 166
6dc87a0f 167 $c->finalize_cookies;
168
49490aab 169 if ( my $location = $c->response->redirect ) {
23f9d934 170 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
6dc87a0f 171 $c->response->header( Location => $location );
e7c0c583 172 $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
6dc87a0f 173 }
174
969647fd 175 if ( $#{ $c->error } >= 0 ) {
176 $c->finalize_error;
23f9d934 177 }
178
36b3abcb 179 if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
969647fd 180 $c->finalize_error;
181 }
fc7ec1d9 182
c4695f3a 183 if ( $c->response->output && !$c->response->content_length ) {
39de91b0 184 use bytes; # play safe with a utf8 aware perl
49490aab 185 $c->response->content_length( length $c->response->output );
fc7ec1d9 186 }
969647fd 187
fc7ec1d9 188 my $status = $c->finalize_headers;
189 $c->finalize_output;
190 return $status;
191}
192
6dc87a0f 193=item $c->finalize_cookies
194
195Finalize cookies.
196
197=cut
198
199sub finalize_cookies {
200 my $c = shift;
201
202 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
203 my $cookie = CGI::Cookie->new(
204 -name => $name,
205 -value => $cookie->{value},
206 -expires => $cookie->{expires},
207 -domain => $cookie->{domain},
208 -path => $cookie->{path},
209 -secure => $cookie->{secure} || 0
210 );
211
212 $c->res->headers->push_header( 'Set-Cookie' => $cookie->as_string );
213 }
214}
215
969647fd 216=item $c->finalize_error
217
ca39d576 218Finalize error.
969647fd 219
220=cut
221
222sub finalize_error {
223 my $c = shift;
224
225 $c->res->headers->content_type('text/html');
226 my $name = $c->config->{name} || 'Catalyst Application';
227
228 my ( $title, $error, $infos );
229 if ( $c->debug ) {
230 $error = join '<br/>', @{ $c->error };
231 $error ||= 'No output';
232 $title = $name = "$name on Catalyst $Catalyst::VERSION";
233 my $req = encode_entities Dumper $c->req;
234 my $res = encode_entities Dumper $c->res;
235 my $stash = encode_entities Dumper $c->stash;
236 $infos = <<"";
237<br/>
238<b><u>Request</u></b><br/>
239<pre>$req</pre>
240<b><u>Response</u></b><br/>
241<pre>$res</pre>
242<b><u>Stash</u></b><br/>
243<pre>$stash</pre>
244
245 }
246 else {
247 $title = $name;
248 $error = '';
249 $infos = <<"";
250<pre>
251(en) Please come back later
252(de) Bitte versuchen sie es spaeter nocheinmal
253(nl) Gelieve te komen later terug
254(no) Vennligst prov igjen senere
255(fr) Veuillez revenir plus tard
256(es) Vuelto por favor mas adelante
257(pt) Voltado por favor mais tarde
258(it) Ritornato prego più successivamente
259</pre>
260
261 $name = '';
262 }
263 $c->res->output( <<"" );
264<html>
265<head>
266 <title>$title</title>
267 <style type="text/css">
268 body {
269 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
270 Tahoma, Arial, helvetica, sans-serif;
271 color: #ddd;
272 background-color: #eee;
273 margin: 0px;
274 padding: 0px;
275 }
276 div.box {
277 background-color: #ccc;
278 border: 1px solid #aaa;
279 padding: 4px;
280 margin: 10px;
281 -moz-border-radius: 10px;
282 }
283 div.error {
284 background-color: #977;
285 border: 1px solid #755;
286 padding: 8px;
287 margin: 4px;
288 margin-bottom: 10px;
289 -moz-border-radius: 10px;
290 }
291 div.infos {
292 background-color: #797;
293 border: 1px solid #575;
294 padding: 8px;
295 margin: 4px;
296 margin-bottom: 10px;
297 -moz-border-radius: 10px;
298 }
299 div.name {
300 background-color: #779;
301 border: 1px solid #557;
302 padding: 8px;
303 margin: 4px;
304 -moz-border-radius: 10px;
305 }
306 </style>
307</head>
308<body>
309 <div class="box">
310 <div class="error">$error</div>
311 <div class="infos">$infos</div>
312 <div class="name">$name</div>
313 </div>
314</body>
315</html>
316
317}
318
23f9d934 319=item $c->finalize_headers
fc7ec1d9 320
ca39d576 321Finalize headers.
fc7ec1d9 322
323=cut
324
325sub finalize_headers { }
326
23f9d934 327=item $c->finalize_output
fc7ec1d9 328
ca39d576 329Finalize output.
fc7ec1d9 330
331=cut
332
333sub finalize_output { }
334
b76d7db8 335=item $c->handler( $class, $r )
fc7ec1d9 336
ca39d576 337Handles the request.
fc7ec1d9 338
339=cut
340
6dc87a0f 341sub handler {
342 my ( $class, $engine ) = @_;
fc7ec1d9 343
344 # Always expect worst case!
345 my $status = -1;
346 eval {
d41516b2 347 my @stats = ();
99fe1710 348
fc7ec1d9 349 my $handler = sub {
6dc87a0f 350 my $c = $class->prepare($engine);
d41516b2 351 $c->{stats} = \@stats;
63b763c5 352 $c->dispatch;
fc7ec1d9 353 return $c->finalize;
354 };
99fe1710 355
fc7ec1d9 356 if ( $class->debug ) {
357 my $elapsed;
358 ( $elapsed, $status ) = $class->benchmark($handler);
359 $elapsed = sprintf '%f', $elapsed;
360 my $av = sprintf '%.3f', 1 / $elapsed;
0f7ecc53 361 my $t = Text::ASCIITable->new;
362 $t->setCols( 'Action', 'Time' );
3f36a3a3 363 $t->setColWidth( 'Action', 64, 1 );
364 $t->setColWidth( 'Time', 9, 1 );
0822f9a4 365
cd677e12 366 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
0f7ecc53 367 $class->log->info( "Request took $elapsed" . "s ($av/s)",
368 $t->draw );
fc7ec1d9 369 }
370 else { $status = &$handler }
99fe1710 371
fc7ec1d9 372 };
99fe1710 373
fc7ec1d9 374 if ( my $error = $@ ) {
375 chomp $error;
376 $class->log->error(qq/Caught exception in engine "$error"/);
377 }
99fe1710 378
fc7ec1d9 379 $COUNT++;
380 return $status;
381}
382
23f9d934 383=item $c->prepare($r)
fc7ec1d9 384
a554cc3b 385Turns the engine-specific request( Apache, CGI ... )
386into a Catalyst context .
fc7ec1d9 387
388=cut
389
390sub prepare {
391 my ( $class, $r ) = @_;
99fe1710 392
fc7ec1d9 393 my $c = bless {
394 request => Catalyst::Request->new(
395 {
396 arguments => [],
397 cookies => {},
398 headers => HTTP::Headers->new,
399 parameters => {},
400 snippets => [],
401 uploads => {}
402 }
403 ),
404 response => Catalyst::Response->new(
405 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
406 ),
b768faa3 407 stash => {},
408 state => 0
fc7ec1d9 409 }, $class;
99fe1710 410
fc7ec1d9 411 if ( $c->debug ) {
412 my $secs = time - $START || 1;
413 my $av = sprintf '%.3f', $COUNT / $secs;
1a0250cb 414 $c->log->debug('**********************************');
fc7ec1d9 415 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1a0250cb 416 $c->log->debug('**********************************');
fc7ec1d9 417 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
418 }
99fe1710 419
fc7ec1d9 420 $c->prepare_request($r);
421 $c->prepare_path;
ac733264 422 $c->prepare_headers;
61bacdcc 423 $c->prepare_input;
1a80619d 424 $c->prepare_cookies;
0556eb49 425 $c->prepare_connection;
99fe1710 426
0556eb49 427 my $method = $c->req->method || '';
428 my $path = $c->req->path || '';
429 my $hostname = $c->req->hostname || '';
430 my $address = $c->req->address || '';
431 $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
432 if $c->debug;
99fe1710 433
fc7ec1d9 434 $c->prepare_action;
435 $c->prepare_parameters;
c85ff642 436
437 if ( $c->debug && keys %{ $c->req->params } ) {
0f7ecc53 438 my $t = Text::ASCIITable->new;
439 $t->setCols( 'Key', 'Value' );
0822f9a4 440 $t->setColWidth( 'Key', 37, 1 );
441 $t->setColWidth( 'Value', 36, 1 );
c85ff642 442 for my $key ( keys %{ $c->req->params } ) {
b5524568 443 my $value = $c->req->params->{$key} || '';
cd677e12 444 $t->addRow( $key, $value );
c85ff642 445 }
0f7ecc53 446 $c->log->debug( 'Parameters are', $t->draw );
c85ff642 447 }
99fe1710 448
fc7ec1d9 449 $c->prepare_uploads;
450 return $c;
451}
452
23f9d934 453=item $c->prepare_action
fc7ec1d9 454
ca39d576 455Prepare action.
fc7ec1d9 456
457=cut
458
459sub prepare_action {
460 my $c = shift;
461 my $path = $c->req->path;
462 my @path = split /\//, $c->req->path;
463 $c->req->args( \my @args );
99fe1710 464
fc7ec1d9 465 while (@path) {
7833fdfc 466 $path = join '/', @path;
0169d3a8 467 if ( my $result = ${ $c->get_action($path) }[0] ) {
fc7ec1d9 468
469 # It's a regex
470 if ($#$result) {
7e5adedd 471 my $match = $result->[1];
472 my @snippets = @{ $result->[2] };
81f6fc50 473 $c->log->debug(
474 qq/Requested action is "$path" and matched "$match"/)
fc7ec1d9 475 if $c->debug;
476 $c->log->debug(
477 'Snippets are "' . join( ' ', @snippets ) . '"' )
478 if ( $c->debug && @snippets );
479 $c->req->action($match);
480 $c->req->snippets( \@snippets );
481 }
99fe1710 482
fc7ec1d9 483 else {
484 $c->req->action($path);
81f6fc50 485 $c->log->debug(qq/Requested action is "$path"/) if $c->debug;
fc7ec1d9 486 }
99fe1710 487
fc7ec1d9 488 $c->req->match($path);
fc7ec1d9 489 last;
490 }
491 unshift @args, pop @path;
492 }
99fe1710 493
fc7ec1d9 494 unless ( $c->req->action ) {
ac733264 495 $c->req->action('default');
87e67021 496 $c->req->match('');
fc7ec1d9 497 }
99fe1710 498
5783a9a5 499 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
500 if ( $c->debug && @args );
fc7ec1d9 501}
502
c9afa5fc 503=item $c->prepare_connection
0556eb49 504
ca39d576 505Prepare connection.
0556eb49 506
507=cut
508
509sub prepare_connection { }
510
c9afa5fc 511=item $c->prepare_cookies
fc7ec1d9 512
ca39d576 513Prepare cookies.
fc7ec1d9 514
515=cut
516
6dc87a0f 517sub prepare_cookies {
518 my $c = shift;
519
520 if ( my $header = $c->request->header('Cookie') ) {
521 $c->req->cookies( { CGI::Cookie->parse($header) } );
522 }
523}
fc7ec1d9 524
23f9d934 525=item $c->prepare_headers
fc7ec1d9 526
ca39d576 527Prepare headers.
fc7ec1d9 528
529=cut
530
531sub prepare_headers { }
532
23f9d934 533=item $c->prepare_parameters
fc7ec1d9 534
ca39d576 535Prepare parameters.
fc7ec1d9 536
537=cut
538
61bacdcc 539sub prepare_input { }
540
541=item $c->prepare_input
542
543Prepare message body.
544
545=cut
546
fc7ec1d9 547sub prepare_parameters { }
548
23f9d934 549=item $c->prepare_path
fc7ec1d9 550
ca39d576 551Prepare path and base.
fc7ec1d9 552
553=cut
554
555sub prepare_path { }
556
23f9d934 557=item $c->prepare_request
fc7ec1d9 558
ca39d576 559Prepare the engine request.
fc7ec1d9 560
561=cut
562
563sub prepare_request { }
564
23f9d934 565=item $c->prepare_uploads
fc7ec1d9 566
ca39d576 567Prepare uploads.
fc7ec1d9 568
569=cut
570
571sub prepare_uploads { }
572
c9afa5fc 573=item $c->run
574
ca39d576 575Starts the engine.
c9afa5fc 576
577=cut
578
579sub run { }
580
61b1e958 581=item $c->request
fc7ec1d9 582
ca39d576 583=item $c->req
23f9d934 584
ca39d576 585Returns a C<Catalyst::Request> object.
fc7ec1d9 586
ca39d576 587 my $req = $c->req;
61b1e958 588
589=item $c->response
590
ca39d576 591=item $c->res
592
fc7ec1d9 593Returns a C<Catalyst::Response> object.
594
595 my $res = $c->res;
596
23f9d934 597=item $class->setup
fc7ec1d9 598
ca39d576 599Setup.
fc7ec1d9 600
601 MyApp->setup;
602
603=cut
604
605sub setup {
606 my $self = shift;
607 $self->setup_components;
608 if ( $self->debug ) {
609 my $name = $self->config->{name} || 'Application';
610 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
611 }
612}
613
23f9d934 614=item $class->setup_components
fc7ec1d9 615
ca39d576 616Setup components.
fc7ec1d9 617
618=cut
619
620sub setup_components {
621 my $self = shift;
622
623 # Components
624 my $class = ref $self || $self;
625 eval <<"";
626 package $class;
627 import Module::Pluggable::Fast
628 name => '_components',
629 search => [
630 '$class\::Controller', '$class\::C',
631 '$class\::Model', '$class\::M',
632 '$class\::View', '$class\::V'
633 ];
634
635 if ( my $error = $@ ) {
636 chomp $error;
f88238ea 637 die qq/Couldn't load components "$error"/;
fc7ec1d9 638 }
99fe1710 639
fc7ec1d9 640 $self->components( {} );
1abd6db7 641 my @comps;
ac733264 642 for my $comp ( $self->_components($self) ) {
643 $self->components->{ ref $comp } = $comp;
1abd6db7 644 push @comps, $comp;
4cf083b1 645 }
99fe1710 646
5fbed090 647 my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
648 $t->setCols('Class');
649 $t->setColWidth( 'Class', 75, 1 );
cd677e12 650 $t->addRow($_) for keys %{ $self->components };
5fbed090 651 $self->log->debug( 'Loaded components', $t->draw )
652 if ( @{ $t->{tbl_rows} } && $self->debug );
99fe1710 653
1abd6db7 654 $self->setup_actions( [ $self, @comps ] );
fc7ec1d9 655}
656
63b763c5 657=item $c->state
658
659Contains the return value of the last executed action.
660
23f9d934 661=item $c->stash
fc7ec1d9 662
ca39d576 663Returns a hashref containing all your data.
fc7ec1d9 664
665 $c->stash->{foo} ||= 'yada';
666 print $c->stash->{foo};
667
668=cut
669
670sub stash {
671 my $self = shift;
672 if ( $_[0] ) {
673 my $stash = $_[1] ? {@_} : $_[0];
674 while ( my ( $key, $val ) = each %$stash ) {
675 $self->{stash}->{$key} = $val;
676 }
677 }
678 return $self->{stash};
679}
680
23f9d934 681=back
682
fc7ec1d9 683=head1 AUTHOR
684
685Sebastian Riedel, C<sri@cpan.org>
686
687=head1 COPYRIGHT
688
689This program is free software, you can redistribute it and/or modify it under
690the same terms as Perl itself.
691
692=cut
693
6941;