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