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 | |
13 | You can force Catalyst to display the debug screen at the end of the request by |
14 | placing a die() call in the _end action. |
15 | |
61b1e958 |
16 | sub end : Private { |
17 | my ( $self, $c ) = @_; |
18 | die "testing"; |
19 | } |
fc7ec1d9 |
20 | |
aff93052 |
21 | If you're tired of removing and adding this all the time, you |
22 | can easily add a condition. for example: |
23 | |
24 | die "Testing" if $c->param->{dump_info}; |
25 | |
fc7ec1d9 |
26 | =head2 Disable statistics |
27 | |
28 | Just add this line to your application class if you don't want those nifty |
29 | statistics in your debug messages. |
30 | |
31 | sub Catalyst::Log::info { } |
32 | |
33 | =head2 Scaffolding |
34 | |
35 | Scaffolding is very simple with Catalyst. |
36 | Just use Catalyst::Model::CDBI::CRUD as baseclass. |
37 | |
38 | # lib/MyApp/Model/CDBI.pm |
39 | package MyApp::Model::CDBI; |
40 | |
41 | use strict; |
42 | use base 'Catalyst::Model::CDBI::CRUD'; |
43 | |
44 | __PACKAGE__->config( |
45 | dsn => 'dbi:SQLite:/tmp/myapp.db', |
46 | relationships => 1 |
47 | ); |
48 | |
49 | 1; |
50 | |
51 | # lib/MyApp.pm |
52 | package MyApp; |
53 | |
54 | use Catalyst 'FormValidator'; |
55 | |
56 | __PACKAGE__->config( |
57 | name => 'My Application', |
58 | root => '/home/joeuser/myapp/root' |
59 | ); |
60 | |
61b1e958 |
61 | sub my_table : Global { |
62 | my ( $self, $c ) = @_; |
63 | $c->form( optional => [ MyApp::Model::CDBI::Table->columns ] ); |
64 | $c->forward('MyApp::Model::CDBI::Table'); |
65 | } |
fc7ec1d9 |
66 | |
67 | 1; |
68 | |
69 | Modify the $c->form() parameters to match your needs, and don't forget to copy |
70 | the templates. ;) |
71 | |
deb90705 |
72 | =head2 Uploads with Catalyst |
aba94964 |
73 | |
74 | To implement uploads in Catalyst you need to have a HTML form similiar to |
75 | this: |
76 | |
77 | <form action="/upload" method="post" enctype="multipart/form-data"> |
78 | <input type="hidden" name="form_submit" value="yes"> |
79 | <input type="file" name="my_file"> |
80 | <input type="submit" value="Send"> |
81 | </form> |
82 | |
83 | It's very important not to forget enctype="multipart/form-data" in form, |
84 | if it's not there, uploads just don't work. |
85 | |
86 | Catalyst Controller module 'upload' action: |
87 | |
61b1e958 |
88 | sub upload : Global { |
aba94964 |
89 | my ($self, $c) = @_; |
90 | if ($c->req->parameters->{form_submit} eq 'yes') { |
91 | my $filename = $c->req->parameters->{my_file}; |
92 | if ($filename) { |
93 | my $fh = $c->req->uploads->{$filename}->{fh}; |
94 | open(NEW_FILE, ">/tmp/$filename") or die |
95 | "Can't open file for writing: $!"; |
96 | while ($fh->read(my $buf, 32768)) { |
97 | print NEW_FILE $buf; |
98 | } |
99 | close(NEW_FILE); |
100 | } |
101 | } |
102 | $c->stash->{template} = 'upload_form.tt'; |
103 | $c->forward('MyApp::V::View'); |
61b1e958 |
104 | } |
aba94964 |
105 | |
106 | If you want to upload bigger files than 1MB, then just add to your Controller |
107 | module: |
108 | |
109 | $CGI::Simple::POST_MAX = 1048576000; |
110 | |
deb90705 |
111 | =head2 Authentication with Catalyst::Plugin::Authentication::CDBI |
112 | |
113 | There are (at least) two ways to implement authentication with this plugin: |
114 | 1) only checking username and password |
115 | 2) checking username, password and the roles the user has |
116 | |
117 | For both variants you'll need the following code in your MyApp package: |
118 | |
119 | use Catalyst qw/Session::FastMmap Static Authentication::CDBI/; |
120 | |
121 | MyApp->config( authentication => { user_class => 'MyApp::M::MyApp::Users', |
122 | user_field => 'email', |
123 | password_field => 'password' }); |
124 | |
125 | 'user_class' is a Class::DBI class for your users table. |
126 | 'user_field' tells which field is used for username lookup (might be |
127 | email, first name, surname etc). |
128 | 'password_field' is, well, password field in your table and by default |
129 | password is stored in plain text. Authentication::CDBI looks for 'user' |
130 | and 'password' fields in table, if they're not defined in the config. |
131 | |
132 | In PostgreSQL users table might be something like: |
133 | |
134 | CREATE TABLE users ( |
135 | user_id serial, |
136 | name varchar(100), |
137 | surname varchar(100), |
138 | password varchar(100), |
139 | email varchar(100), |
140 | primary key(user_id) |
141 | ); |
142 | |
143 | We'll discuss the first variant for now: |
144 | 1. user:password login / auth without roles |
145 | |
146 | To log in a user you might use a action like this: |
147 | |
61b1e958 |
148 | sub 'login' : Local { |
deb90705 |
149 | my ($self, $c) = @_; |
150 | if ($c->req->params->{username}) { |
151 | $c->session_login($c->req->params->{username}, |
61b1e958 |
152 | $c->req->params->{password} ); |
deb90705 |
153 | if ($c->req->{user}) { |
154 | $c->forward('?restricted_area'); |
155 | } |
156 | } |
61b1e958 |
157 | } |
deb90705 |
158 | |
159 | $c->req->params->{username} and $c->req->params->{password} are html |
61b1e958 |
160 | form parameters from a login form. If login succeeds, then |
161 | $c->req->{user} contains the username of the authenticated user. |
deb90705 |
162 | |
61b1e958 |
163 | If you want to remember the users login status inbetween further |
164 | requests, then just use the $c->session_login method, Catalyst will |
165 | create a session id, session cookie and automatically append session |
166 | id to all urls. So all you have to do, is just check $c->req->{user} |
167 | where needed. |
deb90705 |
168 | |
169 | To log out user, just call $c->session_logout. |
170 | |
171 | Now lets take a look at the second variant: |
172 | 2. user:password login / auth with roles |
173 | |
174 | To use roles you need to add to MyApp->config in the 'authentication' |
175 | section following parameters: |
176 | |
177 | role_class => 'MyApp::M::MyApp::Roles', |
178 | user_role_class => 'MyApp::M::MyApp::UserRoles', |
179 | user_role_user_field => 'user_id', |
180 | user_role_role_field => 'role_id', |
181 | |
182 | Corresponding tables in PostgreSQL could look like this: |
183 | |
184 | CREATE TABLE roles ( |
185 | role_id serial, |
186 | name varchar(100), |
187 | primary key(role_id) |
188 | ); |
189 | |
190 | CREATE TABLE user_roles ( |
191 | user_role_id serial, |
192 | user_id int, |
193 | role_id int, |
194 | primary key(user_role_id), |
195 | foreign key(user_id) references users(user_id), |
196 | foreign key(role_id) references roles(role_id) |
197 | ); |
198 | |
61b1e958 |
199 | The 'roles' table is a list of role names and the 'user_role' table is |
200 | used for the user -> role lookup. |
deb90705 |
201 | |
61b1e958 |
202 | Now if a logged in user wants to see a location which is allowed only |
203 | for people with 'admin' role then in you controller you can check it |
204 | with: |
deb90705 |
205 | |
61b1e958 |
206 | sub add : Local { |
deb90705 |
207 | my ($self, $c) = @_; |
208 | if ($c->roles(qw/admin/)) { |
209 | $c->req->output("Your account has the role 'admin.'"); |
210 | } else { |
211 | $c->req->output("You're not allowed to be here"); |
212 | } |
61b1e958 |
213 | } |
deb90705 |
214 | |
215 | One thing you might need is to forward non-authenticated users to login |
216 | form, if they try to access restricted areas. If you want to do this |
217 | controller-wide (if you have one controller for admin section) then it's |
218 | best to add user check to '!begin' action: |
219 | |
61b1e958 |
220 | sub begin : Private { |
deb90705 |
221 | my ($self, $c) = @_; |
222 | unless ($c->req->{user}) { |
223 | $c->req->action(undef); ## notice this!! |
224 | $c->forward('?login'); |
225 | } |
61b1e958 |
226 | } |
deb90705 |
227 | |
228 | Pay attention to $c->req->action(undef). This is needed, because of the |
229 | way $c->forward works - forward to login gets called, but after that |
61b1e958 |
230 | Catalyst executes anyway the action defined in the uri (eg. if you |
231 | tried to watch /add, then first 'begin' forwards to 'login', but after |
232 | that anyway 'add' is executed). So $c->req->action(undef) undefines any |
deb90705 |
233 | actions that were to be called and forwards user where we want him/her |
234 | to be. |
235 | |
236 | And this is all you need to do, isn't Catalyst wonderful? |
237 | |
145074c2 |
238 | |
239 | =head2 How to use Catalyst without mod_perl |
240 | |
241 | Catalyst applications give optimum performance when run under mod_perl. |
61b1e958 |
242 | However sometimes mod_perl is not an option, and running under CGI is |
243 | just too slow. There are two alternatives to mod_perl that give |
244 | reasonable performance: FastCGI and PersistentPerl. |
145074c2 |
245 | |
246 | B<Using FastCGI> |
247 | |
61b1e958 |
248 | To quote from L<http://www.fastcgi.com/>: "FastCGI is a language |
249 | independent, scalable, extension to CGI that provides high performance |
250 | without the limitations of specific server APIs." Web server support |
251 | is provided for Apache in the form of C<mod_fastcgi> and there is Perl |
252 | support in the C<FCGI> module. To convert a CGI Catalyst application |
253 | to FastCGI one needs to initialize an C<FCGI::Request> object and loop |
254 | while the C<Accept> method returns zero. The following code shows how |
255 | it is done - and it also works as a normal, single-shot CGI script. |
145074c2 |
256 | |
257 | #!/usr/bin/perl |
258 | use strict; |
259 | use FCGI; |
260 | use MyApp; |
261 | |
262 | my $request = FCGI::Request(); |
263 | while ($request->Accept() >= 0) { |
1c61c726 |
264 | MyApp->run; |
145074c2 |
265 | } |
266 | |
61b1e958 |
267 | Any initialization code should be included outside the request-accept |
268 | loop. |
145074c2 |
269 | |
270 | There is one little complication, which is that C<MyApp->run> outputs a |
61b1e958 |
271 | complete HTTP response including the status line (e.g.: |
272 | "C<HTTP/1.1 200>"). |
273 | FastCGI just wants a set of headers, so the sample code captures the |
274 | output and drops the first line if it is an HTTP status line (note: |
275 | this may change). |
276 | |
277 | The Apache C<mod_fastcgi> module is provided by a number of Linux |
278 | distros and is straightforward to compile for most Unix-like systems. |
279 | The module provides a FastCGI Process Manager, which manages FastCGI |
280 | scripts. You configure your script as a FastCGI script with the |
281 | following Apache configuration directives: |
145074c2 |
282 | |
283 | <Location /fcgi-bin> |
284 | AddHandler fastcgi-script fcgi |
285 | </Location> |
286 | |
287 | or: |
288 | |
289 | <Location /fcgi-bin> |
290 | SetHandler fastcgi-script |
291 | Action fastcgi-script /path/to/fcgi-bin/fcgi-script |
292 | </Location> |
293 | |
294 | C<mod_fastcgi> provides a number of options for controlling the FastCGI |
295 | scripts spawned; it also allows scripts to be run to handle the |
296 | authentication, authorization and access check phases. |
297 | |
61b1e958 |
298 | For more information see the FastCGI documentation, the C<FCGI> module |
299 | and L<http://www.fastcgi.com/>. |
145074c2 |
300 | |
301 | |
302 | B<PersistentPerl> |
303 | |
61b1e958 |
304 | PersistentPerl (previously known as C<CGI::SpeedyCGI>) is a persistent |
305 | Perl interpreter. After the script is initially run, instead of |
306 | exiting, the perl interpreter is kept running. During subsequent runs, |
307 | this interpreter is used to handle new executions instead of starting |
308 | a new perl interpreter each time. A very fast frontend program contacts |
309 | the persistent Perl process, which is usually already running, to do |
310 | the work and return the results. |
311 | PersistentPerl can be used to speed up perl CGI scripts. It also |
312 | provides an Apache module so that scripts can be run without the |
313 | overhead of doing a fork/exec for each request. |
145074c2 |
314 | |
61b1e958 |
315 | The code for PersistentPerl is simpler than for FastCGI; rather than |
316 | waiting in an accept loop the script runs to completion, however |
317 | variables are not reinitialized on subsequent runs but maintain their |
318 | values from the previous run. |
145074c2 |
319 | |
320 | |
321 | #!/usr/bin/perperl |
322 | use strict; |
323 | use vars qw($output $initialized); |
324 | use PersistentPerl; |
325 | use MyApp; |
326 | |
327 | if (!$initialized++) { |
328 | # initialization code - set up database, etc |
329 | if ($PersistentPerl::i_am_per_perl) { |
330 | # PP-specific initialization code |
331 | } |
332 | } |
1c61c726 |
333 | |
334 | MyApp->run; |
145074c2 |
335 | |
336 | For more information see the C<PersistentPerl> documentation. |
337 | |
338 | |
fc7ec1d9 |
339 | =head1 AUTHOR |
340 | |
341 | Sebastian Riedel, C<sri@oook.de> |
deb90705 |
342 | Danijel Milicevic C<me@danijel.de> |
343 | Viljo Marrandi C<vilts@yahoo.com> |
61b1e958 |
344 | Marcus Ramberg C<mramberg@cpan.org> |
fc7ec1d9 |
345 | |
346 | =head1 COPYRIGHT |
347 | |
61b1e958 |
348 | This program is free software, you can redistribute it and/or modify it |
349 | under the same terms as Perl itself. |