Updated manifest
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
CommitLineData
fc7ec1d9 1package Catalyst;
2
3use strict;
fbcc39ad 4use base 'Catalyst::Base';
5use bytes;
fc7ec1d9 6use UNIVERSAL::require;
a2f2cde9 7use Catalyst::Exception;
fc7ec1d9 8use Catalyst::Log;
fbcc39ad 9use Catalyst::Request;
10use Catalyst::Request::Upload;
11use Catalyst::Response;
812a28c9 12use Catalyst::Utils;
5d9a6d47 13use NEXT;
fbcc39ad 14use Text::ASCIITable;
4f6748f1 15use Path::Class;
fbcc39ad 16use Time::HiRes qw/gettimeofday tv_interval/;
17use URI;
18use Scalar::Util qw/weaken/;
fc7ec1d9 19
66e28e3f 20__PACKAGE__->mk_accessors(
21 qw/counter depth request response state action namespace/
22);
10dd6896 23
fbcc39ad 24# Laziness++
25*comp = \&component;
26*req = \&request;
27*res = \&response;
28
29# For backwards compatibility
30*finalize_output = \&finalize_body;
31
32# For statistics
33our $COUNT = 1;
34our $START = time;
35our $RECURSION = 1000;
36our $DETACH = "catalyst_detach\n";
37
38require Module::Pluggable::Fast;
39
40# Helper script generation
6844bc1c 41our $CATALYST_SCRIPT_GEN = 8;
fbcc39ad 42
43__PACKAGE__->mk_classdata($_)
44 for qw/components arguments dispatcher engine log/;
45
46our $VERSION = '5.49_01';
47
189e2a51 48sub version { return $Catalyst::VERSION }
49
fbcc39ad 50sub import {
51 my ( $class, @arguments ) = @_;
52
53 # We have to limit $class to Catalyst to avoid pushing Catalyst upon every
54 # callers @ISA.
55 return unless $class eq 'Catalyst';
56
57 my $caller = caller(0);
58
59 unless ( $caller->isa('Catalyst') ) {
60 no strict 'refs';
61 push @{"$caller\::ISA"}, $class;
62 }
63
64 $caller->arguments( [@arguments] );
65 $caller->setup_home;
66}
fc7ec1d9 67
68=head1 NAME
69
70Catalyst - The Elegant MVC Web Application Framework
71
72=head1 SYNOPSIS
73
74 # use the helper to start a new application
91864987 75 catalyst.pl MyApp
fc7ec1d9 76 cd MyApp
77
78 # add models, views, controllers
ae4e40a7 79 script/myapp_create.pl model Something
80 script/myapp_create.pl view Stuff
81 script/myapp_create.pl controller Yada
fc7ec1d9 82
83 # built in testserver
ae4e40a7 84 script/myapp_server.pl
fc7ec1d9 85
86 # command line interface
ae4e40a7 87 script/myapp_test.pl /yada
fc7ec1d9 88
89
fc7ec1d9 90 use Catalyst;
91
92 use Catalyst qw/My::Module My::OtherModule/;
93
94 use Catalyst '-Debug';
95
96 use Catalyst qw/-Debug -Engine=CGI/;
97
5a8ed4fe 98 sub default : Private { $_[1]->res->output('Hello') } );
99
e3dc9d78 100 sub index : Path('/index.html') {
5a8ed4fe 101 my ( $self, $c ) = @_;
102 $c->res->output('Hello');
064834ea 103 $c->forward('foo');
5a8ed4fe 104 }
105
064834ea 106 sub product : Regex('^product[_]*(\d*).html$') {
5a8ed4fe 107 my ( $self, $c ) = @_;
108 $c->stash->{template} = 'product.tt';
109 $c->stash->{product} = $c->req->snippets->[0];
110 }
fc7ec1d9 111
3803e98f 112See also L<Catalyst::Manual::Intro>
113
fc7ec1d9 114=head1 DESCRIPTION
115
fc7ec1d9 116The key concept of Catalyst is DRY (Don't Repeat Yourself).
117
118See L<Catalyst::Manual> for more documentation.
119
23f9d934 120Catalyst plugins can be loaded by naming them as arguments to the "use Catalyst" statement.
1985c30b 121Omit the C<Catalyst::Plugin::> prefix from the plugin name,
23f9d934 122so C<Catalyst::Plugin::My::Module> becomes C<My::Module>.
fc7ec1d9 123
124 use Catalyst 'My::Module';
125
26e73131 126Special flags like -Debug and -Engine can also be specified as arguments when
23f9d934 127Catalyst is loaded:
fc7ec1d9 128
129 use Catalyst qw/-Debug My::Module/;
130
23f9d934 131The position of plugins and flags in the chain is important, because they are
132loaded in exactly the order that they appear.
fc7ec1d9 133
23f9d934 134The following flags are supported:
135
136=over 4
137
138=item -Debug
139
140enables debug output, i.e.:
fc7ec1d9 141
142 use Catalyst '-Debug';
143
23f9d934 144this is equivalent to:
fc7ec1d9 145
146 use Catalyst;
147 sub debug { 1 }
148
fbcc39ad 149=item -Dispatcher
150
151Force Catalyst to use a specific dispatcher.
152
23f9d934 153=item -Engine
fc7ec1d9 154
155Force Catalyst to use a specific engine.
23f9d934 156Omit the C<Catalyst::Engine::> prefix of the engine name, i.e.:
fc7ec1d9 157
158 use Catalyst '-Engine=CGI';
159
fbcc39ad 160=item -Home
161
162Force Catalyst to use a specific home directory.
163
164=item -Log
165
166Specify log level.
167
23f9d934 168=back
fc7ec1d9 169
23f9d934 170=head1 METHODS
171
172=over 4
173
66e28e3f 174=item $c->action
175
176Accessor for the current action
177
fbcc39ad 178=item $c->comp($name)
179
180=item $c->component($name)
181
182Get a component object by name.
183
184 $c->comp('MyApp::Model::MyModel')->do_stuff;
185
186=cut
187
188sub component {
189 my $c = shift;
190
191 if (@_) {
192
193 my $name = shift;
194
195 my $appclass = ref $c || $c;
196
197 my @names = (
198 $name, "${appclass}::${name}",
199 map { "${appclass}::${_}::${name}" } qw/M V C/
200 );
201
202 foreach my $try (@names) {
203
204 if ( exists $c->components->{$try} ) {
205
206 return $c->components->{$try};
207 }
208 }
209
210 foreach my $component ( keys %{ $c->components } ) {
211
212 return $c->components->{$component} if $component =~ /$name/i;
213 }
214
215 }
216
217 return sort keys %{ $c->components };
218}
219
220=item config
221
222Returns a hashref containing your applications settings.
223
23f9d934 224=item debug
fc7ec1d9 225
226Overload to enable debug messages.
227
228=cut
229
230sub debug { 0 }
231
fbcc39ad 232=item $c->detach( $command [, \@arguments ] )
fc7ec1d9 233
fbcc39ad 234Like C<forward> but doesn't return.
fc7ec1d9 235
236=cut
237
fbcc39ad 238sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) }
239
240=item $c->dispatcher
241
242Contains the dispatcher instance.
243Stringifies to class.
244
245=item $c->forward( $command [, \@arguments ] )
246
247Forward processing to a private action or a method from a class.
248If you define a class without method it will default to process().
249also takes an optional arrayref containing arguments to be passed
250to the new function. $c->req->args will be reset upon returning
251from the function.
252
253 $c->forward('/foo');
254 $c->forward('index');
255 $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
256 $c->forward('MyApp::View::TT');
257
258=cut
259
260sub forward { my $c = shift; $c->dispatcher->forward( $c, @_ ) }
261
66e28e3f 262=item $c->namespace
263
264Accessor to the namespace of the current action
265
01033d73 266=item $c->path_to(@path)
267
268Merges C<@path> with $c->config->{home} and returns a L<Path::Class> object.
269
270For example:
271
272 $c->path_to( 'db', 'sqlite.db' );
273
274=cut
275
276sub path_to {
277 my ( $c, @path ) = @_;
278 my $path = dir( $c->config->{home}, @path );
279 if ( -d $path ) { return $path }
280 else { return file( $c->config->{home}, @path ) }
281}
282
fbcc39ad 283=item $c->setup
284
285Setup.
286
287 $c->setup;
288
289=cut
290
291sub setup {
0319a12c 292 my ( $class, @arguments ) = @_;
599b5295 293
fbcc39ad 294 unless ( $class->isa('Catalyst') ) {
953b0e15 295
fbcc39ad 296 Catalyst::Exception->throw(
297 message => qq/'$class' does not inherit from Catalyst/ );
1c99e125 298 }
0319a12c 299
fbcc39ad 300 if ( $class->arguments ) {
301 @arguments = ( @arguments, @{ $class->arguments } );
302 }
303
304 # Process options
305 my $flags = {};
306
307 foreach (@arguments) {
308
309 if (/^-Debug$/) {
310 $flags->{log} =
311 ( $flags->{log} ) ? 'debug,' . $flags->{log} : 'debug';
312 }
313 elsif (/^-(\w+)=?(.*)$/) {
314 $flags->{ lc $1 } = $2;
315 }
316 else {
317 push @{ $flags->{plugins} }, $_;
318 }
319 }
320
321 $class->setup_log( delete $flags->{log} );
322 $class->setup_plugins( delete $flags->{plugins} );
323 $class->setup_dispatcher( delete $flags->{dispatcher} );
324 $class->setup_engine( delete $flags->{engine} );
325 $class->setup_home( delete $flags->{home} );
326
327 for my $flag ( sort keys %{$flags} ) {
328
329 if ( my $code = $class->can( 'setup_' . $flag ) ) {
330 &$code( $class, delete $flags->{$flag} );
331 }
332 else {
333 $class->log->warn(qq/Unknown flag "$flag"/);
334 }
335 }
336
337 $class->log->warn( "You are running an old helper script! "
338 . "Please update your scripts by regenerating the "
339 . "application and copying over the new scripts." )
340 if ( $ENV{CATALYST_SCRIPT_GEN}
341 && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::CATALYST_SCRIPT_GEN ) );
342
343 if ( $class->debug ) {
344
345 my @plugins = ();
346
347 {
348 no strict 'refs';
349 @plugins = grep { /^Catalyst::Plugin/ } @{"$class\::ISA"};
350 }
351
352 if (@plugins) {
353 my $t = Text::ASCIITable->new;
354 $t->setOptions( 'hide_HeadRow', 1 );
355 $t->setOptions( 'hide_HeadLine', 1 );
356 $t->setCols('Class');
357 $t->setColWidth( 'Class', 75, 1 );
358 $t->addRow($_) for @plugins;
359 $class->log->debug( "Loaded plugins:\n" . $t->draw );
360 }
361
362 my $dispatcher = $class->dispatcher;
363 my $engine = $class->engine;
364 my $home = $class->config->{home};
365
366 $class->log->debug(qq/Loaded dispatcher "$dispatcher"/);
367 $class->log->debug(qq/Loaded engine "$engine"/);
368
369 $home
370 ? ( -d $home )
371 ? $class->log->debug(qq/Found home "$home"/)
372 : $class->log->debug(qq/Home "$home" doesn't exist/)
373 : $class->log->debug(q/Couldn't find home/);
374 }
375
376 # Call plugins setup
377 {
378 no warnings qw/redefine/;
379 local *setup = sub { };
380 $class->setup;
381 }
382
383 # Initialize our data structure
384 $class->components( {} );
385
386 $class->setup_components;
387
388 if ( $class->debug ) {
389 my $t = Text::ASCIITable->new;
390 $t->setOptions( 'hide_HeadRow', 1 );
391 $t->setOptions( 'hide_HeadLine', 1 );
392 $t->setCols('Class');
393 $t->setColWidth( 'Class', 75, 1 );
394 $t->addRow($_) for sort keys %{ $class->components };
395 $class->log->debug( "Loaded components:\n" . $t->draw )
396 if ( @{ $t->{tbl_rows} } );
397 }
398
399 # Add our self to components, since we are also a component
400 $class->components->{$class} = $class;
401
402 $class->setup_actions;
403
404 if ( $class->debug ) {
405 my $name = $class->config->{name} || 'Application';
406 $class->log->info("$name powered by Catalyst $Catalyst::VERSION");
407 }
408 $class->log->_flush() if $class->log->can('_flush');
409}
410
189e2a51 411=item $c->uri_for($path,[@args])
fbcc39ad 412
413Merges path with $c->request->base for absolute uri's and with
414$c->request->match for relative uri's, then returns a normalized
189e2a51 415L<URI> object. If any args are passed, they are added at the end
416of the path.
fbcc39ad 417
418=cut
419
420sub uri_for {
00e6a2b7 421 my ( $c, $path, @args ) = @_;
fbcc39ad 422 my $base = $c->request->base->clone;
423 my $basepath = $base->path;
424 $basepath =~ s/\/$//;
fdba7a9d 425 $basepath .= '/';
fbcc39ad 426 my $match = $c->request->match;
00e6a2b7 427
189e2a51 428 # massage match, empty if absolute path
fbcc39ad 429 $match =~ s/^\///;
430 $match .= '/' if $match;
431 $match = '' if $path =~ /^\//;
432 $path =~ s/^\///;
00e6a2b7 433
189e2a51 434 # join args with '/', or a blank string
00e6a2b7 435 my $args = ( scalar @args ? '/' . join( '/', @args ) : '' );
436 return URI->new_abs( URI->new_abs( "$path$args", "$basepath$match" ),
437 $base )->canonical;
fbcc39ad 438}
439
440=item $c->error
441
442=item $c->error($error, ...)
443
444=item $c->error($arrayref)
445
446Returns an arrayref containing error messages.
447
448 my @error = @{ $c->error };
449
450Add a new error.
451
452 $c->error('Something bad happened');
453
00e6a2b7 454Clean errors.
455
456 $c->error(0);
457
fbcc39ad 458=cut
459
460sub error {
461 my $c = shift;
00e6a2b7 462 if ( $_[0] ) {
463 my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
464 push @{ $c->{error} }, @$error;
465 }
466 elsif ( defined $_[0] ) { $c->{error} = undef }
467 return $c->{error} || [];
0319a12c 468}
469
470=item $c->engine
471
fbcc39ad 472Contains the engine instance.
473Stringifies to the class.
fc7ec1d9 474
0319a12c 475=item $c->log
476
477Contains the logging object. Unless it is already set Catalyst sets this up with a
478C<Catalyst::Log> object. To use your own log class:
479
480 $c->log( MyLogger->new );
481 $c->log->info("now logging with my own logger!");
482
483Your log class should implement the methods described in the C<Catalyst::Log>
484man page.
485
486=item $c->plugin( $name, $class, @args )
487
488Instant plugins for Catalyst.
489Classdata accessor/mutator will be created, class loaded and instantiated.
490
491 MyApp->plugin( 'prototype', 'HTML::Prototype' );
492
493 $c->prototype->define_javascript_functions;
494
495=cut
496
497sub plugin {
498 my ( $class, $name, $plugin, @args ) = @_;
499 $plugin->require;
500
501 if ( my $error = $UNIVERSAL::require::ERROR ) {
502 Catalyst::Exception->throw(
fbcc39ad 503 message => qq/Couldn't load instant plugin "$plugin", "$error"/ );
0319a12c 504 }
505
506 eval { $plugin->import };
507 $class->mk_classdata($name);
508 my $obj;
509 eval { $obj = $plugin->new(@args) };
510
fbcc39ad 511 if ($@) {
512 Catalyst::Exception->throw( message =>
513 qq/Couldn't instantiate instant plugin "$plugin", "$@"/ );
0319a12c 514 }
515
516 $class->$name($obj);
517 $class->log->debug(qq/Initialized instant plugin "$plugin" as "$name"/)
518 if $class->debug;
519}
520
fbcc39ad 521=item $c->request
522
523=item $c->req
524
525Returns a C<Catalyst::Request> object.
526
527 my $req = $c->req;
528
529=item $c->response
530
531=item $c->res
532
533Returns a C<Catalyst::Response> object.
534
535 my $res = $c->res;
536
537=item $c->state
538
539Contains the return value of the last executed action.
540
541=item $c->stash
542
543Returns a hashref containing all your data.
544
fbcc39ad 545 print $c->stash->{foo};
546
23eb3f51 547Keys may be set in the stash by assigning to the hash reference, or by passing
548either a single hash reference or a list of key/value pairs as arguments.
549
550For example:
551
552 $c->stash->{foo} ||= 'yada';
553 $c->stash( { moose => 'majestic', qux => 0 } );
554 $c->stash( bar => 1, gorch => 2 );
555
fbcc39ad 556=cut
557
558sub stash {
559 my $c = shift;
560 if (@_) {
561 my $stash = @_ > 1 ? {@_} : $_[0];
562 while ( my ( $key, $val ) = each %$stash ) {
563 $c->{stash}->{$key} = $val;
564 }
565 }
566 return $c->{stash};
567}
568
2c63fc07 569=item $c->welcome_message
ab2374d3 570
571Returns the Catalyst welcome HTML page.
572
573=cut
574
575sub welcome_message {
bf1f2c60 576 my $c = shift;
577 my $name = $c->config->{name};
578 my $logo = $c->uri_for('/static/images/catalyst_logo.png');
579 my $prefix = Catalyst::Utils::appprefix( ref $c );
ab2374d3 580 return <<"EOF";
581<html>
582 <head>
583 <title>$name on Catalyst $VERSION</title>
584 <style type="text/css">
585 body {
586 text-align: center;
587 padding-left: 50%;
588 color: #000;
589 background-color: #eee;
590 }
591 div#content {
592 width: 640px;
593 margin-left: -320px;
594 margin-top: 10px;
595 margin-bottom: 10px;
596 text-align: left;
597 background-color: #ccc;
598 border: 1px solid #aaa;
599 -moz-border-radius: 10px;
600 }
d84c4dab 601 p, h1, h2 {
ab2374d3 602 margin-left: 20px;
603 margin-right: 20px;
16215972 604 font-family: verdana, tahoma, sans-serif;
ab2374d3 605 }
d84c4dab 606 a {
607 font-family: verdana, tahoma, sans-serif;
608 }
d114e033 609 :link, :visited {
610 text-decoration: none;
611 color: #b00;
612 border-bottom: 1px dotted #bbb;
613 }
614 :link:hover, :visited:hover {
d114e033 615 color: #555;
616 }
ab2374d3 617 div#topbar {
618 margin: 0px;
619 }
3e82a295 620 pre {
3e82a295 621 margin: 10px;
622 padding: 8px;
623 }
ab2374d3 624 div#answers {
625 padding: 8px;
626 margin: 10px;
d114e033 627 background-color: #fff;
ab2374d3 628 border: 1px solid #aaa;
629 -moz-border-radius: 10px;
630 }
631 h1 {
33108eaf 632 font-size: 0.9em;
633 font-weight: normal;
ab2374d3 634 text-align: center;
635 }
636 h2 {
637 font-size: 1.0em;
638 }
639 p {
640 font-size: 0.9em;
641 }
ae7c5252 642 p img {
643 float: right;
644 margin-left: 10px;
645 }
33108eaf 646 b#appname {
647 font-size: 1.6em;
ab2374d3 648 }
649 </style>
650 </head>
651 <body>
652 <div id="content">
653 <div id="topbar">
33108eaf 654 <h1><b id="appname">$name</b> on <a href="http://catalyst.perl.org">Catalyst</a>
d84c4dab 655 $VERSION</h1>
ab2374d3 656 </div>
657 <div id="answers">
ae7c5252 658 <p>
f68d720e 659 <img src="$logo"/>
ae7c5252 660 </p>
4b8cb778 661 <p>Welcome to the wonderful world of Catalyst.
f92fd545 662 This <a href="http://en.wikipedia.org/wiki/MVC">MVC</a>
663 framework will make web development something you had
664 never expected it to be: Fun, rewarding and quick.</p>
ab2374d3 665 <h2>What to do now?</h2>
4b8cb778 666 <p>That really depends on what <b>you</b> want to do.
ab2374d3 667 We do, however, provide you with a few starting points.</p>
668 <p>If you want to jump right into web development with Catalyst
5db7f9a1 669 you might want to check out the documentation.</p>
bf1f2c60 670 <pre><code>perldoc <a href="http://cpansearch.perl.org/dist/Catalyst/lib/Catalyst/Manual/Intro.pod">Catalyst::Manual::Intro</a>
671perldoc <a href="http://cpansearch.perl.org/dist/Catalyst/lib/Catalyst/Manual.pod">Catalyst::Manual</a></code></pre>
ab2374d3 672 <h2>What to do next?</h2>
f5681c92 673 <p>Next it's time to write an actual application. Use the
bf1f2c60 674 helper scripts to generate <a href="http://cpansearch.perl.org/search?query=Catalyst%3A%3AController%3A%3A&mode=all">controllers</a>,
675 <a href="http://cpansearch.perl.org/search?query=Catalyst%3A%3AModel%3A%3A&mode=all">models</a> and
676 <a href="http://cpansearch.perl.org/search?query=Catalyst%3A%3AView%3A%3A&mode=all">views</a>,
677 they can save you a lot of work.</p>
678 <pre><code>script/${prefix}_create.pl -help</code></pre>
679 <p>Also, be sure to check out the vast and growing
680 collection of <a href="http://cpansearch.perl.org/search?query=Catalyst%3A%3APlugin%3A%3A&mode=all">plugins for Catalyst on CPAN</a>,
681 you are likely to find what you need there.
f5681c92 682 </p>
683
82245cc4 684 <h2>Need help?</h2>
f5681c92 685 <p>Catalyst has a very active community. Here are the main places to
686 get in touch with us.</p>
16215972 687 <ul>
688 <li>
2b9a7d76 689 <a href="http://dev.catalyst.perl.org">Wiki</a>
16215972 690 </li>
691 <li>
692 <a href="http://lists.rawmode.org/mailman/listinfo/catalyst">Mailing-List</a>
693 </li>
694 <li>
ea7cd80d 695 <a href="irc://irc.perl.org/catalyst">IRC channel #catalyst on irc.perl.org</a>
16215972 696 </li>
697 </ul>
ab2374d3 698 <h2>In conclusion</h2>
ea7cd80d 699 <p>The Catalyst team hope you will enjoy using Catalyst as much
f5681c92 700 as we enjoyed making it. Please contact us if you have ideas
701 for improvement or other feedback.</p>
ab2374d3 702 </div>
703 </div>
704 </body>
705</html>
706EOF
707}
708
fbcc39ad 709=back
710
711=head1 INTERNAL METHODS
712
713=over 4
714
715=item $c->benchmark($coderef)
716
717Takes a coderef with arguments and returns elapsed time as float.
718
719 my ( $elapsed, $status ) = $c->benchmark( sub { return 1 } );
720 $c->log->info( sprintf "Processing took %f seconds", $elapsed );
721
722=cut
723
724sub benchmark {
725 my $c = shift;
726 my $code = shift;
727 my $time = [gettimeofday];
728 my @return = &$code(@_);
729 my $elapsed = tv_interval $time;
730 return wantarray ? ( $elapsed, @return ) : $elapsed;
731}
732
733=item $c->components
734
735Contains the components.
736
737=item $c->counter
738
739Returns a hashref containing coderefs and execution counts.
740(Needed for deep recursion detection)
741
742=item $c->depth
743
744Returns the actual forward depth.
745
746=item $c->dispatch
747
748Dispatch request to actions.
749
750=cut
751
752sub dispatch { my $c = shift; $c->dispatcher->dispatch( $c, @_ ) }
753
754=item $c->execute($class, $coderef)
755
756Execute a coderef in given class and catch exceptions.
757Errors are available via $c->error.
758
759=cut
760
761sub execute {
762 my ( $c, $class, $code ) = @_;
763 $class = $c->components->{$class} || $class;
764 $c->state(0);
765 my $callsub = ( caller(1) )[3];
766
767 my $action = '';
768 if ( $c->debug ) {
769 $action = "$code";
770 $action = "/$action" unless $action =~ /\-\>/;
771 $c->counter->{"$code"}++;
772
773 if ( $c->counter->{"$code"} > $RECURSION ) {
774 my $error = qq/Deep recursion detected in "$action"/;
775 $c->log->error($error);
776 $c->error($error);
777 $c->state(0);
778 return $c->state;
779 }
780
781 $action = "-> $action" if $callsub =~ /forward$/;
782 }
783 $c->{depth}++;
784 eval {
00e6a2b7 785 if ( $c->debug )
786 {
fbcc39ad 787 my ( $elapsed, @state ) =
788 $c->benchmark( $code, $class, $c, @{ $c->req->args } );
789 push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
790 $c->state(@state);
791 }
7cfcfd27 792 else {
00e6a2b7 793 $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 );
7cfcfd27 794 }
fbcc39ad 795 };
796 $c->{depth}--;
797
798 if ( my $error = $@ ) {
799
800 if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
801 else {
802 unless ( ref $error ) {
803 chomp $error;
804 $error = qq/Caught exception "$error"/;
805 }
806
807 $c->log->error($error);
808 $c->error($error);
809 $c->state(0);
810 }
811 }
812 return $c->state;
813}
814
815=item $c->finalize
816
817Finalize request.
818
819=cut
820
821sub finalize {
822 my $c = shift;
823
824 $c->finalize_uploads;
825
826 # Error
827 if ( $#{ $c->error } >= 0 ) {
828 $c->finalize_error;
829 }
830
831 $c->finalize_headers;
832
833 # HEAD request
834 if ( $c->request->method eq 'HEAD' ) {
835 $c->response->body('');
836 }
837
838 $c->finalize_body;
839
840 return $c->response->status;
841}
842
843=item $c->finalize_body
844
845Finalize body.
846
847=cut
848
849sub finalize_body { my $c = shift; $c->engine->finalize_body( $c, @_ ) }
850
851=item $c->finalize_cookies
852
853Finalize cookies.
854
855=cut
856
857sub finalize_cookies { my $c = shift; $c->engine->finalize_cookies( $c, @_ ) }
858
859=item $c->finalize_error
860
861Finalize error.
862
863=cut
864
865sub finalize_error { my $c = shift; $c->engine->finalize_error( $c, @_ ) }
866
867=item $c->finalize_headers
868
869Finalize headers.
870
871=cut
872
873sub finalize_headers {
874 my $c = shift;
875
876 # Check if we already finalized headers
877 return if $c->response->{_finalized_headers};
878
879 # Handle redirects
880 if ( my $location = $c->response->redirect ) {
881 $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
882 $c->response->header( Location => $location );
883 }
884
885 # Content-Length
886 if ( $c->response->body && !$c->response->content_length ) {
887 $c->response->content_length( bytes::length( $c->response->body ) );
888 }
889
890 # Errors
891 if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
892 $c->response->headers->remove_header("Content-Length");
893 $c->response->body('');
894 }
895
896 $c->finalize_cookies;
897
898 $c->engine->finalize_headers( $c, @_ );
899
900 # Done
901 $c->response->{_finalized_headers} = 1;
902}
903
904=item $c->finalize_output
905
906An alias for finalize_body.
907
908=item $c->finalize_read
909
910Finalize the input after reading is complete.
911
912=cut
913
914sub finalize_read { my $c = shift; $c->engine->finalize_read( $c, @_ ) }
915
916=item $c->finalize_uploads
917
918Finalize uploads. Cleans up any temporary files.
919
920=cut
921
922sub finalize_uploads { my $c = shift; $c->engine->finalize_uploads( $c, @_ ) }
923
924=item $c->get_action( $action, $namespace, $inherit )
925
926Get an action in a given namespace.
927
928=cut
929
930sub get_action { my $c = shift; $c->dispatcher->get_action( $c, @_ ) }
931
932=item handle_request( $class, @arguments )
933
934Handles the request.
935
936=cut
937
938sub handle_request {
939 my ( $class, @arguments ) = @_;
940
941 # Always expect worst case!
942 my $status = -1;
943 eval {
944 my @stats = ();
945
946 my $handler = sub {
947 my $c = $class->prepare(@arguments);
948 $c->{stats} = \@stats;
949 $c->dispatch;
950 return $c->finalize;
951 };
952
953 if ( $class->debug ) {
954 my $elapsed;
955 ( $elapsed, $status ) = $class->benchmark($handler);
956 $elapsed = sprintf '%f', $elapsed;
957 my $av = sprintf '%.3f',
958 ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
959 my $t = Text::ASCIITable->new;
960 $t->setCols( 'Action', 'Time' );
961 $t->setColWidth( 'Action', 64, 1 );
962 $t->setColWidth( 'Time', 9, 1 );
963
964 for my $stat (@stats) { $t->addRow( $stat->[0], $stat->[1] ) }
965 $class->log->info(
966 "Request took ${elapsed}s ($av/s)\n" . $t->draw );
967 }
968 else { $status = &$handler }
969
970 };
971
972 if ( my $error = $@ ) {
973 chomp $error;
974 $class->log->error(qq/Caught exception in engine "$error"/);
975 }
976
977 $COUNT++;
978 $class->log->_flush() if $class->log->can('_flush');
979 return $status;
980}
981
982=item $c->prepare(@arguments)
983
984Turns the engine-specific request( Apache, CGI ... )
985into a Catalyst context .
986
987=cut
988
989sub prepare {
990 my ( $class, @arguments ) = @_;
991
992 my $c = bless {
993 counter => {},
994 depth => 0,
995 request => Catalyst::Request->new(
996 {
997 arguments => [],
998 body_parameters => {},
999 cookies => {},
fbcc39ad 1000 headers => HTTP::Headers->new,
1001 parameters => {},
1002 query_parameters => {},
1003 secure => 0,
1004 snippets => [],
1005 uploads => {}
1006 }
1007 ),
1008 response => Catalyst::Response->new(
1009 {
1010 body => '',
1011 cookies => {},
fbcc39ad 1012 headers => HTTP::Headers->new(),
1013 status => 200
1014 }
1015 ),
1016 stash => {},
1017 state => 0
1018 }, $class;
1019
1020 # For on-demand data
1021 $c->request->{_context} = $c;
1022 $c->response->{_context} = $c;
1023 weaken( $c->request->{_context} );
1024 weaken( $c->response->{_context} );
1025
1026 if ( $c->debug ) {
1027 my $secs = time - $START || 1;
1028 my $av = sprintf '%.3f', $COUNT / $secs;
1029 $c->log->debug('**********************************');
1030 $c->log->debug("* Request $COUNT ($av/s) [$$]");
1031 $c->log->debug('**********************************');
1032 $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
1033 }
1034
1035 $c->prepare_request(@arguments);
1036 $c->prepare_connection;
1037 $c->prepare_query_parameters;
1038 $c->prepare_headers;
1039 $c->prepare_cookies;
1040 $c->prepare_path;
1041
1042 # On-demand parsing
1043 $c->prepare_body unless $c->config->{parse_on_demand};
1044
1045 $c->prepare_action;
1046 my $method = $c->req->method || '';
1047 my $path = $c->req->path || '';
1048 my $address = $c->req->address || '';
1049
1050 $c->log->debug(qq/"$method" request for "$path" from $address/)
1051 if $c->debug;
1052
1053 return $c;
1054}
1055
1056=item $c->prepare_action
1057
1058Prepare action.
1059
1060=cut
1061
1062sub prepare_action { my $c = shift; $c->dispatcher->prepare_action( $c, @_ ) }
1063
1064=item $c->prepare_body
1065
1066Prepare message body.
1067
1068=cut
1069
1070sub prepare_body {
1071 my $c = shift;
1072
1073 # Do we run for the first time?
1074 return if defined $c->request->{_body};
1075
1076 # Initialize on-demand data
1077 $c->engine->prepare_body( $c, @_ );
1078 $c->prepare_parameters;
1079 $c->prepare_uploads;
1080
1081 if ( $c->debug && keys %{ $c->req->body_parameters } ) {
1082 my $t = Text::ASCIITable->new;
1083 $t->setCols( 'Key', 'Value' );
1084 $t->setColWidth( 'Key', 37, 1 );
1085 $t->setColWidth( 'Value', 36, 1 );
1086 $t->alignCol( 'Value', 'right' );
1087 for my $key ( sort keys %{ $c->req->body_parameters } ) {
1088 my $param = $c->req->body_parameters->{$key};
1089 my $value = defined($param) ? $param : '';
1090 $t->addRow( $key,
1091 ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
1092 }
1093 $c->log->debug( "Body Parameters are:\n" . $t->draw );
1094 }
1095}
1096
4bd82c41 1097=item $c->prepare_body_chunk( $chunk )
1098
1099Prepare a chunk of data before sending it to HTTP::Body.
1100
1101=cut
1102
4f5ebacd 1103sub prepare_body_chunk {
1104 my $c = shift;
4bd82c41 1105 $c->engine->prepare_body_chunk( $c, @_ );
1106}
1107
fbcc39ad 1108=item $c->prepare_body_parameters
1109
1110Prepare body parameters.
1111
1112=cut
1113
1114sub prepare_body_parameters {
1115 my $c = shift;
1116 $c->engine->prepare_body_parameters( $c, @_ );
1117}
1118
1119=item $c->prepare_connection
1120
1121Prepare connection.
1122
1123=cut
1124
1125sub prepare_connection {
1126 my $c = shift;
1127 $c->engine->prepare_connection( $c, @_ );
1128}
1129
1130=item $c->prepare_cookies
1131
1132Prepare cookies.
1133
1134=cut
1135
1136sub prepare_cookies { my $c = shift; $c->engine->prepare_cookies( $c, @_ ) }
1137
1138=item $c->prepare_headers
1139
1140Prepare headers.
1141
1142=cut
1143
1144sub prepare_headers { my $c = shift; $c->engine->prepare_headers( $c, @_ ) }
1145
1146=item $c->prepare_parameters
1147
1148Prepare parameters.
1149
1150=cut
1151
1152sub prepare_parameters {
1153 my $c = shift;
1154 $c->prepare_body_parameters;
1155 $c->engine->prepare_parameters( $c, @_ );
1156}
1157
1158=item $c->prepare_path
1159
1160Prepare path and base.
1161
1162=cut
1163
1164sub prepare_path { my $c = shift; $c->engine->prepare_path( $c, @_ ) }
1165
1166=item $c->prepare_query_parameters
1167
1168Prepare query parameters.
1169
1170=cut
1171
1172sub prepare_query_parameters {
1173 my $c = shift;
1174
1175 $c->engine->prepare_query_parameters( $c, @_ );
1176
1177 if ( $c->debug && keys %{ $c->request->query_parameters } ) {
1178 my $t = Text::ASCIITable->new;
1179 $t->setCols( 'Key', 'Value' );
1180 $t->setColWidth( 'Key', 37, 1 );
1181 $t->setColWidth( 'Value', 36, 1 );
1182 $t->alignCol( 'Value', 'right' );
1183 for my $key ( sort keys %{ $c->req->query_parameters } ) {
1184 my $param = $c->req->query_parameters->{$key};
1185 my $value = defined($param) ? $param : '';
1186 $t->addRow( $key,
1187 ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
1188 }
1189 $c->log->debug( "Query Parameters are:\n" . $t->draw );
1190 }
1191}
1192
1193=item $c->prepare_read
1194
1195Prepare the input for reading.
1196
1197=cut
1198
1199sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) }
1200
1201=item $c->prepare_request
1202
1203Prepare the engine request.
1204
1205=cut
1206
1207sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) }
1208
1209=item $c->prepare_uploads
1210
1211Prepare uploads.
1212
1213=cut
1214
1215sub prepare_uploads {
1216 my $c = shift;
1217
1218 $c->engine->prepare_uploads( $c, @_ );
1219
1220 if ( $c->debug && keys %{ $c->request->uploads } ) {
1221 my $t = Text::ASCIITable->new;
bc2beef5 1222 $t->setCols( 'Key', 'Filename', 'Type', 'Size' );
1223 $t->setColWidth( 'Key', 12, 1 );
1224 $t->setColWidth( 'Filename', 28, 1 );
1225 $t->setColWidth( 'Type', 18, 1 );
fbcc39ad 1226 $t->setColWidth( 'Size', 9, 1 );
1227 $t->alignCol( 'Size', 'left' );
1228 for my $key ( sort keys %{ $c->request->uploads } ) {
1229 my $upload = $c->request->uploads->{$key};
1230 for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) {
bc2beef5 1231 $t->addRow( $key, $u->filename, $u->type, $u->size );
fbcc39ad 1232 }
1233 }
1234 $c->log->debug( "File Uploads are:\n" . $t->draw );
1235 }
1236}
1237
1238=item $c->prepare_write
1239
1240Prepare the output for writing.
1241
1242=cut
1243
1244sub prepare_write { my $c = shift; $c->engine->prepare_write( $c, @_ ) }
1245
1246=item $c->read( [$maxlength] )
1247
1248Read a chunk of data from the request body. This method is designed to be
1249used in a while loop, reading $maxlength bytes on every call. $maxlength
1250defaults to the size of the request if not specified.
1251
1252You have to set MyApp->config->{parse_on_demand} to use this directly.
1253
1254=cut
1255
1256sub read { my $c = shift; return $c->engine->read( $c, @_ ) }
1257
1258=item $c->run
1259
1260Starts the engine.
1261
1262=cut
1263
1264sub run { my $c = shift; return $c->engine->run( $c, @_ ) }
1265
1266=item $c->set_action( $action, $code, $namespace, $attrs )
1267
1268Set an action in a given namespace.
1269
1270=cut
1271
1272sub set_action { my $c = shift; $c->dispatcher->set_action( $c, @_ ) }
1273
1274=item $c->setup_actions($component)
1275
1276Setup actions for a component.
1277
1278=cut
1279
1280sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) }
1281
1282=item $c->setup_components
1283
1284Setup components.
1285
1286=cut
1287
1288sub setup_components {
1289 my $class = shift;
1290
1291 my $callback = sub {
1292 my ( $component, $context ) = @_;
1293
1294 unless ( $component->isa('Catalyst::Base') ) {
1295 return $component;
1296 }
1297
1298 my $suffix = Catalyst::Utils::class2classsuffix($component);
1299 my $config = $class->config->{$suffix} || {};
1300
1301 my $instance;
1302
1303 eval { $instance = $component->new( $context, $config ); };
1304
1305 if ( my $error = $@ ) {
1306
1307 chomp $error;
1308
1309 Catalyst::Exception->throw( message =>
1310 qq/Couldn't instantiate component "$component", "$error"/ );
1311 }
1312
1313 Catalyst::Exception->throw( message =>
1314qq/Couldn't instantiate component "$component", "new() didn't return a object"/
1315 )
1316 unless ref $instance;
1317 return $instance;
1318 };
1319
1320 eval {
1321 Module::Pluggable::Fast->import(
1322 name => '_catalyst_components',
1323 search => [
1324 "$class\::Controller", "$class\::C",
1325 "$class\::Model", "$class\::M",
1326 "$class\::View", "$class\::V"
1327 ],
1328 callback => $callback
1329 );
1330 };
1331
1332 if ( my $error = $@ ) {
1333
1334 chomp $error;
1335
1336 Catalyst::Exception->throw(
1337 message => qq/Couldn't load components "$error"/ );
1338 }
1339
1340 for my $component ( $class->_catalyst_components($class) ) {
1341 $class->components->{ ref $component || $component } = $component;
1342 }
1343}
1344
1345=item $c->setup_dispatcher
1346
1347=cut
1348
1349sub setup_dispatcher {
1350 my ( $class, $dispatcher ) = @_;
1351
1352 if ($dispatcher) {
1353 $dispatcher = 'Catalyst::Dispatcher::' . $dispatcher;
1354 }
1355
1356 if ( $ENV{CATALYST_DISPATCHER} ) {
1357 $dispatcher = 'Catalyst::Dispatcher::' . $ENV{CATALYST_DISPATCHER};
1358 }
1359
1360 if ( $ENV{ uc($class) . '_DISPATCHER' } ) {
1361 $dispatcher =
1362 'Catalyst::Dispatcher::' . $ENV{ uc($class) . '_DISPATCHER' };
1363 }
1364
1365 unless ($dispatcher) {
1366 $dispatcher = 'Catalyst::Dispatcher';
1367 }
1368
1369 $dispatcher->require;
1370
1371 if ($@) {
1372 Catalyst::Exception->throw(
1373 message => qq/Couldn't load dispatcher "$dispatcher", "$@"/ );
1374 }
1375
1376 # dispatcher instance
1377 $class->dispatcher( $dispatcher->new );
1378}
1379
1380=item $c->setup_engine
1381
1382=cut
1383
1384sub setup_engine {
1385 my ( $class, $engine ) = @_;
1386
1387 if ($engine) {
1388 $engine = 'Catalyst::Engine::' . $engine;
1389 }
1390
1391 if ( $ENV{CATALYST_ENGINE} ) {
1392 $engine = 'Catalyst::Engine::' . $ENV{CATALYST_ENGINE};
1393 }
1394
1395 if ( $ENV{ uc($class) . '_ENGINE' } ) {
1396 $engine = 'Catalyst::Engine::' . $ENV{ uc($class) . '_ENGINE' };
1397 }
1398
1399 if ( !$engine && $ENV{MOD_PERL} ) {
1400
1401 # create the apache method
1402 {
1403 no strict 'refs';
1404 *{"$class\::apache"} = sub { shift->engine->apache };
1405 }
1406
1407 my ( $software, $version ) =
1408 $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/;
1409
1410 $version =~ s/_//g;
1411 $version =~ s/(\.[^.]+)\./$1/g;
1412
1413 if ( $software eq 'mod_perl' ) {
1414
1415 if ( $version >= 1.99922 ) {
1416 $engine = 'Catalyst::Engine::Apache2::MP20';
1417 }
1418
1419 elsif ( $version >= 1.9901 ) {
1420 $engine = 'Catalyst::Engine::Apache2::MP19';
1421 }
1422
1423 elsif ( $version >= 1.24 ) {
1424 $engine = 'Catalyst::Engine::Apache::MP13';
1425 }
1426
1427 else {
1428 Catalyst::Exception->throw( message =>
1429 qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ );
1430 }
1431
1432 # install the correct mod_perl handler
1433 if ( $version >= 1.9901 ) {
1434 *handler = sub : method {
1435 shift->handle_request(@_);
1436 };
1437 }
1438 else {
1439 *handler = sub ($$) { shift->handle_request(@_) };
1440 }
1441
1442 }
1443
1444 elsif ( $software eq 'Zeus-Perl' ) {
1445 $engine = 'Catalyst::Engine::Zeus';
1446 }
1447
1448 else {
1449 Catalyst::Exception->throw(
1450 message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ );
1451 }
1452 }
1453
1454 unless ($engine) {
1455 $engine = 'Catalyst::Engine::CGI';
1456 }
1457
1458 $engine->require;
1459
1460 if ($@) {
1461 Catalyst::Exception->throw( message =>
1462qq/Couldn't load engine "$engine" (maybe you forgot to install it?), "$@"/
1463 );
1464 }
1465
1466 # engine instance
1467 $class->engine( $engine->new );
1468}
1469
1470=item $c->setup_home
1471
1472=cut
1473
1474sub setup_home {
1475 my ( $class, $home ) = @_;
1476
1477 if ( $ENV{CATALYST_HOME} ) {
1478 $home = $ENV{CATALYST_HOME};
1479 }
1480
1481 if ( $ENV{ uc($class) . '_HOME' } ) {
1482 $home = $ENV{ uc($class) . '_HOME' };
1483 }
1484
1485 unless ($home) {
1486 $home = Catalyst::Utils::home($class);
1487 }
1488
1489 if ($home) {
1490 $class->config->{home} ||= $home;
1491 $class->config->{root} ||= dir($home)->subdir('root');
1492 }
1493}
1494
1495=item $c->setup_log
1496
1497=cut
1498
1499sub setup_log {
1500 my ( $class, $debug ) = @_;
1501
1502 unless ( $class->log ) {
1503 $class->log( Catalyst::Log->new );
1504 }
1505
1506 if ( $ENV{CATALYST_DEBUG} || $ENV{ uc($class) . '_DEBUG' } || $debug ) {
1507 no strict 'refs';
1508 *{"$class\::debug"} = sub { 1 };
1509 $class->log->debug('Debug messages enabled');
1510 }
1511}
1512
1513=item $c->setup_plugins
1514
1515=cut
1516
1517sub setup_plugins {
1518 my ( $class, $plugins ) = @_;
1519
1520 $plugins ||= [];
1521 for my $plugin ( reverse @$plugins ) {
1522
1523 $plugin = "Catalyst::Plugin::$plugin";
1524
1525 $plugin->require;
1526
1527 if ($@) {
1528 Catalyst::Exception->throw(
1529 message => qq/Couldn't load plugin "$plugin", "$@"/ );
1530 }
1531
1532 {
1533 no strict 'refs';
1534 unshift @{"$class\::ISA"}, $plugin;
1535 }
1536 }
1537}
1538
1539=item $c->write( $data )
1540
1541Writes $data to the output stream. When using this method directly, you will
1542need to manually set the Content-Length header to the length of your output
1543data, if known.
1544
1545=cut
1546
4f5ebacd 1547sub write {
1548 my $c = shift;
1549
1550 # Finalize headers if someone manually writes output
1551 $c->finalize_headers;
1552
1553 return $c->engine->write( $c, @_ );
1554}
fbcc39ad 1555
23f9d934 1556=back
1557
d2ee9760 1558=head1 CASE SENSITIVITY
1559
1560By default Catalyst is not case sensitive, so C<MyApp::C::FOO::Bar> becomes
1561C</foo/bar>.
1562
1563But you can activate case sensitivity with a config parameter.
1564
1565 MyApp->config->{case_sensitive} = 1;
1566
fbcc39ad 1567So C<MyApp::C::Foo::Bar> becomes C</Foo/Bar>.
1568
1569=head1 ON-DEMAND PARSER
1570
1571The request body is usually parsed at the beginning of a request,
1572but if you want to handle input yourself or speed things up a bit
1573you can enable on-demand parsing with a config parameter.
1574
1575 MyApp->config->{parse_on_demand} = 1;
1576
1577=head1 PROXY SUPPORT
1578
1579Many production servers operate using the common double-server approach, with
1580a lightweight frontend web server passing requests to a larger backend
1581server. An application running on the backend server must deal with two
1582problems: the remote user always appears to be '127.0.0.1' and the server's
1583hostname will appear to be 'localhost' regardless of the virtual host the
1584user connected through.
1585
1586Catalyst will automatically detect this situation when you are running both
1587the frontend and backend servers on the same machine. The following changes
1588are made to the request.
1589
1590 $c->req->address is set to the user's real IP address, as read from the
1591 HTTP_X_FORWARDED_FOR header.
1592
1593 The host value for $c->req->base and $c->req->uri is set to the real host,
1594 as read from the HTTP_X_FORWARDED_HOST header.
1595
1596Obviously, your web server must support these 2 headers for this to work.
1597
1598In a more complex server farm environment where you may have your frontend
1599proxy server(s) on different machines, you will need to set a configuration
1600option to tell Catalyst to read the proxied data from the headers.
1601
1602 MyApp->config->{using_frontend_proxy} = 1;
1603
1604If you do not wish to use the proxy support at all, you may set:
d1a31ac6 1605
fbcc39ad 1606 MyApp->config->{ignore_frontend_proxy} = 1;
1607
1608=head1 THREAD SAFETY
1609
1610Catalyst has been tested under Apache 2's threading mpm_worker, mpm_winnt,
1611and the standalone forking HTTP server on Windows. We believe the Catalyst
1612core to be thread-safe.
1613
1614If you plan to operate in a threaded environment, remember that all other
1615modules you are using must also be thread-safe. Some modules, most notably
1616DBD::SQLite, are not thread-safe.
d1a31ac6 1617
3cb1db8c 1618=head1 SUPPORT
1619
1620IRC:
1621
1622 Join #catalyst on irc.perl.org.
1623
1624Mailing-Lists:
1625
1626 http://lists.rawmode.org/mailman/listinfo/catalyst
1627 http://lists.rawmode.org/mailman/listinfo/catalyst-dev
1985c30b 1628
432d507d 1629Web:
1630
1631 http://catalyst.perl.org
1632
fc7ec1d9 1633=head1 SEE ALSO
1634
61b1e958 1635=over 4
1636
1637=item L<Catalyst::Manual> - The Catalyst Manual
1638
1639=item L<Catalyst::Engine> - Core Engine
1640
1641=item L<Catalyst::Log> - The Log Class.
1642
1643=item L<Catalyst::Request> - The Request Object
1644
1645=item L<Catalyst::Response> - The Response Object
1646
1647=item L<Catalyst::Test> - The test suite.
1648
1649=back
fc7ec1d9 1650
15f0b5b7 1651=head1 CREDITS
fc7ec1d9 1652
15f0b5b7 1653Andy Grundman
1654
fbcc39ad 1655Andy Wardley
1656
33108eaf 1657Andreas Marienborg
1658
f4a57de4 1659Andrew Bramble
1660
15f0b5b7 1661Andrew Ford
1662
1663Andrew Ruthven
1664
fbcc39ad 1665Arthur Bergman
1666
15f0b5b7 1667Autrijus Tang
1668
1669Christian Hansen
1670
1671Christopher Hicks
1672
1673Dan Sully
1674
1675Danijel Milicevic
1676
1677David Naughton
1678
1679Gary Ashton Jones
1680
1681Geoff Richards
1682
1683Jesse Sheidlower
1684
fbcc39ad 1685Jesse Vincent
1686
15f0b5b7 1687Jody Belka
1688
1689Johan Lindstrom
1690
1691Juan Camacho
1692
1693Leon Brocard
1694
1695Marcus Ramberg
1696
1697Matt S Trout
1698
71c3bcc3 1699Robert Sedlacek
1700
15f0b5b7 1701Tatsuhiko Miyagawa
fc7ec1d9 1702
51f0308d 1703Ulf Edvinsson
1704
bdcb95ef 1705Yuval Kogman
1706
51f0308d 1707=head1 AUTHOR
1708
1709Sebastian Riedel, C<sri@oook.de>
1710
fc7ec1d9 1711=head1 LICENSE
1712
9ce5ab63 1713This library is free software, you can redistribute it and/or modify it under
41ca9ba7 1714the same terms as Perl itself.
fc7ec1d9 1715
1716=cut
1717
17181;