Remove not_implemented from the list of allowed methods
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / REST.pm
CommitLineData
256c894f 1package Catalyst::Action::REST;
2
930013e6 3use Moose;
4use namespace::autoclean;
256c894f 5
930013e6 6extends 'Catalyst::Action';
7ad87df9 7use Class::Inspector;
9a76221e 8use Catalyst::Request::REST;
ebba5325 9use Catalyst::Controller::REST;
10
9c5c9bd1 11BEGIN { require 5.008001; }
256c894f 12
44fa7f94 13our $VERSION = '1.04';
a66af307 14$VERSION = eval $VERSION;
9a76221e 15
24748286 16sub BUILDARGS {
17 my $class = shift;
18 my $config = shift;
19 Catalyst::Request::REST->_insert_self_into( $config->{class} );
20 return $class->SUPER::BUILDARGS($config, @_);
5132f5e4 21}
7328f0ab 22
23=head1 NAME
24
25Catalyst::Action::REST - Automated REST Method Dispatching
26
27=head1 SYNOPSIS
28
5e87ec47 29 sub foo :Local :ActionClass('REST') {
30 ... do setup for HTTP method specific handlers ...
31 }
7328f0ab 32
44d48631 33 sub foo_GET {
7328f0ab 34 ... do something for GET requests ...
35 }
36
4ee24376 37 # alternatively use an Action
e51849a0 38 sub foo_PUT : Action {
4ee24376 39 ... do something for PUT requests ...
7328f0ab 40 }
41
42=head1 DESCRIPTION
43
44This Action handles doing automatic method dispatching for REST requests. It
45takes a normal Catalyst action, and changes the dispatch to append an
4ee24376 46underscore and method name. First it will try dispatching to an action with
47the generated name, and failing that it will try to dispatch to a regular
48method.
7328f0ab 49
50For example, in the synopsis above, calling GET on "/foo" would result in
51the foo_GET method being dispatched.
52
44d48631 53If a method is requested that is not implemented, this action will
54return a status 405 (Method Not Found). It will populate the "Allow" header
5e87ec47 55with the list of implemented request methods. You can override this behavior
56by implementing a custom 405 handler like so:
57
58 sub foo_not_implemented {
59 ... handle not implemented methods ...
60 }
61
62If you do not provide an _OPTIONS subroutine, we will automatically respond
63with a 200 OK. The "Allow" header will be populated with the list of
80c886bb 64implemented request methods. If you do not provide an _HEAD either, we will
65auto dispatch to the _GET one in case it exists.
7328f0ab 66
5e87ec47 67It is likely that you really want to look at L<Catalyst::Controller::REST>,
68which brings this class together with automatic Serialization of requests
69and responses.
398c5a1b 70
85aa4e18 71When you use this module, it adds the L<Catalyst::TraitFor::Request::REST>
72role to your request class.
9a76221e 73
7328f0ab 74=head1 METHODS
75
76=over 4
77
78=item dispatch
79
80This method overrides the default dispatch mechanism to the re-dispatching
81mechanism described above.
82
83=cut
d34c067a 84
256c894f 85sub dispatch {
bb4130f6 86 my $self = shift;
d34c067a 87 my $c = shift;
256c894f 88
3faede66 89 my $rest_method = $self->name . "_" . uc( $c->request->method );
679978b1 90
295fe4d5 91 return $self->_dispatch_rest_method( $c, $rest_method );
92}
93
94sub _dispatch_rest_method {
95 my $self = shift;
96 my $c = shift;
97 my $rest_method = shift;
d5252c20 98 my $req = $c->request;
295fe4d5 99
100 my $controller = $c->component( $self->class );
101
3faede66 102 my ($code, $name);
679978b1 103
3c4306f2 104 # Execute normal 'foo' action.
d5252c20 105 $c->execute( $self->class, $self, @{ $req->args } );
3c4306f2 106
3faede66 107 # Common case, for foo_GET etc
7656dd12 108 if ( $code = $controller->action_for($rest_method) ) {
d5252c20 109 return $c->forward( $code, $req->args ); # Forward to foo_GET if it's an action
82b48c61 110 }
111 elsif ($code = $controller->can($rest_method)) {
a34f2309 112 $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
256c894f 113 }
256c894f 114
d5252c20 115 # Generic handling for foo_*
3faede66 116 if (!$code) {
d5252c20 117 my $code_action = {
118 OPTIONS => sub {
119 $name = $rest_method;
120 $code = sub { $self->_return_options($self->name, @_) };
121 },
e566b7c7 122 HEAD => sub {
123 $rest_method =~ s{_HEAD$}{_GET}i;
124 $self->_dispatch_rest_method($c, $rest_method);
125 },
d5252c20 126 default => sub {
127 # Otherwise, not implemented.
128 $name = $self->name . "_not_implemented";
129 $code = $controller->can($name) # User method
130 # Generic not implemented
131 || sub { $self->_return_not_implemented($self->name, @_) };
132 },
133 };
9e4398c8 134 my ( $http_method, $action_name ) = ( $rest_method, $self->name );
135 $http_method =~ s{\Q$action_name\E\_}{};
136 my $respond = ($code_action->{$http_method}
e566b7c7 137 || $code_action->{'default'})->();
138 return $respond unless $name;
679978b1 139 }
256c894f 140
3faede66 141 # localise stuff so we can dispatch the action 'as normal, but get
142 # different stats shown, and different code run.
3c4306f2 143 # Also get the full path for the action, and make it look like a forward
3faede66 144 local $self->{code} = $code;
3c4306f2 145 my @name = split m{/}, $self->reverse;
146 $name[-1] = $name;
147 local $self->{reverse} = "-> " . join('/', @name);
d34c067a 148
d5252c20 149 $c->execute( $self->class, $self, @{ $req->args } );
d34c067a 150}
151
b0c2ebd4 152sub get_allowed_methods {
d2d93101 153 my ( $self, $controller, $c, $name ) = @_;
679978b1 154 my $class = ref($controller) ? ref($controller) : $controller;
a08d447b 155 my $methods = {
156 map { /^$name\_(.+)$/ ? ( $1 => 1 ) : () }
157 @{ Class::Inspector->methods($class) }
158 };
159 $methods->{'HEAD'} = 1 if $methods->{'GET'};
258f6e7c 160 delete $methods->{'not_implemented'};
a08d447b 161 return keys %$methods;
679978b1 162};
163
164sub _return_options {
3faede66 165 my ( $self, $method_name, $controller, $c) = @_;
b0c2ebd4 166 my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
679978b1 167 $c->response->content_type('text/plain');
168 $c->response->status(200);
169 $c->response->header( 'Allow' => \@allowed );
ffcae141 170 $c->response->body(q{});
d34c067a 171}
172
173sub _return_not_implemented {
3faede66 174 my ( $self, $method_name, $controller, $c ) = @_;
d34c067a 175
b0c2ebd4 176 my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
7ad87df9 177 $c->response->content_type('text/plain');
178 $c->response->status(405);
eccb2137 179 $c->response->header( 'Allow' => \@allowed );
180 $c->response->body( "Method "
181 . $c->request->method
182 . " not implemented for "
679978b1 183 . $c->uri_for( $method_name ) );
7ad87df9 184}
185
24748286 186__PACKAGE__->meta->make_immutable;
187
256c894f 1881;
7328f0ab 189
190=back
191
192=head1 SEE ALSO
193
85aa4e18 194You likely want to look at L<Catalyst::Controller::REST>, which implements a
195sensible set of defaults for a controller doing REST.
196
197This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
786c212f 198your request class. If you're writing a web application which provides RESTful
d6ece98c 199responses and still needs to accommodate web browsers, you may prefer to use
85aa4e18 200L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.
7328f0ab 201
202L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
203
5d7480da 204=head1 TROUBLESHOOTING
205
206=over 4
207
208=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
209
69ad525b 210A: Most likely, you haven't set Content-type equal to "application/json", or
211one of the accepted return formats. You can do this by setting it in your query
212accepted return formats. You can do this by setting it in your query string
213thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
5d7480da 214
10bcd217 215B<NOTE> Apache will refuse %2F unless configured otherwise.
216Make sure C<AllowEncodedSlashes On> is in your httpd.conf file in order
69ad525b 217for this to run smoothly.
5d7480da 218
69ad525b 219=back
7328f0ab 220
5cb5f6bb 221=head1 AUTHOR
222
410b24f8 223Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
5cb5f6bb 224
225Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
226
2f7533ed 227=head1 CONTRIBUTORS
398c5a1b 228
04882f72 229Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
2f7533ed 230
231John Goulah
33e5de96 232
04882f72 233Christopher Laco
33e5de96 234
04882f72 235Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
97b3cf7c 236
7580fa2b 237Hans Dieter Pearcey
238
16b5133c 239Brian Phillips E<lt>bphillips@cpan.orgE<gt>
8bf1f20e 240
410b24f8 241Dave Rolsky E<lt>autarch@urth.orgE<gt>
76a96edc 242
04882f72 243Luke Saunders
244
245Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
246
247J. Shirley E<lt>jshirley@gmail.comE<gt>
248
259c53c7 249Gavin Henry E<lt>ghenry@surevoip.co.ukE<gt>
250
8aa1a2ee 251Gerv http://www.gerv.net/
252
253Colin Newell <colin@opusvl.com>
254
239c8eef 255Wallace Reis E<lt>wreis@cpan.orgE<gt>
256
5cb5f6bb 257=head1 COPYRIGHT
2f7533ed 258
259c53c7 259Copyright (c) 2006-2012 the above named AUTHOR and CONTRIBUTORS
2f7533ed 260
7328f0ab 261=head1 LICENSE
262
263You may distribute this code under the same terms as Perl itself.
264
265=cut
d34c067a 266