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