Commit | Line | Data |
fc7ec1d9 |
1 | =head1 NAME |
2 | |
3 | Catalyst::Manual::Cookbook - Cooking with Catalyst |
4 | |
5 | =head1 DESCRIPTION |
6 | |
aba94964 |
7 | Yummy code like your mum used to bake! |
fc7ec1d9 |
8 | |
9 | =head1 RECIPES |
10 | |
11 | =head2 Force debug screen |
12 | |
eff5f524 |
13 | You can force Catalyst to display the debug screen at the end of the request by |
14 | placing a C<die()> call in the C<end> action. |
fc7ec1d9 |
15 | |
61b1e958 |
16 | sub end : Private { |
17 | my ( $self, $c ) = @_; |
2343e117 |
18 | die "forced debug"; |
61b1e958 |
19 | } |
fc7ec1d9 |
20 | |
379ca371 |
21 | If you're tired of removing and adding this all the time, you can add a |
22 | condition in the C<end> action. For example: |
23 | |
24 | sub end : Private { |
25 | my ( $self, $c ) = @_; |
26 | die "forced debug" if $c->req->params->{dump_info}; |
27 | } |
28 | |
29 | Then just add to your query string C<"&dump_info=1">, or the like, to |
30 | force debug output. |
aff93052 |
31 | |
aff93052 |
32 | |
fc7ec1d9 |
33 | =head2 Disable statistics |
34 | |
35 | Just add this line to your application class if you don't want those nifty |
36 | statistics in your debug messages. |
37 | |
38 | sub Catalyst::Log::info { } |
39 | |
40 | =head2 Scaffolding |
41 | |
42 | Scaffolding is very simple with Catalyst. |
51ef2818 |
43 | Just use Catalyst::Model::CDBI::CRUD as your base class. |
fc7ec1d9 |
44 | |
45 | # lib/MyApp/Model/CDBI.pm |
46 | package MyApp::Model::CDBI; |
47 | |
48 | use strict; |
49 | use base 'Catalyst::Model::CDBI::CRUD'; |
50 | |
51 | __PACKAGE__->config( |
52 | dsn => 'dbi:SQLite:/tmp/myapp.db', |
53 | relationships => 1 |
54 | ); |
55 | |
56 | 1; |
57 | |
58 | # lib/MyApp.pm |
59 | package MyApp; |
60 | |
61 | use Catalyst 'FormValidator'; |
62 | |
63 | __PACKAGE__->config( |
64 | name => 'My Application', |
65 | root => '/home/joeuser/myapp/root' |
66 | ); |
67 | |
61b1e958 |
68 | sub my_table : Global { |
69 | my ( $self, $c ) = @_; |
70 | $c->form( optional => [ MyApp::Model::CDBI::Table->columns ] ); |
71 | $c->forward('MyApp::Model::CDBI::Table'); |
72 | } |
fc7ec1d9 |
73 | |
74 | 1; |
75 | |
379ca371 |
76 | Modify the C<$c-E<gt>form()> parameters to match your needs, and don't |
77 | forget to copy the templates into the template root. Can't find the |
78 | templates? They were in the CRUD model distribution, so you can do |
79 | B<look Catalyst::Model::CDBI::CRUD> from the CPAN shell to find them. |
80 | |
81 | Other Scaffolding modules are in development at the time of writing. |
fc7ec1d9 |
82 | |
5c0ff128 |
83 | =head2 Single file upload with Catalyst |
aba94964 |
84 | |
85 | To implement uploads in Catalyst you need to have a HTML form similiar to |
86 | this: |
87 | |
88 | <form action="/upload" method="post" enctype="multipart/form-data"> |
89 | <input type="hidden" name="form_submit" value="yes"> |
90 | <input type="file" name="my_file"> |
91 | <input type="submit" value="Send"> |
92 | </form> |
93 | |
379ca371 |
94 | It's very important not to forget C<enctype="multipart/form-data"> in |
95 | the form. |
aba94964 |
96 | |
97 | Catalyst Controller module 'upload' action: |
98 | |
5c0ff128 |
99 | sub upload : Global { |
100 | my ($self, $c) = @_; |
4d89569d |
101 | |
102 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
103 | |
104 | if ( my $upload = $c->request->upload('my_file') ) { |
47ae6960 |
105 | |
5c0ff128 |
106 | my $filename = $upload->filename; |
47ae6960 |
107 | my $target = "/tmp/upload/$filename"; |
108 | |
3ffaf022 |
109 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
110 | die( "Failed to copy '$filename' to '$target': $!" ); |
5c0ff128 |
111 | } |
5c0ff128 |
112 | } |
113 | } |
4d89569d |
114 | |
5c0ff128 |
115 | $c->stash->{template} = 'file_upload.html'; |
116 | } |
117 | |
118 | =head2 Multiple file upload with Catalyst |
119 | |
379ca371 |
120 | Code for uploading multiple files from one form needs a few changes: |
5c0ff128 |
121 | |
379ca371 |
122 | The form should have this basic structure: |
5c0ff128 |
123 | |
124 | <form action="/upload" method="post" enctype="multipart/form-data"> |
125 | <input type="hidden" name="form_submit" value="yes"> |
126 | <input type="file" name="file1" size="50"><br> |
127 | <input type="file" name="file2" size="50"><br> |
128 | <input type="file" name="file3" size="50"><br> |
129 | <input type="submit" value="Send"> |
130 | </form> |
131 | |
379ca371 |
132 | And in the controller: |
5c0ff128 |
133 | |
134 | sub upload : Local { |
135 | my ($self, $c) = @_; |
4d89569d |
136 | |
137 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
138 | |
139 | for my $field ( $c->req->upload ) { |
140 | |
02a53b81 |
141 | my $upload = $c->req->upload($field); |
4d89569d |
142 | my $filename = $upload->filename; |
47ae6960 |
143 | my $target = "/tmp/upload/$filename"; |
144 | |
3ffaf022 |
145 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
146 | die( "Failed to copy '$filename' to '$target': $!" ); |
aba94964 |
147 | } |
148 | } |
61b1e958 |
149 | } |
4d89569d |
150 | |
5c0ff128 |
151 | $c->stash->{template} = 'file_upload.html'; |
152 | } |
153 | |
379ca371 |
154 | C<for my $field ($c-E<gt>req->upload)> loops automatically over all file |
155 | input fields and gets input names. After that is basic file saving code, |
156 | just like in single file upload. |
aba94964 |
157 | |
379ca371 |
158 | Notice: C<die>ing might not be what you want to do, when an error |
159 | occurs, but it works as an example. A better idea would be to store |
160 | error C<$!> in $c->stash->{error} and show a custom error template |
161 | displaying this message. |
aba94964 |
162 | |
5c0ff128 |
163 | For more information about uploads and usable methods look at |
379ca371 |
164 | L<Catalyst::Request::Upload> and L<Catalyst::Request>. |
aba94964 |
165 | |
deb90705 |
166 | =head2 Authentication with Catalyst::Plugin::Authentication::CDBI |
167 | |
168 | There are (at least) two ways to implement authentication with this plugin: |
eff5f524 |
169 | 1) only checking username and password; |
379ca371 |
170 | 2) checking username, password, and the roles the user has |
deb90705 |
171 | |
172 | For both variants you'll need the following code in your MyApp package: |
173 | |
174 | use Catalyst qw/Session::FastMmap Static Authentication::CDBI/; |
175 | |
176 | MyApp->config( authentication => { user_class => 'MyApp::M::MyApp::Users', |
177 | user_field => 'email', |
178 | password_field => 'password' }); |
179 | |
180 | 'user_class' is a Class::DBI class for your users table. |
181 | 'user_field' tells which field is used for username lookup (might be |
51ef2818 |
182 | email, first name, surname etc.). |
deb90705 |
183 | 'password_field' is, well, password field in your table and by default |
184 | password is stored in plain text. Authentication::CDBI looks for 'user' |
185 | and 'password' fields in table, if they're not defined in the config. |
186 | |
51ef2818 |
187 | In PostgreSQL, the users table might be something like: |
deb90705 |
188 | |
51ef2818 |
189 | CREATE TABLE users ( |
190 | user_id serial, |
191 | name varchar(100), |
192 | surname varchar(100), |
193 | password varchar(100), |
194 | email varchar(100), |
195 | primary key(user_id) |
196 | ); |
deb90705 |
197 | |
198 | We'll discuss the first variant for now: |
51ef2818 |
199 | 1. user:password login/auth without roles |
deb90705 |
200 | |
51ef2818 |
201 | To log in a user you might use an action like this: |
deb90705 |
202 | |
7c1078a4 |
203 | sub login : Local { |
deb90705 |
204 | my ($self, $c) = @_; |
205 | if ($c->req->params->{username}) { |
206 | $c->session_login($c->req->params->{username}, |
61b1e958 |
207 | $c->req->params->{password} ); |
deb90705 |
208 | if ($c->req->{user}) { |
209 | $c->forward('?restricted_area'); |
210 | } |
211 | } |
61b1e958 |
212 | } |
deb90705 |
213 | |
7c1078a4 |
214 | This action should not go in your MyApp class...if it does, it will |
215 | conflict with the built-in method of the same name. Instead, put it |
216 | in a Controller class. |
217 | |
deb90705 |
218 | $c->req->params->{username} and $c->req->params->{password} are html |
61b1e958 |
219 | form parameters from a login form. If login succeeds, then |
220 | $c->req->{user} contains the username of the authenticated user. |
deb90705 |
221 | |
51ef2818 |
222 | If you want to remember the user's login status in between further |
223 | requests, then just use the C<$c-E<gt>session_login> method. Catalyst will |
224 | create a session id and session cookie and automatically append session |
225 | id to all urls. So all you have to do is just check $c->req->{user} |
61b1e958 |
226 | where needed. |
deb90705 |
227 | |
51ef2818 |
228 | To log out a user, just call $c->session_logout. |
deb90705 |
229 | |
51ef2818 |
230 | Now let's take a look at the second variant: |
231 | 2. user:password login/auth with roles |
deb90705 |
232 | |
51ef2818 |
233 | To use roles you need to add the following parameters to MyApp->config in the 'authentication' section: |
deb90705 |
234 | |
235 | role_class => 'MyApp::M::MyApp::Roles', |
236 | user_role_class => 'MyApp::M::MyApp::UserRoles', |
237 | user_role_user_field => 'user_id', |
238 | user_role_role_field => 'role_id', |
239 | |
240 | Corresponding tables in PostgreSQL could look like this: |
241 | |
51ef2818 |
242 | CREATE TABLE roles ( |
243 | role_id serial, |
244 | name varchar(100), |
245 | primary key(role_id) |
246 | ); |
247 | |
248 | CREATE TABLE user_roles ( |
249 | user_role_id serial, |
250 | user_id int, |
251 | role_id int, |
252 | primary key(user_role_id), |
253 | foreign key(user_id) references users(user_id), |
254 | foreign key(role_id) references roles(role_id) |
255 | ); |
deb90705 |
256 | |
61b1e958 |
257 | The 'roles' table is a list of role names and the 'user_role' table is |
258 | used for the user -> role lookup. |
deb90705 |
259 | |
51ef2818 |
260 | Now if a logged-in user wants to see a location which is allowed only |
261 | for people with an 'admin' role, in your controller you can check it |
61b1e958 |
262 | with: |
deb90705 |
263 | |
61b1e958 |
264 | sub add : Local { |
deb90705 |
265 | my ($self, $c) = @_; |
266 | if ($c->roles(qw/admin/)) { |
267 | $c->req->output("Your account has the role 'admin.'"); |
268 | } else { |
51ef2818 |
269 | $c->req->output("You're not allowed to be here."); |
deb90705 |
270 | } |
61b1e958 |
271 | } |
deb90705 |
272 | |
51ef2818 |
273 | One thing you might need is to forward non-authenticated users to a login |
274 | form if they try to access restricted areas. If you want to do this |
275 | controller-wide (if you have one controller for your admin section) then it's |
276 | best to add a user check to a '!begin' action: |
deb90705 |
277 | |
61b1e958 |
278 | sub begin : Private { |
deb90705 |
279 | my ($self, $c) = @_; |
280 | unless ($c->req->{user}) { |
281 | $c->req->action(undef); ## notice this!! |
282 | $c->forward('?login'); |
283 | } |
61b1e958 |
284 | } |
deb90705 |
285 | |
51ef2818 |
286 | Pay attention to $c->req->action(undef). This is needed because of the |
287 | way $c->forward works - C<forward> to C<login> gets called, but after that |
288 | Catalyst will still execute the action defined in the URI (e.g. if you |
289 | tried to go to C</add>, then first 'begin' will forward to 'login', but after |
290 | that 'add' will nonetheless be executed). So $c->req->action(undef) undefines any |
291 | actions that were to be called and forwards the user where we want him/her |
deb90705 |
292 | to be. |
293 | |
51ef2818 |
294 | And this is all you need to do. |
deb90705 |
295 | |
afb208ae |
296 | =head2 Pass-through login (and other actions) |
297 | |
eff5f524 |
298 | An easy way of having assorted actions that occur during the processing |
299 | of a request that are orthogonal to its actual purpose - logins, silent |
afb208ae |
300 | commands etc. Provide actions for these, but when they're required for |
eff5f524 |
301 | something else fill e.g. a form variable __login and have a sub begin |
302 | like so: |
afb208ae |
303 | |
eff5f524 |
304 | sub begin : Private { |
305 | my ($self, $c) = @_; |
306 | foreach my $action (qw/login docommand foo bar whatever/) { |
307 | if ($c->req->params->{"__${action}"}) { |
308 | $c->forward($action); |
309 | } |
310 | } |
afb208ae |
311 | } |
145074c2 |
312 | |
313 | =head2 How to use Catalyst without mod_perl |
314 | |
315 | Catalyst applications give optimum performance when run under mod_perl. |
61b1e958 |
316 | However sometimes mod_perl is not an option, and running under CGI is |
51ef2818 |
317 | just too slow. There's also an alternative to mod_perl that gives |
dec2a2a9 |
318 | reasonable performance named FastCGI. |
145074c2 |
319 | |
320 | B<Using FastCGI> |
321 | |
61b1e958 |
322 | To quote from L<http://www.fastcgi.com/>: "FastCGI is a language |
323 | independent, scalable, extension to CGI that provides high performance |
324 | without the limitations of specific server APIs." Web server support |
325 | is provided for Apache in the form of C<mod_fastcgi> and there is Perl |
326 | support in the C<FCGI> module. To convert a CGI Catalyst application |
327 | to FastCGI one needs to initialize an C<FCGI::Request> object and loop |
328 | while the C<Accept> method returns zero. The following code shows how |
329 | it is done - and it also works as a normal, single-shot CGI script. |
145074c2 |
330 | |
331 | #!/usr/bin/perl |
332 | use strict; |
333 | use FCGI; |
334 | use MyApp; |
335 | |
336 | my $request = FCGI::Request(); |
337 | while ($request->Accept() >= 0) { |
1c61c726 |
338 | MyApp->run; |
145074c2 |
339 | } |
340 | |
61b1e958 |
341 | Any initialization code should be included outside the request-accept |
342 | loop. |
145074c2 |
343 | |
51ef2818 |
344 | There is one little complication, which is that C<MyApp-E<gt>run> outputs a |
61b1e958 |
345 | complete HTTP response including the status line (e.g.: |
346 | "C<HTTP/1.1 200>"). |
347 | FastCGI just wants a set of headers, so the sample code captures the |
348 | output and drops the first line if it is an HTTP status line (note: |
349 | this may change). |
350 | |
351 | The Apache C<mod_fastcgi> module is provided by a number of Linux |
352 | distros and is straightforward to compile for most Unix-like systems. |
353 | The module provides a FastCGI Process Manager, which manages FastCGI |
354 | scripts. You configure your script as a FastCGI script with the |
355 | following Apache configuration directives: |
145074c2 |
356 | |
357 | <Location /fcgi-bin> |
358 | AddHandler fastcgi-script fcgi |
359 | </Location> |
360 | |
361 | or: |
362 | |
363 | <Location /fcgi-bin> |
364 | SetHandler fastcgi-script |
365 | Action fastcgi-script /path/to/fcgi-bin/fcgi-script |
366 | </Location> |
367 | |
368 | C<mod_fastcgi> provides a number of options for controlling the FastCGI |
369 | scripts spawned; it also allows scripts to be run to handle the |
51ef2818 |
370 | authentication, authorization, and access check phases. |
145074c2 |
371 | |
61b1e958 |
372 | For more information see the FastCGI documentation, the C<FCGI> module |
373 | and L<http://www.fastcgi.com/>. |
eff5f524 |
374 | |
379ca371 |
375 | =head2 Serving static content |
376 | |
377 | Serving static content in Catalyst can be somewhat tricky; this recipe |
378 | shows one possible solution. Using this recipe will serve all static |
379 | content through Catalyst when developing with the built-in HTTP::Daemon |
380 | server, and will make it easy to use Apache to serve the content when |
381 | your app goes into production. |
382 | |
383 | Static content is best served from a single directory within your root |
384 | directory. Having many different directories such as C<root/css> and |
385 | C<root/images> requires more code to manage, because you must separately |
386 | identify each static directory--if you decide to add a C<root/js> |
387 | directory, you'll need to change your code to account for it. In |
388 | contrast, keeping all static directories as subdirectories of a main |
389 | C<root/static> directory makes things much easier to manager. Here's an |
390 | example of a typical root directory structure: |
391 | |
392 | root/ |
393 | root/content.tt |
394 | root/controller/stuff.tt |
395 | root/header.tt |
396 | root/static/ |
397 | root/static/css/main.css |
398 | root/static/images/logo.jpg |
399 | root/static/js/code.js |
400 | |
401 | |
402 | All static content lives under C<root/static> with everything else being |
403 | Template Toolkit files. Now you can identify the static content by |
404 | matching C<static> from within Catalyst. |
405 | |
406 | =head3 Serving with HTTP::Daemon (myapp_server.pl) |
407 | |
408 | To serve these files under the standalone server, we first must load the |
409 | Static plugin. Install L<Catalyst::Plugin::Static> if it's not already |
410 | installed. |
411 | |
412 | In your main application class (MyApp.pm), load the plugin: |
413 | |
414 | use Catalyst qw/-Debug FormValidator Static OtherPlugin/; |
415 | |
416 | You will also need to make sure your end method does I<not> forward |
417 | static content to the view, perhaps like this: |
418 | |
419 | sub end : Private { |
420 | my ( $self, $c ) = @_; |
421 | |
422 | $c->forward( 'MyApp::V::TT' ) |
423 | unless ( $c->res->body || !$c->stash->{template} ); |
424 | } |
425 | |
426 | This code will only forward to the view if a template has been |
427 | previously defined by a controller and if there is not already data in |
428 | C<$c-E<gt>res-E<gt>body>. |
429 | |
430 | Next, create a controller to handle requests for the /static path. Use |
431 | the Helper to save time. This command will create a stub controller as |
432 | C<lib/MyApp/C/Static.pm>. |
433 | |
434 | $ script/myapp_create.pl controller Static |
435 | |
436 | Edit the file and add the following methods: |
437 | |
438 | # serve all files under /static as static files |
439 | sub default : Path('/static') { |
440 | my ( $self, $c ) = @_; |
441 | |
442 | # Optional, allow the browser to cache the content |
443 | $c->res->headers->header( 'Cache-Control' => 'max-age=86400' ); |
444 | |
445 | $c->serve_static; # from Catalyst::Plugin::Static |
446 | } |
447 | |
448 | # also handle requests for /favicon.ico |
449 | sub favicon : Path('/favicon.ico') { |
450 | my ( $self, $c ) = @_; |
451 | |
452 | $c->serve_static; |
453 | } |
454 | |
455 | You can also define a different icon for the browser to use instead of |
456 | favicon.ico by using this in your HTML header: |
457 | |
458 | <link rel="icon" href="/static/myapp.ico" type="image/x-icon" /> |
459 | |
460 | =head3 Common problems |
461 | |
462 | The Static plugin makes use of the C<shared-mime-info> package to |
463 | automatically determine MIME types. This package is notoriously |
464 | difficult to install, especially on win32 and OSX. For OSX the easiest |
465 | path might be to install Fink, then use C<apt-get install |
466 | shared-mime-info>. Restart the server, and everything should be fine. |
467 | |
468 | Make sure you are using the latest version (>= 0.16) for best |
469 | results. If you are having errors serving CSS files, or if they get |
470 | served as text/plain instead of text/css, you may have an outdated |
471 | shared-mime-info version. You may also wish to simply use the following |
472 | code in your Static controller: |
473 | |
474 | if ($c->req->path =~ /css$/i) { |
475 | $c->serve_static( "text/css" ); |
476 | } else { |
477 | $c->serve_static; |
478 | } |
479 | |
480 | =head3 Serving with Apache |
481 | |
482 | When using Apache, you can completely bypass Catalyst and the Static |
483 | controller by intercepting requests for the C<root/static> path at the |
484 | server level. All that is required is to define a DocumentRoot and add a |
485 | separate Location block for your static content. Here is a complete |
486 | config for this application under mod_perl 1.x; variations, some of |
487 | which could be simpler, are left as an exercise for the reader: |
488 | |
489 | <Perl> |
490 | use lib qw(/var/www/MyApp/lib); |
491 | </Perl> |
492 | PerlModule MyApp |
493 | |
494 | <VirtualHost *> |
495 | ServerName myapp.example.com |
496 | DocumentRoot /var/www/MyApp/root |
497 | <Location /> |
498 | SetHandler perl-script |
499 | PerlHandler MyApp |
500 | </Location> |
501 | <LocationMatch "/(static|favicon.ico)"> |
502 | SetHandler default-handler |
503 | </LocationMatch> |
504 | </VirtualHost> |
505 | |
506 | =head2 Forwarding with arguments |
145074c2 |
507 | |
eff5f524 |
508 | Sometimes you want to pass along arguments when forwarding to another |
509 | action. As of version 5.30, arguments can be passed in the call to |
510 | C<forward>; in earlier versions, you can manually set the arguments in |
511 | the Catalyst Request object: |
e6394847 |
512 | |
eff5f524 |
513 | # version 5.30 and later: |
514 | $c->forward('/wherever', [qw/arg1 arg2 arg3/]); |
2343e117 |
515 | |
eff5f524 |
516 | # pre-5.30 |
2343e117 |
517 | $c->req->args([qw/arg1 arg2 arg3/]); |
518 | $c->forward('/wherever'); |
519 | |
eff5f524 |
520 | (See L<Catalyst::Manual::Intro#Flow_Control> for more information on |
521 | passing arguments via C<forward>.) |
522 | |
fc7ec1d9 |
523 | =head1 AUTHOR |
524 | |
525 | Sebastian Riedel, C<sri@oook.de> |
379ca371 |
526 | Danijel Milicevic, C<me@danijel.de> |
527 | Viljo Marrandi, C<vilts@yahoo.com> |
528 | Marcus Ramberg, C<mramberg@cpan.org> |
529 | Andy Grundman, C<andy@hybridized.org> |
eff5f524 |
530 | Marcus Ramberg C<mramberg@cpan.org> |
fc7ec1d9 |
531 | |
532 | =head1 COPYRIGHT |
533 | |
61b1e958 |
534 | This program is free software, you can redistribute it and/or modify it |
535 | under the same terms as Perl itself. |