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