97933f148b240f89e12018632425efcd30205cad
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Controller / REST.pm
1 package Catalyst::Controller::REST;
2 use Moose;
3 use namespace::autoclean;
4
5 our $VERSION = '0.81';
6 $VERSION = eval $VERSION;
7
8 =head1 NAME
9
10 Catalyst::Controller::REST - A RESTful controller
11
12 =head1 SYNOPSIS
13
14     package Foo::Controller::Bar;
15     use Moose;
16     use namespace::autoclean;
17     
18     BEGIN { extends 'Catalyst::Controller::REST' }
19
20     sub thing : Local : ActionClass('REST') { }
21
22     # Answer GET requests to "thing"
23     sub thing_GET {
24        my ( $self, $c ) = @_;
25
26        # Return a 200 OK, with the data in entity
27        # serialized in the body
28        $self->status_ok(
29             $c,
30             entity => {
31                 some => 'data',
32                 foo  => 'is real bar-y',
33             },
34        );
35     }
36
37     # Answer PUT requests to "thing"
38     sub thing_PUT {
39         $radiohead = $req->data->{radiohead};
40         
41         $self->status_created(
42             $c,
43             location => $c->req->uri->as_string,
44             entity => {
45                 radiohead => $radiohead,
46             }
47         );
48     }     
49
50 =head1 DESCRIPTION
51
52 Catalyst::Controller::REST implements a mechanism for building
53 RESTful services in Catalyst.  It does this by extending the
54 normal Catalyst dispatch mechanism to allow for different
55 subroutines to be called based on the HTTP Method requested,
56 while also transparently handling all the serialization/deserialization for
57 you.
58
59 This is probably best served by an example.  In the above
60 controller, we have declared a Local Catalyst action on
61 "sub thing", and have used the ActionClass('REST').
62
63 Below, we have declared "thing_GET" and "thing_PUT".  Any
64 GET requests to thing will be dispatched to "thing_GET",
65 while any PUT requests will be dispatched to "thing_PUT".
66
67 Any unimplemented HTTP methods will be met with a "405 Method Not Allowed"
68 response, automatically containing the proper list of available methods.  You
69 can override this behavior through implementing a custom
70 C<thing_not_implemented> method.
71
72 If you do not provide an OPTIONS handler, we will respond to any OPTIONS
73 requests with a "200 OK", populating the Allowed header automatically.
74
75 Any data included in C<< $c->stash->{'rest'} >> will be serialized for you.
76 The serialization format will be selected based on the content-type
77 of the incoming request.  It is probably easier to use the L<STATUS HELPERS>,
78 which are described below.
79
80 "The HTTP POST, PUT, and OPTIONS methods will all automatically
81 L<deserialize|Catalyst::Action::Deserialize> the contents of
82 C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on 
83 the request's C<Content-type> header. A list of understood serialization
84 formats is L<below|/AVAILABLE SERIALIZERS>.
85
86 If we do not have (or cannot run) a serializer for a given content-type, a 415
87 "Unsupported Media Type" error is generated.
88
89 To make your Controller RESTful, simply have it
90
91   BEGIN { extends 'Catalyst::Controller::REST' }
92
93 =head1 CONFIGURATION
94
95 See L<Catalyst::Action::Serialize/CONFIGURATION>. Note that the C<serialize>
96 key has been deprecated.
97
98 =head1 SERIALIZATION
99
100 Catalyst::Controller::REST will automatically serialize your
101 responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates
102 which serializer to use by mapping a content-type to a Serialization module.
103 We select the content-type based on:
104
105 =over
106
107 =item B<The Content-Type Header>
108
109 If the incoming HTTP Request had a Content-Type header set, we will use it.
110
111 =item B<The content-type Query Parameter>
112
113 If this is a GET request, you can supply a content-type query parameter.
114
115 =item B<Evaluating the Accept Header>
116
117 Finally, if the client provided an Accept header, we will evaluate
118 it and use the best-ranked choice.
119
120 =back
121
122 =head1 AVAILABLE SERIALIZERS
123
124 A given serialization mechanism is only available if you have the underlying
125 modules installed.  For example, you can't use XML::Simple if it's not already
126 installed.
127
128 In addition, each serializer has its quirks in terms of what sorts of data
129 structures it will properly handle.  L<Catalyst::Controller::REST> makes
130 no attempt to save you from yourself in this regard. :)
131
132 =over 2
133
134 =item * C<text/x-yaml> => C<YAML::Syck>
135
136 Returns YAML generated by L<YAML::Syck>.
137
138 =item * C<text/html> => C<YAML::HTML>
139
140 This uses L<YAML::Syck> and L<URI::Find> to generate YAML with all URLs turned
141 to hyperlinks.  Only usable for Serialization.
142
143 =item * C<application/json> => C<JSON>
144
145 Uses L<JSON> to generate JSON output.  It is strongly advised to also have
146 L<JSON::XS> installed.  The C<text/x-json> content type is supported but is
147 deprecated and you will receive warnings in your log.
148
149 =item * C<text/javascript> => C<JSONP>
150
151 If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
152
153 =item * C<text/x-data-dumper> => C<Data::Serializer>
154
155 Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
156
157 =item * C<text/x-data-denter> => C<Data::Serializer>
158
159 Uses the L<Data::Serializer> module to generate L<Data::Denter> output.
160
161 =item * C<text/x-data-taxi> => C<Data::Serializer>
162
163 Uses the L<Data::Serializer> module to generate L<Data::Taxi> output.
164
165 =item * C<application/x-storable> => C<Data::Serializer>
166
167 Uses the L<Data::Serializer> module to generate L<Storable> output.
168
169 =item * C<application/x-freezethaw> => C<Data::Serializer>
170
171 Uses the L<Data::Serializer> module to generate L<FreezeThaw> output.
172
173 =item * C<text/x-config-general> => C<Data::Serializer>
174
175 Uses the L<Data::Serializer> module to generate L<Config::General> output.
176
177 =item * C<text/x-php-serialization> => C<Data::Serializer>
178
179 Uses the L<Data::Serializer> module to generate L<PHP::Serialization> output.
180
181 =item * C<text/xml> => C<XML::Simple>
182
183 Uses L<XML::Simple> to generate XML output.  This is probably not suitable
184 for any real heavy XML work. Due to L<XML::Simple>s requirement that the data
185 you serialize be a HASHREF, we transform outgoing data to be in the form of:
186
187   { data => $yourdata }
188
189 =item * L<View>
190
191 Uses a regular Catalyst view.  For example, if you wanted to have your
192 C<text/html> and C<text/xml> views rendered by TT, set:
193
194   __PACKAGE__->config(
195       map => {
196           'text/html' => [ 'View', 'TT' ],
197           'text/xml'  => [ 'View', 'XML' ],
198       }
199   );
200
201 Your views should have a C<process> method like this:
202
203   sub process {
204       my ( $self, $c, $stash_key ) = @_;
205
206       my $output;
207       eval {
208           $output = $self->serialize( $c->stash->{$stash_key} );
209       };
210       return $@ if $@;
211
212       $c->response->body( $output );
213       return 1;  # important
214   }
215   
216   sub serialize {
217       my ( $self, $data ) = @_;
218
219       my $serialized = ... process $data here ...
220
221       return $serialized;
222   }
223
224 =back
225
226 By default, L<Catalyst::Controller::REST> will return a 
227 C<415 Unsupported Media Type> response if an attempt to use an unsupported
228 content-type is made.  You can ensure that something is always returned by
229 setting the C<default> config option:
230
231   __PACKAGE__->config(default => 'text/x-yaml');
232
233 would make it always fall back to the serializer plugin defined for
234 C<text/x-yaml>.
235
236 =head1 CUSTOM SERIALIZERS
237
238 Implementing new Serialization formats is easy!  Contributions
239 are most welcome!  If you would like to implement a custom serializer, 
240 you should create two new modules in the L<Catalyst::Action::Serialize>
241 and L<Catalyst::Action::Deserialize> namespace.  Then assign your new
242 class to the content-type's you want, and you're done.
243
244 See L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize> 
245 for more information.
246
247 =head1 STATUS HELPERS
248
249 Since so much of REST is in using HTTP, we provide these Status Helpers.
250 Using them will ensure that you are responding with the proper codes,
251 headers, and entities.
252
253 These helpers try and conform to the HTTP 1.1 Specification.  You can
254 refer to it at: L<http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.
255 These routines are all implemented as regular subroutines, and as
256 such require you pass the current context ($c) as the first argument.
257
258 =over
259
260 =cut
261
262 BEGIN { extends 'Catalyst::Controller' }
263 use Params::Validate qw(SCALAR OBJECT);
264
265 __PACKAGE__->mk_accessors(qw(serialize));
266
267 __PACKAGE__->config(
268     'stash_key' => 'rest',
269     'map'       => {
270         'text/html'          => 'YAML::HTML',
271         'text/xml'           => 'XML::Simple',
272         'text/x-yaml'        => 'YAML',
273         'application/json'   => 'JSON',
274         'text/x-json'        => 'JSON',
275         'application/x-javascript'  => 'JSONP',
276         'application/javascript'    => 'JSONP',
277         'text/javascript'    => 'JSONP',
278         'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
279         'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
280         'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
281         'application/x-storable'   => [ 'Data::Serializer', 'Storable' ],
282         'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
283         'text/x-config-general'    => [ 'Data::Serializer', 'Config::General' ],
284         'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
285     },
286 );
287
288 sub begin : ActionClass('Deserialize') { }
289
290 sub end : ActionClass('Serialize') { }
291
292 =item status_ok
293
294 Returns a "200 OK" response.  Takes an "entity" to serialize.
295
296 Example:
297
298   $self->status_ok(
299     $c,
300     entity => {
301         radiohead => "Is a good band!",
302     }
303   );
304
305 =cut
306
307 sub status_ok {
308     my $self = shift;
309     my $c    = shift;
310     my %p    = Params::Validate::validate( @_, { entity => 1, }, );
311
312     $c->response->status(200);
313     $self->_set_entity( $c, $p{'entity'} );
314     return 1;
315 }
316
317 =item status_created
318
319 Returns a "201 CREATED" response.  Takes an "entity" to serialize,
320 and a "location" where the created object can be found.
321
322 Example:
323
324   $self->status_created(
325     $c,
326     location => $c->req->uri->as_string,
327     entity => {
328         radiohead => "Is a good band!",
329     }
330   );
331
332 In the above example, we use the requested URI as our location.
333 This is probably what you want for most PUT requests.
334
335 =cut
336
337 sub status_created {
338     my $self = shift;
339     my $c    = shift;
340     my %p    = Params::Validate::validate(
341         @_,
342         {
343             location => { type     => SCALAR | OBJECT },
344             entity   => { optional => 1 },
345         },
346     );
347
348     my $location;
349     if ( ref( $p{'location'} ) ) {
350         $location = $p{'location'}->as_string;
351     } else {
352         $location = $p{'location'};
353     }
354     $c->response->status(201);
355     $c->response->header( 'Location' => $location );
356     $self->_set_entity( $c, $p{'entity'} );
357     return 1;
358 }
359
360 =item status_accepted
361
362 Returns a "202 ACCEPTED" response.  Takes an "entity" to serialize.
363
364 Example:
365
366   $self->status_accepted(
367     $c,
368     entity => {
369         status => "queued",
370     }
371   );
372
373 =cut
374
375 sub status_accepted {
376     my $self = shift;
377     my $c    = shift;
378     my %p    = Params::Validate::validate( @_, { entity => 1, }, );
379
380     $c->response->status(202);
381     $self->_set_entity( $c, $p{'entity'} );
382     return 1;
383 }
384
385 =item status_no_content
386
387 Returns a "204 NO CONTENT" response.
388
389 =cut
390
391 sub status_no_content {
392     my $self = shift;
393     my $c    = shift;
394     $c->response->status(204);
395     $self->_set_entity( $c, undef );
396     return 1.;
397 }
398
399 =item status_bad_request
400
401 Returns a "400 BAD REQUEST" response.  Takes a "message" argument
402 as a scalar, which will become the value of "error" in the serialized
403 response.
404
405 Example:
406
407   $self->status_bad_request(
408     $c,
409     message => "Cannot do what you have asked!",
410   );
411
412 =cut
413
414 sub status_bad_request {
415     my $self = shift;
416     my $c    = shift;
417     my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
418
419     $c->response->status(400);
420     $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug;
421     $self->_set_entity( $c, { error => $p{'message'} } );
422     return 1;
423 }
424
425 =item status_not_found
426
427 Returns a "404 NOT FOUND" response.  Takes a "message" argument
428 as a scalar, which will become the value of "error" in the serialized
429 response.
430
431 Example:
432
433   $self->status_not_found(
434     $c,
435     message => "Cannot find what you were looking for!",
436   );
437
438 =cut
439
440 sub status_not_found {
441     my $self = shift;
442     my $c    = shift;
443     my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
444
445     $c->response->status(404);
446     $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug;
447     $self->_set_entity( $c, { error => $p{'message'} } );
448     return 1;
449 }
450
451 =item gone
452
453 Returns a "41O GONE" response.  Takes a "message" argument as a scalar,
454 which will become the value of "error" in the serialized response.
455
456 Example:
457
458   $self->status_gone(
459     $c,
460     message => "The document have been deleted by foo",
461   );
462
463 =cut
464
465 sub status_gone {
466     my $self = shift;
467     my $c    = shift;
468     my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
469
470     $c->response->status(410);
471     $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug;
472     $self->_set_entity( $c, { error => $p{'message'} } );
473     return 1;
474 }
475
476 sub _set_entity {
477     my $self   = shift;
478     my $c      = shift;
479     my $entity = shift;
480     if ( defined($entity) ) {
481         $c->stash->{ $self->{'stash_key'} } = $entity;
482     }
483     return 1;
484 }
485
486 =back
487
488 =head1 MANUAL RESPONSES
489
490 If you want to construct your responses yourself, all you need to
491 do is put the object you want serialized in $c->stash->{'rest'}.
492
493 =head1 IMPLEMENTATION DETAILS
494
495 This Controller ties together L<Catalyst::Action::REST>,
496 L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>.  It should be suitable for most applications.  You should be aware that it:
497
498 =over 4
499
500 =item Configures the Serialization Actions
501
502 This class provides a default configuration for Serialization.  It is currently:
503
504   __PACKAGE__->config(
505       'stash_key' => 'rest',
506       'map'       => {
507          'text/html'          => 'YAML::HTML',
508          'text/xml'           => 'XML::Simple',
509          'text/x-yaml'        => 'YAML',
510          'application/json'   => 'JSON',
511          'text/x-json'        => 'JSON',
512          'application/x-javascript' => 'JSONP',
513          'application/javascript'   => 'JSONP',
514          'text/javascript'    => 'JSONP',
515          'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
516          'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
517          'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
518          'application/x-storable'   => [ 'Data::Serializer', 'Storable' ],
519          'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
520          'text/x-config-general'    => [ 'Data::Serializer', 'Config::General' ],
521          'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
522       },
523   );
524
525 You can read the full set of options for this configuration block in
526 L<Catalyst::Action::Serialize>.
527
528 =item Sets a C<begin> and C<end> method for you
529
530 The C<begin> method uses L<Catalyst::Action::Deserialize>.  The C<end>
531 method uses L<Catalyst::Action::Serialize>.  If you want to override
532 either behavior, simply implement your own C<begin> and C<end> actions
533 and use MRO::Compat:
534
535   package Foo::Controller::Monkey;
536   use Moose;
537   use namespace::autoclean;
538   
539   BEGIN { extends 'Catalyst::Controller::REST' }
540
541   sub begin :Private {
542     my ($self, $c) = @_;
543     ... do things before Deserializing ...
544     $self->maybe::next::method($c);
545     ... do things after Deserializing ...
546   }
547
548   sub end :Private {
549     my ($self, $c) = @_;
550     ... do things before Serializing ...
551     $self->maybe::next::method($c);
552     ... do things after Serializing ...
553   }
554
555 =back
556
557 =head1 A MILD WARNING
558
559 I have code in production using L<Catalyst::Controller::REST>.  That said,
560 it is still under development, and it's possible that things may change
561 between releases.  I promise to not break things unnecessarily. :)
562
563 =head1 SEE ALSO
564
565 L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
566 L<Catalyst::Action::Deserialize>
567
568 For help with REST in general:
569
570 The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
571
572 Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
573
574 The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
575
576 =head1 AUTHORS
577
578 See L<Catalyst::Action::REST> for authors.
579
580 =head1 LICENSE
581
582 You may distribute this code under the same terms as Perl itself.
583
584 =cut
585
586 1;