Fix default OPTIONS handler
[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
d0822465 13our $VERSION = '1.02';
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
64implemented request methods.
7328f0ab 65
5e87ec47 66It is likely that you really want to look at L<Catalyst::Controller::REST>,
67which brings this class together with automatic Serialization of requests
68and responses.
398c5a1b 69
85aa4e18 70When you use this module, it adds the L<Catalyst::TraitFor::Request::REST>
71role to your request class.
9a76221e 72
7328f0ab 73=head1 METHODS
74
75=over 4
76
77=item dispatch
78
79This method overrides the default dispatch mechanism to the re-dispatching
80mechanism described above.
81
82=cut
d34c067a 83
256c894f 84sub dispatch {
bb4130f6 85 my $self = shift;
d34c067a 86 my $c = shift;
256c894f 87
3faede66 88 my $rest_method = $self->name . "_" . uc( $c->request->method );
679978b1 89
295fe4d5 90 return $self->_dispatch_rest_method( $c, $rest_method );
91}
92
93sub _dispatch_rest_method {
94 my $self = shift;
95 my $c = shift;
96 my $rest_method = shift;
d5252c20 97 my $req = $c->request;
295fe4d5 98
99 my $controller = $c->component( $self->class );
100
3faede66 101 my ($code, $name);
679978b1 102
3c4306f2 103 # Execute normal 'foo' action.
d5252c20 104 $c->execute( $self->class, $self, @{ $req->args } );
3c4306f2 105
3faede66 106 # Common case, for foo_GET etc
7656dd12 107 if ( $code = $controller->action_for($rest_method) ) {
d5252c20 108 return $c->forward( $code, $req->args ); # Forward to foo_GET if it's an action
82b48c61 109 }
110 elsif ($code = $controller->can($rest_method)) {
a34f2309 111 $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
256c894f 112 }
256c894f 113
d5252c20 114 # Generic handling for foo_*
3faede66 115 if (!$code) {
d5252c20 116 my $code_action = {
117 OPTIONS => sub {
118 $name = $rest_method;
119 $code = sub { $self->_return_options($self->name, @_) };
120 },
e566b7c7 121 HEAD => sub {
122 $rest_method =~ s{_HEAD$}{_GET}i;
123 $self->_dispatch_rest_method($c, $rest_method);
124 },
d5252c20 125 default => sub {
126 # Otherwise, not implemented.
127 $name = $self->name . "_not_implemented";
128 $code = $controller->can($name) # User method
129 # Generic not implemented
130 || sub { $self->_return_not_implemented($self->name, @_) };
131 },
132 };
e566b7c7 133 my $respond = ($code_action->{$req->method}
134 || $code_action->{'default'})->();
135 return $respond unless $name;
679978b1 136 }
256c894f 137
3faede66 138 # localise stuff so we can dispatch the action 'as normal, but get
139 # different stats shown, and different code run.
3c4306f2 140 # Also get the full path for the action, and make it look like a forward
3faede66 141 local $self->{code} = $code;
3c4306f2 142 my @name = split m{/}, $self->reverse;
143 $name[-1] = $name;
144 local $self->{reverse} = "-> " . join('/', @name);
d34c067a 145
d5252c20 146 $c->execute( $self->class, $self, @{ $req->args } );
d34c067a 147}
148
149sub _get_allowed_methods {
d2d93101 150 my ( $self, $controller, $c, $name ) = @_;
679978b1 151 my $class = ref($controller) ? ref($controller) : $controller;
846b6b07 152 my $methods = Class::Inspector->methods($class);
153 return map { /^$name\_(.+)$/ } @$methods;
679978b1 154};
155
156sub _return_options {
3faede66 157 my ( $self, $method_name, $controller, $c) = @_;
d2d93101 158 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
679978b1 159 $c->response->content_type('text/plain');
160 $c->response->status(200);
161 $c->response->header( 'Allow' => \@allowed );
ffcae141 162 $c->response->body(q{});
d34c067a 163}
164
165sub _return_not_implemented {
3faede66 166 my ( $self, $method_name, $controller, $c ) = @_;
d34c067a 167
d2d93101 168 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
7ad87df9 169 $c->response->content_type('text/plain');
170 $c->response->status(405);
eccb2137 171 $c->response->header( 'Allow' => \@allowed );
172 $c->response->body( "Method "
173 . $c->request->method
174 . " not implemented for "
679978b1 175 . $c->uri_for( $method_name ) );
7ad87df9 176}
177
24748286 178__PACKAGE__->meta->make_immutable;
179
256c894f 1801;
7328f0ab 181
182=back
183
184=head1 SEE ALSO
185
85aa4e18 186You likely want to look at L<Catalyst::Controller::REST>, which implements a
187sensible set of defaults for a controller doing REST.
188
189This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
786c212f 190your request class. If you're writing a web application which provides RESTful
d6ece98c 191responses and still needs to accommodate web browsers, you may prefer to use
85aa4e18 192L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.
7328f0ab 193
194L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
195
5d7480da 196=head1 TROUBLESHOOTING
197
198=over 4
199
200=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
201
69ad525b 202A: Most likely, you haven't set Content-type equal to "application/json", or
203one of the accepted return formats. You can do this by setting it in your query
204accepted return formats. You can do this by setting it in your query string
205thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
5d7480da 206
10bcd217 207B<NOTE> Apache will refuse %2F unless configured otherwise.
208Make sure C<AllowEncodedSlashes On> is in your httpd.conf file in order
69ad525b 209for this to run smoothly.
5d7480da 210
69ad525b 211=back
7328f0ab 212
5cb5f6bb 213=head1 AUTHOR
214
410b24f8 215Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
5cb5f6bb 216
217Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
218
2f7533ed 219=head1 CONTRIBUTORS
398c5a1b 220
04882f72 221Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
2f7533ed 222
223John Goulah
33e5de96 224
04882f72 225Christopher Laco
33e5de96 226
04882f72 227Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
97b3cf7c 228
7580fa2b 229Hans Dieter Pearcey
230
16b5133c 231Brian Phillips E<lt>bphillips@cpan.orgE<gt>
8bf1f20e 232
410b24f8 233Dave Rolsky E<lt>autarch@urth.orgE<gt>
76a96edc 234
04882f72 235Luke Saunders
236
237Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
238
239J. Shirley E<lt>jshirley@gmail.comE<gt>
240
259c53c7 241Gavin Henry E<lt>ghenry@surevoip.co.ukE<gt>
242
8aa1a2ee 243Gerv http://www.gerv.net/
244
245Colin Newell <colin@opusvl.com>
246
5cb5f6bb 247=head1 COPYRIGHT
2f7533ed 248
259c53c7 249Copyright (c) 2006-2012 the above named AUTHOR and CONTRIBUTORS
2f7533ed 250
7328f0ab 251=head1 LICENSE
252
253You may distribute this code under the same terms as Perl itself.
254
255=cut
d34c067a 256