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