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