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