Merge branch 'master' into roles-saner
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / REST.pm
CommitLineData
256c894f 1#
2# REST.pm
be3c588a 3# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
256c894f 4# Created on: 10/12/2006 03:00:32 PM PDT
5#
256c894f 6
7package Catalyst::Action::REST;
bcf4a936 8use Moose;
7ad87df9 9use Class::Inspector;
7e08f674 10use Moose::Util qw(does_role);
7e08f674 11use Catalyst::RequestRole::REST;
ebba5325 12use Catalyst::Controller::REST;
7e08f674 13use namespace::clean -except => 'meta';
ebba5325 14
bcf4a936 15extends 'Catalyst::Action';
ebba5325 16
9c5c9bd1 17BEGIN { require 5.008001; }
256c894f 18
db8bb647 19our $VERSION = '0.74';
9a76221e 20
7328f0ab 21=head1 NAME
22
23Catalyst::Action::REST - Automated REST Method Dispatching
24
25=head1 SYNOPSIS
26
5e87ec47 27 sub foo :Local :ActionClass('REST') {
28 ... do setup for HTTP method specific handlers ...
29 }
7328f0ab 30
afa581cf 31 sub foo_GET {
7328f0ab 32 ... do something for GET requests ...
33 }
34
afa581cf 35 sub foo_PUT {
7328f0ab 36 ... do somethign for PUT requests ...
37 }
38
39=head1 DESCRIPTION
40
41This Action handles doing automatic method dispatching for REST requests. It
42takes a normal Catalyst action, and changes the dispatch to append an
afa581cf 43underscore and method name.
7328f0ab 44
45For example, in the synopsis above, calling GET on "/foo" would result in
46the foo_GET method being dispatched.
47
afa581cf 48If a method is requested that is not implemented, this action will
49return a status 405 (Method Not Found). It will populate the "Allow" header
5e87ec47 50with the list of implemented request methods. You can override this behavior
51by implementing a custom 405 handler like so:
52
53 sub foo_not_implemented {
54 ... handle not implemented methods ...
55 }
56
57If you do not provide an _OPTIONS subroutine, we will automatically respond
58with a 200 OK. The "Allow" header will be populated with the list of
59implemented request methods.
7328f0ab 60
5e87ec47 61It is likely that you really want to look at L<Catalyst::Controller::REST>,
62which brings this class together with automatic Serialization of requests
63and responses.
398c5a1b 64
9a76221e 65When you use this module, the request class will be changed to
66L<Catalyst::Request::REST>.
67
7328f0ab 68=head1 METHODS
69
70=over 4
71
72=item dispatch
73
74This method overrides the default dispatch mechanism to the re-dispatching
75mechanism described above.
76
77=cut
d34c067a 78
256c894f 79sub dispatch {
bb4130f6 80 my $self = shift;
d34c067a 81 my $c = shift;
256c894f 82
7e08f674 83 Catalyst::RequestRole::REST->meta->apply($c->request)
84 unless does_role($c->request, 'Catalyst::RequestRole::REST');
85
2f91bf68 86 my $controller = $c->component( $self->class );
3faede66 87 my $rest_method = $self->name . "_" . uc( $c->request->method );
679978b1 88
3faede66 89 my ($code, $name);
679978b1 90
3faede66 91 # Common case, for foo_GET etc
92 if ($code = $controller->can($rest_method)) {
93 # Exceute normal action
94 $c->execute( $self->class, $self, @{ $c->req->args } );
95 $name = $rest_method;
256c894f 96 }
3faede66 97
98 # Generic handling for foo_OPTIONS
99 if (!$code && $c->request->method eq "OPTIONS") {
100 $name = $rest_method;
101 $code = sub { $self->_return_options($self->name, @_) };
102 }
103
104 # Otherwise, not implemented.
105 if (!$code) {
106 $name = $self->name . "_not_implemented";
107 $code = $controller->can($name) # User method
108 # Generic not implemented
109 || sub { $self->_return_not_implemented($self->name, @_) };
679978b1 110 }
256c894f 111
3faede66 112 # localise stuff so we can dispatch the action 'as normal, but get
113 # different stats shown, and different code run.
114 local $self->{code} = $code;
115 local $self->{reverse} = $name;
d34c067a 116
679978b1 117 $c->execute( $self->class, $self, @{ $c->req->args } );
d34c067a 118}
119
d2d93101 120sub _get_allowed_methods {
121 my ( $self, $controller, $c, $name ) = @_;
679978b1 122 my $class = ref($controller) ? ref($controller) : $controller;
123 my $methods = Class::Inspector->methods($class);
7ad87df9 124 my @allowed;
eccb2137 125 foreach my $method ( @{$methods} ) {
eccb2137 126 if ( $method =~ /^$name\_(.+)$/ ) {
127 push( @allowed, $1 );
7ad87df9 128 }
129 }
d34c067a 130 return @allowed;
679978b1 131};
132
133sub _return_options {
3faede66 134 my ( $self, $method_name, $controller, $c) = @_;
d2d93101 135 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
679978b1 136 $c->response->content_type('text/plain');
137 $c->response->status(200);
138 $c->response->header( 'Allow' => \@allowed );
d34c067a 139}
140
141sub _return_not_implemented {
3faede66 142 my ( $self, $method_name, $controller, $c ) = @_;
d34c067a 143
d2d93101 144 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
7ad87df9 145 $c->response->content_type('text/plain');
146 $c->response->status(405);
eccb2137 147 $c->response->header( 'Allow' => \@allowed );
148 $c->response->body( "Method "
149 . $c->request->method
150 . " not implemented for "
679978b1 151 . $c->uri_for( $method_name ) );
7ad87df9 152}
153
256c894f 1541;
7328f0ab 155
156=back
157
158=head1 SEE ALSO
159
160You likely want to look at L<Catalyst::Controller::REST>, which implements
161a sensible set of defaults for a controller doing REST.
162
163L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
164
5d7480da 165=head1 TROUBLESHOOTING
166
167=over 4
168
169=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
170
69ad525b 171A: Most likely, you haven't set Content-type equal to "application/json", or
172one of the accepted return formats. You can do this by setting it in your query
173accepted return formats. You can do this by setting it in your query string
174thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
5d7480da 175
69ad525b 176B<NOTE> Apache will refuse %2F unless configured otherise.
177Make sure C<< AllowEncodedSlashes On >> is in your httpd.conf file in orde
178for this to run smoothly.
5d7480da 179
69ad525b 180=back
5d7480da 181
2f7533ed 182=head1 CONTRIBUTORS
398c5a1b 183
2f7533ed 184Christopher Laco
185
186Luke Saunders
187
188John Goulah
33e5de96 189
190Daisuke Maki <daisuke@endeworks.jp>
191
97b3cf7c 192J. Shirley <jshirley@gmail.com>
193
7580fa2b 194Hans Dieter Pearcey
195
97b3cf7c 196Tomas Doran (t0m) <bobtfish@bobtfish.net>
197
2f7533ed 198=head1 AUTHOR
199
200Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
201
97b3cf7c 202Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
2f7533ed 203
7328f0ab 204=head1 LICENSE
205
206You may distribute this code under the same terms as Perl itself.
207
208=cut
d34c067a 209