X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Action-REST.git;a=blobdiff_plain;f=lib%2FCatalyst%2FController%2FREST.pm;h=1017cb104ca223c8e77169cccf320b65eab4a670;hp=892fb9bc79868947d9bae6035f9ad3bf8827ce18;hb=e601addaf89882fccbc824c1a53328f0d049b32b;hpb=17d910bd120ee243917756ae8c4281e621a4f986 diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index 892fb9b..1017cb1 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -49,16 +49,25 @@ Below, we have declared "thing_GET" and "thing_PUT". Any GET requests to thing will be dispatched to "thing_GET", while any PUT requests will be dispatched to "thing_PUT". -Any unimplemented HTTP METHODS will be met with a "405 Method Not Allowed" -response, automatically containing the proper list of available methods. +Any unimplemented HTTP methods will be met with a "405 Method Not Allowed" +response, automatically containing the proper list of available methods. You +can override this behavior through implementing a custom +C method. + +If you do not provide an OPTIONS handler, we will respond to any OPTIONS +requests with a "200 OK", populating the Allowed header automatically. + +Any data included in C<< $c->stash->{'rest'} >> will be serialized for you. +The serialization format will be selected based on the content-type +of the incoming request. It is probably easier to use the L, +which are described below. The HTTP POST, PUT, and OPTIONS methods will all automatically deserialize the contents of $c->request->body based on the requests content-type header. A list of understood serialization formats is below. -Also included in this class are several helper methods, which -will automatically handle setting up proper response objects -for you. +If we do not have (or cannot run) a serializer for a given content-type, a 415 +"Unsupported Media Type" error is generated. To make your Controller RESTful, simply have it @@ -67,22 +76,117 @@ To make your Controller RESTful, simply have it =head1 SERIALIZATION Catalyst::Controller::REST will automatically serialize your -responses. The currently implemented serialization formats are: +responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates +which serializer to use by mapping a content-type to a Serialization module. +We select the content-type based on: + +=over 2 + +=item B + +If the incoming HTTP Request had a Content-Type header set, we will use it. + +=item B + +If this is a GET request, you can supply a content-type query parameter. + +=item B + +Finally, if the client provided an Accept header, we will evaluate +it and use the best-ranked choice. + +=back + +=head1 AVAILABLE SERIALIZERS + +A given serialization mechanism is only available if you have the underlying +modules installed. For example, you can't use XML::Simple if it's not already +installed. + +In addition, each serializer has it's quirks in terms of what sorts of data +structures it will properly handle. L makes +no attempt to svae you from yourself in this regard. :) + +=over 2 + +=item C => C + +Returns YAML generated by L. + +=item C => C + +This uses L and L to generate YAML with all URLs turned +to hyperlinks. Only useable for Serialization. + +=item C => C + +Uses L to generate JSON output + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses the L module to generate L output. + +=item C => C + +Uses L to generate XML output. This is probably not suitable +for any real heavy XML work. Due to Ls requirement that the data +you serialize be a HASHREF, we transform outgoing data to be in the form of: + + { data => $yourdata } + +=back + +By default, L will return a C<415 Unsupported Media Type> response if an attempt to use an unsupported content-type is made. You +can ensure that something is always returned by setting the C config +option: - text/x-yaml -> YAML::Syck - text/x-data-dumper -> Data::Serializer + __PACKAGE__->config->{'serialize'}->{'default'} = 'YAML'; -By default, L will use YAML as -the serialization format. +Would make it always fall back to YAML. Implementing new Serialization formats is easy! Contributions are most welcome! See L and L for more information. +=head1 CUSTOM SERIALIZERS + +If you would like to implement a custom serializer, you should create two new +modules in the L and +L namespace. Then assign your new class +to the content-type's you want, and you're done. + =head1 STATUS HELPERS +Since so much of REST is in using HTTP, we provide these Status Helpers. +Using them will ensure that you are responding with the proper codes, +headers, and entities. + These helpers try and conform to the HTTP 1.1 Specification. You can -refer to it at: http://www.w3.org/Protocols/rfc2616/rfc2616.txt. +refer to it at: L. These routines are all implemented as regular subroutines, and as such require you pass the current context ($c) as the first argument. @@ -99,19 +203,28 @@ __PACKAGE__->mk_accessors(qw(serialize)); __PACKAGE__->config( serialize => { - 'default' => 'YAML', 'stash_key' => 'rest', 'map' => { + 'text/html' => 'YAML::HTML', + 'text/xml' => 'XML::Simple', 'text/x-yaml' => 'YAML', + 'text/x-json' => 'JSON', 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], + 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ], + 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ], + 'application/x-storable' => [ 'Data::Serializer', 'Storable' ], + 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ], + 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ], + 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ], }, } ); +sub begin : ActionClass('Deserialize') { +} -sub begin : ActionClass('Deserialize') {} - -sub end : ActionClass('Serialize') { } +sub end : ActionClass('Serialize') { +} =item status_ok @@ -130,15 +243,11 @@ Example: sub status_ok { my $self = shift; - my $c = shift; - my %p = validate(@_, - { - entity => 1, - }, - ); + my $c = shift; + my %p = validate( @_, { entity => 1, }, ); $c->response->status(200); - $self->_set_entity($c, $p{'entity'}); + $self->_set_entity( $c, $p{'entity'} ); return 1; } @@ -164,23 +273,24 @@ This is probably what you want for most PUT requests. sub status_created { my $self = shift; - my $c = shift; - my %p = validate(@_, + my $c = shift; + my %p = validate( + @_, { - location => { type => SCALAR | OBJECT }, - entity => { optional => 1 }, + location => { type => SCALAR | OBJECT }, + entity => { optional => 1 }, }, ); my $location; - if (ref($p{'location'})) { + if ( ref( $p{'location'} ) ) { $location = $p{'location'}->as_string; } else { $location = $p{'location'}; } $c->response->status(201); - $c->response->header('Location' => $location); - $self->_set_entity($c, $p{'entity'}); + $c->response->header( 'Location' => $location ); + $self->_set_entity( $c, $p{'entity'} ); return 1; } @@ -198,17 +308,14 @@ Example: ); =cut + sub status_accepted { my $self = shift; - my $c = shift; - my %p = validate(@_, - { - entity => 1, - }, - ); + my $c = shift; + my %p = validate( @_, { entity => 1, }, ); $c->response->status(202); - $self->_set_entity($c, $p{'entity'}); + $self->_set_entity( $c, $p{'entity'} ); return 1; } @@ -226,18 +333,15 @@ Example: ); =cut + sub status_bad_request { my $self = shift; - my $c = shift; - my %p = validate(@_, - { - message => { type => SCALAR }, - }, - ); + my $c = shift; + my %p = validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(400); - $c->log->debug("Status Bad Request: " . $p{'message'}); - $self->_set_entity($c, { error => $p{'message'} }); + $c->log->debug( "Status Bad Request: " . $p{'message'} ); + $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } @@ -255,27 +359,24 @@ Example: ); =cut + sub status_not_found { my $self = shift; - my $c = shift; - my %p = validate(@_, - { - message => { type => SCALAR }, - }, - ); + my $c = shift; + my %p = validate( @_, { message => { type => SCALAR }, }, ); $c->response->status(404); - $c->log->debug("Status Not Found: " . $p{'message'}); - $self->_set_entity($c, { error => $p{'message'} }); + $c->log->debug( "Status Not Found: " . $p{'message'} ); + $self->_set_entity( $c, { error => $p{'message'} } ); return 1; } sub _set_entity { - my $self = shift; - my $c = shift; + my $self = shift; + my $c = shift; my $entity = shift; - if (defined($entity)) { - $c->stash->{$self->config->{'serialize'}->{'stash_key'}} = $entity; + if ( defined($entity) ) { + $c->stash->{ $self->config->{'serialize'}->{'stash_key'} } = $entity; } return 1; } @@ -287,6 +388,73 @@ sub _set_entity { If you want to construct your responses yourself, all you need to do is put the object you want serialized in $c->stash->{'rest'}. +=head1 IMPLEMENTATION DETAILS + +This Controller ties together L, +L and L. It should be suitable for most applications. You should be aware that it: + +=over 4 + +=item Configures the Serialization Actions + +This class provides a default configuration for Serialization. It is currently: + + __PACKAGE__->config( + serialize => { + 'stash_key' => 'rest', + 'map' => { + 'text/html' => 'YAML::HTML', + 'text/xml' => 'XML::Simple', + 'text/x-yaml' => 'YAML', + 'text/x-json' => 'JSON', + 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], + 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ], + 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ], + 'application/x-storable' => [ 'Data::Serializer', 'Storable' +], + 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' +], + 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ] +, + 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serializat +ion' ], + }, + } + ); + +You can read the full set of options for this configuration block in +L. + +=item Sets a C and C method for you + +The C method uses L. The C +method uses L. If you want to override +either behavior, simply implement your own C and C actions +and use NEXT: + + my Foo::Controller::Monkey; + use base qw(Catalyst::Controller::REST); + + sub begin :Private { + my ($self, $c) = @_; + ... do things before Deserializing ... + $self->NEXT::begin($c); + ... do things after Deserializing ... + } + + sub end :Private { + my ($self, $c) = @_; + ... do things before Serializing ... + $self->NEXT::end($c); + ... do things after Serializing ... + } + +=head1 A MILD WARNING + +I have code in production using L. That said, +it is still under development, and it's possible that things may change +between releases. I promise to not break things unneccesarily. :) + =head1 SEE ALSO L, L,