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