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