1Catalyst::Manual::CookbUosoekr(3C)ontributed Perl DocumeCnattaatliyosnt::Manual::Cookbook(3)
2
3
4
6 Catalyst::Manual::Cookbook - Cooking with Catalyst
7
9 Yummy code like your mum used to bake!
10
13 These recipes cover some basic stuff that is worth knowing for catalyst
14 developers.
15
16 Delivering a Custom Error Page
17
18 By default, Catalyst will display its own error page whenever it
19 encounters an error in your application. When running under "-Debug"
20 mode, the error page is a useful screen including the error message and
21 Data::Dump output of the relevant parts of the $c context object. When
22 not in "-Debug", users see a simple "Please come back later" screen.
23
24 To use a custom error page, use a special "end" method to short-circuit
25 the error processing. The following is an example; you might want to
26 adjust it further depending on the needs of your application (for exam‐
27 ple, any calls to "fillform" will probably need to go into this "end"
28 method; see Catalyst::Plugin::FillInForm).
29
30 sub end : Private {
31 my ( $self, $c ) = @_;
32
33 if ( scalar @{ $c->error } ) {
34 $c->stash->{errors} = $c->error;
35 $c->stash->{template} = 'errors.tt';
36 $c->forward('MyApp::View::TT');
37 $c->error(0);
38 }
39
40 return 1 if $c->response->status =~ /^3\d\d$/;
41 return 1 if $c->response->body;
42
43 unless ( $c->response->content_type ) {
44 $c->response->content_type('text/html; charset=utf-8');
45 }
46
47 $c->forward('MyApp::View::TT');
48 }
49
50 You can manually set errors in your code to trigger this page by call‐
51 ing
52
53 $c->error( 'You broke me!' );
54
55 Disable statistics
56
57 Just add this line to your application class if you don't want those
58 nifty statistics in your debug messages.
59
60 sub Catalyst::Log::info { }
61
62 Enable debug status in the environment
63
64 Normally you enable the debugging info by adding the "-Debug" flag to
65 your "use Catalyst" statement. However, you can also enable it using
66 environment variable, so you can (for example) get debug info without
67 modifying your application scripts. Just set "CATALYST_DEBUG" or
68 "<MYAPP>_DEBUG" to a true value.
69
70 Sessions
71
72 When you have your users identified, you will want to somehow remember
73 that fact, to save them from having to identify themselves for every
74 single page. One way to do this is to send the username and password
75 parameters in every single page, but that's ugly, and won't work for
76 static pages.
77
78 Sessions are a method of saving data related to some transaction, and
79 giving the whole collection a single ID. This ID is then given to the
80 user to return to us on every page they visit while logged in. The
81 usual way to do this is using a browser cookie.
82
83 Catalyst uses two types of plugins to represent sessions:
84
85 State
86
87 A State module is used to keep track of the state of the session
88 between the users browser, and your application.
89
90 A common example is the Cookie state module, which sends the browser a
91 cookie containing the session ID. It will use default value for the
92 cookie name and domain, so will "just work" when used.
93
94 Store
95
96 A Store module is used to hold all the data relating to your session,
97 for example the users ID, or the items for their shopping cart. You can
98 store data in memory (FastMmap), in a file (File) or in a database
99 (DBI).
100
101 Authentication magic
102
103 If you have included the session modules in your application, the
104 Authentication modules will automagically use your session to save and
105 retrieve the user data for you.
106
107 Using a session
108
109 Once the session modules are loaded, the session is available as
110 "$c->session", and can be writen to and read from as a simple hash ref‐
111 erence.
112
113 EXAMPLE
114
115 use Catalyst qw/
116 Session
117 Session::Store::FastMmap
118 Session::State::Cookie
119 /;
120
121 ## Write data into the session
122
123 sub add_item : Local {
124 my ( $self, $c ) = @_;
125
126 my $item_id = $c->req->param("item");
127
128 push @{ $c->session->{items} }, $item_id;
129
130 }
131
132 ## A page later we retrieve the data from the session:
133
134 sub get_items : Local {
135 my ( $self, $c ) = @_;
136
137 $c->stash->{items_to_display} = $c->session->{items};
138
139 }
140
141 More information
142
143 <http://search.cpan.org/dist/Catalyst-Plugin-Session>
144
145 <http://search.cpan.org/dist/Catalyst-Plugin-Session-State-Cookie>
146
147 <http://search.cpan.org/dist/Catalyst-Plugin-Session-State-URI>
148
149 <http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-FastMmap>
150
151 <http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-File>
152
153 <http://search.cpan.org/dist/Catalyst-Plugin-Session-Store-DBI>
154
155 Configure your application
156
157 You configure your application with the "config" method in your appli‐
158 cation class. This can be hard-coded, or brought in from a separate
159 configuration file.
160
161 Using YAML
162
163 YAML is a method for creating flexible and readable configuration
164 files. It's a great way to keep your Catalyst application configuration
165 in one easy-to-understand location.
166
167 In your application class (e.g. "lib/MyApp.pm"):
168
169 use YAML;
170 # application setup
171 __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') );
172 __PACKAGE__->setup;
173
174 Now create "myapp.yml" in your application home:
175
176 --- #YAML:1.0
177 # DO NOT USE TABS FOR INDENTATION OR label/value SEPARATION!!!
178 name: MyApp
179
180 # session; perldoc Catalyst::Plugin::Session::FastMmap
181 session:
182 expires: '3600'
183 rewrite: '0'
184 storage: '/tmp/myapp.session'
185
186 # emails; perldoc Catalyst::Plugin::Email
187 # this passes options as an array :(
188 email:
189 - SMTP
190 - localhost
191
192 This is equivalent to:
193
194 # configure base package
195 __PACKAGE__->config( name => MyApp );
196 # configure authentication
197 __PACKAGE__->config->{authentication} = {
198 user_class => 'MyApp::Model::MyDB::Customer',
199 ...
200 };
201 # configure sessions
202 __PACKAGE__->config->{session} = {
203 expires => 3600,
204 ...
205 };
206 # configure email sending
207 __PACKAGE__->config->{email} = [qw/SMTP localhost/];
208
209 See also YAML.
210
212 Catalyst uses Module::Pluggable to load Models, Views and Controllers.
213 Module::Pluggable will scan through all directories and load modules it
214 finds. Sometimes you might want to skip some of these directories, for
215 example when your version control system makes a subdirectory with
216 meta-information in every version-controlled directory. While Catalyst
217 skips subversion and CVS directories already, there are other source
218 control systems. Here is the configuration you need to add their
219 directories to the list to skip.
220
221 You can make catalyst skip these directories using the Catalyst config:
222
223 # Configure the application
224 __PACKAGE__->config(
225 name => 'MyApp',
226 setup_components => { except => qr/SCCS/ },
227 );
228
229 See the Module::Pluggable manual page for more information on except
230 and other options.
231
233 Most multiuser, and some single user web applications require that
234 users identify themselves, and the application is often required to
235 define those roles. The recipes below describe some ways of doing
236 this.
237
238 Authentication (logging in)
239
240 This is extensively covered in other documentation; see in particular
241 Catalyst::Plugin::Authentication and the Authentication chapter of the
242 Tutorial at Catalyst::Manual::Tutorial::Authorization.
243
244 Pass-through login (and other actions)
245
246 An easy way of having assorted actions that occur during the processing
247 of a request that are orthogonal to its actual purpose - logins, silent
248 commands etc. Provide actions for these, but when they're required for
249 something else fill e.g. a form variable __login and have a sub begin
250 like so:
251
252 sub begin : Private {
253 my ($self, $c) = @_;
254 foreach my $action (qw/login docommand foo bar whatever/) {
255 if ($c->req->params->{"__${action}"}) {
256 $c->forward($action);
257 }
258 }
259 }
260
261 Role-based Authorization
262
263 For more advanced access control, you may want to consider using role-
264 based authorization. This means you can assign different roles to each
265 user, e.g. "user", "admin", etc.
266
267 The "login" and "logout" methods and view template are exactly the same
268 as in the previous example.
269
270 The Catalyst::Plugin::Authorization::Roles plugin is required when
271 implementing roles:
272
273 use Catalyst qw/
274 Authentication
275 Authentication::Credential::Password
276 Authentication::Store::Htpasswd
277 Authorization::Roles
278 /;
279
280 Roles are implemented automatically when using Catalyst::Authentica‐
281 tion::Store::Htpasswd:
282
283 # no additional role configuration required
284 __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile";
285
286 Or can be set up manually when using Catalyst::Authentica‐
287 tion::Store::DBIC:
288
289 # Authorization using a many-to-many role relationship
290 __PACKAGE__->config->{authorization}{dbic} = {
291 'role_class' => 'My::Model::DBIC::Role',
292 'role_field' => 'name',
293 'user_role_user_field' => 'user',
294
295 # DBIx::Class only (omit if using Class::DBI)
296 'role_rel' => 'user_role',
297
298 # Class::DBI only, (omit if using DBIx::Class)
299 'user_role_class' => 'My::Model::CDBI::UserRole'
300 'user_role_role_field' => 'role',
301 };
302
303 To restrict access to any action, you can use the "check_user_roles"
304 method:
305
306 sub restricted : Local {
307 my ( $self, $c ) = @_;
308
309 $c->detach("unauthorized")
310 unless $c->check_user_roles( "admin" );
311
312 # do something restricted here
313 }
314
315 You can also use the "assert_user_roles" method. This just gives an
316 error if the current user does not have one of the required roles:
317
318 sub also_restricted : Global {
319 my ( $self, $c ) = @_;
320 $c->assert_user_roles( qw/ user admin / );
321 }
322
323 Authentication/Authorization
324
325 This is done in several steps:
326
327 Verification
328 Getting the user to identify themselves, by giving you some piece
329 of information known only to you and the user. Then you can assume
330 that the user is who they say they are. This is called credential
331 verification.
332
333 Authorization
334 Making sure the user only accesses functions you want them to
335 access. This is done by checking the verified users data against
336 your internal list of groups, or allowed persons for the current
337 page.
338
339 Modules
340
341 The Catalyst Authentication system is made up of many interacting mod‐
342 ules, to give you the most flexibility possible.
343
344 Credential verifiers
345
346 A Credential module tables the user input, and passes it to a Store, or
347 some other system, for verification. Typically, a user object is cre‐
348 ated by either this module or the Store and made accessible by a
349 "$c->user" call.
350
351 Examples:
352
353 Password - Simple username/password checking.
354 HTTPD - Checks using basic HTTP auth.
355 TypeKey - Check using the typekey system.
356
357 Storage backends
358
359 A Storage backend contains the actual data representing the users. It
360 is queried by the credential verifiers. Updating the store is not done
361 within this system, you will need to do it yourself.
362
363 Examples:
364
365 DBIC - Storage using a database.
366 Minimal - Storage using a simple hash (for testing).
367
368 User objects
369
370 A User object is created by either the storage backend or the creden‐
371 tial verifier, and filled with the retrieved user information.
372
373 Examples:
374
375 Hash - A simple hash of keys and values.
376
377 ACL authorization
378
379 ACL stands for Access Control List. The ACL plugin allows you to regu‐
380 late access on a path by path basis, by listing which users, or roles,
381 have access to which paths.
382
383 Roles authorization
384
385 Authorization by roles is for assigning users to groups, which can then
386 be assigned to ACLs, or just checked when needed.
387
388 Logging in
389
390 When you have chosen your modules, all you need to do is call the
391 "$c->login" method. If called with no parameters, it will try to find
392 suitable parameters, such as username and password, or you can pass it
393 these values.
394
395 Checking roles
396
397 Role checking is done by using the "$c->check_user_roles" method, this
398 will check using the currently logged in user (via "$c->user"). You
399 pass it the name of a role to check, and it returns true if the user is
400 a member.
401
402 EXAMPLE
403
404 use Catalyst qw/Authentication
405 Authentication::Credential::Password
406 Authentication::Store::Htpasswd
407 Authorization::Roles/;
408
409 __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile";
410
411 sub login : Local {
412 my ($self, $c) = @_;
413
414 if ( my $user = $c->req->param("user")
415 and my $password = $c->req->param("password") )
416 {
417 if ( $c->login( $user, $password ) ) {
418 $c->res->body( "hello " . $c->user->name );
419 } else {
420 # login incorrect
421 }
422 }
423 else {
424 # invalid form input
425 }
426 }
427
428 sub restricted : Local {
429 my ( $self, $c ) = @_;
430
431 $c->detach("unauthorized")
432 unless $c->check_user_roles( "admin" );
433
434 # do something restricted here
435 }
436
437 Using authentication in a testing environment
438
439 Ideally, to write tests for authentication/authorization code one would
440 first set up a test database with known data, then use Test::WWW::Mech‐
441 anize::Catalyst to simulate a user logging in. Unfortunately the former
442 can be rather awkward, which is why it's a good thing that the authen‐
443 tication framework is so flexible.
444
445 Instead of using a test database, one can simply change the authentica‐
446 tion store to something a bit easier to deal with in a testing environ‐
447 ment. Additionally, this has the advantage of not modifying one's data‐
448 base, which can be problematic if one forgets to use the testing
449 instead of production database.
450
451 e.g.,
452
453 use Catalyst::Plugin::Authentication::Store::Minimal::Backend;
454
455 # Sets up the user `test_user' with password `test_pass'
456 MyApp->default_auth_store(
457 Catalyst::Plugin::Authentication::Store::Minimal::Backend->new({
458 test_user => { password => 'test_pass' },
459 })
460 );
461
462 Now, your test code can call "$c-"login('test_user', 'test_pass')> and
463 successfully login, without messing with the database at all.
464
465 More information
466
467 <http://search.cpan.org/perldoc?Catalyst::Plugin::Authentication> has a
468 longer explanation.
469
470 Authorization
471
472 Introduction
473
474 Authorization is the step that comes after authentication. Authentica‐
475 tion establishes that the user agent is really representing the user we
476 think it's representing, and then authorization determines what this
477 user is allowed to do.
478
479 Role Based Access Control
480
481 Under role based access control each user is allowed to perform any
482 number of roles. For example, at a zoo no one but specially trained
483 personnel can enter the moose cage (Mynd you, møøse bites kan be pretty
484 nasti!). For example:
485
486 package Zoo::Controller::MooseCage;
487
488 sub feed_moose : Local {
489 my ( $self, $c ) = @_;
490
491 $c->model( "Moose" )->eat( $c->req->param("food") );
492 }
493
494 With this action, anyone can just come into the moose cage and feed the
495 moose, which is a very dangerous thing. We need to restrict this
496 action, so that only a qualified moose feeder can perform that action.
497
498 The Authorization::Roles plugin let's us perform role based access con‐
499 trol checks. Let's load it:
500
501 use Catalyst qw/
502 Authentication # yadda yadda
503 Authorization::Roles
504 /;
505
506 And now our action should look like this:
507
508 sub feed_moose : Local {
509 my ( $self, $c ) = @_;
510
511 if ( $c->check_roles( "moose_feeder" ) ) {
512 $c->model( "Moose" )->eat( $c->req->param("food") );
513 } else {
514 $c->stash->{error} = "unauthorized";
515 }
516 }
517
518 This checks "$c->user", and only if the user has all the roles in the
519 list, a true value is returned.
520
521 "check_roles" has a sister method, "assert_roles", which throws an
522 exception if any roles are missing.
523
524 Some roles that might actually make sense in, say, a forum application:
525
526 · administrator
527
528 · moderator
529
530 each with a distinct task (system administration versus content admin‐
531 istration).
532
533 Access Control Lists
534
535 Checking for roles all the time can be tedious and error prone.
536
537 The Authorization::ACL plugin let's us declare where we'd like checks
538 to be done automatically for us.
539
540 For example, we may want to completely block out anyone who isn't a
541 "moose_feeder" from the entire "MooseCage" controller:
542
543 Zoo->deny_access_unless( "/moose_cage", [qw/moose_feeder/] );
544
545 The role list behaves in the same way as "check_roles". However, the
546 ACL plugin isn't limited to just interacting with the Roles plugin. We
547 can use a code reference instead. For example, to allow either moose
548 trainers or moose feeders into the moose cage, we can create a more
549 complex check:
550
551 Zoo->deny_access_unless( "/moose_cage", sub {
552 my $c = shift;
553 $c->check_roles( "moose_trainer" ) ⎪⎪ $c->check_roles( "moose_feeder" );
554 });
555
556 The more specific a role, the earlier it will be checked. Let's say
557 moose feeders are now restricted to only the "feed_moose" action, while
558 moose trainers get access everywhere:
559
560 Zoo->deny_access_unless( "/moose_cage", [qw/moose_trainer/] );
561 Zoo->allow_access_if( "/moose_cage/feed_moose", [qw/moose_feeder/]);
562
563 When the "feed_moose" action is accessed the second check will be made.
564 If the user is a "moose_feeder", then access will be immediately
565 granted. Otherwise, the next rule in line will be tested - the one
566 checking for a "moose_trainer". If this rule is not satisfied, access
567 will be immediately denied.
568
569 Rules applied to the same path will be checked in the order they were
570 added.
571
572 Lastly, handling access denial events is done by creating an
573 "access_denied" private action:
574
575 sub access_denied : Private {
576 my ( $self, $c, $action ) = @_;
577
578 }
579
580 This action works much like auto, in that it is inherited across names‐
581 paces (not like object oriented code). This means that the
582 "access_denied" action which is nearest to the action which was blocked
583 will be triggered.
584
585 If this action does not exist, an error will be thrown, which you can
586 clean up in your "end" private action instead.
587
588 Also, it's important to note that if you restrict access to "/" then
589 "end", "default", etc will also be restricted.
590
591 MyApp->acl_allow_root_internals;
592
593 will create rules that permit access to "end", "begin", and "auto" in
594 the root of your app (but not in any other controller).
595
597 Models are where application data belongs. Catalyst is exteremely
598 flexible with the kind of models that it can use. The recipes here are
599 just the start.
600
601 Using existing DBIC (etc.) classes with Catalyst
602
603 Many people have existing Model classes that they would like to use
604 with Catalyst (or, conversely, they want to write Catalyst models that
605 can be used outside of Catalyst, e.g. in a cron job). It's trivial to
606 write a simple component in Catalyst that slurps in an outside Model:
607
608 package MyApp::Model::DB;
609 use base qw/Catalyst::Model::DBIC::Schema/;
610 __PACKAGE__->config(
611 schema_class => 'Some::DBIC::Schema',
612 connect_info => ['dbi:SQLite:foo.db', '', '', {AutoCommit=>1}];
613 );
614 1;
615
616 and that's it! Now "Some::DBIC::Schema" is part of your Cat app as
617 "MyApp::Model::DB".
618
619 DBIx::Class as a Catalyst Model
620
621 See Catalyst::Model::DBIC::Schema.
622
623 XMLRPC
624
625 Unlike SOAP, XMLRPC is a very simple (and imo elegant) web-services
626 protocol, exchanging small XML messages like these:
627
628 Request:
629
630 POST /api HTTP/1.1
631 TE: deflate,gzip;q=0.3
632 Connection: TE, close
633 Accept: text/xml
634 Accept: multipart/*
635 Host: 127.0.0.1:3000
636 User-Agent: SOAP::Lite/Perl/0.60
637 Content-Length: 192
638 Content-Type: text/xml
639
640 <?xml version="1.0" encoding="UTF-8"?>
641 <methodCall>
642 <methodName>add</methodName>
643 <params>
644 <param><value><int>1</int></value></param>
645 <param><value><int>2</int></value></param>
646 </params>
647 </methodCall>
648
649 Response:
650
651 Connection: close
652 Date: Tue, 20 Dec 2005 07:45:55 GMT
653 Content-Length: 133
654 Content-Type: text/xml
655 Status: 200
656 X-Catalyst: 5.70
657
658 <?xml version="1.0" encoding="us-ascii"?>
659 <methodResponse>
660 <params>
661 <param><value><int>3</int></value></param>
662 </params>
663 </methodResponse>
664
665 Now follow these few steps to implement the application:
666
667 1. Install Catalyst (5.61 or later), Catalyst::Plugin::XMLRPC (0.06 or
668 later) and SOAP::Lite (for XMLRPCsh.pl).
669
670 2. Create an application framework:
671
672 % catalyst.pl MyApp
673 ...
674 % cd MyApp
675
676 3. Add the XMLRPC plugin to MyApp.pm
677
678 use Catalyst qw/-Debug Static::Simple XMLRPC/;
679
680 4. Add an API controller
681
682 % ./script/myapp_create.pl controller API
683
684 5. Add a XMLRPC redispatch method and an add method with Remote
685 attribute to lib/MyApp/Controller/API.pm
686
687 sub default : Private {
688 my ( $self, $c ) = @_;
689 $c->xmlrpc;
690 }
691
692 sub add : Remote {
693 my ( $self, $c, $a, $b ) = @_;
694 return $a + $b;
695 }
696
697 The default action is the entry point for each XMLRPC request. It will
698 redispatch every request to methods with Remote attribute in the same
699 class.
700
701 The "add" method is not a traditional action; it has no private or pub‐
702 lic path. Only the XMLRPC dispatcher knows it exists.
703
704 6. That's it! You have built your first web service. Let's test it with
705 XMLRPCsh.pl (part of SOAP::Lite):
706
707 % ./script/myapp_server.pl
708 ...
709 % XMLRPCsh.pl http://127.0.0.1:3000/api
710 Usage: method[(parameters)]
711 > add( 1, 2 )
712 --- XMLRPC RESULT ---
713 '3'
714
715 Tip
716
717 Your return data type is usually auto-detected, but you can easily
718 enforce a specific one.
719
720 sub add : Remote {
721 my ( $self, $c, $a, $b ) = @_;
722 return RPC::XML::int->new( $a + $b );
723 }
724
726 Views pertain to the display of your application. As with models, cat‐
727 alyst is uncommonly flexible. The recipes below are just a start.
728
729 Catalyst::View::TT
730
731 One of the first things you probably want to do when starting a new
732 Catalyst application is set up your View. Catalyst doesn't care how you
733 display your data; you can choose to generate HTML, PDF files, or plain
734 text if you wanted.
735
736 Most Catalyst applications use a template system to generate their
737 HTML, and though there are several template systems available, Template
738 Toolkit is probably the most popular.
739
740 Once again, the Catalyst developers have done all the hard work, and
741 made things easy for the rest of us. Catalyst::View::TT provides the
742 interface to Template Toolkit, and provides Helpers which let us set it
743 up that much more easily.
744
745 Creating your View
746
747 Catalyst::View::TT provides two different helpers for us to use: TT and
748 TTSite.
749
750 TT
751
752 Create a basic Template Toolkit View using the provided helper script:
753
754 script/myapp_create.pl view TT TT
755
756 This will create lib/MyApp/View/MyView.pm, which is going to be pretty
757 empty to start. However, it sets everything up that you need to get
758 started. You can now define which template you want and forward to your
759 view. For instance:
760
761 sub hello : Local {
762 my ( $self, $c ) = @_;
763
764 $c->stash->{template} = 'hello.tt';
765
766 $c->forward( $c->view('TT') );
767 }
768
769 In practice you wouldn't do the forwarding manually, but would use Cat‐
770 alyst::Action::RenderView.
771
772 TTSite
773
774 Although the TT helper does create a functional, working view, you may
775 find yourself having to create the same template files and changing the
776 same options every time you create a new application. The TTSite helper
777 saves us even more time by creating the basic templates and setting
778 some common options for us.
779
780 Once again, you can use the helper script:
781
782 script/myapp_create.pl view TT TTSite
783
784 This time, the helper sets several options for us in the generated
785 View.
786
787 __PACKAGE__->config({
788 CATALYST_VAR => 'Catalyst',
789 INCLUDE_PATH => [
790 MyApp->path_to( 'root', 'src' ),
791 MyApp->path_to( 'root', 'lib' )
792 ],
793 PRE_PROCESS => 'config/main',
794 WRAPPER => 'site/wrapper',
795 ERROR => 'error.tt2',
796 TIMER => 0
797 });
798
799 · INCLUDE_PATH defines the directories that Template Toolkit should
800 search for the template files.
801
802 · PRE_PROCESS is used to process configuration options which are com‐
803 mon to every template file.
804
805 · WRAPPER is a file which is processed with each template, usually
806 used to easily provide a common header and footer for every page.
807
808 In addition to setting these options, the TTSite helper also created
809 the template and config files for us! In the 'root' directory, you'll
810 notice two new directories: src and lib.
811
812 Several configuration files in root/lib/config are called by
813 PRE_PROCESS.
814
815 The files in root/lib/site are the site-wide templates, called by WRAP‐
816 PER, and display the html framework, control the layout, and provide
817 the templates for the header and footer of your page. Using the tem‐
818 plate organization provided makes it much easier to standardize pages
819 and make changes when they are (inevitably) needed.
820
821 The template files that you will create for your application will go
822 into root/src, and you don't need to worry about putting the the <html>
823 or <head> sections; just put in the content. The WRAPPER will the rest
824 of the page around your template for you.
825
826 $c->stash
827
828 Of course, having the template system include the header and footer for
829 you isn't all that we want our templates to do. We need to be able to
830 put data into our templates, and have it appear where and how we want
831 it, right? That's where the stash comes in.
832
833 In our controllers, we can add data to the stash, and then access it
834 from the template. For instance:
835
836 sub hello : Local {
837 my ( $self, $c ) = @_;
838
839 $c->stash->{name} = 'Adam';
840
841 $c->stash->{template} = 'hello.tt';
842
843 $c->forward( $c->view('TT') );
844 }
845
846 Then, in hello.tt:
847
848 <strong>Hello, [% name %]!</strong>
849
850 When you view this page, it will display "Hello, Adam!"
851
852 All of the information in your stash is available, by its name/key, in
853 your templates. And your data don't have to be plain, old, boring
854 scalars. You can pass array references and hash references, too.
855
856 In your controller:
857
858 sub hello : Local {
859 my ( $self, $c ) = @_;
860
861 $c->stash->{names} = [ 'Adam', 'Dave', 'John' ];
862
863 $c->stash->{template} = 'hello.tt';
864
865 $c->forward( $c->view('TT') );
866 }
867
868 In hello.tt:
869
870 [% FOREACH name IN names %]
871 <strong>Hello, [% name %]!</strong><br />
872 [% END %]
873
874 This allowed us to loop through each item in the arrayref, and display
875 a line for each name that we have.
876
877 This is the most basic usage, but Template Toolkit is quite powerful,
878 and allows you to truly keep your presentation logic separate from the
879 rest of your application.
880
881 $c->uri_for()
882
883 One of my favorite things about Catalyst is the ability to move an
884 application around without having to worry that everything is going to
885 break. One of the areas that used to be a problem was with the http
886 links in your template files. For example, suppose you have an applica‐
887 tion installed at http://www.domain.com/Calendar. The links point to
888 "/Calendar", "/Calendar/2005", "/Calendar/2005/10", etc. If you move
889 the application to be at http://www.mydomain.com/Tools/Calendar, then
890 all of those links will suddenly break.
891
892 That's where $c->uri_for() comes in. This function will merge its
893 parameters with either the base location for the app, or its current
894 namespace. Let's take a look at a couple of examples.
895
896 In your template, you can use the following:
897
898 <a href="[% c.uri_for('/login') %]">Login Here</a>
899
900 Although the parameter starts with a forward slash, this is relative to
901 the application root, not the webserver root. This is important to
902 remember. So, if your application is installed at
903 http://www.domain.com/Calendar, then the link would be http://www.mydo‐
904 main.com/Calendar/Login. If you move your application to a different
905 domain or path, then that link will still be correct.
906
907 Likewise,
908
909 <a href="[% c.uri_for('2005','10', '24') %]">October, 24 2005</a>
910
911 The first parameter does NOT have a forward slash, and so it will be
912 relative to the current namespace. If the application is installed at
913 http://www.domain.com/Calendar. and if the template is called from
914 MyApp::Controller::Display, then the link would become
915 http://www.domain.com/Calendar/Display/2005/10/24.
916
917 Once again, this allows you to move your application around without
918 having to worry about broken links. But there's something else, as
919 well. Since the links are generated by uri_for, you can use the same
920 template file by several different controllers, and each controller
921 will get the links that its supposed to. Since we believe in Don't
922 Repeat Yourself, this is particularly helpful if you have common ele‐
923 ments in your site that you want to keep in one file.
924
925 Further Reading:
926
927 <http://search.cpan.org/perldoc?Catalyst>
928
929 <http://search.cpan.org/perldoc?Catalyst%3A%3AView%3A%3ATT>
930
931 <http://search.cpan.org/perldoc?Template>
932
933 Adding RSS feeds
934
935 Adding RSS feeds to your Catalyst applications is simple. We'll see two
936 different aproaches here, but the basic premise is that you forward to
937 the normal view action first to get the objects, then handle the output
938 differently.
939
940 Using TT templates
941
942 This is the aproach used in Agave (<http://dev.rawmode.org/>).
943
944 sub rss : Local {
945 my ($self,$c) = @_;
946 $c->forward('view');
947 $c->stash->{template}='rss.tt';
948 }
949
950 Then you need a template. Here's the one from Agave:
951
952 <?xml version="1.0" encoding="UTF-8"?>
953 <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
954 <channel>
955 <title>[ [% blog.name ⎪⎪ c.config.name ⎪⎪ "Agave" %] ] RSS Feed</title>
956 <link>[% base %]</link>
957 <description>Recent posts</description>
958 <language>en-us</language>
959 <ttl>40</ttl>
960 [% WHILE (post = posts.next) %]
961 <item>
962 <title>[% post.title %]</title>
963 <description>[% post.formatted_teaser⎪html%]</description>
964 <pubDate>[% post.pub_date %]</pubDate>
965 <guid>[% post.full_uri %]</guid>
966 <link>[% post.full_uri %]</link>
967 <dc:creator>[% post.author.screenname %]</dc:creator>
968 </item>
969 [% END %]
970 </channel>
971 </rss>
972
973 Using XML::Feed
974
975 A more robust solution is to use XML::Feed, as was done in the Catalyst
976 Advent Calendar. Assuming we have a "view" action that populates
977 'entries' with some DBIx::Class iterator, the code would look something
978 like this:
979
980 sub rss : Local {
981 my ($self,$c) = @_;
982 $c->forward('view'); # get the entries
983
984 my $feed = XML::Feed->new('RSS');
985 $feed->title( $c->config->{name} . ' RSS Feed' );
986 $feed->link( $c->req->base ); # link to the site.
987 $feed->description('Catalyst advent calendar'); Some description
988
989 # Process the entries
990 while( my $entry = $c->stash->{entries}->next ) {
991 my $feed_entry = XML::Feed::Entry->new('RSS');
992 $feed_entry->title($entry->title);
993 $feed_entry->link( $c->uri_for($entry->link) );
994 $feed_entry->issued( DateTime->from_epoch(epoch => $entry->created) );
995 $feed->add_entry($feed_entry);
996 }
997 $c->res->body( $feed->as_xml );
998 }
999
1000 A little more code in the controller, but with this approach you're
1001 pretty sure to get something that validates.
1002
1003 Note that for both of the above aproaches, you'll need to set the con‐
1004 tent type like this:
1005
1006 $c->res->content_type('application/rss+xml');
1007
1008 Final words
1009
1010 You could generalize the second variant easily by replacing 'RSS' with
1011 a variable, so you can generate Atom feeds with the same code.
1012
1013 Now, go ahead and make RSS feeds for all your stuff. The world *needs*
1014 updates on your goldfish!
1015
1016 Forcing the browser to download content
1017
1018 Sometimes you need your application to send content for download. For
1019 example, you can generate a comma-separated values (CSV) file for your
1020 users to download and import into their spreadsheet program.
1021
1022 Let's say you have an "Orders" controller which generates a CSV file in
1023 the "export" action (i.e., "http://localhost:3000/orders/export"):
1024
1025 sub export : Local Args(0) {
1026 my ( $self, $c ) = @_;
1027
1028 # In a real application, you'd generate this from the database
1029 my $csv = "1,5.99\n2,29.99\n3,3.99\n";
1030
1031 $c->res->content_type('text/comma-separated-values');
1032 $c->res->body($csv);
1033 }
1034
1035 Normally the browser uses the last part of the URI to generate a file‐
1036 name for data it cannot display. In this case your browser would likely
1037 ask you to save a file named "export".
1038
1039 Luckily you can have the browser download the content with a specific
1040 filename by setting the "Content-Disposition" header:
1041
1042 my $filename = 'Important Orders.csv';
1043 $c->res->header('Content-Disposition', qq[attachment; filename="$filename"]);
1044
1045 Note the use of quotes around the filename; this ensures that any spa‐
1046 ces in the filename are handled by the browser.
1047
1048 Put this right before calling "$c->res->body" and your browser will
1049 download a file named "Important Orders.csv" instead of "export".
1050
1051 You can also use this to have the browser download content which it
1052 normally displays, such as JPEG images or even HTML. Just be sure to
1053 set the appropriate content type and disposition.
1054
1056 Controllers are the main point of communication between the web server
1057 and your application. Here we explore some aspects of how they work.
1058
1059 Extending RenderView (formerly DefaultEnd)
1060
1061 The recommended approach for an "end" action is to use Cata‐
1062 lyst::Action::RenderView (taking the place of Catalyst::Plugin::Defaul‐
1063 tEnd), which does what you usually need. However there are times when
1064 you need to add a bit to it, but don't want to write your own "end"
1065 action.
1066
1067 You can extend it like this:
1068
1069 To add something to an "end" action that is called before rendering
1070 (this is likely to be what you want), simply place it in the "end"
1071 method:
1072
1073 sub end : ActionClass('RenderView') {
1074 my ( $self, $c ) = @_;
1075 # do stuff here; the RenderView action is called afterwards
1076 }
1077
1078 To add things to an "end" action that are called after rendering, you
1079 can set it up like this:
1080
1081 sub render : ActionClass('RenderView') { }
1082
1083 sub end : Private {
1084 my ( $self, $c ) = @_;
1085 $c->forward('render');
1086 # do stuff here
1087 }
1088
1089 Action Types
1090
1091 Introduction
1092
1093 A Catalyst application is driven by one or more Controller modules.
1094 There are a number of ways that Catalyst can decide which of the meth‐
1095 ods in your controller modules it should call. Controller methods are
1096 also called actions, because they determine how your catalyst applica‐
1097 tion should (re-)act to any given URL. When the application is started
1098 up, catalyst looks at all your actions, and decides which URLs they map
1099 to.
1100
1101 Type attributes
1102
1103 Each action is a normal method in your controller, except that it has
1104 an attribute attached. These can be one of several types.
1105
1106 Assume our Controller module starts with the following package declara‐
1107 tion:
1108
1109 package MyApp::Controller::Buckets;
1110
1111 and we are running our application on localhost, port 3000 (the test
1112 server default).
1113
1114 Path
1115 A Path attribute also takes an argument, this can be either a rela‐
1116 tive or an absolute path. A relative path will be relative to the
1117 controller namespace, an absolute path will represent an exact
1118 matching URL.
1119
1120 sub my_handles : Path('handles') { .. }
1121
1122 becomes
1123
1124 http://localhost:3000/buckets/handles
1125
1126 and
1127
1128 sub my_handles : Path('/handles') { .. }
1129
1130 becomes
1131
1132 http://localhost:3000/handles
1133
1134 Local
1135 When using a Local attribute, no parameters are needed, instead,
1136 the name of the action is matched in the URL. The namespaces cre‐
1137 ated by the name of the controller package is always part of the
1138 URL.
1139
1140 sub my_handles : Local { .. }
1141
1142 becomes
1143
1144 http://localhost:3000/buckets/my_handles
1145
1146 Global
1147 A Global attribute is similar to a Local attribute, except that the
1148 namespace of the controller is ignored, and matching starts at
1149 root.
1150
1151 sub my_handles : Global { .. }
1152
1153 becomes
1154
1155 http://localhost:3000/my_handles
1156
1157 Regex
1158 By now you should have figured that a Regex attribute is just what
1159 it sounds like. This one takes a regular expression, and matches
1160 starting from root. These differ from the rest as they can match
1161 multiple URLs.
1162
1163 sub my_handles : Regex('^handles') { .. }
1164
1165 matches
1166
1167 http://localhost:3000/handles
1168
1169 and
1170
1171 http://localhost:3000/handles_and_other_parts
1172
1173 etc.
1174
1175 LocalRegex
1176 A LocalRegex is similar to a Regex, except it only matches below
1177 the current controller namespace.
1178
1179 sub my_handles : LocalRegex(^handles') { .. }
1180
1181 matches
1182
1183 http://localhost:3000/buckets/handles
1184
1185 and
1186
1187 http://localhost:3000/buckets/handles_and_other_parts
1188
1189 etc.
1190
1191 Private
1192 Last but not least, there is the Private attribute, which allows
1193 you to create your own internal actions, which can be forwarded to,
1194 but won't be matched as URLs.
1195
1196 sub my_handles : Private { .. }
1197
1198 becomes nothing at all..
1199
1200 Catalyst also predefines some special Private actions, which you
1201 can override, these are:
1202
1203 default
1204 The default action will be called, if no other matching action
1205 is found. If you don't have one of these in your namespace, or
1206 any sub part of your namespace, you'll get an error page
1207 instead. If you want to find out where it was the user was try‐
1208 ing to go, you can look in the request object using
1209 "$c->req->path".
1210
1211 sub default : Private { .. }
1212
1213 works for all unknown URLs, in this controller namespace, or
1214 every one if put directly into MyApp.pm.
1215
1216 index
1217 The index action is called when someone tries to visit the
1218 exact namespace of your controller. If index, default and
1219 matching Path actions are defined, then index will be used
1220 instead of default and Path.
1221
1222 sub index : Private { .. }
1223
1224 becomes
1225
1226 http://localhost:3000/buckets
1227
1228 begin
1229 The begin action is called at the beginning of every request
1230 involving this namespace directly, before other matching
1231 actions are called. It can be used to set up variables/data for
1232 this particular part of your app. A single begin action is
1233 called, its always the one most relevant to the current names‐
1234 pace.
1235
1236 sub begin : Private { .. }
1237
1238 is called once when
1239
1240 http://localhost:3000/bucket/(anything)?
1241
1242 is visited.
1243
1244 end Like begin, this action is always called for the namespace it
1245 is in, after every other action has finished. It is commonly
1246 used to forward processing to the View component. A single end
1247 action is called, its always the one most relevant to the cur‐
1248 rent namespace.
1249
1250 sub end : Private { .. }
1251
1252 is called once after any actions when
1253
1254 http://localhost:3000/bucket/(anything)?
1255
1256 is visited.
1257
1258 auto
1259 Lastly, the auto action is magic in that every auto action in
1260 the chain of paths up to and including the ending namespace,
1261 will be called. (In contrast, only one of the begin/end/default
1262 actions will be called, the relevant one).
1263
1264 package MyApp.pm;
1265 sub auto : Private { .. }
1266
1267 and
1268
1269 sub auto : Private { .. }
1270
1271 will both be called when visiting
1272
1273 http://localhost:3000/bucket/(anything)?
1274
1275 A word of warning
1276
1277 Due to possible namespace conflicts with Plugins, it is advised to only
1278 put the pre-defined Private actions in your main MyApp.pm file, all
1279 others should go in a Controller module.
1280
1281 More Information
1282
1283 <http://search.cpan.org/author/SRI/Catalyst-5.61/lib/Catalyst/Man‐
1284 ual/Intro.pod>
1285
1286 <http://dev.catalyst.perl.org/wiki/FlowChart>
1287
1288 Component-based Subrequests
1289
1290 See Catalyst::Plugin::SubRequest.
1291
1292 File uploads
1293
1294 Single file upload with Catalyst
1295
1296 To implement uploads in Catalyst, you need to have a HTML form similar
1297 to this:
1298
1299 <form action="/upload" method="post" enctype="multipart/form-data">
1300 <input type="hidden" name="form_submit" value="yes">
1301 <input type="file" name="my_file">
1302 <input type="submit" value="Send">
1303 </form>
1304
1305 It's very important not to forget "enctype="multipart/form-data"" in
1306 the form.
1307
1308 Catalyst Controller module 'upload' action:
1309
1310 sub upload : Global {
1311 my ($self, $c) = @_;
1312
1313 if ( $c->request->parameters->{form_submit} eq 'yes' ) {
1314
1315 if ( my $upload = $c->request->upload('my_file') ) {
1316
1317 my $filename = $upload->filename;
1318 my $target = "/tmp/upload/$filename";
1319
1320 unless ( $upload->link_to($target) ⎪⎪ $upload->copy_to($target) ) {
1321 die( "Failed to copy '$filename' to '$target': $!" );
1322 }
1323 }
1324 }
1325
1326 $c->stash->{template} = 'file_upload.html';
1327 }
1328
1329 Multiple file upload with Catalyst
1330
1331 Code for uploading multiple files from one form needs a few changes:
1332
1333 The form should have this basic structure:
1334
1335 <form action="/upload" method="post" enctype="multipart/form-data">
1336 <input type="hidden" name="form_submit" value="yes">
1337 <input type="file" name="file1" size="50"><br>
1338 <input type="file" name="file2" size="50"><br>
1339 <input type="file" name="file3" size="50"><br>
1340 <input type="submit" value="Send">
1341 </form>
1342
1343 And in the controller:
1344
1345 sub upload : Local {
1346 my ($self, $c) = @_;
1347
1348 if ( $c->request->parameters->{form_submit} eq 'yes' ) {
1349
1350 for my $field ( $c->req->upload ) {
1351
1352 my $upload = $c->req->upload($field);
1353 my $filename = $upload->filename;
1354 my $target = "/tmp/upload/$filename";
1355
1356 unless ( $upload->link_to($target) ⎪⎪ $upload->copy_to($target) ) {
1357 die( "Failed to copy '$filename' to '$target': $!" );
1358 }
1359 }
1360 }
1361
1362 $c->stash->{template} = 'file_upload.html';
1363 }
1364
1365 "for my $field ($c->req-"upload)> loops automatically over all file
1366 input fields and gets input names. After that is basic file saving
1367 code, just like in single file upload.
1368
1369 Notice: "die"ing might not be what you want to do, when an error
1370 occurs, but it works as an example. A better idea would be to store
1371 error $! in $c->stash->{error} and show a custom error template dis‐
1372 playing this message.
1373
1374 For more information about uploads and usable methods look at Cata‐
1375 lyst::Request::Upload and Catalyst::Request.
1376
1377 Forwarding with arguments
1378
1379 Sometimes you want to pass along arguments when forwarding to another
1380 action. As of version 5.30, arguments can be passed in the call to
1381 "forward"; in earlier versions, you can manually set the arguments in
1382 the Catalyst Request object:
1383
1384 # version 5.30 and later:
1385 $c->forward('/wherever', [qw/arg1 arg2 arg3/]);
1386
1387 # pre-5.30
1388 $c->req->args([qw/arg1 arg2 arg3/]);
1389 $c->forward('/wherever');
1390
1391 (See the Catalyst::Manual::Intro Flow_Control section for more informa‐
1392 tion on passing arguments via "forward".)
1393
1395 The recipes below describe aspects of the deployment process, including
1396 web server engines and tips to improve application efficiency.
1397
1398 mod_perl Deployment
1399
1400 mod_perl is the best solution for many applications, but we'll list
1401 some pros and cons so you can decide for yourself. The other produc‐
1402 tion deployment option is FastCGI, for which see below.
1403
1404 Pros
1405
1406 Speed
1407
1408 mod_perl is very fast and your app will benefit from being loaded in
1409 memory within each Apache process.
1410
1411 Shared memory for multiple apps
1412
1413 If you need to run several Catalyst apps on the same server, mod_perl
1414 will share the memory for common modules.
1415
1416 Cons
1417
1418 Memory usage
1419
1420 Since your application is fully loaded in memory, every Apache process
1421 will be rather large. This means a large Apache process will be tied
1422 up while serving static files, large files, or dealing with slow
1423 clients. For this reason, it is best to run a two-tiered web architec‐
1424 ture with a lightweight frontend server passing dynamic requests to a
1425 large backend mod_perl server.
1426
1427 Reloading
1428
1429 Any changes made to the core code of your app require a full Apache
1430 restart. Catalyst does not support Apache::Reload or StatINC. This is
1431 another good reason to run a frontend web server where you can set up
1432 an "ErrorDocument 502" page to report that your app is down for mainte‐
1433 nance.
1434
1435 Cannot run multiple versions of the same app
1436
1437 It is not possible to run two different versions of the same applica‐
1438 tion in the same Apache instance because the namespaces will collide.
1439
1440 Setup
1441
1442 Now that we have that out of the way, let's talk about setting up
1443 mod_perl to run a Catalyst app.
1444
1445 1. Install Catalyst::Engine::Apache
1446
1447 You should install the latest versions of both Catalyst and Cata‐
1448 lyst::Engine::Apache. The Apache engines were separated from the Cata‐
1449 lyst core in version 5.50 to allow for updates to the engine without
1450 requiring a new Catalyst release.
1451
1452 2. Install Apache with mod_perl
1453
1454 Both Apache 1.3 and Apache 2 are supported, although Apache 2 is highly
1455 recommended. With Apache 2, make sure you are using the prefork MPM
1456 and not the worker MPM. The reason for this is that many Perl modules
1457 are not thread-safe and may have problems running within the threaded
1458 worker environment. Catalyst is thread-safe however, so if you know
1459 what you're doing, you may be able to run using worker.
1460
1461 In Debian, the following commands should get you going.
1462
1463 apt-get install apache2-mpm-prefork
1464 apt-get install libapache2-mod-perl2
1465
1466 3. Configure your application
1467
1468 Every Catalyst application will automagically become a mod_perl handler
1469 when run within mod_perl. This makes the configuration extremely easy.
1470 Here is a basic Apache 2 configuration.
1471
1472 PerlSwitches -I/var/www/MyApp/lib
1473 PerlModule MyApp
1474
1475 <Location />
1476 SetHandler modperl
1477 PerlResponseHandler MyApp
1478 </Location>
1479
1480 The most important line here is "PerlModule MyApp". This causes
1481 mod_perl to preload your entire application into shared memory, includ‐
1482 ing all of your controller, model, and view classes and configuration.
1483 If you have -Debug mode enabled, you will see the startup output scroll
1484 by when you first start Apache.
1485
1486 For an example Apache 1.3 configuration, please see the documentation
1487 for Catalyst::Engine::Apache::MP13.
1488
1489 Test It
1490
1491 That's it, your app is now a full-fledged mod_perl application! Try it
1492 out by going to http://your.server.com/.
1493
1494 Other Options
1495
1496 Non-root location
1497
1498 You may not always want to run your app at the root of your server or
1499 virtual host. In this case, it's a simple change to run at any non-
1500 root location of your choice.
1501
1502 <Location /myapp>
1503 SetHandler modperl
1504 PerlResponseHandler MyApp
1505 </Location>
1506
1507 When running this way, it is best to make use of the "uri_for" method
1508 in Catalyst for constructing correct links.
1509
1510 Static file handling
1511
1512 Static files can be served directly by Apache for a performance boost.
1513
1514 DocumentRoot /var/www/MyApp/root
1515 <Location /static>
1516 SetHandler default-handler
1517 </Location>
1518
1519 This will let all files within root/static be handled directly by
1520 Apache. In a two-tiered setup, the frontend server should handle
1521 static files. The configuration to do this on the frontend will vary.
1522
1523 Catalyst on shared hosting
1524
1525 So, you want to put your Catalyst app out there for the whole world to
1526 see, but you don't want to break the bank. There is an answer - if you
1527 can get shared hosting with FastCGI and a shell, you can install your
1528 Catalyst app in a local directory on your shared host. First, run
1529
1530 perl -MCPAN -e shell
1531
1532 and go through the standard CPAN configuration process. Then exit out
1533 without installing anything. Next, open your .bashrc and add
1534
1535 export PATH=$HOME/local/bin:$HOME/local/script:$PATH
1536 perlversion=`perl -v ⎪ grep 'built for' ⎪ awk '{print $4}' ⎪ sed -e 's/v//;'`
1537 export PERL5LIB=$HOME/local/share/perl/$perlversion:$HOME/local/lib/perl/$perlversion:$HOME/local/lib:$PERL5LIB
1538
1539 and log out, then back in again (or run ". .bashrc" if you prefer).
1540 Finally, edit ".cpan/CPAN/MyConfig.pm" and add
1541
1542 'make_install_arg' => qq[SITEPREFIX=$ENV{HOME}/local],
1543 'makepl_arg' => qq[INSTALLDIRS=site install_base=$ENV{HOME}/local],
1544
1545 Now you can install the modules you need using CPAN as normal; they
1546 will be installed into your local directory, and perl will pick them
1547 up. Finally, change directory into the root of your virtual host and
1548 symlink your application's script directory in:
1549
1550 cd path/to/mydomain.com
1551 ln -s ~/lib/MyApp/script script
1552
1553 And add the following lines to your .htaccess file (assuming the server
1554 is setup to handle .pl as fcgi - you may need to rename the script to
1555 myapp_fastcgi.fcgi and/or use a SetHandler directive):
1556
1557 RewriteEngine On
1558 RewriteCond %{REQUEST_URI} !^/?script/myapp_fastcgi.pl
1559 RewriteRule ^(.*)$ script/myapp_fastcgi.pl/$1 [PT,L]
1560
1561 Now "http://mydomain.com/" should now Just Work. Congratulations, now
1562 you can tell your friends about your new website (or in our case, tell
1563 the client it's time to pay the invoice :) )
1564
1565 FastCGI Deployment
1566
1567 FastCGI is a high-performance extension to CGI. It is suitable for pro‐
1568 duction environments.
1569
1570 Pros
1571
1572 Speed
1573
1574 FastCGI performs equally as well as mod_perl. Don't let the 'CGI' fool
1575 you; your app runs as multiple persistent processes ready to receive
1576 connections from the web server.
1577
1578 App Server
1579
1580 When using external FastCGI servers, your application runs as a stand‐
1581 alone application server. It may be restarted independently from the
1582 web server. This allows for a more robust environment and faster
1583 reload times when pushing new app changes. The frontend server can
1584 even be configured to display a friendly "down for maintenance" page
1585 while the application is restarting.
1586
1587 Load-balancing
1588
1589 You can launch your application on multiple backend servers and allow
1590 the frontend web server to load-balance between all of them. And of
1591 course, if one goes down, your app continues to run fine.
1592
1593 Multiple versions of the same app
1594
1595 Each FastCGI application is a separate process, so you can run differ‐
1596 ent versions of the same app on a single server.
1597
1598 Can run with threaded Apache
1599
1600 Since your app is not running inside of Apache, the faster mpm_worker
1601 module can be used without worrying about the thread safety of your
1602 application.
1603
1604 Cons
1605
1606 More complex environment
1607
1608 With FastCGI, there are more things to monitor and more processes run‐
1609 ning than when using mod_perl.
1610
1611 Setup
1612
1613 1. Install Apache with mod_fastcgi
1614
1615 mod_fastcgi for Apache is a third party module, and can be found at
1616 <http://www.fastcgi.com/>. It is also packaged in many distributions,
1617 for example, libapache2-mod-fastcgi in Debian.
1618
1619 2. Configure your application
1620
1621 # Serve static content directly
1622 DocumentRoot /var/www/MyApp/root
1623 Alias /static /var/www/MyApp/root/static
1624
1625 FastCgiServer /var/www/MyApp/script/myapp_fastcgi.pl -processes 3
1626 Alias /myapp/ /var/www/MyApp/script/myapp_fastcgi.pl/
1627
1628 # Or, run at the root
1629 Alias / /var/www/MyApp/script/myapp_fastcgi.pl/
1630
1631 The above commands will launch 3 app processes and make the app avail‐
1632 able at /myapp/
1633
1634 Standalone server mode
1635
1636 While not as easy as the previous method, running your app as an exter‐
1637 nal server gives you much more flexibility.
1638
1639 First, launch your app as a standalone server listening on a socket.
1640
1641 script/myapp_fastcgi.pl -l /tmp/myapp.socket -n 5 -p /tmp/myapp.pid -d
1642
1643 You can also listen on a TCP port if your web server is not on the same
1644 machine.
1645
1646 script/myapp_fastcgi.pl -l :8080 -n 5 -p /tmp/myapp.pid -d
1647
1648 You will probably want to write an init script to handle starting/stop‐
1649 ping of the app using the pid file.
1650
1651 Now, we simply configure Apache to connect to the running server.
1652
1653 # 502 is a Bad Gateway error, and will occur if the backend server is down
1654 # This allows us to display a friendly static page that says "down for
1655 # maintenance"
1656 Alias /_errors /var/www/MyApp/root/error-pages
1657 ErrorDocument 502 /_errors/502.html
1658
1659 FastCgiExternalServer /tmp/myapp.fcgi -socket /tmp/myapp.socket
1660 Alias /myapp/ /tmp/myapp.fcgi/
1661
1662 # Or, run at the root
1663 Alias / /tmp/myapp.fcgi/
1664
1665 More Info
1666
1667 Catalyst::Engine::FastCGI.
1668
1669 Development server deployment
1670
1671 The development server is a mini web server written in perl. If you
1672 expect a low number of hits or you don't need mod_perl/FastCGI speed,
1673 you could use the development server as the application server with a
1674 lightweight proxy web server at the front. However, be aware that
1675 there are known issues, especially with Internet Explorer. Many of
1676 these issues can be dealt with by running the server with the -k
1677 (keepalive) option but be aware for more complex applications this may
1678 not be suitable. Consider using Catalyst::Engine::HTTP::POE. This
1679 recipe is easily adapted for POE as well.
1680
1681 Pros
1682
1683 As this is an application server setup, the pros are the same as
1684 FastCGI (with the exception of speed). It is also:
1685
1686 Simple
1687
1688 The development server is what you create your code on, so if it works
1689 here, it should work in production!
1690
1691 Cons
1692
1693 Speed
1694
1695 Not as fast as mod_perl or FastCGI. Needs to fork for each request that
1696 comes in - make sure static files are served by the web server to save
1697 forking.
1698
1699 Setup
1700
1701 Start up the development server
1702
1703 script/myapp_server.pl -p 8080 -k -f -pidfile=/tmp/myapp.pid -daemon
1704
1705 You will probably want to write an init script to handle stop/starting
1706 the app using the pid file.
1707
1708 Configuring Apache
1709
1710 Make sure mod_proxy is enabled and add:
1711
1712 # Serve static content directly
1713 DocumentRoot /var/www/MyApp/root
1714 Alias /static /var/www/MyApp/root/static
1715
1716 ProxyRequests Off
1717 <Proxy *>
1718 Order deny,allow
1719 Allow from all
1720 </Proxy>
1721 ProxyPass / http://localhost:8080/
1722 ProxyPassReverse / http://localhost:8080/
1723
1724 You can wrap the above within a VirtualHost container if you want dif‐
1725 ferent apps served on the same host.
1726
1727 Quick deployment: Building PAR Packages
1728
1729 You have an application running on your development box, but then you
1730 have to quickly move it to another one for demonstration/deploy‐
1731 ment/testing...
1732
1733 PAR packages can save you from a lot of trouble here. They are usual
1734 Zip files that contain a blib tree; you can even include all prereqs
1735 and a perl interpreter by setting a few flags!
1736
1737 Follow these few points to try it out!
1738
1739 1. Install Catalyst and PAR 0.89 (or later)
1740
1741 % perl -MCPAN -e 'install Catalyst'
1742 ...
1743 % perl -MCPAN -e 'install PAR'
1744 ...
1745
1746 2. Create a application
1747
1748 % catalyst.pl MyApp
1749 ...
1750 % cd MyApp
1751
1752 Recent versions of Catalyst (5.62 and up) include Module::Install::Cat‐
1753 alyst, which simplifies the process greatly. From the shell in your
1754 application directory:
1755
1756 % perl Makefile.PL
1757 % make catalyst_par
1758
1759 Congratulations! Your package "myapp.par" is ready, the following steps
1760 are just optional.
1761
1762 3. Test your PAR package with "parl" (no typo)
1763
1764 % parl myapp.par
1765 Usage:
1766 [parl] myapp[.par] [script] [arguments]
1767
1768 Examples:
1769 parl myapp.par myapp_server.pl -r
1770 myapp myapp_cgi.pl
1771
1772 Available scripts:
1773 myapp_cgi.pl
1774 myapp_create.pl
1775 myapp_fastcgi.pl
1776 myapp_server.pl
1777 myapp_test.pl
1778
1779 % parl myapp.par myapp_server.pl
1780 You can connect to your server at http://localhost:3000
1781
1782 Yes, this nifty little starter application gets automatically included.
1783 You can also use "catalyst_par_script('myapp_server.pl')" to set a
1784 default script to execute.
1785
1786 6. Want to create a binary that includes the Perl interpreter?
1787
1788 % pp -o myapp myapp.par
1789 % ./myapp myapp_server.pl
1790 You can connect to your server at http://localhost:3000
1791
1792 Serving static content
1793
1794 Serving static content in Catalyst used to be somewhat tricky; the use
1795 of Catalyst::Plugin::Static::Simple makes everything much easier. This
1796 plugin will automatically serve your static content during development,
1797 but allows you to easily switch to Apache (or other server) in a pro‐
1798 duction environment.
1799
1800 Introduction to Static::Simple
1801
1802 Static::Simple is a plugin that will help to serve static content for
1803 your application. By default, it will serve most types of files,
1804 excluding some standard Template Toolkit extensions, out of your root
1805 file directory. All files are served by path, so if images/me.jpg is
1806 requested, then root/images/me.jpg is found and served.
1807
1808 Usage
1809
1810 Using the plugin is as simple as setting your use line in MyApp.pm to
1811 include:
1812
1813 use Catalyst qw/Static::Simple/;
1814
1815 and already files will be served.
1816
1817 Configuring
1818
1819 Static content is best served from a single directory within your root
1820 directory. Having many different directories such as "root/css" and
1821 "root/images" requires more code to manage, because you must separately
1822 identify each static directory--if you decide to add a "root/js" direc‐
1823 tory, you'll need to change your code to account for it. In contrast,
1824 keeping all static directories as subdirectories of a main
1825 "root/static" directory makes things much easier to manage. Here's an
1826 example of a typical root directory structure:
1827
1828 root/
1829 root/content.tt
1830 root/controller/stuff.tt
1831 root/header.tt
1832 root/static/
1833 root/static/css/main.css
1834 root/static/images/logo.jpg
1835 root/static/js/code.js
1836
1837 All static content lives under "root/static", with everything else
1838 being Template Toolkit files.
1839
1840 Include Path
1841 You may of course want to change the default locations, and make
1842 Static::Simple look somewhere else, this is as easy as:
1843
1844 MyApp->config->{static}->{include_path} = [
1845 MyApp->config->{root},
1846 '/path/to/my/files'
1847 ];
1848
1849 When you override include_path, it will not automatically append
1850 the normal root path, so you need to add it yourself if you still
1851 want it. These will be searched in order given, and the first
1852 matching file served.
1853
1854 Static directories
1855 If you want to force some directories to be only static, you can
1856 set them using paths relative to the root dir, or regular expres‐
1857 sions:
1858
1859 MyApp->config->{static}->{dirs} = [
1860 'static',
1861 qr/^(images⎪css)/,
1862 ];
1863
1864 File extensions
1865 By default, the following extensions are not served (that is, they
1866 will be processed by Catalyst): tmpl, tt, tt2, html, xhtml. This
1867 list can be replaced easily:
1868
1869 MyApp->config->{static}->{ignore_extensions} = [
1870 qw/tmpl tt tt2 html xhtml/
1871 ];
1872
1873 Ignoring directories
1874 Entire directories can be ignored. If used with include_path,
1875 directories relative to the include_path dirs will also be ignored:
1876
1877 MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
1878
1879 More information
1880
1881 <http://search.cpan.org/dist/Catalyst-Plugin-Static-Simple/>
1882
1883 Serving manually with the Static plugin with HTTP::Daemon
1884 (myapp_server.pl)
1885
1886 In some situations you might want to control things more directly,
1887 using Catalyst::Plugin::Static.
1888
1889 In your main application class (MyApp.pm), load the plugin:
1890
1891 use Catalyst qw/-Debug FormValidator Static OtherPlugin/;
1892
1893 You will also need to make sure your end method does not forward static
1894 content to the view, perhaps like this:
1895
1896 sub end : Private {
1897 my ( $self, $c ) = @_;
1898
1899 $c->forward( 'MyApp::View::TT' )
1900 unless ( $c->res->body ⎪⎪ !$c->stash->{template} );
1901 }
1902
1903 This code will only forward to the view if a template has been previ‐
1904 ously defined by a controller and if there is not already data in
1905 "$c->res->body".
1906
1907 Next, create a controller to handle requests for the /static path. Use
1908 the Helper to save time. This command will create a stub controller as
1909 "lib/MyApp/Controller/Static.pm".
1910
1911 $ script/myapp_create.pl controller Static
1912
1913 Edit the file and add the following methods:
1914
1915 # serve all files under /static as static files
1916 sub default : Path('/static') {
1917 my ( $self, $c ) = @_;
1918
1919 # Optional, allow the browser to cache the content
1920 $c->res->headers->header( 'Cache-Control' => 'max-age=86400' );
1921
1922 $c->serve_static; # from Catalyst::Plugin::Static
1923 }
1924
1925 # also handle requests for /favicon.ico
1926 sub favicon : Path('/favicon.ico') {
1927 my ( $self, $c ) = @_;
1928
1929 $c->serve_static;
1930 }
1931
1932 You can also define a different icon for the browser to use instead of
1933 favicon.ico by using this in your HTML header:
1934
1935 <link rel="icon" href="/static/myapp.ico" type="image/x-icon" />
1936
1937 Common problems with the Static plugin
1938
1939 The Static plugin makes use of the "shared-mime-info" package to auto‐
1940 matically determine MIME types. This package is notoriously difficult
1941 to install, especially on win32 and OS X. For OS X the easiest path
1942 might be to install Fink, then use "apt-get install shared-mime-info".
1943 Restart the server, and everything should be fine.
1944
1945 Make sure you are using the latest version (>= 0.16) for best results.
1946 If you are having errors serving CSS files, or if they get served as
1947 text/plain instead of text/css, you may have an outdated shared-mime-
1948 info version. You may also wish to simply use the following code in
1949 your Static controller:
1950
1951 if ($c->req->path =~ /css$/i) {
1952 $c->serve_static( "text/css" );
1953 } else {
1954 $c->serve_static;
1955 }
1956
1957 Serving Static Files with Apache
1958
1959 When using Apache, you can bypass Catalyst and any Static plugins/con‐
1960 trollers controller by intercepting requests for the "root/static" path
1961 at the server level. All that is required is to define a DocumentRoot
1962 and add a separate Location block for your static content. Here is a
1963 complete config for this application under mod_perl 1.x:
1964
1965 <Perl>
1966 use lib qw(/var/www/MyApp/lib);
1967 </Perl>
1968 PerlModule MyApp
1969
1970 <VirtualHost *>
1971 ServerName myapp.example.com
1972 DocumentRoot /var/www/MyApp/root
1973 <Location />
1974 SetHandler perl-script
1975 PerlHandler MyApp
1976 </Location>
1977 <LocationMatch "/(static⎪favicon.ico)">
1978 SetHandler default-handler
1979 </LocationMatch>
1980 </VirtualHost>
1981
1982 And here's a simpler example that'll get you started:
1983
1984 Alias /static/ "/my/static/files/"
1985 <Location "/static">
1986 SetHandler none
1987 </Location>
1988
1989 Caching
1990
1991 Catalyst makes it easy to employ several different types of caching to
1992 speed up your applications.
1993
1994 Cache Plugins
1995
1996 There are three wrapper plugins around common CPAN cache modules:
1997 Cache::FastMmap, Cache::FileCache, and Cache::Memcached. These can be
1998 used to cache the result of slow operations.
1999
2000 This very page you're viewing makes use of the FileCache plugin to
2001 cache the rendered XHTML version of the source POD document. This is
2002 an ideal application for a cache because the source document changes
2003 infrequently but may be viewed many times.
2004
2005 use Catalyst qw/Cache::FileCache/;
2006
2007 ...
2008
2009 use File::stat;
2010 sub render_pod : Local {
2011 my ( self, $c ) = @_;
2012
2013 # the cache is keyed on the filename and the modification time
2014 # to check for updates to the file.
2015 my $file = $c->path_to( 'root', '2005', '11.pod' );
2016 my $mtime = ( stat $file )->mtime;
2017
2018 my $cached_pod = $c->cache->get("$file $mtime");
2019 if ( !$cached_pod ) {
2020 $cached_pod = do_slow_pod_rendering();
2021 # cache the result for 12 hours
2022 $c->cache->set( "$file $mtime", $cached_pod, '12h' );
2023 }
2024 $c->stash->{pod} = $cached_pod;
2025 }
2026
2027 We could actually cache the result forever, but using a value such as
2028 12 hours allows old entries to be automatically expired when they are
2029 no longer needed.
2030
2031 Page Caching
2032
2033 Another method of caching is to cache the entire HTML page. While this
2034 is traditionally handled by a front-end proxy server like Squid, the
2035 Catalyst PageCache plugin makes it trivial to cache the entire output
2036 from frequently-used or slow actions.
2037
2038 Many sites have a busy content-filled front page that might look some‐
2039 thing like this. It probably takes a while to process, and will do the
2040 exact same thing for every single user who views the page.
2041
2042 sub front_page : Path('/') {
2043 my ( $self, $c ) = @_;
2044
2045 $c->forward( 'get_news_articles' );
2046 $c->forward( 'build_lots_of_boxes' );
2047 $c->forward( 'more_slow_stuff' );
2048
2049 $c->stash->{template} = 'index.tt';
2050 }
2051
2052 We can add the PageCache plugin to speed things up.
2053
2054 use Catalyst qw/Cache::FileCache PageCache/;
2055
2056 sub front_page : Path ('/') {
2057 my ( $self, $c ) = @_;
2058
2059 $c->cache_page( 300 );
2060
2061 # same processing as above
2062 }
2063
2064 Now the entire output of the front page, from <html> to </html>, will
2065 be cached for 5 minutes. After 5 minutes, the next request will
2066 rebuild the page and it will be re-cached.
2067
2068 Note that the page cache is keyed on the page URI plus all parameters,
2069 so requests for / and /?foo=bar will result in different cache items.
2070 Also, only GET requests will be cached by the plugin.
2071
2072 You can even get that front-end Squid proxy to help out by enabling
2073 HTTP headers for the cached page.
2074
2075 MyApp->config->{page_cache}->{set_http_headers} = 1;
2076
2077 This would now set the following headers so proxies and browsers may
2078 cache the content themselves.
2079
2080 Cache-Control: max-age=($expire_time - time)
2081 Expires: $expire_time
2082 Last-Modified: $cache_created_time
2083
2084 Template Caching
2085
2086 Template Toolkit provides support for caching compiled versions of your
2087 templates. To enable this in Catalyst, use the following configura‐
2088 tion. TT will cache compiled templates keyed on the file mtime, so
2089 changes will still be automatically detected.
2090
2091 package MyApp::View::TT;
2092
2093 use strict;
2094 use warnings;
2095 use base 'Catalyst::View::TT';
2096
2097 __PACKAGE__->config(
2098 COMPILE_DIR => '/tmp/template_cache',
2099 );
2100
2101 1;
2102
2103 More Info
2104
2105 See the documentation for each cache plugin for more details and other
2106 available configuration options.
2107
2108 Catalyst::Plugin::Cache::FastMmap Catalyst::Plugin::Cache::FileCache
2109 Catalyst::Plugin::Cache::Memcached Catalyst::Plugin::PageCache
2110 <http://search.cpan.org/dist/Template-Toolkit/lib/Template/Manual/Con‐
2111 fig.pod#Caching_and_Compiling_Options>
2112
2114 Testing is an integral part of the web application development process.
2115 Tests make multi developer teams easier to coordinate, and they help
2116 ensure that there are no nasty surprises after upgrades or alterations.
2117
2118 Testing
2119
2120 Catalyst provides a convenient way of testing your application during
2121 development and before deployment in a real environment.
2122
2123 "Catalyst::Test" makes it possible to run the same tests both locally
2124 (without an external daemon) and against a remote server via HTTP.
2125
2126 Tests
2127
2128 Let's examine a skeleton application's "t/" directory:
2129
2130 mundus:~/MyApp chansen$ ls -l t/
2131 total 24
2132 -rw-r--r-- 1 chansen chansen 95 18 Dec 20:50 01app.t
2133 -rw-r--r-- 1 chansen chansen 190 18 Dec 20:50 02pod.t
2134 -rw-r--r-- 1 chansen chansen 213 18 Dec 20:50 03podcoverage.t
2135
2136 "01app.t"
2137 Verifies that the application loads, compiles, and returns a suc‐
2138 cessful response.
2139
2140 "02pod.t"
2141 Verifies that all POD is free from errors. Only executed if the
2142 "TEST_POD" environment variable is true.
2143
2144 "03podcoverage.t"
2145 Verifies that all methods/functions have POD coverage. Only exe‐
2146 cuted if the "TEST_POD" environment variable is true.
2147
2148 Creating tests
2149
2150 mundus:~/MyApp chansen$ cat t/01app.t ⎪ perl -ne 'printf( "%2d %s", $., $_ )'
2151 1 use Test::More tests => 2;
2152 2 use_ok( Catalyst::Test, 'MyApp' );
2153 3
2154 4 ok( request('/')->is_success );
2155
2156 The first line declares how many tests we are going to run, in this
2157 case two. The second line tests and loads our application in test mode.
2158 The fourth line verifies that our application returns a successful
2159 response.
2160
2161 "Catalyst::Test" exports two functions, "request" and "get". Each can
2162 take three different arguments:
2163
2164 A string which is a relative or absolute URI.
2165 request('/my/path');
2166 request('http://www.host.com/my/path');
2167
2168 An instance of "URI".
2169 request( URI->new('http://www.host.com/my/path') );
2170
2171 An instance of "HTTP::Request".
2172 request( HTTP::Request->new( GET => 'http://www.host.com/my/path') );
2173
2174 "request" returns an instance of "HTTP::Response" and "get" returns the
2175 content (body) of the response.
2176
2177 Running tests locally
2178
2179 mundus:~/MyApp chansen$ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib/ t/
2180 t/01app............ok
2181 t/02pod............ok
2182 t/03podcoverage....ok
2183 All tests successful.
2184 Files=3, Tests=4, 2 wallclock secs ( 1.60 cusr + 0.36 csys = 1.96 CPU)
2185
2186 "CATALYST_DEBUG=0" ensures that debugging is off; if it's enabled you
2187 will see debug logs between tests.
2188
2189 "TEST_POD=1" enables POD checking and coverage.
2190
2191 "prove" A command-line tool that makes it easy to run tests. You can
2192 find out more about it from the links below.
2193
2194 Running tests remotely
2195
2196 mundus:~/MyApp chansen$ CATALYST_SERVER=http://localhost:3000/ prove --lib lib/ t/01app.t
2197 t/01app....ok
2198 All tests successful.
2199 Files=1, Tests=2, 0 wallclock secs ( 0.40 cusr + 0.01 csys = 0.41 CPU)
2200
2201 "CATALYST_SERVER=http://localhost:3000/" is the absolute deployment URI
2202 of your application. In "CGI" or "FastCGI" it should be the host and
2203 path to the script.
2204
2205 "Test::WWW::Mechanize" and Catalyst
2206
2207 Be sure to check out "Test::WWW::Mechanize::Catalyst". It makes it easy
2208 to test HTML, forms and links. A short example of usage:
2209
2210 use Test::More tests => 6;
2211 use_ok( Test::WWW::Mechanize::Catalyst, 'MyApp' );
2212
2213 my $mech = Test::WWW::Mechanize::Catalyst->new;
2214 $mech->get_ok("http://localhost/", 'Got index page');
2215 $mech->title_like( qr/^MyApp on Catalyst/, 'Got right index title' );
2216 ok( $mech->find_link( text_regex => qr/^Wiki/i ), 'Found link to Wiki' );
2217 ok( $mech->find_link( text_regex => qr/^Mailing-List/i ), 'Found link to Mailing-List' );
2218 ok( $mech->find_link( text_regex => qr/^IRC channel/i ), 'Found link to IRC channel' );
2219
2220 Further Reading
2221
2222 Catalyst::Test
2223 <http://search.cpan.org/dist/Catalyst/lib/Catalyst/Test.pm>
2224
2225 Test::WWW::Mechanize::Catalyst
2226 <http://search.cpan.org/dist/Test-WWW-Mechanize-Cata‐
2227 lyst/lib/Test/WWW/Mechanize/Catalyst.pm>
2228
2229 Test::WWW::Mechanize
2230 <http://search.cpan.org/dist/Test-WWW-Mechanize/Mechanize.pm>
2231
2232 WWW::Mechanize
2233 <http://search.cpan.org/dist/WWW-Mechanize/lib/WWW/Mechanize.pm>
2234
2235 LWP::UserAgent
2236 <http://search.cpan.org/dist/libwww-perl/lib/LWP/UserAgent.pm>
2237
2238 HTML::Form
2239 <http://search.cpan.org/dist/libwww-perl/lib/HTML/Form.pm>
2240
2241 HTTP::Message
2242 <http://search.cpan.org/dist/libwww-perl/lib/HTTP/Message.pm>
2243
2244 HTTP::Request
2245 <http://search.cpan.org/dist/libwww-perl/lib/HTTP/Request.pm>
2246
2247 HTTP::Request::Common
2248 <http://search.cpan.org/dist/libwww-perl/lib/HTTP/Request/Com‐
2249 mon.pm>
2250
2251 HTTP::Response
2252 <http://search.cpan.org/dist/libwww-perl/lib/HTTP/Response.pm>
2253
2254 HTTP::Status
2255 <http://search.cpan.org/dist/libwww-perl/lib/HTTP/Status.pm>
2256
2257 URI <http://search.cpan.org/dist/URI/URI.pm>
2258
2259 Test::More
2260 <http://search.cpan.org/dist/Test-Simple/lib/Test/More.pm>
2261
2262 Test::Pod
2263 <http://search.cpan.org/dist/Test-Pod/Pod.pm>
2264
2265 Test::Pod::Coverage
2266 <http://search.cpan.org/dist/Test-Pod-Coverage/Coverage.pm>
2267
2268 prove (Test::Harness)
2269 <http://search.cpan.org/dist/Test-Harness/bin/prove>
2270
2271 More Information
2272
2273 <http://search.cpan.org/perldoc?Catalyst::Plugin::Authorization::Roles>
2274 <http://search.cpan.org/perldoc?Catalyst::Plugin::Authorization::ACL>
2275
2277 Sebastian Riedel "sri@oook.de"
2278
2279 Danijel Milicevic "me@danijel.de"
2280
2281 Viljo Marrandi "vilts@yahoo.com"
2282
2283 Marcus Ramberg "mramberg@cpan.org"
2284
2285 Jesse Sheidlower "jester@panix.com"
2286
2287 Andy Grundman "andy@hybridized.org"
2288
2289 Chisel Wright "pause@herlpacker.co.uk"
2290
2291 Will Hawes "info@whawes.co.uk"
2292
2293 Gavin Henry "ghenry@perl.me.uk"
2294
2295 Kieren Diment "kd@totaldatasolution.com"
2296
2298 This document is free, you can redistribute it and/or modify it under
2299 the same terms as Perl itself.
2300
2301
2302
2303perl v5.8.8 2007-02-28 Catalyst::Manual::Cookbook(3)