File Coverage

lib/Catalyst/Controller/REST.pm
Criterion Covered Total %
statement 16 104 15.3
branch 0 18 0.0
condition n/a
subroutine 8 21 38.1
pod 13 14 92.8
total 37 157 23.5


line stmt bran cond sub pod time code
1             package Catalyst::Controller::REST;
2             $Catalyst::Controller::REST::VERSION = '1.21';
3 12     12   15464236 use Moose;
  12         34  
  12         54  
4 12     12   61729 use namespace::autoclean;
  12         23  
  12         94  
5              
6             =head1 NAME
7            
8             Catalyst::Controller::REST - A RESTful controller
9            
10             =head1 SYNOPSIS
11            
12             package Foo::Controller::Bar;
13             use Moose;
14             use namespace::autoclean;
15            
16             BEGIN { extends 'Catalyst::Controller::REST' }
17            
18             sub thing : Local : ActionClass('REST') { }
19            
20             # Answer GET requests to "thing"
21             sub thing_GET {
22             my ( $self, $c ) = @_;
23            
24             # Return a 200 OK, with the data in entity
25             # serialized in the body
26             $self->status_ok(
27             $c,
28             entity => {
29             some => 'data',
30             foo => 'is real bar-y',
31             },
32             );
33             }
34            
35             # Answer PUT requests to "thing"
36             sub thing_PUT {
37             my ( $self, $c ) = @_;
38            
39             $radiohead = $c->req->data->{radiohead};
40            
41             $self->status_created(
42             $c,
43             location => $c->req->uri,
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             You can also add a hash in your controller config to pass options to the json object.
150             There are two options. C<json_options> are used when decoding incoming JSON, and C<json_options_encode>
151             is used when encoding JSON for output.
152            
153             For instance, to relax permissions when deserializing input, add:
154            
155             __PACKAGE__->config(
156             json_options => { relaxed => 1 }
157             )
158            
159             To indent the JSON output so it becomes more human readable, add:
160            
161             __PACKAGE__->config(
162             json_options_encode => { indent => 1 }
163             )
164            
165            
166             =item * C<text/javascript> => C<JSONP>
167            
168             If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
169            
170             Note - this is disabled by default as it can be a security risk if you are unaware.
171            
172             The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript',
173             'application/javascript'.
174            
175             =item * C<text/x-data-dumper> => C<Data::Serializer>
176            
177             Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
178            
179             =item * C<text/x-data-denter> => C<Data::Serializer>
180            
181             Uses the L<Data::Serializer> module to generate L<Data::Denter> output.
182            
183             =item * C<text/x-data-taxi> => C<Data::Serializer>
184            
185             Uses the L<Data::Serializer> module to generate L<Data::Taxi> output.
186            
187             =item * C<text/x-config-general> => C<Data::Serializer>
188            
189             Uses the L<Data::Serializer> module to generate L<Config::General> output.
190            
191             =item * C<text/x-php-serialization> => C<Data::Serializer>
192            
193             Uses the L<Data::Serializer> module to generate L<PHP::Serialization> output.
194            
195             =item * C<text/xml> => C<XML::Simple>
196            
197             Uses L<XML::Simple> to generate XML output. This is probably not suitable
198             for any real heavy XML work. Due to L<XML::Simple>s requirement that the data
199             you serialize be a HASHREF, we transform outgoing data to be in the form of:
200            
201             { data => $yourdata }
202            
203             =item * L<View>
204            
205             Uses a regular Catalyst view. For example, if you wanted to have your
206             C<text/html> and C<text/xml> views rendered by TT, set:
207            
208             __PACKAGE__->config(
209             map => {
210             'text/html' => [ 'View', 'TT' ],
211             'text/xml' => [ 'View', 'XML' ],
212             }
213             );
214            
215             Your views should have a C<process> method like this:
216            
217             sub process {
218             my ( $self, $c, $stash_key ) = @_;
219            
220             my $output;
221             eval {
222             $output = $self->serialize( $c->stash->{$stash_key} );
223             };
224             return $@ if $@;
225            
226             $c->response->body( $output );
227             return 1; # important
228             }
229            
230             sub serialize {
231             my ( $self, $data ) = @_;
232            
233             my $serialized = ... process $data here ...
234            
235             return $serialized;
236             }
237            
238             =item * Callback
239            
240             For infinite flexibility, you can provide a callback for the
241             deserialization/serialization steps.
242            
243             __PACKAGE__->config(
244             map => {
245             'text/xml' => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ],
246             }
247             );
248            
249             The C<deserialize> callback is passed a string that is the body of the
250             request and is expected to return a scalar value that results from
251             the deserialization. The C<serialize> callback is passed the data
252             structure that needs to be serialized and must return a string suitable
253             for returning in the HTTP response. In addition to receiving the scalar
254             to act on, both callbacks are passed the controller object and the context
255             (i.e. C<$c>) as the second and third arguments.
256            
257             =back
258            
259             By default, L<Catalyst::Controller::REST> will return a
260             C<415 Unsupported Media Type> response if an attempt to use an unsupported
261             content-type is made. You can ensure that something is always returned by
262             setting the C<default> config option:
263            
264             __PACKAGE__->config(default => 'text/x-yaml');
265            
266             would make it always fall back to the serializer plugin defined for
267             C<text/x-yaml>.
268            
269             =head1 CUSTOM SERIALIZERS
270            
271             Implementing new Serialization formats is easy! Contributions
272             are most welcome! If you would like to implement a custom serializer,
273             you should create two new modules in the L<Catalyst::Action::Serialize>
274             and L<Catalyst::Action::Deserialize> namespace. Then assign your new
275             class to the content-type's you want, and you're done.
276            
277             See L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>
278             for more information.
279            
280             =head1 STATUS HELPERS
281            
282             Since so much of REST is in using HTTP, we provide these Status Helpers.
283             Using them will ensure that you are responding with the proper codes,
284             headers, and entities.
285            
286             These helpers try and conform to the HTTP 1.1 Specification. You can
287             refer to it at: L<http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.
288             These routines are all implemented as regular subroutines, and as
289             such require you pass the current context ($c) as the first argument.
290            
291             =over
292            
293             =cut
294              
295 12     12   1169 BEGIN { extends 'Catalyst::Controller' }
296 12     12   65232 use Params::Validate qw(SCALAR OBJECT);
  12         6418  
  12         1735  
297              
298             __PACKAGE__->mk_accessors(qw(serialize));
299              
300             __PACKAGE__->config(
301                 'stash_key' => 'rest',
302                 'map' => {
303                     'text/xml' => 'XML::Simple',
304                     'application/json' => 'JSON',
305                     'text/x-json' => 'JSON',
306                 },
307                 'compliance_mode' => 0,
308             );
309              
310 12     12 1 80 sub begin : ActionClass('Deserialize') { }
  12     14   22  
  12         113  
311              
312 12     12 1 12558 sub end : ActionClass('Serialize') { }
  12     16   28  
  12         43  
313              
314             =item status_ok
315            
316             Returns a "200 OK" response. Takes an "entity" to serialize.
317            
318             Example:
319            
320             $self->status_ok(
321             $c,
322             entity => {
323             radiohead => "Is a good band!",
324             }
325             );
326            
327             =cut
328              
329             sub status_ok {
330 0     0 1       my $self = shift;
331 0               my $c = shift;
332 0               my %p = Params::Validate::validate( @_, { entity => 1, }, );
333              
334 0               $c->response->status(200);
335 0               $self->_set_entity( $c, $p{'entity'} );
336 0               return 1;
337             }
338              
339             =item status_created
340            
341             Returns a "201 CREATED" response. Takes an "entity" to serialize,
342             and a "location" where the created object can be found.
343            
344             Example:
345            
346             $self->status_created(
347             $c,
348             location => $c->req->uri,
349             entity => {
350             radiohead => "Is a good band!",
351             }
352             );
353            
354             In the above example, we use the requested URI as our location.
355             This is probably what you want for most PUT requests.
356            
357             =cut
358              
359             sub status_created {
360 0     0 1       my $self = shift;
361 0               my $c = shift;
362 0               my %p = Params::Validate::validate(
363                     @_,
364                     {
365                         location => { type => SCALAR | OBJECT },
366                         entity => { optional => 1 },
367                     },
368                 );
369              
370 0               $c->response->status(201);
371 0               $c->response->header( 'Location' => $p{location} );
372 0               $self->_set_entity( $c, $p{'entity'} );
373 0               return 1;
374             }
375              
376             =item status_accepted
377            
378             Returns a "202 ACCEPTED" response. Takes an "entity" to serialize.
379             Also takes optional "location" for queue type scenarios.
380            
381             Example:
382            
383             $self->status_accepted(
384             $c,
385             location => $c->req->uri,
386             entity => {
387             status => "queued",
388             }
389             );
390            
391             =cut
392              
393             sub status_accepted {
394 0     0 1       my $self = shift;
395 0               my $c = shift;
396 0               my %p = Params::Validate::validate(
397                     @_,
398                     {
399                         location => { type => SCALAR | OBJECT, optional => 1 },
400                         entity => 1,
401                     },
402                 );
403              
404 0               $c->response->status(202);
405 0 0             $c->response->header( 'Location' => $p{location} ) if exists $p{location};
406 0               $self->_set_entity( $c, $p{'entity'} );
407 0               return 1;
408             }
409              
410             =item status_no_content
411            
412             Returns a "204 NO CONTENT" response.
413            
414             =cut
415              
416             sub status_no_content {
417 0     0 1       my $self = shift;
418 0               my $c = shift;
419 0               $c->response->status(204);
420 0               $self->_set_entity( $c, undef );
421 0               return 1;
422             }
423              
424             =item status_multiple_choices
425            
426             Returns a "300 MULTIPLE CHOICES" response. Takes an "entity" to serialize, which should
427             provide list of possible locations. Also takes optional "location" for preferred choice.
428            
429             =cut
430              
431             sub status_multiple_choices {
432 0     0 1       my $self = shift;
433 0               my $c = shift;
434 0               my %p = Params::Validate::validate(
435                     @_,
436                     {
437                         entity => 1,
438                         location => { type => SCALAR | OBJECT, optional => 1 },
439                     },
440                 );
441              
442 0               $c->response->status(300);
443 0 0             $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
444 0               $self->_set_entity( $c, $p{'entity'} );
445 0               return 1;
446             }
447              
448             =item status_found
449            
450             Returns a "302 FOUND" response. Takes an "entity" to serialize.
451             Also takes optional "location".
452            
453             =cut
454              
455             sub status_found {
456 0     0 1       my $self = shift;
457 0               my $c = shift;
458 0               my %p = Params::Validate::validate(
459                     @_,
460                     {
461                         entity => 1,
462                         location => { type => SCALAR | OBJECT, optional => 1 },
463                     },
464                 );
465              
466 0               $c->response->status(302);
467 0 0             $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
468 0               $self->_set_entity( $c, $p{'entity'} );
469 0               return 1;
470             }
471              
472             =item status_bad_request
473            
474             Returns a "400 BAD REQUEST" response. Takes a "message" argument
475             as a scalar, which will become the value of "error" in the serialized
476             response.
477            
478             Example:
479            
480             $self->status_bad_request(
481             $c,
482             message => "Cannot do what you have asked!",
483             );
484            
485             =cut
486              
487             sub status_bad_request {
488 0     0 1       my $self = shift;
489 0               my $c = shift;
490 0               my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
491              
492 0               $c->response->status(400);
493 0 0             $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug;
494 0               $self->_set_entity( $c, { error => $p{'message'} } );
495 0               return 1;
496             }
497              
498             =item status_forbidden
499            
500             Returns a "403 FORBIDDEN" response. Takes a "message" argument
501             as a scalar, which will become the value of "error" in the serialized
502             response.
503            
504             Example:
505            
506             $self->status_forbidden(
507             $c,
508             message => "access denied",
509             );
510            
511             =cut
512              
513             sub status_forbidden {
514 0     0 1       my $self = shift;
515 0               my $c = shift;
516 0               my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
517              
518 0               $c->response->status(403);
519 0 0             $c->log->debug( "Status Forbidden: " . $p{'message'} ) if $c->debug;
520 0               $self->_set_entity( $c, { error => $p{'message'} } );
521 0               return 1;
522             }
523              
524             =item status_not_found
525            
526             Returns a "404 NOT FOUND" response. Takes a "message" argument
527             as a scalar, which will become the value of "error" in the serialized
528             response.
529            
530             Example:
531            
532             $self->status_not_found(
533             $c,
534             message => "Cannot find what you were looking for!",
535             );
536            
537             =cut
538              
539             sub status_not_found {
540 0     0 1       my $self = shift;
541 0               my $c = shift;
542 0               my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
543              
544 0               $c->response->status(404);
545 0 0             $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug;
546 0               $self->_set_entity( $c, { error => $p{'message'} } );
547 0               return 1;
548             }
549              
550             =item gone
551            
552             Returns a "41O GONE" response. Takes a "message" argument as a scalar,
553             which will become the value of "error" in the serialized response.
554            
555             Example:
556            
557             $self->status_gone(
558             $c,
559             message => "The document have been deleted by foo",
560             );
561            
562             =cut
563              
564             sub status_gone {
565 0     0 0       my $self = shift;
566 0               my $c = shift;
567 0               my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
568              
569 0               $c->response->status(410);
570 0 0             $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug;
571 0               $self->_set_entity( $c, { error => $p{'message'} } );
572 0               return 1;
573             }
574              
575             =item status_see_other
576            
577             Returns a "303 See Other" response. Takes an optional "entity" to serialize,
578             and a "location" where the client should redirect to.
579            
580             Example:
581            
582             $self->status_see_other(
583             $c,
584             location => $some_other_url,
585             entity => {
586             radiohead => "Is a good band!",
587             }
588             );
589            
590             =cut
591              
592             sub status_see_other {
593 0     0 1       my $self = shift;
594 0               my $c = shift;
595 0               my %p = Params::Validate::validate(
596                     @_,
597                     {
598                         location => { type => SCALAR | OBJECT },
599                         entity => { optional => 1 },
600                     },
601                 );
602              
603 0               $c->response->status(303);
604 0               $c->response->header( 'Location' => $p{location} );
605 0               $self->_set_entity( $c, $p{'entity'} );
606 0               return 1;
607             }
608              
609             =item status_moved
610            
611             Returns a "301 MOVED" response. Takes an "entity" to serialize, and a
612             "location" where the created object can be found.
613            
614             Example:
615            
616             $self->status_moved(
617             $c,
618             location => '/somewhere/else',
619             entity => {
620             radiohead => "Is a good band!",
621             },
622             );
623            
624             =cut
625              
626             sub status_moved {
627 0     0 1      my $self = shift;
628 0              my $c = shift;
629 0              my %p = Params::Validate::validate(
630                   @_,
631                   {
632                      location => { type => SCALAR | OBJECT },
633                      entity => { optional => 1 },
634                   },
635                );
636              
637                my $location = ref $p{location}
638                   ? $p{location}->as_string
639                   : $p{location}
640 0 0            ;
641              
642 0              $c->response->status(301);
643 0              $c->response->header( Location => $location );
644 0              $self->_set_entity($c, $p{entity});
645 0              return 1;
646             }
647              
648             sub _set_entity {
649 0     0         my $self = shift;
650 0               my $c = shift;
651 0               my $entity = shift;
652 0 0             if ( defined($entity) ) {
653 0                   $c->stash->{ $self->{'stash_key'} } = $entity;
654                 }
655 0               return 1;
656             }
657              
658             =back
659            
660             =head1 MANUAL RESPONSES
661            
662             If you want to construct your responses yourself, all you need to
663             do is put the object you want serialized in $c->stash->{'rest'}.
664            
665             =head1 IMPLEMENTATION DETAILS
666            
667             This Controller ties together L<Catalyst::Action::REST>,
668             L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>. It should be suitable for most applications. You should be aware that it:
669            
670             =over 4
671            
672             =item Configures the Serialization Actions
673            
674             This class provides a default configuration for Serialization. It is currently:
675            
676             __PACKAGE__->config(
677             'stash_key' => 'rest',
678             'map' => {
679             'text/html' => 'YAML::HTML',
680             'text/xml' => 'XML::Simple',
681             'text/x-yaml' => 'YAML',
682             'application/json' => 'JSON',
683             'text/x-json' => 'JSON',
684             'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
685             'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
686             'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ],
687             'application/x-storable' => [ 'Data::Serializer', 'Storable' ],
688             'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
689             'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ],
690             'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
691             },
692             );
693            
694             You can read the full set of options for this configuration block in
695             L<Catalyst::Action::Serialize>.
696            
697             =item Sets a C<begin> and C<end> method for you
698            
699             The C<begin> method uses L<Catalyst::Action::Deserialize>. The C<end>
700             method uses L<Catalyst::Action::Serialize>. If you want to override
701             either behavior, simply implement your own C<begin> and C<end> actions
702             and forward to another action with the Serialize and/or Deserialize
703             action classes:
704            
705             package Foo::Controller::Monkey;
706             use Moose;
707             use namespace::autoclean;
708            
709             BEGIN { extends 'Catalyst::Controller::REST' }
710            
711             sub begin : Private {
712             my ($self, $c) = @_;
713             ... do things before Deserializing ...
714             $c->forward('deserialize');
715             ... do things after Deserializing ...
716             }
717            
718             sub deserialize : ActionClass('Deserialize') {}
719            
720             sub end :Private {
721             my ($self, $c) = @_;
722             ... do things before Serializing ...
723             $c->forward('serialize');
724             ... do things after Serializing ...
725             }
726            
727             sub serialize : ActionClass('Serialize') {}
728            
729             If you need to deserialize multipart requests (i.e. REST data in
730             one part and file uploads in others) you can do so by using the
731             L<Catalyst::Action::DeserializeMultiPart> action class.
732            
733             =back
734            
735             =head1 A MILD WARNING
736            
737             I have code in production using L<Catalyst::Controller::REST>. That said,
738             it is still under development, and it's possible that things may change
739             between releases. I promise to not break things unnecessarily. :)
740            
741             =head1 SEE ALSO
742            
743             L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
744             L<Catalyst::Action::Deserialize>
745            
746             For help with REST in general:
747            
748             The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
749            
750             Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
751            
752             The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
753            
754             =head1 AUTHORS
755            
756             See L<Catalyst::Action::REST> for authors.
757            
758             =head1 LICENSE
759            
760             You may distribute this code under the same terms as Perl itself.
761            
762             =cut
763              
764             __PACKAGE__->meta->make_immutable;
765              
766             1;
767