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