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