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