.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .if !\nF .nr F 0 .if \nF>0 \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} .\} .\" ======================================================================== .\" .IX Title "Catalyst::Controller::REST 3pm" .TH Catalyst::Controller::REST 3pm "2017-12-11" "perl v5.26.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Catalyst::Controller::REST \- A RESTful controller .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 3 \& package Foo::Controller::Bar; \& use Moose; \& use namespace::autoclean; \& \& BEGIN { extends \*(AqCatalyst::Controller::REST\*(Aq } \& \& sub thing : Local : ActionClass(\*(AqREST\*(Aq) { } \& \& # Answer GET requests to "thing" \& sub thing_GET { \& my ( $self, $c ) = @_; \& \& # Return a 200 OK, with the data in entity \& # serialized in the body \& $self\->status_ok( \& $c, \& entity => { \& some => \*(Aqdata\*(Aq, \& foo => \*(Aqis real bar\-y\*(Aq, \& }, \& ); \& } \& \& # Answer PUT requests to "thing" \& sub thing_PUT { \& my ( $self, $c ) = @_; \& \& $radiohead = $c\->req\->data\->{radiohead}; \& \& $self\->status_created( \& $c, \& location => $c\->req\->uri, \& entity => { \& radiohead => $radiohead, \& } \& ); \& } .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" Catalyst::Controller::REST implements a mechanism for building RESTful services in Catalyst. It does this by extending the normal Catalyst dispatch mechanism to allow for different subroutines to be called based on the \s-1HTTP\s0 Method requested, while also transparently handling all the serialization/deserialization for you. .PP This is probably best served by an example. In the above controller, we have declared a Local Catalyst action on \&\*(L"sub thing\*(R", and have used the ActionClass('\s-1REST\s0'). .PP Below, we have declared \*(L"thing_GET\*(R" and \*(L"thing_PUT\*(R". Any \&\s-1GET\s0 requests to thing will be dispatched to \*(L"thing_GET\*(R", while any \s-1PUT\s0 requests will be dispatched to \*(L"thing_PUT\*(R". .PP Any unimplemented \s-1HTTP\s0 methods will be met with a \*(L"405 Method Not Allowed\*(R" response, automatically containing the proper list of available methods. You can override this behavior through implementing a custom \&\f(CW\*(C`thing_not_implemented\*(C'\fR method. .PP If you do not provide an \s-1OPTIONS\s0 handler, we will respond to any \s-1OPTIONS\s0 requests with a \*(L"200 \s-1OK\*(R",\s0 populating the Allowed header automatically. .PP Any data included in \f(CW\*(C`$c\->stash\->{\*(Aqrest\*(Aq}\*(C'\fR 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"\s-1STATUS HELPERS\*(R"\s0, which are described below. .PP "The \s-1HTTP POST, PUT,\s0 and \s-1OPTIONS\s0 methods will all automatically deserialize the contents of \&\f(CW\*(C`$c\->request\->body\*(C'\fR into the \f(CW\*(C`$c\->request\->data\*(C'\fR hashref", based on the request's \f(CW\*(C`Content\-type\*(C'\fR header. A list of understood serialization formats is below. .PP If we do not have (or cannot run) a serializer for a given content-type, a 415 \&\*(L"Unsupported Media Type\*(R" error is generated. .PP To make your Controller RESTful, simply have it .PP .Vb 1 \& BEGIN { extends \*(AqCatalyst::Controller::REST\*(Aq } .Ve .SH "CONFIGURATION" .IX Header "CONFIGURATION" See \*(L"\s-1CONFIGURATION\*(R"\s0 in Catalyst::Action::Serialize. Note that the \f(CW\*(C`serialize\*(C'\fR key has been deprecated. .SH "SERIALIZATION" .IX Header "SERIALIZATION" Catalyst::Controller::REST will automatically serialize your responses, and deserialize any \s-1POST, PUT\s0 or \s-1OPTIONS\s0 requests. It evaluates which serializer to use by mapping a content-type to a Serialization module. We select the content-type based on: .IP "\fBThe Content-Type Header\fR" 4 .IX Item "The Content-Type Header" If the incoming \s-1HTTP\s0 Request had a Content-Type header set, we will use it. .IP "\fBThe content-type Query Parameter\fR" 4 .IX Item "The content-type Query Parameter" If this is a \s-1GET\s0 request, you can supply a content-type query parameter. .IP "\fBEvaluating the Accept Header\fR" 4 .IX Item "Evaluating the Accept Header" Finally, if the client provided an Accept header, we will evaluate it and use the best-ranked choice. .SH "AVAILABLE SERIALIZERS" .IX Header "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. .PP In addition, each serializer has its quirks in terms of what sorts of data structures it will properly handle. Catalyst::Controller::REST makes no attempt to save you from yourself in this regard. :) .IP "\(bu" 2 \&\f(CW\*(C`text/x\-yaml\*(C'\fR => \f(CW\*(C`YAML::Syck\*(C'\fR .Sp Returns \s-1YAML\s0 generated by YAML::Syck. .IP "\(bu" 2 \&\f(CW\*(C`text/html\*(C'\fR => \f(CW\*(C`YAML::HTML\*(C'\fR .Sp This uses YAML::Syck and URI::Find to generate \s-1YAML\s0 with all URLs turned to hyperlinks. Only usable for Serialization. .IP "\(bu" 2 \&\f(CW\*(C`application/json\*(C'\fR => \f(CW\*(C`JSON\*(C'\fR .Sp Uses \s-1JSON\s0 to generate \s-1JSON\s0 output. It is strongly advised to also have \&\s-1JSON::XS\s0 installed. The \f(CW\*(C`text/x\-json\*(C'\fR content type is supported but is deprecated and you will receive warnings in your log. .Sp You can also add a hash in your controller config to pass options to the json object. There are two options. \f(CW\*(C`json_options\*(C'\fR are used when decoding incoming \s-1JSON,\s0 and \f(CW\*(C`json_options_encode\*(C'\fR is used when encoding \s-1JSON\s0 for output. .Sp For instance, to relax permissions when deserializing input, add: .Sp .Vb 3 \& _\|_PACKAGE_\|_\->config( \& json_options => { relaxed => 1 } \& ) .Ve .Sp To indent the \s-1JSON\s0 output so it becomes more human readable, add: .Sp .Vb 3 \& _\|_PACKAGE_\|_\->config( \& json_options_encode => { indent => 1 } \& ) .Ve .IP "\(bu" 2 \&\f(CW\*(C`text/javascript\*(C'\fR => \f(CW\*(C`JSONP\*(C'\fR .Sp If a callback=? parameter is passed, this returns javascript in the form of: \f(CW$callbac\fRk($serializedJSON); .Sp Note \- this is disabled by default as it can be a security risk if you are unaware. .Sp The usual \s-1MIME\s0 types for this serialization format are: 'text/javascript', 'application/x\-javascript', \&'application/javascript'. .IP "\(bu" 2 \&\f(CW\*(C`text/x\-data\-dumper\*(C'\fR => \f(CW\*(C`Data::Serializer\*(C'\fR .Sp Uses the Data::Serializer module to generate Data::Dumper output. .IP "\(bu" 2 \&\f(CW\*(C`text/x\-data\-denter\*(C'\fR => \f(CW\*(C`Data::Serializer\*(C'\fR .Sp Uses the Data::Serializer module to generate Data::Denter output. .IP "\(bu" 2 \&\f(CW\*(C`text/x\-data\-taxi\*(C'\fR => \f(CW\*(C`Data::Serializer\*(C'\fR .Sp Uses the Data::Serializer module to generate Data::Taxi output. .IP "\(bu" 2 \&\f(CW\*(C`text/x\-config\-general\*(C'\fR => \f(CW\*(C`Data::Serializer\*(C'\fR .Sp Uses the Data::Serializer module to generate Config::General output. .IP "\(bu" 2 \&\f(CW\*(C`text/x\-php\-serialization\*(C'\fR => \f(CW\*(C`Data::Serializer\*(C'\fR .Sp Uses the Data::Serializer module to generate PHP::Serialization output. .IP "\(bu" 2 \&\f(CW\*(C`text/xml\*(C'\fR => \f(CW\*(C`XML::Simple\*(C'\fR .Sp Uses XML::Simple to generate \s-1XML\s0 output. This is probably not suitable for any real heavy \s-1XML\s0 work. Due to XML::Simples requirement that the data you serialize be a \s-1HASHREF,\s0 we transform outgoing data to be in the form of: .Sp .Vb 1 \& { data => $yourdata } .Ve .IP "\(bu" 2 View .Sp Uses a regular Catalyst view. For example, if you wanted to have your \&\f(CW\*(C`text/html\*(C'\fR and \f(CW\*(C`text/xml\*(C'\fR views rendered by \s-1TT,\s0 set: .Sp .Vb 6 \& _\|_PACKAGE_\|_\->config( \& map => { \& \*(Aqtext/html\*(Aq => [ \*(AqView\*(Aq, \*(AqTT\*(Aq ], \& \*(Aqtext/xml\*(Aq => [ \*(AqView\*(Aq, \*(AqXML\*(Aq ], \& } \& ); .Ve .Sp Your views should have a \f(CW\*(C`process\*(C'\fR method like this: .Sp .Vb 2 \& sub process { \& my ( $self, $c, $stash_key ) = @_; \& \& my $output; \& eval { \& $output = $self\->serialize( $c\->stash\->{$stash_key} ); \& }; \& return $@ if $@; \& \& $c\->response\->body( $output ); \& return 1; # important \& } \& \& sub serialize { \& my ( $self, $data ) = @_; \& \& my $serialized = ... process $data here ... \& \& return $serialized; \& } .Ve .IP "\(bu" 2 Callback .Sp For infinite flexibility, you can provide a callback for the deserialization/serialization steps. .Sp .Vb 5 \& _\|_PACKAGE_\|_\->config( \& map => { \& \*(Aqtext/xml\*(Aq => [ \*(AqCallback\*(Aq, { deserialize => \e&parse_xml, serialize => \e&render_xml } ], \& } \& ); .Ve .Sp The \f(CW\*(C`deserialize\*(C'\fR callback is passed a string that is the body of the request and is expected to return a scalar value that results from the deserialization. The \f(CW\*(C`serialize\*(C'\fR callback is passed the data structure that needs to be serialized and must return a string suitable for returning in the \s-1HTTP\s0 response. In addition to receiving the scalar to act on, both callbacks are passed the controller object and the context (i.e. \f(CW$c\fR) as the second and third arguments. .PP By default, Catalyst::Controller::REST will return a \&\f(CW\*(C`415 Unsupported Media Type\*(C'\fR response if an attempt to use an unsupported content-type is made. You can ensure that something is always returned by setting the \f(CW\*(C`default\*(C'\fR config option: .PP .Vb 1 \& _\|_PACKAGE_\|_\->config(default => \*(Aqtext/x\-yaml\*(Aq); .Ve .PP would make it always fall back to the serializer plugin defined for \&\f(CW\*(C`text/x\-yaml\*(C'\fR. .SH "CUSTOM SERIALIZERS" .IX Header "CUSTOM SERIALIZERS" Implementing new Serialization formats is easy! Contributions are most welcome! If you would like to implement a custom serializer, you should create two new modules in the Catalyst::Action::Serialize and Catalyst::Action::Deserialize namespace. Then assign your new class to the content-type's you want, and you're done. .PP See Catalyst::Action::Serialize and Catalyst::Action::Deserialize for more information. .SH "STATUS HELPERS" .IX Header "STATUS HELPERS" Since so much of \s-1REST\s0 is in using \s-1HTTP,\s0 we provide these Status Helpers. Using them will ensure that you are responding with the proper codes, headers, and entities. .PP These helpers try and conform to the \s-1HTTP 1.1\s0 Specification. You can refer to it at: . These routines are all implemented as regular subroutines, and as such require you pass the current context ($c) as the first argument. .IP "status_ok" 4 .IX Item "status_ok" Returns a \*(L"200 \s-1OK\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize. .Sp Example: .Sp .Vb 6 \& $self\->status_ok( \& $c, \& entity => { \& radiohead => "Is a good band!", \& } \& ); .Ve .IP "status_created" 4 .IX Item "status_created" Returns a \*(L"201 \s-1CREATED\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize, and a \*(L"location\*(R" where the created object can be found. .Sp Example: .Sp .Vb 7 \& $self\->status_created( \& $c, \& location => $c\->req\->uri, \& entity => { \& radiohead => "Is a good band!", \& } \& ); .Ve .Sp In the above example, we use the requested \s-1URI\s0 as our location. This is probably what you want for most \s-1PUT\s0 requests. .IP "status_accepted" 4 .IX Item "status_accepted" Returns a \*(L"202 \s-1ACCEPTED\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize. Also takes optional \*(L"location\*(R" for queue type scenarios. .Sp Example: .Sp .Vb 7 \& $self\->status_accepted( \& $c, \& location => $c\->req\->uri, \& entity => { \& status => "queued", \& } \& ); .Ve .IP "status_no_content" 4 .IX Item "status_no_content" Returns a \*(L"204 \s-1NO CONTENT\*(R"\s0 response. .IP "status_multiple_choices" 4 .IX Item "status_multiple_choices" Returns a \*(L"300 \s-1MULTIPLE CHOICES\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize, which should provide list of possible locations. Also takes optional \*(L"location\*(R" for preferred choice. .IP "status_found" 4 .IX Item "status_found" Returns a \*(L"302 \s-1FOUND\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize. Also takes optional \*(L"location\*(R". .IP "status_bad_request" 4 .IX Item "status_bad_request" Returns a \*(L"400 \s-1BAD REQUEST\*(R"\s0 response. Takes a \*(L"message\*(R" argument as a scalar, which will become the value of \*(L"error\*(R" in the serialized response. .Sp Example: .Sp .Vb 4 \& $self\->status_bad_request( \& $c, \& message => "Cannot do what you have asked!", \& ); .Ve .IP "status_forbidden" 4 .IX Item "status_forbidden" Returns a \*(L"403 \s-1FORBIDDEN\*(R"\s0 response. Takes a \*(L"message\*(R" argument as a scalar, which will become the value of \*(L"error\*(R" in the serialized response. .Sp Example: .Sp .Vb 4 \& $self\->status_forbidden( \& $c, \& message => "access denied", \& ); .Ve .IP "status_not_found" 4 .IX Item "status_not_found" Returns a \*(L"404 \s-1NOT FOUND\*(R"\s0 response. Takes a \*(L"message\*(R" argument as a scalar, which will become the value of \*(L"error\*(R" in the serialized response. .Sp Example: .Sp .Vb 4 \& $self\->status_not_found( \& $c, \& message => "Cannot find what you were looking for!", \& ); .Ve .IP "gone" 4 .IX Item "gone" Returns a \*(L"41O \s-1GONE\*(R"\s0 response. Takes a \*(L"message\*(R" argument as a scalar, which will become the value of \*(L"error\*(R" in the serialized response. .Sp Example: .Sp .Vb 4 \& $self\->status_gone( \& $c, \& message => "The document have been deleted by foo", \& ); .Ve .IP "status_see_other" 4 .IX Item "status_see_other" Returns a \*(L"303 See Other\*(R" response. Takes an optional \*(L"entity\*(R" to serialize, and a \*(L"location\*(R" where the client should redirect to. .Sp Example: .Sp .Vb 7 \& $self\->status_see_other( \& $c, \& location => $some_other_url, \& entity => { \& radiohead => "Is a good band!", \& } \& ); .Ve .IP "status_moved" 4 .IX Item "status_moved" Returns a \*(L"301 \s-1MOVED\*(R"\s0 response. Takes an \*(L"entity\*(R" to serialize, and a \&\*(L"location\*(R" where the created object can be found. .Sp Example: .Sp .Vb 7 \& $self\->status_moved( \& $c, \& location => \*(Aq/somewhere/else\*(Aq, \& entity => { \& radiohead => "Is a good band!", \& }, \& ); .Ve .SH "MANUAL RESPONSES" .IX Header "MANUAL RESPONSES" If you want to construct your responses yourself, all you need to do is put the object you want serialized in \f(CW$c\fR\->stash\->{'rest'}. .SH "IMPLEMENTATION DETAILS" .IX Header "IMPLEMENTATION DETAILS" This Controller ties together Catalyst::Action::REST, Catalyst::Action::Serialize and Catalyst::Action::Deserialize. It should be suitable for most applications. You should be aware that it: .IP "Configures the Serialization Actions" 4 .IX Item "Configures the Serialization Actions" This class provides a default configuration for Serialization. It is currently: .Sp .Vb 10 \& _\|_PACKAGE_\|_\->config( \& \*(Aqstash_key\*(Aq => \*(Aqrest\*(Aq, \& \*(Aqmap\*(Aq => { \& \*(Aqtext/html\*(Aq => \*(AqYAML::HTML\*(Aq, \& \*(Aqtext/xml\*(Aq => \*(AqXML::Simple\*(Aq, \& \*(Aqtext/x\-yaml\*(Aq => \*(AqYAML\*(Aq, \& \*(Aqapplication/json\*(Aq => \*(AqJSON\*(Aq, \& \*(Aqtext/x\-json\*(Aq => \*(AqJSON\*(Aq, \& \*(Aqtext/x\-data\-dumper\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqData::Dumper\*(Aq ], \& \*(Aqtext/x\-data\-denter\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqData::Denter\*(Aq ], \& \*(Aqtext/x\-data\-taxi\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqData::Taxi\*(Aq ], \& \*(Aqapplication/x\-storable\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqStorable\*(Aq ], \& \*(Aqapplication/x\-freezethaw\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqFreezeThaw\*(Aq ], \& \*(Aqtext/x\-config\-general\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqConfig::General\*(Aq ], \& \*(Aqtext/x\-php\-serialization\*(Aq => [ \*(AqData::Serializer\*(Aq, \*(AqPHP::Serialization\*(Aq ], \& }, \& ); .Ve .Sp You can read the full set of options for this configuration block in Catalyst::Action::Serialize. .ie n .IP "Sets a ""begin"" and ""end"" method for you" 4 .el .IP "Sets a \f(CWbegin\fR and \f(CWend\fR method for you" 4 .IX Item "Sets a begin and end method for you" The \f(CW\*(C`begin\*(C'\fR method uses Catalyst::Action::Deserialize. The \f(CW\*(C`end\*(C'\fR method uses Catalyst::Action::Serialize. If you want to override either behavior, simply implement your own \f(CW\*(C`begin\*(C'\fR and \f(CW\*(C`end\*(C'\fR actions and forward to another action with the Serialize and/or Deserialize action classes: .Sp .Vb 3 \& package Foo::Controller::Monkey; \& use Moose; \& use namespace::autoclean; \& \& BEGIN { extends \*(AqCatalyst::Controller::REST\*(Aq } \& \& sub begin : Private { \& my ($self, $c) = @_; \& ... do things before Deserializing ... \& $c\->forward(\*(Aqdeserialize\*(Aq); \& ... do things after Deserializing ... \& } \& \& sub deserialize : ActionClass(\*(AqDeserialize\*(Aq) {} \& \& sub end :Private { \& my ($self, $c) = @_; \& ... do things before Serializing ... \& $c\->forward(\*(Aqserialize\*(Aq); \& ... do things after Serializing ... \& } \& \& sub serialize : ActionClass(\*(AqSerialize\*(Aq) {} .Ve .Sp If you need to deserialize multipart requests (i.e. \s-1REST\s0 data in one part and file uploads in others) you can do so by using the Catalyst::Action::DeserializeMultiPart action class. .SH "A MILD WARNING" .IX Header "A MILD WARNING" I have code in production using Catalyst::Controller::REST. That said, it is still under development, and it's possible that things may change between releases. I promise to not break things unnecessarily. :) .SH "SEE ALSO" .IX Header "SEE ALSO" Catalyst::Action::REST, Catalyst::Action::Serialize, Catalyst::Action::Deserialize .PP For help with \s-1REST\s0 in general: .PP The \s-1HTTP 1.1\s0 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt .PP Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer .PP The \s-1REST\s0 Wiki: http://rest.blueoxen.net/cgi\-bin/wiki.pl?FrontPage .SH "AUTHORS" .IX Header "AUTHORS" See Catalyst::Action::REST for authors. .SH "LICENSE" .IX Header "LICENSE" You may distribute this code under the same terms as Perl itself.