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