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