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