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. |
fc7ec1d9 |
43 | |
4b8cb778 |
44 | The recommended way is to use Catalyst::Helper::Controller::Scaffold. |
fc7ec1d9 |
45 | |
4b8cb778 |
46 | Just install this module, and to scaffold a Class::DBI Model class, do the following: |
fc7ec1d9 |
47 | |
4b8cb778 |
48 | ./script/myapp_create controller <name> Scaffold <CDBI::Class>Scaffolding |
fc7ec1d9 |
49 | |
fc7ec1d9 |
50 | |
fc7ec1d9 |
51 | |
fc7ec1d9 |
52 | |
822fe954 |
53 | =head2 File uploads |
54 | |
55 | =head3 Single file upload with Catalyst |
aba94964 |
56 | |
26e73131 |
57 | To implement uploads in Catalyst, you need to have a HTML form similar to |
aba94964 |
58 | this: |
59 | |
60 | <form action="/upload" method="post" enctype="multipart/form-data"> |
61 | <input type="hidden" name="form_submit" value="yes"> |
62 | <input type="file" name="my_file"> |
63 | <input type="submit" value="Send"> |
64 | </form> |
65 | |
379ca371 |
66 | It's very important not to forget C<enctype="multipart/form-data"> in |
67 | the form. |
aba94964 |
68 | |
69 | Catalyst Controller module 'upload' action: |
70 | |
5c0ff128 |
71 | sub upload : Global { |
72 | my ($self, $c) = @_; |
4d89569d |
73 | |
74 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
75 | |
76 | if ( my $upload = $c->request->upload('my_file') ) { |
b248fa4a |
77 | |
5c0ff128 |
78 | my $filename = $upload->filename; |
47ae6960 |
79 | my $target = "/tmp/upload/$filename"; |
b248fa4a |
80 | |
3ffaf022 |
81 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
82 | die( "Failed to copy '$filename' to '$target': $!" ); |
5c0ff128 |
83 | } |
5c0ff128 |
84 | } |
85 | } |
b248fa4a |
86 | |
5c0ff128 |
87 | $c->stash->{template} = 'file_upload.html'; |
88 | } |
89 | |
822fe954 |
90 | =head3 Multiple file upload with Catalyst |
5c0ff128 |
91 | |
379ca371 |
92 | Code for uploading multiple files from one form needs a few changes: |
5c0ff128 |
93 | |
379ca371 |
94 | The form should have this basic structure: |
5c0ff128 |
95 | |
96 | <form action="/upload" method="post" enctype="multipart/form-data"> |
97 | <input type="hidden" name="form_submit" value="yes"> |
98 | <input type="file" name="file1" size="50"><br> |
99 | <input type="file" name="file2" size="50"><br> |
100 | <input type="file" name="file3" size="50"><br> |
101 | <input type="submit" value="Send"> |
102 | </form> |
103 | |
379ca371 |
104 | And in the controller: |
5c0ff128 |
105 | |
106 | sub upload : Local { |
107 | my ($self, $c) = @_; |
4d89569d |
108 | |
109 | if ( $c->request->parameters->{form_submit} eq 'yes' ) { |
110 | |
111 | for my $field ( $c->req->upload ) { |
112 | |
02a53b81 |
113 | my $upload = $c->req->upload($field); |
4d89569d |
114 | my $filename = $upload->filename; |
47ae6960 |
115 | my $target = "/tmp/upload/$filename"; |
b248fa4a |
116 | |
3ffaf022 |
117 | unless ( $upload->link_to($target) || $upload->copy_to($target) ) { |
47ae6960 |
118 | die( "Failed to copy '$filename' to '$target': $!" ); |
aba94964 |
119 | } |
120 | } |
61b1e958 |
121 | } |
4d89569d |
122 | |
5c0ff128 |
123 | $c->stash->{template} = 'file_upload.html'; |
124 | } |
125 | |
379ca371 |
126 | C<for my $field ($c-E<gt>req->upload)> loops automatically over all file |
127 | input fields and gets input names. After that is basic file saving code, |
128 | just like in single file upload. |
aba94964 |
129 | |
379ca371 |
130 | Notice: C<die>ing might not be what you want to do, when an error |
131 | occurs, but it works as an example. A better idea would be to store |
132 | error C<$!> in $c->stash->{error} and show a custom error template |
133 | displaying this message. |
aba94964 |
134 | |
5c0ff128 |
135 | For more information about uploads and usable methods look at |
379ca371 |
136 | L<Catalyst::Request::Upload> and L<Catalyst::Request>. |
aba94964 |
137 | |
e112461a |
138 | =head2 Authentication with Catalyst::Plugin::Authentication |
139 | |
140 | In this example, we'll use the |
141 | L<Catalyst::Plugin::Authentication::Store::DBIC> store and the |
142 | L<Catalyst::Plugin::Authentication::Credential::Password> credentials. |
143 | |
144 | In the lib/MyApp.pm package, we'll need to change the C<use Catalyst;> |
145 | line to include the following modules: |
146 | |
147 | use Catalyst qw/ |
148 | ConfigLoader |
149 | Authentication |
150 | Authentication::Store::DBIC |
151 | Authentication::Credential::Password |
152 | Session |
153 | Session::Store::FastMmap |
154 | Session::State::Cookie |
155 | HTML::Widget |
156 | Static::Simple |
157 | /; |
158 | |
159 | The Session, Session::Store::* and Session::State::* modules listed above |
160 | ensure that we stay logged-in across multiple page-views. |
161 | |
162 | In our MyApp.yml configuration file, we'll need to add: |
163 | |
164 | authentication: |
165 | dbic: |
166 | user_class: MyApp::Model::DBIC::User |
167 | user_field: username |
168 | password_field: password |
169 | password_type: hashed |
170 | password_hash_type: SHA-1 |
171 | |
172 | 'user_class' is a DBIx::Class package for your users table. |
173 | 'user_field' tells which field (column) is used for username lookup. |
174 | 'password_field' is the password field in your table. |
175 | The above settings for 'password_type' and 'password_hash_type' ensure that |
176 | the password won't be stored in the database in clear text. |
177 | |
178 | In SQLite, the users table might be something like: |
179 | |
180 | CREATE TABLE user ( |
181 | id INTEGER PRIMARY KEY, |
182 | username VARCHAR(100), |
183 | password VARCHAR(100) |
184 | ); |
185 | |
186 | Now we need to create a DBIC::SchemaLoader component for this database |
187 | (changing "myapp.db" to wherever your SQLite database is). |
188 | |
189 | script/myapp_create.pl model DBIC DBIC::SchemaLoader 'dbi:SQLite:myapp.db' |
190 | |
191 | Now we can start creating our page controllers and templates. |
192 | For our homepage, we create the file "root/index.tt" containing: |
193 | |
194 | <html> |
195 | <body> |
196 | [% IF c.user %] |
197 | <p>hello [% c.user.username %]</p> |
198 | <p><a href="[% c.uri_for( '/logout' ) %]">logout</a></p> |
199 | [% ELSE %] |
200 | <p><a href="[% c.uri_for( '/login' ) %]">login</a></p> |
201 | [% END %] |
202 | </body> |
203 | </html> |
204 | |
205 | If the user is logged in, they will be shown their name, and a logout link. |
206 | Otherwise, they will be shown a login link. |
207 | |
208 | To display the homepage, we can uncomment the C<default> and C<end> |
209 | subroutines in lib/MyApp/Controller/Root.pm and populate them as so: |
210 | |
211 | sub default : Private { |
212 | my ( $self, $c ) = @_; |
213 | |
214 | $c->stash->{template} = 'index.tt'; |
215 | } |
deb90705 |
216 | |
e112461a |
217 | sub end : Private { |
218 | my ( $self, $c ) = @_; |
219 | |
220 | $c->forward( $c->view('TT') ) |
221 | unless $c->response->body || $c->response->redirect; |
222 | } |
deb90705 |
223 | |
e112461a |
224 | The login template is very simple, as L<HTML::Widget> will handle the |
225 | HTML form creation for use. This is saved as "root/login.tt". |
deb90705 |
226 | |
e112461a |
227 | <html> |
228 | <head> |
229 | <link href="[% c.uri_for('/static/simple.css') %]" rel="stylesheet" type="text/css"> |
230 | </head> |
231 | <body> |
232 | [% result %] |
233 | </body> |
234 | </html> |
deb90705 |
235 | |
e112461a |
236 | For the HTML form to look correct, we also copy the C<simple.css> file |
237 | from the L<HTML::Widget> distribution into our "root/static" folder. |
238 | This file is automatically server by the L<Catalyst::Plugin::Static::Simple> |
239 | module which we loaded in our lib/MyApp.pm package. |
deb90705 |
240 | |
e112461a |
241 | To handle login requests, we first create a controller, like so: |
deb90705 |
242 | |
e112461a |
243 | script/myapp_create.pl controller Login |
deb90705 |
244 | |
e112461a |
245 | In the lib/MyApp/Controller/Login.pm package, we can then uncomment the |
246 | C<default> subroutine, and populate it, as below. |
deb90705 |
247 | |
e112461a |
248 | First the widget is created, it needs the 'action' set, and 'username' and |
249 | 'password' fields and a submit button added. |
deb90705 |
250 | |
e112461a |
251 | Then, if we've received a username and password in the request, we attempt |
252 | to login. If successful, we redirect to the homepage; if not the login form |
253 | will be displayed again. |
deb90705 |
254 | |
e112461a |
255 | sub default : Private { |
256 | my ( $self, $c ) = @_; |
257 | |
258 | $c->widget->method('POST')->action( $c->uri_for('/login') ); |
259 | $c->widget->element( 'Textfield', 'username' )->label( 'Username' ); |
260 | $c->widget->element( 'Password', 'password' )->label( 'Password' ); |
261 | $c->widget->element( 'Submit' )->value( 'Login' ); |
262 | |
263 | my $result = $c->widget->process( $c->req ); |
264 | |
265 | if ( my $user = $result->param('username') |
266 | and my $pass = $result->param('password') ) |
267 | { |
268 | if ( $c->login( $user, $pass ) ) { |
269 | $c->response->redirect( $c->uri_for( "/" ) ); |
270 | return; |
deb90705 |
271 | } |
272 | } |
e112461a |
273 | |
274 | $c->stash->{template} = 'login.tt'; |
275 | $c->stash->{result} = $result; |
61b1e958 |
276 | } |
deb90705 |
277 | |
e112461a |
278 | To handle logout's, we create a new controller: |
deb90705 |
279 | |
e112461a |
280 | script/myapp_create.pl controller Logout |
deb90705 |
281 | |
e112461a |
282 | Then in the lib/MyApp/Controller/Logout.pm package, we change the |
283 | C<default> subroutine, to logout and then redirect back to the |
284 | homepage. |
51ef2818 |
285 | |
e112461a |
286 | sub default : Private { |
287 | my ( $self, $c ) = @_; |
288 | |
289 | $c->logout; |
290 | |
291 | $c->response->redirect( $c->uri_for( "/" ) ); |
61b1e958 |
292 | } |
deb90705 |
293 | |
e112461a |
294 | Remember that to test this, we would first need to add a user to the |
295 | database, ensuring that the password field is saved as the SHA1 hash |
296 | of our desired password. |
deb90705 |
297 | |
deb90705 |
298 | |
afb208ae |
299 | =head2 Pass-through login (and other actions) |
300 | |
eff5f524 |
301 | An easy way of having assorted actions that occur during the processing |
302 | of a request that are orthogonal to its actual purpose - logins, silent |
afb208ae |
303 | commands etc. Provide actions for these, but when they're required for |
eff5f524 |
304 | something else fill e.g. a form variable __login and have a sub begin |
305 | like so: |
afb208ae |
306 | |
eff5f524 |
307 | sub begin : Private { |
308 | my ($self, $c) = @_; |
309 | foreach my $action (qw/login docommand foo bar whatever/) { |
310 | if ($c->req->params->{"__${action}"}) { |
311 | $c->forward($action); |
312 | } |
313 | } |
afb208ae |
314 | } |
145074c2 |
315 | |
316 | =head2 How to use Catalyst without mod_perl |
317 | |
318 | Catalyst applications give optimum performance when run under mod_perl. |
61b1e958 |
319 | However sometimes mod_perl is not an option, and running under CGI is |
51ef2818 |
320 | just too slow. There's also an alternative to mod_perl that gives |
dec2a2a9 |
321 | reasonable performance named FastCGI. |
145074c2 |
322 | |
822fe954 |
323 | =head3 Using FastCGI |
145074c2 |
324 | |
61b1e958 |
325 | To quote from L<http://www.fastcgi.com/>: "FastCGI is a language |
326 | independent, scalable, extension to CGI that provides high performance |
327 | without the limitations of specific server APIs." Web server support |
328 | is provided for Apache in the form of C<mod_fastcgi> and there is Perl |
329 | support in the C<FCGI> module. To convert a CGI Catalyst application |
330 | to FastCGI one needs to initialize an C<FCGI::Request> object and loop |
331 | while the C<Accept> method returns zero. The following code shows how |
332 | it is done - and it also works as a normal, single-shot CGI script. |
145074c2 |
333 | |
334 | #!/usr/bin/perl |
335 | use strict; |
336 | use FCGI; |
337 | use MyApp; |
338 | |
339 | my $request = FCGI::Request(); |
340 | while ($request->Accept() >= 0) { |
1c61c726 |
341 | MyApp->run; |
145074c2 |
342 | } |
343 | |
61b1e958 |
344 | Any initialization code should be included outside the request-accept |
345 | loop. |
145074c2 |
346 | |
51ef2818 |
347 | There is one little complication, which is that C<MyApp-E<gt>run> outputs a |
61b1e958 |
348 | complete HTTP response including the status line (e.g.: |
349 | "C<HTTP/1.1 200>"). |
350 | FastCGI just wants a set of headers, so the sample code captures the |
351 | output and drops the first line if it is an HTTP status line (note: |
352 | this may change). |
353 | |
354 | The Apache C<mod_fastcgi> module is provided by a number of Linux |
de6fb80a |
355 | distro's and is straightforward to compile for most Unix-like systems. |
61b1e958 |
356 | The module provides a FastCGI Process Manager, which manages FastCGI |
357 | scripts. You configure your script as a FastCGI script with the |
358 | following Apache configuration directives: |
145074c2 |
359 | |
360 | <Location /fcgi-bin> |
361 | AddHandler fastcgi-script fcgi |
362 | </Location> |
363 | |
364 | or: |
365 | |
366 | <Location /fcgi-bin> |
367 | SetHandler fastcgi-script |
368 | Action fastcgi-script /path/to/fcgi-bin/fcgi-script |
369 | </Location> |
370 | |
371 | C<mod_fastcgi> provides a number of options for controlling the FastCGI |
372 | scripts spawned; it also allows scripts to be run to handle the |
51ef2818 |
373 | authentication, authorization, and access check phases. |
145074c2 |
374 | |
61b1e958 |
375 | For more information see the FastCGI documentation, the C<FCGI> module |
376 | and L<http://www.fastcgi.com/>. |
b248fa4a |
377 | |
379ca371 |
378 | =head2 Serving static content |
379 | |
380 | Serving static content in Catalyst can be somewhat tricky; this recipe |
381 | shows one possible solution. Using this recipe will serve all static |
382 | content through Catalyst when developing with the built-in HTTP::Daemon |
383 | server, and will make it easy to use Apache to serve the content when |
384 | your app goes into production. |
385 | |
386 | Static content is best served from a single directory within your root |
387 | directory. Having many different directories such as C<root/css> and |
388 | C<root/images> requires more code to manage, because you must separately |
389 | identify each static directory--if you decide to add a C<root/js> |
390 | directory, you'll need to change your code to account for it. In |
391 | contrast, keeping all static directories as subdirectories of a main |
392 | C<root/static> directory makes things much easier to manager. Here's an |
393 | example of a typical root directory structure: |
394 | |
395 | root/ |
396 | root/content.tt |
397 | root/controller/stuff.tt |
398 | root/header.tt |
399 | root/static/ |
400 | root/static/css/main.css |
401 | root/static/images/logo.jpg |
402 | root/static/js/code.js |
403 | |
404 | |
405 | All static content lives under C<root/static> with everything else being |
406 | Template Toolkit files. Now you can identify the static content by |
407 | matching C<static> from within Catalyst. |
408 | |
409 | =head3 Serving with HTTP::Daemon (myapp_server.pl) |
410 | |
411 | To serve these files under the standalone server, we first must load the |
412 | Static plugin. Install L<Catalyst::Plugin::Static> if it's not already |
413 | installed. |
414 | |
415 | In your main application class (MyApp.pm), load the plugin: |
416 | |
417 | use Catalyst qw/-Debug FormValidator Static OtherPlugin/; |
418 | |
419 | You will also need to make sure your end method does I<not> forward |
420 | static content to the view, perhaps like this: |
421 | |
422 | sub end : Private { |
423 | my ( $self, $c ) = @_; |
424 | |
425 | $c->forward( 'MyApp::V::TT' ) |
426 | unless ( $c->res->body || !$c->stash->{template} ); |
427 | } |
428 | |
429 | This code will only forward to the view if a template has been |
430 | previously defined by a controller and if there is not already data in |
431 | C<$c-E<gt>res-E<gt>body>. |
432 | |
433 | Next, create a controller to handle requests for the /static path. Use |
434 | the Helper to save time. This command will create a stub controller as |
435 | C<lib/MyApp/C/Static.pm>. |
436 | |
437 | $ script/myapp_create.pl controller Static |
438 | |
439 | Edit the file and add the following methods: |
440 | |
441 | # serve all files under /static as static files |
442 | sub default : Path('/static') { |
443 | my ( $self, $c ) = @_; |
b248fa4a |
444 | |
379ca371 |
445 | # Optional, allow the browser to cache the content |
446 | $c->res->headers->header( 'Cache-Control' => 'max-age=86400' ); |
447 | |
448 | $c->serve_static; # from Catalyst::Plugin::Static |
449 | } |
450 | |
451 | # also handle requests for /favicon.ico |
452 | sub favicon : Path('/favicon.ico') { |
453 | my ( $self, $c ) = @_; |
b248fa4a |
454 | |
379ca371 |
455 | $c->serve_static; |
456 | } |
457 | |
458 | You can also define a different icon for the browser to use instead of |
459 | favicon.ico by using this in your HTML header: |
460 | |
461 | <link rel="icon" href="/static/myapp.ico" type="image/x-icon" /> |
462 | |
463 | =head3 Common problems |
464 | |
465 | The Static plugin makes use of the C<shared-mime-info> package to |
466 | automatically determine MIME types. This package is notoriously |
26e73131 |
467 | difficult to install, especially on win32 and OS X. For OS X the easiest |
379ca371 |
468 | path might be to install Fink, then use C<apt-get install |
469 | shared-mime-info>. Restart the server, and everything should be fine. |
470 | |
471 | Make sure you are using the latest version (>= 0.16) for best |
472 | results. If you are having errors serving CSS files, or if they get |
473 | served as text/plain instead of text/css, you may have an outdated |
474 | shared-mime-info version. You may also wish to simply use the following |
475 | code in your Static controller: |
476 | |
477 | if ($c->req->path =~ /css$/i) { |
478 | $c->serve_static( "text/css" ); |
479 | } else { |
480 | $c->serve_static; |
481 | } |
482 | |
483 | =head3 Serving with Apache |
484 | |
485 | When using Apache, you can completely bypass Catalyst and the Static |
486 | controller by intercepting requests for the C<root/static> path at the |
487 | server level. All that is required is to define a DocumentRoot and add a |
488 | separate Location block for your static content. Here is a complete |
6d23e1f4 |
489 | config for this application under mod_perl 1.x: |
379ca371 |
490 | |
491 | <Perl> |
492 | use lib qw(/var/www/MyApp/lib); |
493 | </Perl> |
494 | PerlModule MyApp |
b248fa4a |
495 | |
379ca371 |
496 | <VirtualHost *> |
497 | ServerName myapp.example.com |
498 | DocumentRoot /var/www/MyApp/root |
499 | <Location /> |
500 | SetHandler perl-script |
501 | PerlHandler MyApp |
502 | </Location> |
503 | <LocationMatch "/(static|favicon.ico)"> |
504 | SetHandler default-handler |
505 | </LocationMatch> |
506 | </VirtualHost> |
507 | |
6d23e1f4 |
508 | And here's a simpler example that'll get you started: |
509 | |
510 | Alias /static/ "/my/static/files/" |
511 | <Location "/static"> |
512 | SetHandler none |
513 | </Location> |
514 | |
379ca371 |
515 | =head2 Forwarding with arguments |
145074c2 |
516 | |
eff5f524 |
517 | Sometimes you want to pass along arguments when forwarding to another |
518 | action. As of version 5.30, arguments can be passed in the call to |
519 | C<forward>; in earlier versions, you can manually set the arguments in |
520 | the Catalyst Request object: |
e6394847 |
521 | |
eff5f524 |
522 | # version 5.30 and later: |
523 | $c->forward('/wherever', [qw/arg1 arg2 arg3/]); |
2343e117 |
524 | |
eff5f524 |
525 | # pre-5.30 |
2343e117 |
526 | $c->req->args([qw/arg1 arg2 arg3/]); |
527 | $c->forward('/wherever'); |
528 | |
b248fa4a |
529 | (See the L<Catalyst::Manual::Intro> Flow_Control section for more |
530 | information on passing arguments via C<forward>.) |
eff5f524 |
531 | |
822fe954 |
532 | =head2 Configure your application |
533 | |
534 | You configure your application with the C<config> method in your |
535 | application class. This can be hard-coded, or brought in from a |
536 | separate configuration file. |
537 | |
538 | =head3 Using YAML |
539 | |
540 | YAML is a method for creating flexible and readable configuration |
541 | files. It's a great way to keep your Catalyst application configuration |
542 | in one easy-to-understand location. |
543 | |
544 | In your application class (e.g. C<lib/MyApp.pm>): |
545 | |
546 | use YAML; |
547 | # application setup |
548 | __PACKAGE__->config( YAML::LoadFile(__PACKAGE__->config->{'home'} . '/myapp.yml') ); |
549 | __PACKAGE__->setup; |
550 | |
551 | Now create C<myapp.yml> in your application home: |
552 | |
553 | --- #YAML:1.0 |
554 | # DO NOT USE TABS FOR INDENTATION OR label/value SEPARATION!!! |
555 | name: MyApp |
556 | |
822fe954 |
557 | # session; perldoc Catalyst::Plugin::Session::FastMmap |
558 | session: |
559 | expires: '3600' |
560 | rewrite: '0' |
561 | storage: '/tmp/myapp.session' |
562 | |
563 | # emails; perldoc Catalyst::Plugin::Email |
564 | # this passes options as an array :( |
565 | email: |
566 | - SMTP |
567 | - localhost |
568 | |
569 | This is equivalent to: |
570 | |
571 | # configure base package |
572 | __PACKAGE__->config( name => MyApp ); |
573 | # configure authentication |
574 | __PACKAGE__->config->{authentication} = { |
575 | user_class => 'MyApp::M::MyDB::Customer', |
576 | ... |
577 | }; |
578 | # configure sessions |
579 | __PACKAGE__->config->{session} = { |
580 | expires => 3600, |
581 | ... |
582 | }; |
583 | # configure email sending |
584 | __PACKAGE__->config->{email} = [qw/SMTP localhost/]; |
585 | |
586 | See also L<YAML>. |
587 | |
e112461a |
588 | =head2 Using existing DBIC (etc.) classes with Catalyst |
3912ee04 |
589 | |
590 | Many people have existing Model classes that they would like to use with |
591 | Catalyst (or, conversely, they want to write Catalyst models that can be |
592 | used outside of Catalyst, e.g. in a cron job). It's trivial to write a |
593 | simple component in Catalyst that slurps in an outside Model: |
594 | |
e112461a |
595 | package MyApp::Model::DB; |
596 | use base qw/Catalyst::Model::DBIC::Schema/; |
597 | __PACKAGE__->config( |
598 | schema_class => 'Some::DBIC::Schema', |
599 | connect_info => ['dbi:SQLite:foo.db', '', '', {AutoCommit=>1}]; |
600 | ); |
3912ee04 |
601 | 1; |
602 | |
e112461a |
603 | and that's it! Now C<Some::DBIC::Schema> is part of your |
604 | Cat app as C<MyApp::Model::DB>. |
3912ee04 |
605 | |
6d23e1f4 |
606 | =head2 Delivering a Custom Error Page |
f25a3283 |
607 | |
6d23e1f4 |
608 | By default, Catalyst will display its own error page whenever it |
609 | encounters an error in your application. When running under C<-Debug> |
610 | mode, the error page is a useful screen including the error message and |
f63c03e4 |
611 | L<Data::Dump> output of the relevant parts of the C<$c> context object. |
612 | When not in C<-Debug>, users see a simple "Please come back later" screen. |
f25a3283 |
613 | |
26e73131 |
614 | To use a custom error page, use a special C<end> method to short-circuit |
6d23e1f4 |
615 | the error processing. The following is an example; you might want to |
616 | adjust it further depending on the needs of your application (for |
617 | example, any calls to C<fillform> will probably need to go into this |
618 | C<end> method; see L<Catalyst::Plugin::FillInForm>). |
f25a3283 |
619 | |
6d23e1f4 |
620 | sub end : Private { |
621 | my ( $self, $c ) = @_; |
b248fa4a |
622 | |
6d23e1f4 |
623 | if ( scalar @{ $c->error } ) { |
624 | $c->stash->{errors} = $c->error; |
625 | $c->stash->{template} = 'errors.tt'; |
626 | $c->forward('MyApp::View::TT'); |
121baf93 |
627 | $c->error(0); |
6d23e1f4 |
628 | } |
b248fa4a |
629 | |
6d23e1f4 |
630 | return 1 if $c->response->status =~ /^3\d\d$/; |
631 | return 1 if $c->response->body; |
b248fa4a |
632 | |
6d23e1f4 |
633 | unless ( $c->response->content_type ) { |
634 | $c->response->content_type('text/html; charset=utf-8'); |
635 | } |
b248fa4a |
636 | |
6d23e1f4 |
637 | $c->forward('MyApp::View::TT'); |
638 | } |
f25a3283 |
639 | |
6d23e1f4 |
640 | You can manually set errors in your code to trigger this page by calling |
f25a3283 |
641 | |
6d23e1f4 |
642 | $c->error( 'You broke me!' ); |
f25a3283 |
643 | |
d761e049 |
644 | =head2 Require user logins |
645 | |
646 | It's often useful to restrict access to your application to a set of |
647 | registered users, forcing everyone else to the login page until they're |
648 | signed in. |
649 | |
650 | To implement this in your application make sure you have a customer |
651 | table with username and password fields and a corresponding Model class |
652 | in your Catalyst application, then make the following changes: |
653 | |
654 | =head3 lib/MyApp.pm |
655 | |
158c8782 |
656 | use Catalyst qw/ |
657 | Authentication |
658 | Authentication::Store::DBIC |
659 | Authentication::Credential::Password |
660 | /; |
661 | |
662 | __PACKAGE__->config->{authentication}->{dbic} = { |
663 | 'user_class' => 'My::Model::DBIC::User', |
d761e049 |
664 | 'user_field' => 'username', |
158c8782 |
665 | 'password_field' => 'password' |
666 | 'password_type' => 'hashed', |
667 | 'password_hash_type'=> 'SHA-1' |
d761e049 |
668 | }; |
669 | |
670 | sub auto : Private { |
671 | my ($self, $c) = @_; |
672 | my $login_path = 'user/login'; |
673 | |
674 | # allow people to actually reach the login page! |
158c8782 |
675 | if ($c->request->path eq $login_path) { |
d761e049 |
676 | return 1; |
677 | } |
678 | |
158c8782 |
679 | # if a user doesn't exist, force login |
680 | if ( !$c->user_exists ) { |
d761e049 |
681 | # force the login screen to be shown |
158c8782 |
682 | $c->response->redirect($c->request->base . $login_path); |
d761e049 |
683 | } |
684 | |
158c8782 |
685 | # otherwise, we have a user - continue with the processing chain |
d761e049 |
686 | return 1; |
687 | } |
688 | |
689 | =head3 lib/MyApp/C/User.pm |
690 | |
691 | sub login : Path('/user/login') { |
692 | my ($self, $c) = @_; |
693 | |
694 | # default template |
695 | $c->stash->{'template'} = "user/login.tt"; |
696 | # default form message |
697 | $c->stash->{'message'} = 'Please enter your username and password'; |
698 | |
158c8782 |
699 | if ( $c->request->param('username') ) { |
d761e049 |
700 | # try to log the user in |
158c8782 |
701 | # login() is provided by ::Authentication::Credential::Password |
702 | if( $c->login( |
703 | $c->request->param('username'), |
704 | $c->request->param('password'), |
705 | ); |
b248fa4a |
706 | |
158c8782 |
707 | # if login() returns 1, user is now logged in |
708 | $c->response->redirect('/some/page'); |
d761e049 |
709 | } |
710 | |
711 | # otherwise we failed to login, try again! |
158c8782 |
712 | $c->stash->{'message'} = |
713 | 'Unable to authenticate the login details supplied'; |
d761e049 |
714 | } |
715 | } |
716 | |
717 | sub logout : Path('/user/logout') { |
718 | my ($self, $c) = @_; |
158c8782 |
719 | # log the user out |
720 | $c->logout; |
d761e049 |
721 | |
722 | # do the 'default' action |
158c8782 |
723 | $c->response->redirect($c->request->base); |
724 | } |
d761e049 |
725 | |
726 | |
727 | =head3 root/base/user/login.tt |
728 | |
729 | [% INCLUDE header.tt %] |
730 | <form action="/user/login" method="POST" name="login_form"> |
731 | [% message %]<br /> |
732 | <label for="username">username:</label><br /> |
733 | <input type="text" id="username" name="username" /><br /> |
734 | |
735 | <label for="password">password:</label><br /> |
736 | <input type="password" id="password" name="password" /><br /> |
737 | |
738 | <input type="submit" value="log in" name="form_submit" /> |
739 | </form> |
740 | [% INCLUDE footer.tt %] |
741 | |
158c8782 |
742 | =head2 Role-based Authorization |
743 | |
744 | For more advanced access control, you may want to consider using role-based |
745 | authorization. This means you can assign different roles to each user, e.g. |
746 | "user", "admin", etc. |
747 | |
748 | The C<login> and C<logout> methods and view template are exactly the same as |
749 | in the previous example. |
750 | |
751 | The L<Catalyst::Plugin::Authorization::Roles> plugin is required when |
752 | implementing roles: |
753 | |
754 | use Catalyst qw/ |
755 | Authentication |
756 | Authentication::Credential::Password |
757 | Authentication::Store::Htpasswd |
758 | Authorization::Roles |
759 | /; |
760 | |
761 | Roles are implemented automatically when using |
de6fb80a |
762 | L<Catalyst::Authentication::Store::Htpasswd>: |
158c8782 |
763 | |
764 | # no additional role configuration required |
765 | __PACKAGE__->config->{authentication}{htpasswd} = "passwdfile"; |
766 | |
767 | Or can be set up manually when using L<Catalyst::Authentication::Store::DBIC>: |
768 | |
769 | # Authorization using a many-to-many role relationship |
770 | __PACKAGE__->config->{authorization}{dbic} = { |
771 | 'role_class' => 'My::Model::DBIC::Role', |
772 | 'role_field' => 'name', |
773 | 'user_role_user_field' => 'user', |
774 | |
775 | # DBIx::Class only (omit if using Class::DBI) |
776 | 'role_rel' => 'user_role', |
777 | |
778 | # Class::DBI only, (omit if using DBIx::Class) |
779 | 'user_role_class' => 'My::Model::CDBI::UserRole' |
780 | 'user_role_role_field' => 'role', |
781 | }; |
782 | |
783 | To restrict access to any action, you can use the C<check_user_roles> method: |
784 | |
785 | sub restricted : Local { |
786 | my ( $self, $c ) = @_; |
787 | |
788 | $c->detach("unauthorized") |
789 | unless $c->check_user_roles( "admin" ); |
790 | |
791 | # do something restricted here |
792 | } |
793 | |
794 | You can also use the C<assert_user_roles> method. This just gives an error if |
795 | the current user does not have one of the required roles: |
796 | |
797 | sub also_restricted : Global { |
798 | my ( $self, $c ) = @_; |
799 | $c->assert_user_roles( qw/ user admin / ); |
800 | } |
801 | |
fc7ec1d9 |
802 | =head1 AUTHOR |
803 | |
804 | Sebastian Riedel, C<sri@oook.de> |
379ca371 |
805 | Danijel Milicevic, C<me@danijel.de> |
806 | Viljo Marrandi, C<vilts@yahoo.com> |
822fe954 |
807 | Marcus Ramberg, C<mramberg@cpan.org> |
808 | Jesse Sheidlower, C<jester@panix.com> |
379ca371 |
809 | Andy Grundman, C<andy@hybridized.org> |
3912ee04 |
810 | Chisel Wright, C<pause@herlpacker.co.uk> |
158c8782 |
811 | Will Hawes, C<info@whawes.co.uk> |
de6fb80a |
812 | Gavin Henry, C<ghenry@cpan.org> (Spell checking) |
813 | |
fc7ec1d9 |
814 | |
815 | =head1 COPYRIGHT |
816 | |
61b1e958 |
817 | This program is free software, you can redistribute it and/or modify it |
818 | under the same terms as Perl itself. |