Commit | Line | Data |
7e4aa7c6 |
1 | =encoding utf8 |
2 | |
cb93c9d7 |
3 | =head1 NAME |
4 | |
5 | Catalyst::Manual::Cookbook - Cooking with Catalyst |
6 | |
7 | =head1 DESCRIPTION |
8 | |
9 | Yummy code like your mum used to bake! |
10 | |
11 | =head1 RECIPES |
12 | |
13 | =head1 Basics |
14 | |
c718cfb6 |
15 | These recipes cover some basic stuff that is worth knowing for |
46a5f2f5 |
16 | Catalyst developers. |
cb93c9d7 |
17 | |
18 | =head2 Delivering a Custom Error Page |
19 | |
20 | By default, Catalyst will display its own error page whenever it |
21 | encounters an error in your application. When running under C<-Debug> |
c718cfb6 |
22 | mode, the error page is a useful screen including the error message |
23 | and L<Data::Dump> output of the relevant parts of the C<$c> context |
24 | object. When not in C<-Debug>, users see a simple "Please come back |
25 | later" screen. |
cb93c9d7 |
26 | |
c718cfb6 |
27 | To use a custom error page, use a special C<end> method to |
28 | short-circuit the error processing. The following is an example; you |
29 | might want to adjust it further depending on the needs of your |
30 | application (for example, any calls to C<fillform> will probably need |
31 | to go into this C<end> method; see L<Catalyst::Plugin::FillInForm>). |
cb93c9d7 |
32 | |
33 | sub end : Private { |
34 | my ( $self, $c ) = @_; |
35 | |
36 | if ( scalar @{ $c->error } ) { |
37 | $c->stash->{errors} = $c->error; |
bf6900ba |
38 | for my $error ( @{ $c->error } ) { |
39 | $c->log->error($error); |
40 | } |
cb93c9d7 |
41 | $c->stash->{template} = 'errors.tt'; |
42 | $c->forward('MyApp::View::TT'); |
bf6900ba |
43 | $c->clear_errors; |
cb93c9d7 |
44 | } |
45 | |
46 | return 1 if $c->response->status =~ /^3\d\d$/; |
47 | return 1 if $c->response->body; |
48 | |
49 | unless ( $c->response->content_type ) { |
50 | $c->response->content_type('text/html; charset=utf-8'); |
51 | } |
52 | |
53 | $c->forward('MyApp::View::TT'); |
54 | } |
55 | |
56 | You can manually set errors in your code to trigger this page by calling |
57 | |
58 | $c->error( 'You broke me!' ); |
59 | |
60 | =head2 Disable statistics |
61 | |
c718cfb6 |
62 | Just add this line to your application class if you don't want those |
63 | nifty statistics in your debug messages. |
cb93c9d7 |
64 | |
65 | sub Catalyst::Log::info { } |
66 | |
67 | =head2 Enable debug status in the environment |
68 | |
69 | Normally you enable the debugging info by adding the C<-Debug> flag to |
b411df01 |
70 | your C<use Catalyst> statement . However, you can also enable it using |
71 | environment variable, so you can (for example) get debug info without |
72 | modifying your application scripts. Just set C<CATALYST_DEBUG> or |
429d1caf |
73 | C<< <MYAPP>_DEBUG >> to a true value. |
cb93c9d7 |
74 | |
75 | =head2 Sessions |
76 | |
c718cfb6 |
77 | When you have your users identified, you will want to somehow remember |
78 | that fact, to save them from having to identify themselves for every |
79 | single page. One way to do this is to send the username and password |
80 | parameters in every single page, but that's ugly, and won't work for |
81 | static pages. |
cb93c9d7 |
82 | |
c718cfb6 |
83 | Sessions are a method of saving data related to some transaction, and |
84 | giving the whole collection a single ID. This ID is then given to the |
85 | user to return to us on every page they visit while logged in. The |
86 | usual way to do this is using a browser cookie. |
cb93c9d7 |
87 | |
88 | Catalyst uses two types of plugins to represent sessions: |
89 | |
90 | =head3 State |
91 | |
c718cfb6 |
92 | A State module is used to keep track of the state of the session |
93 | between the users browser, and your application. |
cb93c9d7 |
94 | |
c718cfb6 |
95 | A common example is the Cookie state module, which sends the browser a |
96 | cookie containing the session ID. It will use default value for the |
97 | cookie name and domain, so will "just work" when used. |
cb93c9d7 |
98 | |
99 | =head3 Store |
100 | |
c718cfb6 |
101 | A Store module is used to hold all the data relating to your session, |
102 | for example the users ID, or the items for their shopping cart. You |
103 | can store data in memory (FastMmap), in a file (File) or in a database |
104 | (DBI). |
cb93c9d7 |
105 | |
106 | =head3 Authentication magic |
107 | |
108 | If you have included the session modules in your application, the |
109 | Authentication modules will automagically use your session to save and |
110 | retrieve the user data for you. |
111 | |
112 | =head3 Using a session |
113 | |
114 | Once the session modules are loaded, the session is available as C<< |
5336f546 |
115 | $c->session >>, and can be written to and read from as a simple hash |
c718cfb6 |
116 | reference. |
cb93c9d7 |
117 | |
118 | =head3 EXAMPLE |
119 | |
02bb2b5a |
120 | package MyApp; |
121 | use Moose; |
122 | use namespace::autoclean; |
123 | |
124 | use Catalyst qw/ |
125 | Session |
126 | Session::Store::FastMmap |
127 | Session::State::Cookie |
128 | /; |
129 | extends 'Catalyst'; |
130 | __PACKAGE__->setup; |
131 | |
132 | package MyApp::Controller::Foo; |
133 | use Moose; |
134 | use namespace::autoclean; |
135 | BEGIN { extends 'Catalyst::Controller' }; |
136 | ## Write data into the session |
137 | |
138 | sub add_item : Local { |
139 | my ( $self, $c ) = @_; |
cb93c9d7 |
140 | |
02bb2b5a |
141 | my $item_id = $c->req->params->{item}; |
cb93c9d7 |
142 | |
02bb2b5a |
143 | push @{ $c->session->{items} }, $item_id; |
144 | } |
cb93c9d7 |
145 | |
02bb2b5a |
146 | ## A page later we retrieve the data from the session: |
cb93c9d7 |
147 | |
02bb2b5a |
148 | sub get_items : Local { |
149 | my ( $self, $c ) = @_; |
cb93c9d7 |
150 | |
02bb2b5a |
151 | $c->stash->{items_to_display} = $c->session->{items}; |
152 | } |
cb93c9d7 |
153 | |
154 | |
155 | =head3 More information |
156 | |
6f660c96 |
157 | L<Catalyst::Plugin::Session> |
cb93c9d7 |
158 | |
6f660c96 |
159 | L<Catalyst::Plugin::Session::State::Cookie> |
cb93c9d7 |
160 | |
6f660c96 |
161 | L<Catalyst::Plugin::Session::State::URI> |
cb93c9d7 |
162 | |
6f660c96 |
163 | L<Catalyst::Plugin::Session::Store::FastMmap> |
cb93c9d7 |
164 | |
6f660c96 |
165 | L<Catalyst::Plugin::Session::Store::File> |
cb93c9d7 |
166 | |
6f660c96 |
167 | L<Catalyst::Plugin::Session::Store::DBI> |
cb93c9d7 |
168 | |
169 | =head2 Configure your application |
170 | |
171 | You configure your application with the C<config> method in your |
172 | application class. This can be hard-coded, or brought in from a |
173 | separate configuration file. |
174 | |
c010ae0d |
175 | =head3 Using Config::General |
cb93c9d7 |
176 | |
388f66e0 |
177 | L<Config::General> is a method for creating flexible |
c010ae0d |
178 | and readable configuration files. It's a great way to keep your |
179 | Catalyst application configuration in one easy-to-understand location. |
cb93c9d7 |
180 | |
f4e9de4a |
181 | Now create F<myapp.conf> in your application home: |
cb93c9d7 |
182 | |
c010ae0d |
183 | name MyApp |
cb93c9d7 |
184 | |
185 | # session; perldoc Catalyst::Plugin::Session::FastMmap |
c010ae0d |
186 | <Session> |
187 | expires 3600 |
188 | rewrite 0 |
189 | storage /tmp/myapp.session |
190 | </Session> |
cb93c9d7 |
191 | |
192 | # emails; perldoc Catalyst::Plugin::Email |
193 | # this passes options as an array :( |
c010ae0d |
194 | Mail SMTP |
195 | Mail localhost |
cb93c9d7 |
196 | |
197 | This is equivalent to: |
198 | |
199 | # configure base package |
200 | __PACKAGE__->config( name => MyApp ); |
201 | # configure authentication |
19a5b486 |
202 | __PACKAGE__->config( |
203 | 'Plugin::Authentication' => { |
204 | user_class => 'MyApp::Model::MyDB::Customer', |
205 | ... |
206 | }, |
207 | _; |
cb93c9d7 |
208 | # configure sessions |
19a5b486 |
209 | __PACKAGE__->config( |
210 | session => { |
211 | expires => 3600, |
212 | ... |
213 | }, |
214 | ); |
cb93c9d7 |
215 | # configure email sending |
19a5b486 |
216 | __PACKAGE__->config( email => [qw/SMTP localhost/] ); |
cb93c9d7 |
217 | |
552daee0 |
218 | L<Catalyst> explains precedence of multiple sources for configuration |
219 | values, how to access the values in your components, and many 'base' |
220 | config variables used internally. |
221 | |
388f66e0 |
222 | See also L<Config::General>. |
cb93c9d7 |
223 | |
224 | =head1 Skipping your VCS's directories |
225 | |
5efd5cc6 |
226 | Catalyst uses L<Module::Pluggable> to load Models, Views, and Controllers. |
227 | L<Module::Pluggable> will scan through all directories and load modules |
cb93c9d7 |
228 | it finds. Sometimes you might want to skip some of these directories, |
229 | for example when your version control system makes a subdirectory with |
230 | meta-information in every version-controlled directory. While |
231 | Catalyst skips subversion and CVS directories already, there are other |
232 | source control systems. Here is the configuration you need to add |
233 | their directories to the list to skip. |
234 | |
46a5f2f5 |
235 | You can make Catalyst skip these directories using the Catalyst config: |
cb93c9d7 |
236 | |
237 | # Configure the application |
238 | __PACKAGE__->config( |
239 | name => 'MyApp', |
240 | setup_components => { except => qr/SCCS/ }, |
241 | ); |
242 | |
5efd5cc6 |
243 | See the L<Module::Pluggable> manual page for more information on B<except> |
cb93c9d7 |
244 | and other options. |
245 | |
246 | =head1 Users and Access Control |
247 | |
46a5f2f5 |
248 | Most multiuser, and some single-user web applications require that |
cb93c9d7 |
249 | users identify themselves, and the application is often required to |
250 | define those roles. The recipes below describe some ways of doing |
251 | this. |
252 | |
253 | =head2 Authentication (logging in) |
254 | |
255 | This is extensively covered in other documentation; see in particular |
256 | L<Catalyst::Plugin::Authentication> and the Authentication chapter |
5641c0c1 |
257 | of the Tutorial at L<Catalyst::Manual::Tutorial::06_Authorization>. |
cb93c9d7 |
258 | |
259 | =head2 Pass-through login (and other actions) |
260 | |
261 | An easy way of having assorted actions that occur during the processing |
262 | of a request that are orthogonal to its actual purpose - logins, silent |
263 | commands etc. Provide actions for these, but when they're required for |
264 | something else fill e.g. a form variable __login and have a sub begin |
265 | like so: |
266 | |
267 | sub begin : Private { |
268 | my ($self, $c) = @_; |
269 | foreach my $action (qw/login docommand foo bar whatever/) { |
270 | if ($c->req->params->{"__${action}"}) { |
271 | $c->forward($action); |
272 | } |
273 | } |
274 | } |
275 | |
cb93c9d7 |
276 | =head2 Authentication/Authorization |
277 | |
278 | This is done in several steps: |
279 | |
280 | =over 4 |
281 | |
282 | =item Verification |
283 | |
284 | Getting the user to identify themselves, by giving you some piece of |
c718cfb6 |
285 | information known only to you and the user. Then you can assume that |
286 | the user is who they say they are. This is called B<credential |
287 | verification>. |
cb93c9d7 |
288 | |
289 | =item Authorization |
290 | |
c718cfb6 |
291 | Making sure the user only accesses functions you want them to |
46a5f2f5 |
292 | access. This is done by checking the verified user's data against your |
c718cfb6 |
293 | internal list of groups, or allowed persons for the current page. |
cb93c9d7 |
294 | |
295 | =back |
296 | |
297 | =head3 Modules |
298 | |
c718cfb6 |
299 | The Catalyst Authentication system is made up of many interacting |
300 | modules, to give you the most flexibility possible. |
cb93c9d7 |
301 | |
302 | =head4 Credential verifiers |
303 | |
c718cfb6 |
304 | A Credential module tables the user input, and passes it to a Store, |
305 | or some other system, for verification. Typically, a user object is |
306 | created by either this module or the Store and made accessible by a |
307 | C<< $c->user >> call. |
cb93c9d7 |
308 | |
309 | Examples: |
310 | |
02bb2b5a |
311 | Password - Simple username/password checking. |
312 | HTTPD - Checks using basic HTTP auth. |
313 | TypeKey - Check using the typekey system. |
cb93c9d7 |
314 | |
315 | =head3 Storage backends |
316 | |
c718cfb6 |
317 | A Storage backend contains the actual data representing the users. It |
318 | is queried by the credential verifiers. Updating the store is not done |
46a5f2f5 |
319 | within this system; you will need to do it yourself. |
cb93c9d7 |
320 | |
321 | Examples: |
322 | |
02bb2b5a |
323 | DBIC - Storage using a database via DBIx::Class. |
324 | Minimal - Storage using a simple hash (for testing). |
cb93c9d7 |
325 | |
326 | =head3 User objects |
327 | |
c718cfb6 |
328 | A User object is created by either the storage backend or the |
46a5f2f5 |
329 | credential verifier, and is filled with the retrieved user information. |
cb93c9d7 |
330 | |
331 | Examples: |
332 | |
02bb2b5a |
333 | Hash - A simple hash of keys and values. |
cb93c9d7 |
334 | |
335 | =head3 ACL authorization |
336 | |
c718cfb6 |
337 | ACL stands for Access Control List. The ACL plugin allows you to |
46a5f2f5 |
338 | regulate access on a path-by-path basis, by listing which users, or |
c718cfb6 |
339 | roles, have access to which paths. |
cb93c9d7 |
340 | |
341 | =head3 Roles authorization |
342 | |
c718cfb6 |
343 | Authorization by roles is for assigning users to groups, which can |
344 | then be assigned to ACLs, or just checked when needed. |
cb93c9d7 |
345 | |
346 | =head3 Logging in |
347 | |
348 | When you have chosen your modules, all you need to do is call the C<< |
b1a08fe1 |
349 | $c->authenticate >> method. If called with no parameters, it will try to find |
c718cfb6 |
350 | suitable parameters, such as B<username> and B<password>, or you can |
351 | pass it these values. |
cb93c9d7 |
352 | |
353 | =head3 Checking roles |
354 | |
46a5f2f5 |
355 | Role checking is done by using the C<< $c->check_user_roles >> method. |
356 | This will check using the currently logged-in user (via C<< $c->user |
c718cfb6 |
357 | >>). You pass it the name of a role to check, and it returns true if |
358 | the user is a member. |
cb93c9d7 |
359 | |
360 | =head3 EXAMPLE |
361 | |
02bb2b5a |
362 | package MyApp; |
363 | use Moose; |
364 | use namespace::autoclean; |
365 | extends qw/Catalyst/; |
366 | use Catalyst qw/ |
367 | Authentication |
368 | Authorization::Roles |
369 | /; |
bbddff00 |
370 | |
02bb2b5a |
371 | __PACKAGE__->config( |
372 | authentication => { |
373 | default_realm => 'test', |
374 | realms => { |
375 | test => { |
376 | credential => { |
377 | class => 'Password', |
378 | password_field => 'password', |
379 | password_type => 'self_check', |
380 | }, |
381 | store => { |
382 | class => 'Htpasswd', |
383 | file => 'htpasswd', |
384 | }, |
385 | }, |
386 | }, |
387 | }, |
388 | ); |
bbddff00 |
389 | |
02bb2b5a |
390 | package MyApp::Controller::Root; |
391 | use Moose; |
392 | use namespace::autoclean; |
31e9c5ef |
393 | |
02bb2b5a |
394 | BEGIN { extends 'Catalyst::Controller' } |
31e9c5ef |
395 | |
02bb2b5a |
396 | __PACKAGE__->config(namespace => ''); |
31e9c5ef |
397 | |
02bb2b5a |
398 | sub login : Local { |
399 | my ($self, $c) = @_; |
cb93c9d7 |
400 | |
02bb2b5a |
401 | if ( my $user = $c->req->params->{user} |
402 | and my $password = $c->req->param->{password} ) |
403 | { |
404 | if ( $c->authenticate( username => $user, password => $password ) ) { |
405 | $c->res->body( "hello " . $c->user->name ); |
406 | } else { |
407 | # login incorrect |
408 | } |
409 | } |
410 | else { |
411 | # invalid form input |
412 | } |
413 | } |
cb93c9d7 |
414 | |
02bb2b5a |
415 | sub restricted : Local { |
416 | my ( $self, $c ) = @_; |
cb93c9d7 |
417 | |
02bb2b5a |
418 | $c->detach("unauthorized") |
419 | unless $c->check_user_roles( "admin" ); |
cb93c9d7 |
420 | |
02bb2b5a |
421 | # do something restricted here |
422 | } |
cb93c9d7 |
423 | |
424 | =head3 Using authentication in a testing environment |
425 | |
46a5f2f5 |
426 | Ideally, to write tests for authentication/authorization code one would |
427 | first set up a test database with known data, then use |
c718cfb6 |
428 | L<Test::WWW::Mechanize::Catalyst> to simulate a user logging |
46a5f2f5 |
429 | in. Unfortunately this can be rather awkward, which is why it's a good |
430 | thing that the authentication framework is so flexible. |
cb93c9d7 |
431 | |
c718cfb6 |
432 | Instead of using a test database, one can simply change the |
433 | authentication store to something a bit easier to deal with in a |
434 | testing environment. Additionally, this has the advantage of not |
435 | modifying one's database, which can be problematic if one forgets to |
436 | use the testing instead of production database. |
cb93c9d7 |
437 | |
46a5f2f5 |
438 | Alternatively, if you want to authenticate real users, but not have to |
439 | worry about their passwords, you can use |
440 | L<Catalyst::Authentication::Credential::Testing> to force all users to |
441 | authenticate with a global password. |
cb93c9d7 |
442 | |
443 | =head3 More information |
444 | |
bbddff00 |
445 | L<Catalyst::Plugin::Authentication> has a longer explanation. |
cb93c9d7 |
446 | |
447 | =head2 Authorization |
448 | |
449 | =head3 Introduction |
450 | |
c718cfb6 |
451 | Authorization is the step that comes after |
46a5f2f5 |
452 | authentication. Authentication establishes that the user agent is really |
453 | representing the user we think it's representing, and then authorization |
454 | determines what this user is allowed to do. |
cb93c9d7 |
455 | |
456 | =head3 Role Based Access Control |
457 | |
c718cfb6 |
458 | Under role based access control each user is allowed to perform any |
459 | number of roles. For example, at a zoo no one but specially trained |
bfa445ed |
460 | personnel can enter the moose cage (Mynd you, møøse bites kan be |
c718cfb6 |
461 | pretty nasti!). For example: |
cb93c9d7 |
462 | |
463 | package Zoo::Controller::MooseCage; |
464 | |
465 | sub feed_moose : Local { |
466 | my ( $self, $c ) = @_; |
467 | |
76776098 |
468 | $c->model( "Moose" )->eat( $c->req->params->{food} ); |
cb93c9d7 |
469 | } |
470 | |
c718cfb6 |
471 | With this action, anyone can just come into the moose cage and feed |
472 | the moose, which is a very dangerous thing. We need to restrict this |
473 | action, so that only a qualified moose feeder can perform that action. |
cb93c9d7 |
474 | |
46a5f2f5 |
475 | The Authorization::Roles plugin lets us perform role based access |
c718cfb6 |
476 | control checks. Let's load it: |
cb93c9d7 |
477 | |
ca7528df |
478 | use parent qw/Catalyst/; |
b411df01 |
479 | use Catalyst qw/ |
b1a08fe1 |
480 | Authentication |
b411df01 |
481 | Authorization::Roles |
482 | /; |
cb93c9d7 |
483 | |
484 | And now our action should look like this: |
485 | |
486 | sub feed_moose : Local { |
487 | my ( $self, $c ) = @_; |
488 | |
489 | if ( $c->check_roles( "moose_feeder" ) ) { |
76776098 |
490 | $c->model( "Moose" )->eat( $c->req->params->{food} ); |
cb93c9d7 |
491 | } else { |
492 | $c->stash->{error} = "unauthorized"; |
493 | } |
494 | } |
495 | |
c718cfb6 |
496 | This checks C<< $c->user >>, and only if the user has B<all> the roles |
497 | in the list, a true value is returned. |
cb93c9d7 |
498 | |
c718cfb6 |
499 | C<check_roles> has a sister method, C<assert_roles>, which throws an |
500 | exception if any roles are missing. |
cb93c9d7 |
501 | |
502 | Some roles that might actually make sense in, say, a forum application: |
503 | |
504 | =over 4 |
505 | |
506 | =item * |
507 | |
508 | administrator |
509 | |
510 | =item * |
511 | |
512 | moderator |
513 | |
514 | =back |
515 | |
c718cfb6 |
516 | each with a distinct task (system administration versus content |
517 | administration). |
cb93c9d7 |
518 | |
519 | =head3 Access Control Lists |
520 | |
521 | Checking for roles all the time can be tedious and error prone. |
522 | |
46a5f2f5 |
523 | The Authorization::ACL plugin lets us declare where we'd like checks |
c718cfb6 |
524 | to be done automatically for us. |
cb93c9d7 |
525 | |
526 | For example, we may want to completely block out anyone who isn't a |
527 | C<moose_feeder> from the entire C<MooseCage> controller: |
528 | |
529 | Zoo->deny_access_unless( "/moose_cage", [qw/moose_feeder/] ); |
530 | |
c718cfb6 |
531 | The role list behaves in the same way as C<check_roles>. However, the |
532 | ACL plugin isn't limited to just interacting with the Roles plugin. We |
533 | can use a code reference instead. For example, to allow either moose |
534 | trainers or moose feeders into the moose cage, we can create a more |
535 | complex check: |
cb93c9d7 |
536 | |
537 | Zoo->deny_access_unless( "/moose_cage", sub { |
538 | my $c = shift; |
539 | $c->check_roles( "moose_trainer" ) || $c->check_roles( "moose_feeder" ); |
540 | }); |
541 | |
c718cfb6 |
542 | The more specific a role, the earlier it will be checked. Let's say |
543 | moose feeders are now restricted to only the C<feed_moose> action, |
544 | while moose trainers get access everywhere: |
cb93c9d7 |
545 | |
546 | Zoo->deny_access_unless( "/moose_cage", [qw/moose_trainer/] ); |
547 | Zoo->allow_access_if( "/moose_cage/feed_moose", [qw/moose_feeder/]); |
548 | |
c718cfb6 |
549 | When the C<feed_moose> action is accessed the second check will be |
550 | made. If the user is a C<moose_feeder>, then access will be |
551 | immediately granted. Otherwise, the next rule in line will be tested - |
552 | the one checking for a C<moose_trainer>. If this rule is not |
553 | satisfied, access will be immediately denied. |
cb93c9d7 |
554 | |
c718cfb6 |
555 | Rules applied to the same path will be checked in the order they were |
556 | added. |
cb93c9d7 |
557 | |
c718cfb6 |
558 | Lastly, handling access denial events is done by creating an |
559 | C<access_denied> private action: |
cb93c9d7 |
560 | |
561 | sub access_denied : Private { |
562 | my ( $self, $c, $action ) = @_; |
cb93c9d7 |
563 | } |
564 | |
c718cfb6 |
565 | This action works much like auto, in that it is inherited across |
566 | namespaces (not like object oriented code). This means that the |
567 | C<access_denied> action which is B<nearest> to the action which was |
568 | blocked will be triggered. |
cb93c9d7 |
569 | |
c718cfb6 |
570 | If this action does not exist, an error will be thrown, which you can |
571 | clean up in your C<end> private action instead. |
cb93c9d7 |
572 | |
c718cfb6 |
573 | Also, it's important to note that if you restrict access to "/" then |
46a5f2f5 |
574 | C<end>, C<default>, etc. will also be restricted. |
cb93c9d7 |
575 | |
02bb2b5a |
576 | MyApp->acl_allow_root_internals; |
cb93c9d7 |
577 | |
578 | will create rules that permit access to C<end>, C<begin>, and C<auto> in the |
579 | root of your app (but not in any other controller). |
580 | |
581 | =head1 Models |
582 | |
433f1ad4 |
583 | Models are where application data belongs. Catalyst is extremely |
cb93c9d7 |
584 | flexible with the kind of models that it can use. The recipes here |
585 | are just the start. |
586 | |
587 | =head2 Using existing DBIC (etc.) classes with Catalyst |
588 | |
c718cfb6 |
589 | Many people have existing Model classes that they would like to use |
590 | with Catalyst (or, conversely, they want to write Catalyst models that |
591 | can be used outside of Catalyst, e.g. in a cron job). It's trivial to |
592 | write a simple component in Catalyst that slurps in an outside Model: |
cb93c9d7 |
593 | |
594 | package MyApp::Model::DB; |
46a5f2f5 |
595 | |
cb93c9d7 |
596 | use base qw/Catalyst::Model::DBIC::Schema/; |
46a5f2f5 |
597 | |
cb93c9d7 |
598 | __PACKAGE__->config( |
599 | schema_class => 'Some::DBIC::Schema', |
0b60bbb5 |
600 | connect_info => ['dbi:SQLite:foo.db', '', '', {AutoCommit=>1}], |
cb93c9d7 |
601 | ); |
46a5f2f5 |
602 | |
cb93c9d7 |
603 | 1; |
604 | |
605 | and that's it! Now C<Some::DBIC::Schema> is part of your |
606 | Cat app as C<MyApp::Model::DB>. |
607 | |
608 | =head2 DBIx::Class as a Catalyst Model |
609 | |
610 | See L<Catalyst::Model::DBIC::Schema>. |
611 | |
8428e94d |
612 | =head2 Create accessors to preload static data once per server instance |
613 | |
614 | When you have data that you want to load just once from the model at |
46a5f2f5 |
615 | startup, instead of for each request, use mk_group_accessors to |
8428e94d |
616 | create accessors and tie them to resultsets in your package that |
46a5f2f5 |
617 | inherits from DBIx::Class::Schema: |
8428e94d |
618 | |
619 | package My::Schema; |
620 | use base qw/DBIx::Class::Schema/; |
621 | __PACKAGE__->register_class('RESULTSOURCEMONIKER', |
622 | 'My::Schema::RESULTSOURCE'); |
623 | __PACKAGE__->mk_group_accessors('simple' => |
624 | qw(ACCESSORNAME1 ACCESSORNAME2 ACCESSORNAMEn)); |
625 | |
626 | sub connection { |
627 | my ($self, @rest) = @_; |
628 | $self->next::method(@rest); |
b1a08fe1 |
629 | # $self is now a live My::Schema object, complete with DB connection |
8428e94d |
630 | |
631 | $self->ACCESSORNAME1([ $self->resultset('RESULTSOURCEMONIKER')->all ]); |
632 | $self->ACCESSORNAME2([ $self->resultset('RESULTSOURCEMONIKER')->search({ COLUMN => { '<' => '30' } })->all ]); |
633 | $self->ACCESSORNAMEn([ $self->resultset('RESULTSOURCEMONIKER')->find(1) ]); |
634 | } |
635 | |
636 | 1; |
637 | |
638 | and now in the controller, you can now access any of these without a |
639 | per-request fetch: |
640 | |
46a5f2f5 |
641 | $c->stash->{something} = $c->model('My::Schema')->schema->ACCESSORNAME; |
8428e94d |
642 | |
643 | |
cb93c9d7 |
644 | =head2 XMLRPC |
645 | |
46a5f2f5 |
646 | Unlike SOAP, XMLRPC is a very simple (and elegant) web-services |
cb93c9d7 |
647 | protocol, exchanging small XML messages like these: |
648 | |
649 | Request: |
650 | |
651 | POST /api HTTP/1.1 |
652 | TE: deflate,gzip;q=0.3 |
653 | Connection: TE, close |
654 | Accept: text/xml |
655 | Accept: multipart/* |
656 | Host: 127.0.0.1:3000 |
657 | User-Agent: SOAP::Lite/Perl/0.60 |
658 | Content-Length: 192 |
659 | Content-Type: text/xml |
660 | |
661 | <?xml version="1.0" encoding="UTF-8"?> |
662 | <methodCall> |
663 | <methodName>add</methodName> |
664 | <params> |
665 | <param><value><int>1</int></value></param> |
666 | <param><value><int>2</int></value></param> |
667 | </params> |
668 | </methodCall> |
669 | |
670 | Response: |
671 | |
672 | Connection: close |
673 | Date: Tue, 20 Dec 2005 07:45:55 GMT |
674 | Content-Length: 133 |
675 | Content-Type: text/xml |
676 | Status: 200 |
677 | X-Catalyst: 5.70 |
678 | |
679 | <?xml version="1.0" encoding="us-ascii"?> |
680 | <methodResponse> |
681 | <params> |
682 | <param><value><int>3</int></value></param> |
683 | </params> |
684 | </methodResponse> |
685 | |
686 | Now follow these few steps to implement the application: |
687 | |
e8200f38 |
688 | =over 4 |
689 | |
690 | =item 1. |
691 | |
692 | Install L<Catalyst> (5.61 or later), L<Catalyst::Plugin::XMLRPC> (0.06 or |
693 | later) and L<SOAP::Lite> (for F<XMLRPCsh.pl>). |
694 | |
695 | =item 2. |
cb93c9d7 |
696 | |
e8200f38 |
697 | Create an application framework: |
cb93c9d7 |
698 | |
699 | % catalyst.pl MyApp |
700 | ... |
701 | % cd MyApp |
702 | |
e8200f38 |
703 | =item 3. |
704 | |
705 | Add the XMLRPC plugin to MyApp.pm |
cb93c9d7 |
706 | |
b411df01 |
707 | use Catalyst qw/-Debug Static::Simple XMLRPC/; |
cb93c9d7 |
708 | |
e8200f38 |
709 | =item 4. |
710 | |
711 | Add an API controller |
cb93c9d7 |
712 | |
713 | % ./script/myapp_create.pl controller API |
714 | |
e8200f38 |
715 | =item 5. |
716 | |
717 | Add a XMLRPC redispatch method and an add method with Remote |
718 | attribute to F<lib/MyApp/Controller/API.pm> |
cb93c9d7 |
719 | |
85d49fb6 |
720 | sub default :Path { |
cb93c9d7 |
721 | my ( $self, $c ) = @_; |
722 | $c->xmlrpc; |
723 | } |
724 | |
725 | sub add : Remote { |
726 | my ( $self, $c, $a, $b ) = @_; |
727 | return $a + $b; |
728 | } |
729 | |
730 | The default action is the entry point for each XMLRPC request. It will |
731 | redispatch every request to methods with Remote attribute in the same |
732 | class. |
733 | |
734 | The C<add> method is not a traditional action; it has no private or |
735 | public path. Only the XMLRPC dispatcher knows it exists. |
736 | |
e8200f38 |
737 | =item 6. |
738 | |
739 | That's it! You have built your first web service. Let's test it with |
740 | F<XMLRPCsh.pl> (part of L<SOAP::Lite>): |
cb93c9d7 |
741 | |
742 | % ./script/myapp_server.pl |
743 | ... |
744 | % XMLRPCsh.pl http://127.0.0.1:3000/api |
745 | Usage: method[(parameters)] |
746 | > add( 1, 2 ) |
747 | --- XMLRPC RESULT --- |
748 | '3' |
749 | |
750 | =head3 Tip |
751 | |
752 | Your return data type is usually auto-detected, but you can easily |
753 | enforce a specific one. |
754 | |
755 | sub add : Remote { |
756 | my ( $self, $c, $a, $b ) = @_; |
757 | return RPC::XML::int->new( $a + $b ); |
758 | } |
cb93c9d7 |
759 | |
760 | =head1 Views |
761 | |
762 | Views pertain to the display of your application. As with models, |
46a5f2f5 |
763 | Catalyst is uncommonly flexible. The recipes below are just a start. |
cb93c9d7 |
764 | |
5efd5cc6 |
765 | =head2 L<Catalyst::View::TT> |
cb93c9d7 |
766 | |
767 | One of the first things you probably want to do when starting a new |
768 | Catalyst application is set up your View. Catalyst doesn't care how you |
769 | display your data; you can choose to generate HTML, PDF files, or plain |
770 | text if you wanted. |
771 | |
772 | Most Catalyst applications use a template system to generate their HTML, |
31e9c5ef |
773 | and though there are several template systems available, |
1febc43a |
774 | L<Template Toolkit|Template> is probably the most popular. |
cb93c9d7 |
775 | |
776 | Once again, the Catalyst developers have done all the hard work, and |
5efd5cc6 |
777 | made things easy for the rest of us. L<Catalyst::View::TT> provides the |
cb93c9d7 |
778 | interface to Template Toolkit, and provides Helpers which let us set it |
779 | up that much more easily. |
780 | |
781 | =head3 Creating your View |
782 | |
5efd5cc6 |
783 | L<Catalyst::View::TT> provides two different helpers for us to use: TT and |
cb93c9d7 |
784 | TTSite. |
785 | |
786 | =head4 TT |
787 | |
788 | Create a basic Template Toolkit View using the provided helper script: |
789 | |
790 | script/myapp_create.pl view TT TT |
791 | |
e8200f38 |
792 | This will create F<lib/MyApp/View/MyView.pm>, which is going to be pretty |
cb93c9d7 |
793 | empty to start. However, it sets everything up that you need to get |
794 | started. You can now define which template you want and forward to your |
795 | view. For instance: |
796 | |
797 | sub hello : Local { |
798 | my ( $self, $c ) = @_; |
799 | |
800 | $c->stash->{template} = 'hello.tt'; |
801 | |
802 | $c->forward( $c->view('TT') ); |
803 | } |
804 | |
805 | In practice you wouldn't do the forwarding manually, but would |
806 | use L<Catalyst::Action::RenderView>. |
807 | |
808 | =head4 TTSite |
809 | |
810 | Although the TT helper does create a functional, working view, you may |
811 | find yourself having to create the same template files and changing the |
812 | same options every time you create a new application. The TTSite helper |
813 | saves us even more time by creating the basic templates and setting some |
814 | common options for us. |
815 | |
816 | Once again, you can use the helper script: |
817 | |
818 | script/myapp_create.pl view TT TTSite |
819 | |
820 | This time, the helper sets several options for us in the generated View. |
821 | |
822 | __PACKAGE__->config({ |
823 | CATALYST_VAR => 'Catalyst', |
824 | INCLUDE_PATH => [ |
825 | MyApp->path_to( 'root', 'src' ), |
826 | MyApp->path_to( 'root', 'lib' ) |
827 | ], |
828 | PRE_PROCESS => 'config/main', |
829 | WRAPPER => 'site/wrapper', |
830 | ERROR => 'error.tt2', |
831 | TIMER => 0 |
832 | }); |
833 | |
834 | =over |
835 | |
080bb620 |
836 | =item * |
cb93c9d7 |
837 | |
e8200f38 |
838 | C<INCLUDE_PATH> defines the directories that Template Toolkit should search |
cb93c9d7 |
839 | for the template files. |
840 | |
080bb620 |
841 | =item * |
cb93c9d7 |
842 | |
e8200f38 |
843 | C<PRE_PROCESS> is used to process configuration options which are common to |
cb93c9d7 |
844 | every template file. |
845 | |
080bb620 |
846 | =item * |
cb93c9d7 |
847 | |
e8200f38 |
848 | C<WRAPPER> is a file which is processed with each template, usually used to |
cb93c9d7 |
849 | easily provide a common header and footer for every page. |
850 | |
851 | =back |
852 | |
853 | In addition to setting these options, the TTSite helper also created the |
854 | template and config files for us! In the 'root' directory, you'll notice |
855 | two new directories: src and lib. |
856 | |
e8200f38 |
857 | Several configuration files in F<root/lib/config> are called by C<PRE_PROCESS>. |
cb93c9d7 |
858 | |
e8200f38 |
859 | The files in F<root/lib/site> are the site-wide templates, called by |
860 | C<WRAPPER>, and display the html framework, control the layout, and provide |
cb93c9d7 |
861 | the templates for the header and footer of your page. Using the template |
862 | organization provided makes it much easier to standardize pages and make |
863 | changes when they are (inevitably) needed. |
864 | |
865 | The template files that you will create for your application will go |
e8200f38 |
866 | into root/src, and you don't need to worry about putting the C<< <html> >> |
867 | or C<< <head> >> sections; just put in the content. The C<WRAPPER> will the rest |
cb93c9d7 |
868 | of the page around your template for you. |
869 | |
870 | |
080bb620 |
871 | =head3 C<< $c->stash >> |
cb93c9d7 |
872 | |
873 | Of course, having the template system include the header and footer for |
874 | you isn't all that we want our templates to do. We need to be able to |
875 | put data into our templates, and have it appear where and how we want |
876 | it, right? That's where the stash comes in. |
877 | |
878 | In our controllers, we can add data to the stash, and then access it |
879 | from the template. For instance: |
880 | |
881 | sub hello : Local { |
882 | my ( $self, $c ) = @_; |
883 | |
884 | $c->stash->{name} = 'Adam'; |
885 | |
886 | $c->stash->{template} = 'hello.tt'; |
887 | |
888 | $c->forward( $c->view('TT') ); |
889 | } |
890 | |
e8200f38 |
891 | Then, in F<hello.tt>: |
cb93c9d7 |
892 | |
893 | <strong>Hello, [% name %]!</strong> |
894 | |
895 | When you view this page, it will display "Hello, Adam!" |
896 | |
897 | All of the information in your stash is available, by its name/key, in |
898 | your templates. And your data don't have to be plain, old, boring |
899 | scalars. You can pass array references and hash references, too. |
900 | |
901 | In your controller: |
902 | |
903 | sub hello : Local { |
904 | my ( $self, $c ) = @_; |
905 | |
906 | $c->stash->{names} = [ 'Adam', 'Dave', 'John' ]; |
907 | |
908 | $c->stash->{template} = 'hello.tt'; |
909 | |
910 | $c->forward( $c->view('TT') ); |
911 | } |
912 | |
913 | In hello.tt: |
914 | |
915 | [% FOREACH name IN names %] |
916 | <strong>Hello, [% name %]!</strong><br /> |
917 | [% END %] |
918 | |
919 | This allowed us to loop through each item in the arrayref, and display a |
920 | line for each name that we have. |
921 | |
922 | This is the most basic usage, but Template Toolkit is quite powerful, |
923 | and allows you to truly keep your presentation logic separate from the |
924 | rest of your application. |
925 | |
080bb620 |
926 | =head3 C<< $c->uri_for() >> |
cb93c9d7 |
927 | |
928 | One of my favorite things about Catalyst is the ability to move an |
929 | application around without having to worry that everything is going to |
930 | break. One of the areas that used to be a problem was with the http |
931 | links in your template files. For example, suppose you have an |
e8200f38 |
932 | application installed at C<http://www.domain.com/Calendar>. The links point |
933 | to "C</Calendar>", "C</Calendar/2005>", "C</Calendar/2005/10>", etc. If you move |
934 | the application to be at C<http://www.mydomain.com/Tools/Calendar>, then |
cb93c9d7 |
935 | all of those links will suddenly break. |
936 | |
080bb620 |
937 | That's where C<< $c->uri_for() >> comes in. This function will merge its |
cb93c9d7 |
938 | parameters with either the base location for the app, or its current |
939 | namespace. Let's take a look at a couple of examples. |
940 | |
941 | In your template, you can use the following: |
942 | |
943 | <a href="[% c.uri_for('/login') %]">Login Here</a> |
944 | |
c718cfb6 |
945 | Although the parameter starts with a forward slash, this is relative |
946 | to the application root, not the webserver root. This is important to |
947 | remember. So, if your application is installed at |
e8200f38 |
948 | C<http://www.domain.com/Calendar>, then the link would be |
949 | C<http://www.mydomain.com/Calendar/Login>. If you move your application |
c718cfb6 |
950 | to a different domain or path, then that link will still be correct. |
cb93c9d7 |
951 | |
952 | Likewise, |
953 | |
954 | <a href="[% c.uri_for('2005','10', '24') %]">October, 24 2005</a> |
955 | |
c718cfb6 |
956 | The first parameter does NOT have a forward slash, and so it will be |
957 | relative to the current namespace. If the application is installed at |
e8200f38 |
958 | C<http://www.domain.com/Calendar>. and if the template is called from |
080bb620 |
959 | C<MyApp::Controller::Display>, then the link would become |
e8200f38 |
960 | C<http://www.domain.com/Calendar/Display/2005/10/24>. |
c718cfb6 |
961 | |
962 | If you want to link to a parent uri of your current namespace you can |
e8200f38 |
963 | prefix the arguments with multiple 'C<../>': |
c718cfb6 |
964 | |
965 | <a href="[% c.uri_for('../../view', stashed_object.id) %]">User view</a> |
cb93c9d7 |
966 | |
c718cfb6 |
967 | Once again, this allows you to move your application around without |
968 | having to worry about broken links. But there's something else, as |
e8200f38 |
969 | well. Since the links are generated by C<uri_for>, you can use the same |
c718cfb6 |
970 | template file by several different controllers, and each controller |
971 | will get the links that its supposed to. Since we believe in Don't |
972 | Repeat Yourself, this is particularly helpful if you have common |
973 | elements in your site that you want to keep in one file. |
cb93c9d7 |
974 | |
975 | Further Reading: |
976 | |
6f660c96 |
977 | L<Catalyst> |
cb93c9d7 |
978 | |
6f660c96 |
979 | L<Catalyst::View::TT> |
cb93c9d7 |
980 | |
6f660c96 |
981 | L<Template> |
cb93c9d7 |
982 | |
b1a08fe1 |
983 | =head2 Adding RSS feeds |
cb93c9d7 |
984 | |
985 | Adding RSS feeds to your Catalyst applications is simple. We'll see two |
433f1ad4 |
986 | different approaches here, but the basic premise is that you forward to |
cb93c9d7 |
987 | the normal view action first to get the objects, then handle the output |
988 | differently. |
989 | |
5efd5cc6 |
990 | =head3 Using L<XML::Feed> |
cb93c9d7 |
991 | |
32cebe9b |
992 | Assuming we have a C<view> action that populates |
5efd5cc6 |
993 | 'entries' with some L<DBIx::Class> iterator, the code would look something |
cb93c9d7 |
994 | like this: |
995 | |
996 | sub rss : Local { |
997 | my ($self,$c) = @_; |
998 | $c->forward('view'); # get the entries |
999 | |
1000 | my $feed = XML::Feed->new('RSS'); |
1001 | $feed->title( $c->config->{name} . ' RSS Feed' ); |
1002 | $feed->link( $c->req->base ); # link to the site. |
1003 | $feed->description('Catalyst advent calendar'); Some description |
1004 | |
1005 | # Process the entries |
1006 | while( my $entry = $c->stash->{entries}->next ) { |
1007 | my $feed_entry = XML::Feed::Entry->new('RSS'); |
1008 | $feed_entry->title($entry->title); |
1009 | $feed_entry->link( $c->uri_for($entry->link) ); |
1010 | $feed_entry->issued( DateTime->from_epoch(epoch => $entry->created) ); |
1011 | $feed->add_entry($feed_entry); |
1012 | } |
1013 | $c->res->body( $feed->as_xml ); |
1014 | } |
1015 | |
32cebe9b |
1016 | With this approach you're |
b1a08fe1 |
1017 | pretty sure to get something that validates. |
cb93c9d7 |
1018 | |
433f1ad4 |
1019 | Note that for both of the above approaches, you'll need to set the |
cb93c9d7 |
1020 | content type like this: |
1021 | |
1022 | $c->res->content_type('application/rss+xml'); |
1023 | |
1024 | =head3 Final words |
1025 | |
1026 | You could generalize the second variant easily by replacing 'RSS' with a |
1027 | variable, so you can generate Atom feeds with the same code. |
1028 | |
1029 | Now, go ahead and make RSS feeds for all your stuff. The world *needs* |
1030 | updates on your goldfish! |
1031 | |
1032 | =head2 Forcing the browser to download content |
1033 | |
1034 | Sometimes you need your application to send content for download. For |
1035 | example, you can generate a comma-separated values (CSV) file for your |
1036 | users to download and import into their spreadsheet program. |
1037 | |
1038 | Let's say you have an C<Orders> controller which generates a CSV file |
1039 | in the C<export> action (i.e., C<http://localhost:3000/orders/export>): |
1040 | |
1041 | sub export : Local Args(0) { |
1042 | my ( $self, $c ) = @_; |
1043 | |
1044 | # In a real application, you'd generate this from the database |
1045 | my $csv = "1,5.99\n2,29.99\n3,3.99\n"; |
1046 | |
1047 | $c->res->content_type('text/comma-separated-values'); |
1048 | $c->res->body($csv); |
1049 | } |
1050 | |
1051 | Normally the browser uses the last part of the URI to generate a |
1052 | filename for data it cannot display. In this case your browser would |
1053 | likely ask you to save a file named C<export>. |
1054 | |
1055 | Luckily you can have the browser download the content with a specific |
1056 | filename by setting the C<Content-Disposition> header: |
1057 | |
1058 | my $filename = 'Important Orders.csv'; |
1059 | $c->res->header('Content-Disposition', qq[attachment; filename="$filename"]); |
1060 | |
1061 | Note the use of quotes around the filename; this ensures that any |
1062 | spaces in the filename are handled by the browser. |
1063 | |
1064 | Put this right before calling C<< $c->res->body >> and your browser |
f4e9de4a |
1065 | will download a file named F<Important Orders.csv> instead of |
cb93c9d7 |
1066 | C<export>. |
1067 | |
1068 | You can also use this to have the browser download content which it |
1069 | normally displays, such as JPEG images or even HTML. Just be sure to |
1070 | set the appropriate content type and disposition. |
1071 | |
1072 | |
1073 | =head1 Controllers |
1074 | |
1075 | Controllers are the main point of communication between the web server |
1076 | and your application. Here we explore some aspects of how they work. |
1077 | |
cb93c9d7 |
1078 | =head2 Action Types |
1079 | |
1080 | =head3 Introduction |
1081 | |
c718cfb6 |
1082 | A Catalyst application is driven by one or more Controller |
1083 | modules. There are a number of ways that Catalyst can decide which of |
1084 | the methods in your controller modules it should call. Controller |
1085 | methods are also called actions, because they determine how your |
1086 | catalyst application should (re-)act to any given URL. When the |
1087 | application is started up, catalyst looks at all your actions, and |
1088 | decides which URLs they map to. |
cb93c9d7 |
1089 | |
1090 | =head3 Type attributes |
1091 | |
1092 | Each action is a normal method in your controller, except that it has an |
a7b39a0c |
1093 | L<attribute|attributes> |
cb93c9d7 |
1094 | attached. These can be one of several types. |
1095 | |
1096 | Assume our Controller module starts with the following package declaration: |
1097 | |
02bb2b5a |
1098 | package MyApp::Controller::Buckets; |
cb93c9d7 |
1099 | |
1100 | and we are running our application on localhost, port 3000 (the test |
1101 | server default). |
1102 | |
1103 | =over 4 |
1104 | |
1105 | =item Path |
1106 | |
1107 | A Path attribute also takes an argument, this can be either a relative |
c718cfb6 |
1108 | or an absolute path. A relative path will be relative to the |
1109 | controller namespace, an absolute path will represent an exact |
1110 | matching URL. |
cb93c9d7 |
1111 | |
02bb2b5a |
1112 | sub my_handles : Path('handles') { .. } |
cb93c9d7 |
1113 | |
1114 | becomes |
1115 | |
02bb2b5a |
1116 | http://localhost:3000/buckets/handles |
cb93c9d7 |
1117 | |
1118 | and |
1119 | |
02bb2b5a |
1120 | sub my_handles : Path('/handles') { .. } |
cb93c9d7 |
1121 | |
b1a08fe1 |
1122 | becomes |
cb93c9d7 |
1123 | |
02bb2b5a |
1124 | http://localhost:3000/handles |
cb93c9d7 |
1125 | |
6daaedc0 |
1126 | See also: L<Catalyst::DispatchType::Path> |
1127 | |
cb93c9d7 |
1128 | =item Local |
1129 | |
c718cfb6 |
1130 | When using a Local attribute, no parameters are needed, instead, the |
1131 | name of the action is matched in the URL. The namespaces created by |
1132 | the name of the controller package is always part of the URL. |
cb93c9d7 |
1133 | |
02bb2b5a |
1134 | sub my_handles : Local { .. } |
cb93c9d7 |
1135 | |
1136 | becomes |
1137 | |
02bb2b5a |
1138 | http://localhost:3000/buckets/my_handles |
cb93c9d7 |
1139 | |
1140 | =item Global |
1141 | |
c718cfb6 |
1142 | A Global attribute is similar to a Local attribute, except that the |
1143 | namespace of the controller is ignored, and matching starts at root. |
cb93c9d7 |
1144 | |
02bb2b5a |
1145 | sub my_handles : Global { .. } |
cb93c9d7 |
1146 | |
1147 | becomes |
1148 | |
02bb2b5a |
1149 | http://localhost:3000/my_handles |
cb93c9d7 |
1150 | |
1151 | =item Regex |
1152 | |
c718cfb6 |
1153 | By now you should have figured that a Regex attribute is just what it |
1154 | sounds like. This one takes a regular expression, and matches starting |
1155 | from root. These differ from the rest as they can match multiple URLs. |
cb93c9d7 |
1156 | |
02bb2b5a |
1157 | sub my_handles : Regex('^handles') { .. } |
cb93c9d7 |
1158 | |
1159 | matches |
1160 | |
02bb2b5a |
1161 | http://localhost:3000/handles |
cb93c9d7 |
1162 | |
b1a08fe1 |
1163 | and |
cb93c9d7 |
1164 | |
02bb2b5a |
1165 | http://localhost:3000/handles_and_other_parts |
cb93c9d7 |
1166 | |
1167 | etc. |
1168 | |
6daaedc0 |
1169 | See also: L<Catalyst::DispatchType::Regex> |
1170 | |
cb93c9d7 |
1171 | =item LocalRegex |
1172 | |
1173 | A LocalRegex is similar to a Regex, except it only matches below the current |
1174 | controller namespace. |
1175 | |
02bb2b5a |
1176 | sub my_handles : LocalRegex(^handles') { .. } |
cb93c9d7 |
1177 | |
1178 | matches |
1179 | |
02bb2b5a |
1180 | http://localhost:3000/buckets/handles |
cb93c9d7 |
1181 | |
1182 | and |
1183 | |
02bb2b5a |
1184 | http://localhost:3000/buckets/handles_and_other_parts |
cb93c9d7 |
1185 | |
1186 | etc. |
1187 | |
6daaedc0 |
1188 | =item Chained |
1189 | |
1190 | See L<Catalyst::DispatchType::Chained> for a description of how the chained |
1191 | dispatch type works. |
1192 | |
cb93c9d7 |
1193 | =item Private |
1194 | |
c718cfb6 |
1195 | Last but not least, there is the Private attribute, which allows you |
1196 | to create your own internal actions, which can be forwarded to, but |
1197 | won't be matched as URLs. |
cb93c9d7 |
1198 | |
02bb2b5a |
1199 | sub my_handles : Private { .. } |
cb93c9d7 |
1200 | |
1201 | becomes nothing at all.. |
1202 | |
c718cfb6 |
1203 | Catalyst also predefines some special Private actions, which you can |
1204 | override, these are: |
cb93c9d7 |
1205 | |
1206 | =over 4 |
1207 | |
1208 | =item default |
1209 | |
c718cfb6 |
1210 | The default action will be called, if no other matching action is |
1211 | found. If you don't have one of these in your namespace, or any sub |
1212 | part of your namespace, you'll get an error page instead. If you want |
1213 | to find out where it was the user was trying to go, you can look in |
1214 | the request object using C<< $c->req->path >>. |
cb93c9d7 |
1215 | |
02bb2b5a |
1216 | sub default :Path { .. } |
cb93c9d7 |
1217 | |
c718cfb6 |
1218 | works for all unknown URLs, in this controller namespace, or every one |
1219 | if put directly into MyApp.pm. |
cb93c9d7 |
1220 | |
b1a08fe1 |
1221 | =item index |
cb93c9d7 |
1222 | |
c718cfb6 |
1223 | The index action is called when someone tries to visit the exact |
1224 | namespace of your controller. If index, default and matching Path |
1225 | actions are defined, then index will be used instead of default and |
1226 | Path. |
cb93c9d7 |
1227 | |
02bb2b5a |
1228 | sub index :Path :Args(0) { .. } |
cb93c9d7 |
1229 | |
1230 | becomes |
1231 | |
02bb2b5a |
1232 | http://localhost:3000/buckets |
cb93c9d7 |
1233 | |
1234 | =item begin |
1235 | |
c718cfb6 |
1236 | The begin action is called at the beginning of every request involving |
1237 | this namespace directly, before other matching actions are called. It |
1238 | can be used to set up variables/data for this particular part of your |
1239 | app. A single begin action is called, its always the one most relevant |
1240 | to the current namespace. |
cb93c9d7 |
1241 | |
02bb2b5a |
1242 | sub begin : Private { .. } |
cb93c9d7 |
1243 | |
b1a08fe1 |
1244 | is called once when |
cb93c9d7 |
1245 | |
02bb2b5a |
1246 | http://localhost:3000/bucket/(anything)? |
cb93c9d7 |
1247 | |
1248 | is visited. |
1249 | |
1250 | =item end |
1251 | |
c718cfb6 |
1252 | Like begin, this action is always called for the namespace it is in, |
1253 | after every other action has finished. It is commonly used to forward |
1254 | processing to the View component. A single end action is called, its |
1255 | always the one most relevant to the current namespace. |
cb93c9d7 |
1256 | |
1257 | |
02bb2b5a |
1258 | sub end : Private { .. } |
cb93c9d7 |
1259 | |
1260 | is called once after any actions when |
1261 | |
02bb2b5a |
1262 | http://localhost:3000/bucket/(anything)? |
cb93c9d7 |
1263 | |
1264 | is visited. |
1265 | |
1266 | =item auto |
1267 | |
c718cfb6 |
1268 | Lastly, the auto action is magic in that B<every> auto action in the |
1269 | chain of paths up to and including the ending namespace, will be |
1270 | called. (In contrast, only one of the begin/end/default actions will |
1271 | be called, the relevant one). |
cb93c9d7 |
1272 | |
02bb2b5a |
1273 | package MyApp::Controller::Root; |
1274 | sub auto : Private { .. } |
cb93c9d7 |
1275 | |
b1a08fe1 |
1276 | and |
cb93c9d7 |
1277 | |
1278 | sub auto : Private { .. } |
1279 | |
b1a08fe1 |
1280 | will both be called when visiting |
cb93c9d7 |
1281 | |
02bb2b5a |
1282 | http://localhost:3000/bucket/(anything)? |
cb93c9d7 |
1283 | |
1284 | =back |
1285 | |
1286 | =back |
1287 | |
1288 | =head3 A word of warning |
1289 | |
e8200f38 |
1290 | You can put root actions in your main F<MyApp.pm> file, but this is deprecated, |
b1a08fe1 |
1291 | please put your actions into your Root controller. |
cb93c9d7 |
1292 | |
7cfce6e1 |
1293 | =head3 Flowchart |
cb93c9d7 |
1294 | |
7cfce6e1 |
1295 | A graphical flowchart of how the dispatcher works can be found on the wiki at |
1296 | L<http://dev.catalyst.perl.org/attachment/wiki/WikiStart/catalyst-flow.png>. |
cb93c9d7 |
1297 | |
7cfce6e1 |
1298 | =head2 DRY Controllers with Chained actions |
6daaedc0 |
1299 | |
1300 | Imagine that you would like the following paths in your application: |
1301 | |
1302 | =over |
1303 | |
1b4425c5 |
1304 | =item B<< /cd/<ID>/track/<ID> >> |
6daaedc0 |
1305 | |
1306 | Displays info on a particular track. |
b1a08fe1 |
1307 | |
6daaedc0 |
1308 | In the case of a multi-volume CD, this is the track sequence. |
1309 | |
1b4425c5 |
1310 | =item B<< /cd/<ID>/volume/<ID>/track/<ID> >> |
6daaedc0 |
1311 | |
1312 | Displays info on a track on a specific volume. |
1313 | |
1314 | =back |
1315 | |
1316 | Here is some example code, showing how to do this with chained controllers: |
1317 | |
1318 | package CD::Controller; |
1319 | use base qw/Catalyst::Controller/; |
b1a08fe1 |
1320 | |
6daaedc0 |
1321 | sub root : Chained('/') PathPart('/cd') CaptureArgs(1) { |
1322 | my ($self, $c, $cd_id) = @_; |
1323 | $c->stash->{cd_id} = $cd_id; |
1324 | $c->stash->{cd} = $self->model('CD')->find_by_id($cd_id); |
1325 | } |
b1a08fe1 |
1326 | |
6daaedc0 |
1327 | sub trackinfo : Chained('track') PathPart('') Args(0) RenderView { |
1328 | my ($self, $c) = @_; |
1329 | } |
b1a08fe1 |
1330 | |
6daaedc0 |
1331 | package CD::Controller::ByTrackSeq; |
1332 | use base qw/CD::Controller/; |
b1a08fe1 |
1333 | |
6daaedc0 |
1334 | sub track : Chained('root') PathPart('track') CaptureArgs(1) { |
1335 | my ($self, $c, $track_seq) = @_; |
1336 | $c->stash->{track} = $self->stash->{cd}->find_track_by_seq($track_seq); |
1337 | } |
b1a08fe1 |
1338 | |
6daaedc0 |
1339 | package CD::Controller::ByTrackVolNo; |
1340 | use base qw/CD::Controller/; |
b1a08fe1 |
1341 | |
6daaedc0 |
1342 | sub volume : Chained('root') PathPart('volume') CaptureArgs(1) { |
1343 | my ($self, $c, $volume) = @_; |
1344 | $c->stash->{volume} = $volume; |
1345 | } |
b1a08fe1 |
1346 | |
6daaedc0 |
1347 | sub track : Chained('volume') PathPart('track') CaptureArgs(1) { |
1348 | my ($self, $c, $track_no) = @_; |
1349 | $c->stash->{track} = $self->stash->{cd}->find_track_by_vol_and_track_no( |
1350 | $c->stash->{volume}, $track_no |
1351 | ); |
1352 | } |
b1a08fe1 |
1353 | |
1354 | Note that adding other actions (i.e. chain endpoints) which operate on a track |
6daaedc0 |
1355 | is simply a matter of adding a new sub to CD::Controller - no code is duplicated, |
1356 | even though there are two different methods of looking up a track. |
1357 | |
1358 | This technique can be expanded as needed to fulfil your requirements - for example, |
1359 | if you inherit the first action of a chain from a base class, then mixing in a |
433f1ad4 |
1360 | different base class can be used to duplicate an entire URL hierarchy at a different |
6daaedc0 |
1361 | point within your application. |
1362 | |
cb93c9d7 |
1363 | =head2 Component-based Subrequests |
1364 | |
1365 | See L<Catalyst::Plugin::SubRequest>. |
1366 | |
1367 | =head2 File uploads |
1368 | |
1369 | =head3 Single file upload with Catalyst |
1370 | |
1371 | To implement uploads in Catalyst, you need to have a HTML form similar to |
1372 | this: |
1373 | |
1374 | <form action="/upload" method="post" enctype="multipart/form-data"> |
02bb2b5a |
1375 | <input type="hidden" name="form_submit" value="yes"> |
1376 | <input type="file" name="my_file"> |
1377 | <input type="submit" value="Send"> |
cb93c9d7 |
1378 | </form> |
1379 | |
1380 | It's very important not to forget C<enctype="multipart/form-data"> in |
1381 | the form. |
1382 | |
1383 | Catalyst Controller module 'upload' action: |
1384 | |
1385 | sub upload : Global { |
1386 | my ($self, $c) = @_; |
1387 | |
1388 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
1389 | |
1390 | if ( my $upload = $c->request->upload('my_file') ) { |
1391 | |
1392 | my $filename = $upload->filename; |
1393 | my $target = "/tmp/upload/$filename"; |
1394 | |
1395 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
1396 | die( "Failed to copy '$filename' to '$target': $!" ); |
1397 | } |
1398 | } |
1399 | } |
1400 | |
1401 | $c->stash->{template} = 'file_upload.html'; |
1402 | } |
1403 | |
1404 | =head3 Multiple file upload with Catalyst |
1405 | |
1406 | Code for uploading multiple files from one form needs a few changes: |
1407 | |
1408 | The form should have this basic structure: |
1409 | |
1410 | <form action="/upload" method="post" enctype="multipart/form-data"> |
02bb2b5a |
1411 | <input type="hidden" name="form_submit" value="yes"> |
1412 | <input type="file" name="file1" size="50"><br> |
1413 | <input type="file" name="file2" size="50"><br> |
1414 | <input type="file" name="file3" size="50"><br> |
1415 | <input type="submit" value="Send"> |
cb93c9d7 |
1416 | </form> |
1417 | |
1418 | And in the controller: |
1419 | |
1420 | sub upload : Local { |
1421 | my ($self, $c) = @_; |
1422 | |
1423 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
1424 | |
1425 | for my $field ( $c->req->upload ) { |
1426 | |
1427 | my $upload = $c->req->upload($field); |
1428 | my $filename = $upload->filename; |
1429 | my $target = "/tmp/upload/$filename"; |
1430 | |
1431 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
1432 | die( "Failed to copy '$filename' to '$target': $!" ); |
1433 | } |
1434 | } |
1435 | } |
1436 | |
1437 | $c->stash->{template} = 'file_upload.html'; |
1438 | } |
1439 | |
34626ece |
1440 | C<< for my $field ($c->req->upload) >> loops automatically over all file |
cb93c9d7 |
1441 | input fields and gets input names. After that is basic file saving code, |
1442 | just like in single file upload. |
1443 | |
1444 | Notice: C<die>ing might not be what you want to do, when an error |
1445 | occurs, but it works as an example. A better idea would be to store |
7dae1813 |
1446 | error C<$!> in C<< $c->stash->{error} >> and show a custom error template |
cb93c9d7 |
1447 | displaying this message. |
1448 | |
1449 | For more information about uploads and usable methods look at |
1450 | L<Catalyst::Request::Upload> and L<Catalyst::Request>. |
1451 | |
1452 | =head2 Forwarding with arguments |
1453 | |
1454 | Sometimes you want to pass along arguments when forwarding to another |
1455 | action. As of version 5.30, arguments can be passed in the call to |
1456 | C<forward>; in earlier versions, you can manually set the arguments in |
1457 | the Catalyst Request object: |
1458 | |
02bb2b5a |
1459 | # version 5.30 and later: |
1460 | $c->forward('/wherever', [qw/arg1 arg2 arg3/]); |
cb93c9d7 |
1461 | |
02bb2b5a |
1462 | # pre-5.30 |
1463 | $c->req->args([qw/arg1 arg2 arg3/]); |
1464 | $c->forward('/wherever'); |
cb93c9d7 |
1465 | |
b1a08fe1 |
1466 | (See the L<Catalyst::Manual::Intro> Flow_Control section for more |
cb93c9d7 |
1467 | information on passing arguments via C<forward>.) |
1468 | |
6daaedc0 |
1469 | =head2 Chained dispatch using base classes, and inner packages. |
1470 | |
02bb2b5a |
1471 | package MyApp::Controller::Base; |
1472 | use base qw/Catalyst::Controller/; |
6daaedc0 |
1473 | |
02bb2b5a |
1474 | sub key1 : Chained('/') |
31e9c5ef |
1475 | |
e2728084 |
1476 | =head2 Extending RenderView (formerly DefaultEnd) |
1477 | |
1478 | The recommended approach for an C<end> action is to use |
1479 | L<Catalyst::Action::RenderView> (taking the place of |
1480 | L<Catalyst::Plugin::DefaultEnd>), which does what you usually need. |
1481 | However there are times when you need to add a bit to it, but don't want |
1482 | to write your own C<end> action. |
1483 | |
1484 | You can extend it like this: |
1485 | |
1486 | To add something to an C<end> action that is called before rendering |
1487 | (this is likely to be what you want), simply place it in the C<end> |
1488 | method: |
1489 | |
1490 | sub end : ActionClass('RenderView') { |
02bb2b5a |
1491 | my ( $self, $c ) = @_; |
1492 | # do stuff here; the RenderView action is called afterwards |
e2728084 |
1493 | } |
1494 | |
1495 | To add things to an C<end> action that are called I<after> rendering, |
1496 | you can set it up like this: |
1497 | |
1498 | sub render : ActionClass('RenderView') { } |
1499 | |
1500 | sub end : Private { |
02bb2b5a |
1501 | my ( $self, $c ) = @_; |
1502 | $c->forward('render'); |
1503 | # do stuff here |
e2728084 |
1504 | } |
1505 | |
31e9c5ef |
1506 | |
cb93c9d7 |
1507 | =head2 Serving static content |
1508 | |
1509 | Serving static content in Catalyst used to be somewhat tricky; the use |
1510 | of L<Catalyst::Plugin::Static::Simple> makes everything much easier. |
1511 | This plugin will automatically serve your static content during development, |
1512 | but allows you to easily switch to Apache (or other server) in a |
1513 | production environment. |
1514 | |
1515 | =head3 Introduction to Static::Simple |
1516 | |
1517 | Static::Simple is a plugin that will help to serve static content for your |
1518 | application. By default, it will serve most types of files, excluding some |
1519 | standard Template Toolkit extensions, out of your B<root> file directory. All |
f4e9de4a |
1520 | files are served by path, so if F<images/me.jpg> is requested, then |
1521 | F<root/images/me.jpg> is found and served. |
cb93c9d7 |
1522 | |
1523 | =head3 Usage |
1524 | |
1525 | Using the plugin is as simple as setting your use line in MyApp.pm to include: |
1526 | |
02bb2b5a |
1527 | use Catalyst qw/Static::Simple/; |
cb93c9d7 |
1528 | |
1529 | and already files will be served. |
1530 | |
1531 | =head3 Configuring |
1532 | |
1533 | Static content is best served from a single directory within your root |
f4e9de4a |
1534 | directory. Having many different directories such as F<root/css> and |
1535 | F<root/images> requires more code to manage, because you must separately |
1536 | identify each static directory--if you decide to add a F<root/js> |
cb93c9d7 |
1537 | directory, you'll need to change your code to account for it. In |
1538 | contrast, keeping all static directories as subdirectories of a main |
f4e9de4a |
1539 | F<root/static> directory makes things much easier to manage. Here's an |
cb93c9d7 |
1540 | example of a typical root directory structure: |
1541 | |
1542 | root/ |
1543 | root/content.tt |
1544 | root/controller/stuff.tt |
1545 | root/header.tt |
1546 | root/static/ |
1547 | root/static/css/main.css |
1548 | root/static/images/logo.jpg |
1549 | root/static/js/code.js |
1550 | |
1551 | |
f4e9de4a |
1552 | All static content lives under F<root/static>, with everything else being |
cb93c9d7 |
1553 | Template Toolkit files. |
1554 | |
1555 | =over 4 |
1556 | |
1557 | =item Include Path |
1558 | |
1559 | You may of course want to change the default locations, and make |
1560 | Static::Simple look somewhere else, this is as easy as: |
1561 | |
02bb2b5a |
1562 | MyApp->config( |
1563 | static => { |
1564 | include_path => [ |
1565 | MyApp->path_to('/'), |
1566 | '/path/to/my/files', |
1567 | ], |
1568 | }, |
1569 | ); |
cb93c9d7 |
1570 | |
1571 | When you override include_path, it will not automatically append the |
1572 | normal root path, so you need to add it yourself if you still want |
1573 | it. These will be searched in order given, and the first matching file |
1574 | served. |
1575 | |
1576 | =item Static directories |
1577 | |
1578 | If you want to force some directories to be only static, you can set |
1579 | them using paths relative to the root dir, or regular expressions: |
1580 | |
02bb2b5a |
1581 | MyApp->config( |
1582 | static => { |
1583 | dirs => [ |
1584 | 'static', |
1585 | qr/^(images|css)/, |
1586 | ], |
1587 | }, |
1588 | ); |
cb93c9d7 |
1589 | |
1590 | =item File extensions |
1591 | |
1592 | By default, the following extensions are not served (that is, they will |
1593 | be processed by Catalyst): B<tmpl, tt, tt2, html, xhtml>. This list can |
1594 | be replaced easily: |
1595 | |
02bb2b5a |
1596 | MyApp->config( |
19a5b486 |
1597 | static => { |
1598 | ignore_extensions => [ |
1599 | qw/tmpl tt tt2 html xhtml/ |
1600 | ], |
1601 | }, |
02bb2b5a |
1602 | ); |
cb93c9d7 |
1603 | |
1604 | =item Ignoring directories |
1605 | |
1606 | Entire directories can be ignored. If used with include_path, |
1607 | directories relative to the include_path dirs will also be ignored: |
1608 | |
02bb2b5a |
1609 | MyApp->config( static => { |
19a5b486 |
1610 | ignore_dirs => [ qw/tmpl css/ ], |
02bb2b5a |
1611 | }); |
cb93c9d7 |
1612 | |
1613 | =back |
1614 | |
1615 | =head3 More information |
1616 | |
6f660c96 |
1617 | L<Catalyst::Plugin::Static::Simple> |
cb93c9d7 |
1618 | |
1619 | =head3 Serving manually with the Static plugin with HTTP::Daemon (myapp_server.pl) |
1620 | |
1621 | In some situations you might want to control things more directly, |
1622 | using L<Catalyst::Plugin::Static>. |
1623 | |
1624 | In your main application class (MyApp.pm), load the plugin: |
1625 | |
b411df01 |
1626 | use Catalyst qw/-Debug FormValidator Static OtherPlugin/; |
cb93c9d7 |
1627 | |
1628 | You will also need to make sure your end method does I<not> forward |
1629 | static content to the view, perhaps like this: |
1630 | |
1631 | sub end : Private { |
1632 | my ( $self, $c ) = @_; |
1633 | |
b1a08fe1 |
1634 | $c->forward( 'MyApp::View::TT' ) |
02bb2b5a |
1635 | unless ( $c->res->body || !$c->stash->{template} ); |
cb93c9d7 |
1636 | } |
1637 | |
1638 | This code will only forward to the view if a template has been |
1639 | previously defined by a controller and if there is not already data in |
429d1caf |
1640 | C<< $c->res->body >>. |
cb93c9d7 |
1641 | |
1642 | Next, create a controller to handle requests for the /static path. Use |
1643 | the Helper to save time. This command will create a stub controller as |
f4e9de4a |
1644 | F<lib/MyApp/Controller/Static.pm>. |
cb93c9d7 |
1645 | |
1646 | $ script/myapp_create.pl controller Static |
1647 | |
1648 | Edit the file and add the following methods: |
1649 | |
1650 | # serve all files under /static as static files |
1651 | sub default : Path('/static') { |
1652 | my ( $self, $c ) = @_; |
1653 | |
1654 | # Optional, allow the browser to cache the content |
1655 | $c->res->headers->header( 'Cache-Control' => 'max-age=86400' ); |
1656 | |
1657 | $c->serve_static; # from Catalyst::Plugin::Static |
1658 | } |
1659 | |
1660 | # also handle requests for /favicon.ico |
1661 | sub favicon : Path('/favicon.ico') { |
1662 | my ( $self, $c ) = @_; |
1663 | |
1664 | $c->serve_static; |
1665 | } |
1666 | |
1667 | You can also define a different icon for the browser to use instead of |
1668 | favicon.ico by using this in your HTML header: |
1669 | |
1670 | <link rel="icon" href="/static/myapp.ico" type="image/x-icon" /> |
1671 | |
1672 | =head3 Common problems with the Static plugin |
1673 | |
1674 | The Static plugin makes use of the C<shared-mime-info> package to |
1675 | automatically determine MIME types. This package is notoriously |
1676 | difficult to install, especially on win32 and OS X. For OS X the easiest |
1677 | path might be to install Fink, then use C<apt-get install |
1678 | shared-mime-info>. Restart the server, and everything should be fine. |
1679 | |
1680 | Make sure you are using the latest version (>= 0.16) for best |
1681 | results. If you are having errors serving CSS files, or if they get |
1682 | served as text/plain instead of text/css, you may have an outdated |
1683 | shared-mime-info version. You may also wish to simply use the following |
1684 | code in your Static controller: |
1685 | |
1686 | if ($c->req->path =~ /css$/i) { |
1687 | $c->serve_static( "text/css" ); |
1688 | } else { |
1689 | $c->serve_static; |
1690 | } |
1691 | |
1692 | =head3 Serving Static Files with Apache |
1693 | |
1694 | When using Apache, you can bypass Catalyst and any Static |
1695 | plugins/controllers controller by intercepting requests for the |
f4e9de4a |
1696 | F<root/static> path at the server level. All that is required is to |
cb93c9d7 |
1697 | define a DocumentRoot and add a separate Location block for your static |
1698 | content. Here is a complete config for this application under mod_perl |
1699 | 1.x: |
1700 | |
1701 | <Perl> |
1702 | use lib qw(/var/www/MyApp/lib); |
1703 | </Perl> |
1704 | PerlModule MyApp |
1705 | |
1706 | <VirtualHost *> |
1707 | ServerName myapp.example.com |
1708 | DocumentRoot /var/www/MyApp/root |
1709 | <Location /> |
1710 | SetHandler perl-script |
1711 | PerlHandler MyApp |
1712 | </Location> |
1713 | <LocationMatch "/(static|favicon.ico)"> |
1714 | SetHandler default-handler |
1715 | </LocationMatch> |
1716 | </VirtualHost> |
1717 | |
1718 | And here's a simpler example that'll get you started: |
1719 | |
1720 | Alias /static/ "/my/static/files/" |
1721 | <Location "/static"> |
1722 | SetHandler none |
1723 | </Location> |
1724 | |
1725 | =head2 Caching |
1726 | |
1727 | Catalyst makes it easy to employ several different types of caching to |
1728 | speed up your applications. |
1729 | |
1730 | =head3 Cache Plugins |
1731 | |
1732 | There are three wrapper plugins around common CPAN cache modules: |
1733 | Cache::FastMmap, Cache::FileCache, and Cache::Memcached. These can be |
1734 | used to cache the result of slow operations. |
1735 | |
ca7528df |
1736 | The Catalyst Advent Calendar uses the FileCache plugin to cache the |
cb93c9d7 |
1737 | rendered XHTML version of the source POD document. This is an ideal |
ca7528df |
1738 | application for a cache because the source document changes |
1739 | infrequently but may be viewed many times. |
cb93c9d7 |
1740 | |
b411df01 |
1741 | use Catalyst qw/Cache::FileCache/; |
b1a08fe1 |
1742 | |
cb93c9d7 |
1743 | ... |
b1a08fe1 |
1744 | |
cb93c9d7 |
1745 | use File::stat; |
1746 | sub render_pod : Local { |
1747 | my ( self, $c ) = @_; |
b1a08fe1 |
1748 | |
cb93c9d7 |
1749 | # the cache is keyed on the filename and the modification time |
1750 | # to check for updates to the file. |
1751 | my $file = $c->path_to( 'root', '2005', '11.pod' ); |
1752 | my $mtime = ( stat $file )->mtime; |
b1a08fe1 |
1753 | |
cb93c9d7 |
1754 | my $cached_pod = $c->cache->get("$file $mtime"); |
1755 | if ( !$cached_pod ) { |
1756 | $cached_pod = do_slow_pod_rendering(); |
1757 | # cache the result for 12 hours |
1758 | $c->cache->set( "$file $mtime", $cached_pod, '12h' ); |
1759 | } |
1760 | $c->stash->{pod} = $cached_pod; |
1761 | } |
b1a08fe1 |
1762 | |
cb93c9d7 |
1763 | We could actually cache the result forever, but using a value such as 12 hours |
1764 | allows old entries to be automatically expired when they are no longer needed. |
1765 | |
1766 | =head3 Page Caching |
1767 | |
1768 | Another method of caching is to cache the entire HTML page. While this is |
5abded07 |
1769 | traditionally handled by a frontend proxy server like Squid, the Catalyst |
cb93c9d7 |
1770 | PageCache plugin makes it trivial to cache the entire output from |
1771 | frequently-used or slow actions. |
1772 | |
1773 | Many sites have a busy content-filled front page that might look something |
1774 | like this. It probably takes a while to process, and will do the exact same |
1775 | thing for every single user who views the page. |
1776 | |
1777 | sub front_page : Path('/') { |
1778 | my ( $self, $c ) = @_; |
b1a08fe1 |
1779 | |
cb93c9d7 |
1780 | $c->forward( 'get_news_articles' ); |
1781 | $c->forward( 'build_lots_of_boxes' ); |
1782 | $c->forward( 'more_slow_stuff' ); |
b1a08fe1 |
1783 | |
cb93c9d7 |
1784 | $c->stash->{template} = 'index.tt'; |
1785 | } |
1786 | |
1787 | We can add the PageCache plugin to speed things up. |
1788 | |
b411df01 |
1789 | use Catalyst qw/Cache::FileCache PageCache/; |
b1a08fe1 |
1790 | |
cb93c9d7 |
1791 | sub front_page : Path ('/') { |
1792 | my ( $self, $c ) = @_; |
b1a08fe1 |
1793 | |
cb93c9d7 |
1794 | $c->cache_page( 300 ); |
b1a08fe1 |
1795 | |
cb93c9d7 |
1796 | # same processing as above |
1797 | } |
b1a08fe1 |
1798 | |
cb93c9d7 |
1799 | Now the entire output of the front page, from <html> to </html>, will be |
1800 | cached for 5 minutes. After 5 minutes, the next request will rebuild the |
1801 | page and it will be re-cached. |
1802 | |
1803 | Note that the page cache is keyed on the page URI plus all parameters, so |
1804 | requests for / and /?foo=bar will result in different cache items. Also, |
1805 | only GET requests will be cached by the plugin. |
1806 | |
5abded07 |
1807 | You can even get that frontend Squid proxy to help out by enabling HTTP |
cb93c9d7 |
1808 | headers for the cached page. |
1809 | |
19a5b486 |
1810 | MyApp->config( |
1811 | page_cache => { |
1812 | set_http_headers => 1, |
1813 | }, |
1814 | ); |
b1a08fe1 |
1815 | |
cb93c9d7 |
1816 | This would now set the following headers so proxies and browsers may cache |
1817 | the content themselves. |
1818 | |
1819 | Cache-Control: max-age=($expire_time - time) |
1820 | Expires: $expire_time |
1821 | Last-Modified: $cache_created_time |
b1a08fe1 |
1822 | |
cb93c9d7 |
1823 | =head3 Template Caching |
1824 | |
1825 | Template Toolkit provides support for caching compiled versions of your |
1826 | templates. To enable this in Catalyst, use the following configuration. |
1827 | TT will cache compiled templates keyed on the file mtime, so changes will |
1828 | still be automatically detected. |
1829 | |
1830 | package MyApp::View::TT; |
b1a08fe1 |
1831 | |
cb93c9d7 |
1832 | use strict; |
1833 | use warnings; |
1834 | use base 'Catalyst::View::TT'; |
b1a08fe1 |
1835 | |
cb93c9d7 |
1836 | __PACKAGE__->config( |
1837 | COMPILE_DIR => '/tmp/template_cache', |
1838 | ); |
b1a08fe1 |
1839 | |
cb93c9d7 |
1840 | 1; |
b1a08fe1 |
1841 | |
cb93c9d7 |
1842 | =head3 More Info |
1843 | |
1844 | See the documentation for each cache plugin for more details and other |
1845 | available configuration options. |
1846 | |
1847 | L<Catalyst::Plugin::Cache::FastMmap> |
1848 | L<Catalyst::Plugin::Cache::FileCache> |
1849 | L<Catalyst::Plugin::Cache::Memcached> |
1850 | L<Catalyst::Plugin::PageCache> |
1851 | L<http://search.cpan.org/dist/Template-Toolkit/lib/Template/Manual/Config.pod#Caching_and_Compiling_Options> |
1852 | |
1853 | =head1 Testing |
1854 | |
1855 | Testing is an integral part of the web application development |
1856 | process. Tests make multi developer teams easier to coordinate, and |
1857 | they help ensure that there are no nasty surprises after upgrades or |
1858 | alterations. |
1859 | |
1860 | =head2 Testing |
1861 | |
b1a08fe1 |
1862 | Catalyst provides a convenient way of testing your application during |
cb93c9d7 |
1863 | development and before deployment in a real environment. |
1864 | |
f4e9de4a |
1865 | L<Catalyst::Test> makes it possible to run the same tests both locally |
cb93c9d7 |
1866 | (without an external daemon) and against a remote server via HTTP. |
1867 | |
1868 | =head3 Tests |
1869 | |
f4e9de4a |
1870 | Let's examine a skeleton application's F<t/> directory: |
cb93c9d7 |
1871 | |
1872 | mundus:~/MyApp chansen$ ls -l t/ |
1873 | total 24 |
1874 | -rw-r--r-- 1 chansen chansen 95 18 Dec 20:50 01app.t |
1875 | -rw-r--r-- 1 chansen chansen 190 18 Dec 20:50 02pod.t |
1876 | -rw-r--r-- 1 chansen chansen 213 18 Dec 20:50 03podcoverage.t |
1877 | |
1878 | =over 4 |
1879 | |
f4e9de4a |
1880 | =item F<01app.t> |
cb93c9d7 |
1881 | |
1882 | Verifies that the application loads, compiles, and returns a successful |
1883 | response. |
1884 | |
f4e9de4a |
1885 | =item F<02pod.t> |
cb93c9d7 |
1886 | |
b1a08fe1 |
1887 | Verifies that all POD is free from errors. Only executed if the C<TEST_POD> |
cb93c9d7 |
1888 | environment variable is true. |
1889 | |
f4e9de4a |
1890 | =item F<03podcoverage.t> |
cb93c9d7 |
1891 | |
1892 | Verifies that all methods/functions have POD coverage. Only executed if the |
1893 | C<TEST_POD> environment variable is true. |
1894 | |
1895 | =back |
1896 | |
1897 | =head3 Creating tests |
1898 | |
1899 | mundus:~/MyApp chansen$ cat t/01app.t | perl -ne 'printf( "%2d %s", $., $_ )' |
1900 | 1 use Test::More tests => 2; |
e5415384 |
1901 | 2 BEGIN { use_ok( Catalyst::Test, 'MyApp' ) } |
cb93c9d7 |
1902 | 3 |
1903 | 4 ok( request('/')->is_success ); |
1904 | |
1905 | The first line declares how many tests we are going to run, in this case |
1906 | two. The second line tests and loads our application in test mode. The |
1907 | fourth line verifies that our application returns a successful response. |
1908 | |
cacb3819 |
1909 | L<Catalyst::Test> exports two functions, C<request> and C<get>. Each can |
cb93c9d7 |
1910 | take three different arguments: |
1911 | |
1912 | =over 4 |
1913 | |
1914 | =item A string which is a relative or absolute URI. |
1915 | |
1916 | request('/my/path'); |
1917 | request('http://www.host.com/my/path'); |
1918 | |
cacb3819 |
1919 | =item An instance of L<URI>. |
cb93c9d7 |
1920 | |
1921 | request( URI->new('http://www.host.com/my/path') ); |
1922 | |
cacb3819 |
1923 | =item An instance of L<HTTP::Request>. |
cb93c9d7 |
1924 | |
1925 | request( HTTP::Request->new( GET => 'http://www.host.com/my/path') ); |
1926 | |
1927 | =back |
1928 | |
cacb3819 |
1929 | C<request> returns an instance of L<HTTP::Response> and C<get> returns the |
cb93c9d7 |
1930 | content (body) of the response. |
1931 | |
1932 | =head3 Running tests locally |
1933 | |
1934 | mundus:~/MyApp chansen$ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib/ t/ |
b1a08fe1 |
1935 | t/01app............ok |
1936 | t/02pod............ok |
1937 | t/03podcoverage....ok |
cb93c9d7 |
1938 | All tests successful. |
1939 | Files=3, Tests=4, 2 wallclock secs ( 1.60 cusr + 0.36 csys = 1.96 CPU) |
b1a08fe1 |
1940 | |
cb93c9d7 |
1941 | C<CATALYST_DEBUG=0> ensures that debugging is off; if it's enabled you |
1942 | will see debug logs between tests. |
1943 | |
1944 | C<TEST_POD=1> enables POD checking and coverage. |
1945 | |
1946 | C<prove> A command-line tool that makes it easy to run tests. You can |
1947 | find out more about it from the links below. |
1948 | |
1949 | =head3 Running tests remotely |
1950 | |
1951 | mundus:~/MyApp chansen$ CATALYST_SERVER=http://localhost:3000/ prove --lib lib/ t/01app.t |
b1a08fe1 |
1952 | t/01app....ok |
cb93c9d7 |
1953 | All tests successful. |
1954 | Files=1, Tests=2, 0 wallclock secs ( 0.40 cusr + 0.01 csys = 0.41 CPU) |
1955 | |
b1a08fe1 |
1956 | C<CATALYST_SERVER=http://localhost:3000/> is the absolute deployment URI of |
1957 | your application. In C<CGI> or C<FastCGI> it should be the host and path |
cb93c9d7 |
1958 | to the script. |
1959 | |
cacb3819 |
1960 | =head3 L<Test::WWW::Mechanize> and Catalyst |
cb93c9d7 |
1961 | |
cacb3819 |
1962 | Be sure to check out L<Test::WWW::Mechanize::Catalyst>. It makes it easy to |
cb93c9d7 |
1963 | test HTML, forms and links. A short example of usage: |
1964 | |
1965 | use Test::More tests => 6; |
e5415384 |
1966 | BEGIN { use_ok( Test::WWW::Mechanize::Catalyst, 'MyApp' ) } |
cb93c9d7 |
1967 | |
1968 | my $mech = Test::WWW::Mechanize::Catalyst->new; |
1969 | $mech->get_ok("http://localhost/", 'Got index page'); |
1970 | $mech->title_like( qr/^MyApp on Catalyst/, 'Got right index title' ); |
1971 | ok( $mech->find_link( text_regex => qr/^Wiki/i ), 'Found link to Wiki' ); |
1972 | ok( $mech->find_link( text_regex => qr/^Mailing-List/i ), 'Found link to Mailing-List' ); |
1973 | ok( $mech->find_link( text_regex => qr/^IRC channel/i ), 'Found link to IRC channel' ); |
1974 | |
1975 | =head3 Further Reading |
1976 | |
1977 | =over 4 |
1978 | |
6f660c96 |
1979 | =item * L<Catalyst::Test> |
cb93c9d7 |
1980 | |
6f660c96 |
1981 | =item * L<Test::WWW::Mechanize::Catalyst> |
cb93c9d7 |
1982 | |
6f660c96 |
1983 | =item * L<Test::WWW::Mechanize> |
cb93c9d7 |
1984 | |
6f660c96 |
1985 | =item * L<WWW::Mechanize> |
cb93c9d7 |
1986 | |
6f660c96 |
1987 | =item * L<LWP::UserAgent> |
cb93c9d7 |
1988 | |
6f660c96 |
1989 | =item * L<HTML::Form> |
cb93c9d7 |
1990 | |
6f660c96 |
1991 | =item * L<HTTP::Message> |
cb93c9d7 |
1992 | |
6f660c96 |
1993 | =item * L<HTTP::Request> |
cb93c9d7 |
1994 | |
6f660c96 |
1995 | =item * L<HTTP::Request::Common> |
cb93c9d7 |
1996 | |
6f660c96 |
1997 | =item * L<HTTP::Response> |
cb93c9d7 |
1998 | |
6f660c96 |
1999 | =item * L<HTTP::Status> |
cb93c9d7 |
2000 | |
6f660c96 |
2001 | =item * L<URI> |
cb93c9d7 |
2002 | |
6f660c96 |
2003 | =item * L<Test::More> |
cb93c9d7 |
2004 | |
6f660c96 |
2005 | =item * L<Test::Pod> |
cb93c9d7 |
2006 | |
6f660c96 |
2007 | =item * L<Test::Pod::Coverage> |
cb93c9d7 |
2008 | |
6f660c96 |
2009 | =item * L<prove> (L<Test::Harness>) |
cb93c9d7 |
2010 | |
6f660c96 |
2011 | =back |
cb93c9d7 |
2012 | |
6f660c96 |
2013 | =head3 More Information |
cb93c9d7 |
2014 | |
6f660c96 |
2015 | =over 4 |
cb93c9d7 |
2016 | |
6f660c96 |
2017 | =item * L<Catalyst::Plugin::Authorization::Roles> |
cb93c9d7 |
2018 | |
6f660c96 |
2019 | =item * L<Catalyst::Plugin::Authorization::ACL> |
cb93c9d7 |
2020 | |
2021 | =back |
2022 | |
cb93c9d7 |
2023 | =head1 AUTHORS |
2024 | |
bbddff00 |
2025 | Catalyst Contributors, see Catalyst.pm |
cb93c9d7 |
2026 | |
2027 | =head1 COPYRIGHT |
2028 | |
bbddff00 |
2029 | This library is free software. You can redistribute it and/or modify it under |
2030 | the same terms as Perl itself. |
cb93c9d7 |
2031 | |
bbddff00 |
2032 | =cut |