debug message for params
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
4use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
5use UNIVERSAL::require;
fc7ec1d9 6use Data::Dumper;
7use HTML::Entities;
8use HTTP::Headers;
9use Time::HiRes qw/gettimeofday tv_interval/;
10use Catalyst::Request;
11use Catalyst::Response;
12
13require Module::Pluggable::Fast;
14
15$Data::Dumper::Terse = 1;
16
17__PACKAGE__->mk_classdata($_) for qw/actions components/;
18__PACKAGE__->mk_accessors(qw/request response/);
19
20__PACKAGE__->actions(
21 { plain => {}, regex => {}, compiled => {}, reverse => {} } );
22
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
40=head2 METHODS
41
42=head3 action
43
44Add one or more actions.
45
46 $c->action( '!foo' => sub { $_[1]->res->output('Foo!') } );
47
48Get an action's class and coderef.
49
50 my ($class, $code) = @{ $c->action('foo') };
51
52Get a list of available actions.
53
54 my @actions = $c->action;
55
56It also automatically calls setup() if needed.
57
58See L<Catalyst::Manual::Intro> for more informations about actions.
59
60=cut
61
62sub action {
63 my $self = shift;
64 $self->setup unless $self->components;
65 $self->actions( {} ) unless $self->actions;
66 my $action;
67 $_[1] ? ( $action = {@_} ) : ( $action = shift );
68 if ( ref $action eq 'HASH' ) {
69 while ( my ( $name, $code ) = each %$action ) {
7833fdfc 70 my $class = caller(0);
fc7ec1d9 71 if ( $name =~ /^\/(.*)\/$/ ) {
72 my $regex = $1;
73 $self->actions->{compiled}->{qr/$regex/} = $name;
74 $self->actions->{regex}->{$name} = [ $class, $code ];
75 }
76 elsif ( $name =~ /^\?(.*)$/ ) {
77 $name = $1;
7833fdfc 78 $name = _prefix( $class, $name );
fc7ec1d9 79 $self->actions->{plain}->{$name} = [ $class, $code ];
80 }
81 elsif ( $name =~ /^\!\?(.*)$/ ) {
82 $name = $1;
7833fdfc 83 $name = _prefix( $class, $name );
fc7ec1d9 84 $name = "\!$name";
85 $self->actions->{plain}->{$name} = [ $class, $code ];
86 }
87 else { $self->actions->{plain}->{$name} = [ $class, $code ] }
88 $self->actions->{reverse}->{"$code"} = $name;
7833fdfc 89 $self->log->debug(qq/"$class" defined "$name" as "$code"/)
fc7ec1d9 90 if $self->debug;
91 }
92 }
93 elsif ($action) {
94 if ( my $p = $self->actions->{plain}->{$action} ) { return [$p] }
95 elsif ( my $r = $self->actions->{regex}->{$action} ) { return [$r] }
96 else {
5783a9a5 97 for my $regex ( keys %{ $self->actions->{compiled} } ) {
98 my $name = $self->actions->{compiled}->{$regex};
fc7ec1d9 99 if ( $action =~ $regex ) {
100 my @snippets;
101 for my $i ( 1 .. 9 ) {
102 no strict 'refs';
103 last unless ${$i};
104 push @snippets, ${$i};
105 }
7e5adedd 106 return [ $self->actions->{regex}->{$name},
107 $name, \@snippets ];
fc7ec1d9 108 }
109 }
110 }
111 return 0;
112 }
113 else {
114 return (
115 keys %{ $self->actions->{plain} },
116 keys %{ $self->actions->{regex} }
117 );
118 }
119}
120
121=head3 benchmark
122
123Takes a coderef with arguments and returns elapsed time as float.
124
125 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
126 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
127
128=cut
129
130sub benchmark {
131 my $c = shift;
132 my $code = shift;
133 my $time = [gettimeofday];
134 my @return = &$code(@_);
135 my $elapsed = tv_interval $time;
136 return wantarray ? ( $elapsed, @return ) : $elapsed;
137}
138
748161c4 139=head3 component / comp
fc7ec1d9 140
141Get a component object by name.
142
143 $c->comp('MyApp::Model::MyModel')->do_stuff;
144
145Regex search for a component.
146
147 $c->comp('mymodel')->do_stuff;
148
149=cut
150
151sub component {
152 my ( $c, $name ) = @_;
153 if ( my $component = $c->components->{$name} ) {
154 return $component;
155 }
156 else {
157 for my $component ( keys %{ $c->components } ) {
158 return $c->components->{$component} if $component =~ /$name/i;
159 }
160 }
161}
162
163=head3 errors
164
165Returns an arrayref containing errors messages.
166
167 my @errors = @{ $c->errors };
168
169Add a new error.
170
171 $c->errors('Something bad happened');
172
173=cut
174
175sub errors {
176 my $c = shift;
177 my $errors = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
178 push @{ $c->{errors} }, @$errors;
179 return $c->{errors};
180}
181
182=head3 finalize
183
184Finalize request.
185
186=cut
187
188sub finalize {
189 my $c = shift;
190 if ( !$c->res->output || $#{ $c->errors } >= 0 ) {
191 $c->res->headers->content_type('text/html');
192 my $name = $c->config->{name} || 'Catalyst Application';
193 my ( $title, $errors, $infos );
194 if ( $c->debug ) {
195 $errors = join '<br/>', @{ $c->errors };
196 $errors ||= 'No output';
197 $title = $name = "$name on Catalyst $Catalyst::VERSION";
198 my $req = encode_entities Dumper $c->req;
199 my $res = encode_entities Dumper $c->res;
200 my $stash = encode_entities Dumper $c->stash;
201 $infos = <<"";
202<br/>
203<b><u>Request</u></b><br/>
204<pre>$req</pre>
205<b><u>Response</u></b><br/>
206<pre>$res</pre>
207<b><u>Stash</u></b><br/>
208<pre>$stash</pre>
209
210 }
211 else {
212 $title = $name;
213 $errors = '';
214 $infos = <<"";
215<pre>
216(en) Please come back later
217(de) Bitte versuchen sie es spaeter nocheinmal
218(nl) Gelieve te komen later terug
219(no) Vennligst prov igjen senere
220(fr) Veuillez revenir plus tard
221(es) Vuelto por favor mas adelante
222(pt) Voltado por favor mais tarde
223(it) Ritornato prego più successivamente
224</pre>
225
226 $name = '';
227 }
228 $c->res->{output} = <<"";
229<html>
230 <head>
231 <title>$title</title>
232 <style type="text/css">
233 body {
234 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
235 Tahoma, Arial, helvetica, sans-serif;
236 color: #ddd;
237 background-color: #eee;
238 margin: 0px;
239 padding: 0px;
240 }
241 div.box {
242 background-color: #ccc;
243 border: 1px solid #aaa;
244 padding: 4px;
245 margin: 10px;
246 -moz-border-radius: 10px;
247 }
248 div.errors {
249 background-color: #977;
250 border: 1px solid #755;
251 padding: 8px;
252 margin: 4px;
253 margin-bottom: 10px;
254 -moz-border-radius: 10px;
255 }
256 div.infos {
257 background-color: #797;
258 border: 1px solid #575;
259 padding: 8px;
260 margin: 4px;
261 margin-bottom: 10px;
262 -moz-border-radius: 10px;
263 }
264 div.name {
265 background-color: #779;
266 border: 1px solid #557;
267 padding: 8px;
268 margin: 4px;
269 -moz-border-radius: 10px;
270 }
271 </style>
272 </head>
273 <body>
274 <div class="box">
275 <div class="errors">$errors</div>
276 <div class="infos">$infos</div>
277 <div class="name">$name</div>
278 </div>
279 </body>
280</html>
281
282 }
283 if ( my $location = $c->res->redirect ) {
284 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
285 $c->res->headers->header( Location => $location );
286 $c->res->status(302);
287 }
288 $c->res->headers->content_length( length $c->res->output );
289 my $status = $c->finalize_headers;
290 $c->finalize_output;
291 return $status;
292}
293
294=head3 finalize_headers
295
296Finalize headers.
297
298=cut
299
300sub finalize_headers { }
301
302=head3 finalize_output
303
304Finalize output.
305
306=cut
307
308sub finalize_output { }
309
310=head3 forward
311
312Forward processing to a private/public action or a method from a class.
313If you define a class without method it will default to process().
314
315 $c->forward('!foo');
316 $c->forward('index.html');
317 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
318 $c->forward('MyApp::View::TT');
319
320=cut
321
322sub forward {
323 my $c = shift;
324 my $command = shift;
325 unless ($command) {
326 $c->log->debug('Nothing to forward to') if $c->debug;
327 return 0;
328 }
329 if ( $command =~ /^\?(.*)$/ ) {
330 $command = $1;
331 my $caller = caller(0);
332 $command = _prefix( $caller, $command );
333 }
334 elsif ( $command =~ /^\!\?(.*)$/ ) {
335 $command = $1;
336 my $caller = caller(0);
337 $command = _prefix( $caller, $command );
338 $command = "\!$command";
339 }
7242d068 340 elsif ( $command =~ /^\!(.*)$/ ) {
341 my $try = $1;
342 my $caller = caller(0);
343 my $prefix = _class2prefix($caller);
344 $try = "!$prefix/$command";
345 $command = $try if $c->actions->{plain}->{$try};
346 }
fc7ec1d9 347 my ( $class, $code );
348 if ( my $action = $c->action($command) ) {
7e5adedd 349 if ( $action->[2] ) {
350 $c->log->debug(qq/Couldn't forward "$command" to regex action/)
351 if $c->debug;
352 return 0;
353 }
fc7ec1d9 354 ( $class, $code ) = @{ $action->[0] };
355 }
356 else {
357 $class = $command;
358 if ( $class =~ /[^\w\:]/ ) {
359 $c->log->debug(qq/Couldn't forward to "$class"/) if $c->debug;
360 return 0;
361 }
362 my $method = shift || 'process';
363 if ( $code = $class->can($method) ) {
364 $c->actions->{reverse}->{"$code"} = "$class->$method";
365 }
366 else {
367 $c->log->debug(qq/Couldn't forward to "$class->$method"/)
368 if $c->debug;
369 return 0;
370 }
371 }
372 $class = $c->components->{$class} || $class;
373 return $c->process( $class, $code );
374}
375
376=head3 handler
377
378Handles the request.
379
380=cut
381
382sub handler {
383 my ( $class, $r ) = @_;
384
385 # Always expect worst case!
386 my $status = -1;
387 eval {
388 my $handler = sub {
389 my $c = $class->prepare($r);
7833fdfc 390 if ( my $action = $c->action( $c->req->action ) ) {
fc7ec1d9 391 my ( $begin, $end );
7833fdfc 392 my $class = ${ $action->[0] }[0];
393 my $prefix = _class2prefix($class);
394 if ($prefix) {
fc7ec1d9 395 if ( $c->actions->{plain}->{"\!$prefix/begin"} ) {
396 $begin = "\!$prefix/begin";
397 }
398 elsif ( $c->actions->{plain}->{'!begin'} ) {
399 $begin = '!begin';
400 }
401 if ( $c->actions->{plain}->{"\!$prefix/end"} ) {
402 $end = "\!$prefix/end";
403 }
404 elsif ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
405 }
7833fdfc 406 else {
407 if ( $c->actions->{plain}->{'!begin'} ) {
408 $begin = '!begin';
409 }
410 if ( $c->actions->{plain}->{'!end'} ) { $end = '!end' }
411 }
fc7ec1d9 412 $c->forward($begin) if $begin;
413 $c->forward( $c->req->action ) if $c->req->action;
414 $c->forward($end) if $end;
415 }
416 else {
417 my $action = $c->req->path;
418 my $error = $action
419 ? qq/Unknown resource "$action"/
7833fdfc 420 : "No default action defined";
fc7ec1d9 421 $c->log->error($error) if $c->debug;
422 $c->errors($error);
423 }
424 return $c->finalize;
425 };
426 if ( $class->debug ) {
427 my $elapsed;
428 ( $elapsed, $status ) = $class->benchmark($handler);
429 $elapsed = sprintf '%f', $elapsed;
430 my $av = sprintf '%.3f', 1 / $elapsed;
431 $class->log->info( "Request took $elapsed" . "s ($av/s)" );
432 }
433 else { $status = &$handler }
434 };
435 if ( my $error = $@ ) {
436 chomp $error;
437 $class->log->error(qq/Caught exception in engine "$error"/);
438 }
439 $COUNT++;
440 return $status;
441}
442
443=head3 prepare
444
445Turns the request (Apache, CGI...) into a Catalyst context.
446
447=cut
448
449sub prepare {
450 my ( $class, $r ) = @_;
451 my $c = bless {
452 request => Catalyst::Request->new(
453 {
454 arguments => [],
455 cookies => {},
456 headers => HTTP::Headers->new,
457 parameters => {},
458 snippets => [],
459 uploads => {}
460 }
461 ),
462 response => Catalyst::Response->new(
463 { cookies => {}, headers => HTTP::Headers->new, status => 200 }
464 ),
465 stash => {}
466 }, $class;
467 if ( $c->debug ) {
468 my $secs = time - $START || 1;
469 my $av = sprintf '%.3f', $COUNT / $secs;
470 $c->log->debug('********************************');
471 $c->log->debug("* Request $COUNT ($av/s) [$$]");
472 $c->log->debug('********************************');
473 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
474 }
475 $c->prepare_request($r);
476 $c->prepare_path;
fc7ec1d9 477 $c->prepare_cookies;
478 $c->prepare_headers;
5783a9a5 479 my $method = $c->req->method;
480 my $path = $c->req->path;
481 $c->log->debug(qq/"$method" request for "$path"/) if $c->debug;
fc7ec1d9 482 $c->prepare_action;
483 $c->prepare_parameters;
c85ff642 484
485 if ( $c->debug && keys %{ $c->req->params } ) {
486 my @params;
487 for my $key ( keys %{ $c->req->params } ) {
488 my $value = $c->req->params->{$key};
489 push @params, "$key=$value";
490 }
491 $c->log->debug( 'Parameters are "' . join( ' ', @params ) . '"' );
492 }
fc7ec1d9 493 $c->prepare_uploads;
494 return $c;
495}
496
497=head3 prepare_action
498
499Prepare action.
500
501=cut
502
503sub prepare_action {
504 my $c = shift;
505 my $path = $c->req->path;
506 my @path = split /\//, $c->req->path;
507 $c->req->args( \my @args );
508 while (@path) {
7833fdfc 509 $path = join '/', @path;
fc7ec1d9 510 if ( my $result = $c->action($path) ) {
511
512 # It's a regex
513 if ($#$result) {
7e5adedd 514 my $match = $result->[1];
515 my @snippets = @{ $result->[2] };
fc7ec1d9 516 $c->log->debug(qq/Requested action "$path" matched "$match"/)
517 if $c->debug;
518 $c->log->debug(
519 'Snippets are "' . join( ' ', @snippets ) . '"' )
520 if ( $c->debug && @snippets );
521 $c->req->action($match);
522 $c->req->snippets( \@snippets );
523 }
524 else {
525 $c->req->action($path);
526 $c->log->debug(qq/Requested action "$path"/) if $c->debug;
527 }
528 $c->req->match($path);
fc7ec1d9 529 last;
530 }
531 unshift @args, pop @path;
532 }
533 unless ( $c->req->action ) {
534 my $prefix = $c->req->args->[0];
535 if ( $prefix && $c->actions->{plain}->{"\!$prefix/default"} ) {
536 $c->req->match('');
537 $c->req->action("\!$prefix/default");
538 $c->log->debug('Using prefixed default action') if $c->debug;
539 }
540 elsif ( $c->actions->{plain}->{'!default'} ) {
541 $c->req->match('');
542 $c->req->action('!default');
543 $c->log->debug('Using default action') if $c->debug;
544 }
545 }
5783a9a5 546 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
547 if ( $c->debug && @args );
fc7ec1d9 548}
549
550=head3 prepare_cookies;
551
552Prepare cookies.
553
554=cut
555
556sub prepare_cookies { }
557
558=head3 prepare_headers
559
560Prepare headers.
561
562=cut
563
564sub prepare_headers { }
565
566=head3 prepare_parameters
567
568Prepare parameters.
569
570=cut
571
572sub prepare_parameters { }
573
574=head3 prepare_path
575
576Prepare path and base.
577
578=cut
579
580sub prepare_path { }
581
582=head3 prepare_request
583
584Prepare the engine request.
585
586=cut
587
588sub prepare_request { }
589
590=head3 prepare_uploads
591
592Prepare uploads.
593
594=cut
595
596sub prepare_uploads { }
597
598=head3 process
599
600Process a coderef in given class and catch exceptions.
601Errors are available via $c->errors.
602
603=cut
604
605sub process {
606 my ( $c, $class, $code ) = @_;
607 my $status;
608 eval {
609 if ( $c->debug )
610 {
611 my $action = $c->actions->{reverse}->{"$code"} || "$code";
612 my $elapsed;
613 ( $elapsed, $status ) =
614 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
615 $c->log->info( sprintf qq/Processing "$action" took %fs/, $elapsed )
616 if $c->debug;
617 }
618 else { $status = &$code( $class, $c, @{ $c->req->args } ) }
619 };
620 if ( my $error = $@ ) {
621 chomp $error;
622 $error = qq/Caught exception "$error"/;
623 $c->log->error($error);
624 $c->errors($error) if $c->debug;
625 return 0;
626 }
627 return $status;
628}
629
630=head3 remove_action
631
632Remove an action.
633
634 $c->remove_action('!foo');
635
636=cut
637
638sub remove_action {
639 my ( $self, $action ) = @_;
640 if ( delete $self->actions->{regex}->{$action} ) {
641 while ( my ( $regex, $name ) = each %{ $self->actions->{compiled} } ) {
642 if ( $name eq $action ) {
643 delete $self->actions->{compiled}->{$regex};
644 last;
645 }
646 }
647 }
648 else {
649 delete $self->actions->{plain}->{$action};
650 }
651}
652
653=head3 request (req)
654
655Returns a C<Catalyst::Request> object.
656
657 my $req = $c->req;
658
659=head3 response (res)
660
661Returns a C<Catalyst::Response> object.
662
663 my $res = $c->res;
664
665=head3 setup
666
667Setup.
668
669 MyApp->setup;
670
671=cut
672
673sub setup {
674 my $self = shift;
675 $self->setup_components;
676 if ( $self->debug ) {
677 my $name = $self->config->{name} || 'Application';
678 $self->log->info("$name powered by Catalyst $Catalyst::VERSION");
679 }
680}
681
682=head3 setup_components
683
684Setup components.
685
686=cut
687
688sub setup_components {
689 my $self = shift;
690
691 # Components
692 my $class = ref $self || $self;
693 eval <<"";
694 package $class;
695 import Module::Pluggable::Fast
696 name => '_components',
697 search => [
698 '$class\::Controller', '$class\::C',
699 '$class\::Model', '$class\::M',
700 '$class\::View', '$class\::V'
701 ];
702
703 if ( my $error = $@ ) {
704 chomp $error;
705 $self->log->error(
706 qq/Couldn't initialize "Module::Pluggable::Fast", "$error"/);
707 }
708 $self->components( {} );
709 for my $component ( $self->_components($self) ) {
710 $self->components->{ ref $component } = $component;
711 }
712 $self->log->debug( 'Initialized components "'
713 . join( ' ', keys %{ $self->components } )
714 . '"' )
715 if $self->debug;
716}
717
718=head3 stash
719
720Returns a hashref containing all your data.
721
722 $c->stash->{foo} ||= 'yada';
723 print $c->stash->{foo};
724
725=cut
726
727sub stash {
728 my $self = shift;
729 if ( $_[0] ) {
730 my $stash = $_[1] ? {@_} : $_[0];
731 while ( my ( $key, $val ) = each %$stash ) {
732 $self->{stash}->{$key} = $val;
733 }
734 }
735 return $self->{stash};
736}
737
738sub _prefix {
739 my ( $class, $name ) = @_;
7833fdfc 740 my $prefix = _class2prefix($class);
741 $name = "$prefix/$name" if $prefix;
742 return $name;
743}
744
745sub _class2prefix {
746 my $class = shift;
fc7ec1d9 747 $class =~ /^.*::[(M)(Model)(V)(View)(C)(Controller)]+::(.*)$/;
748 my $prefix = lc $1 || '';
749 $prefix =~ s/\:\:/_/g;
7833fdfc 750 return $prefix;
fc7ec1d9 751}
752
753=head1 AUTHOR
754
755Sebastian Riedel, C<sri@cpan.org>
756
757=head1 COPYRIGHT
758
759This program is free software, you can redistribute it and/or modify it under
760the same terms as Perl itself.
761
762=cut
763
7641;