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