Catalyst::ActionRole::MatchRequestAccepts - Dispatch actions based on HTTP Accept Header
package MyApp::Controller::Foo;
use Moose;
use namespace::autoclean;
BEGIN {
extends 'Catalyst::Controller::ActionRole';
}
## Add the ActionRole to all the Controller's actions. You can also
## selectively add the ActionRole with the :Does action attribute or in
## controller configuration. See Catalyst::Controller::ActionRole for
## more information.
__PACKAGE__->config(
action_roles => ['MatchRequestAccepts'],
);
## Match for incoming requests with HTTP Accepts: plain/html
sub for_html : Path('foo') Accept('plain/html') { ... }
## Match for incoming requests with HTTP Accepts: application/json
sub for_json : Path('foo') Accept('application/json') { ... }
Lets you specify a match for the HTTP Accept
Header, which is provided by
the Catalyst $ctx->request->headers
object. You might wish to instead
look at Catalyst::Action::REST if you are doing complex applications that
match different incoming request types, but if you are very fussy about how
your actions match, or if you are doing some simple ajaxy bits you might like
to use this instead of a full on package (like Catalyst::Action::REST is.)
Currently the match performed is a pure equalty, no attempt to guess or infer
matches based on similarity are done. If you need to match several variations
you can specify all the variations with multiple attribute declarations. Right
now we don't support expression based matching, such as text/*
, although
adding such would probably not be very hard (although I don't want to make the
logic here slow down our dispatch matching too much).
Please note that if you specify multiple Accept
attributes on a single
action, those will be matched via an OR condition and not an AND condition. In
other words we short circuit match the first action with at least one of the
Accept
values appearing in the requested HTTP headers. I think this is
correct since I imagine the purpose of multiple Accept
attributes would be
to match several acceptable variations of a given type, not to match any of
several unrelated types. However if you have a use case for this please let
me know.
If an action consumes this role, but no Accept
attributes are found, the
action will simple accept all types.
For debugging purposes, if the Catalyst debug flag is enabled, you can
override the HTTP Accept header with the http-accept
query parameter. This
makes it easy to force detect in testing or in your browser. This feature is
NOT available when the debug flag is off.
Also, as usual you can specify attributes and information in th configuration of your Catalyst::Controller subclass:
## Set the 'our_action_json' action to consume this ActionRole. In this
## example GET '/json' would only match if the client request HTTP included
## an Accept: application/json.
__PACKAGE__->config(
action_roles => ['MatchRequestAccepts'],
action => {
our_action_json => { Path => 'json', Accept => 'application/json' },
});
## GET '/foo' will dispatch to either action 'our_action_json' or action
## 'our_action_html' depending on the incoming HTTP Accept.
__PACKAGE__->config(
action => {
our_action_json => {
Does => 'MatchRequestAccepts',
Path => 'foo',
Accept => 'application/json',
},
our_action_html => {
Does => 'MatchRequestAccepts',
Path => 'foo',
Accept => 'text/html',
},
});
There's a functioning Catalyst example application in the test directory for your review as well.
The following example uses Catalyst chaining to match one of two different
types of Accept
headers, and to return the correct HTTP error message if
nothing is matched. This is probably my most common use pattern.
package MyApp::Web::Controller::Chained;
use Moose;
use namespace::autoclean;
BEGIN {
extends 'Catalyst::Controller::ActionRole';
}
__PACKAGE__->config(
action_roles => ['MatchRequestAccepts'],
);
sub root : Chained('/') PathPrefix CaptureArgs(0) {}
sub text_html
: Chained('root') PathPart('') Accept('text/html') Args(0)
{
my ($self, $ctx) = @_;
$ctx->response->body('text_html');
}
sub json
: Chained('root') PathPart('') Accept('application/json') Args(0)
{
my ($self, $ctx) = @_;
$ctx->response->body('json');
}
sub not_accepted
: Chained('root') PathPart('') Args
{
my ($self, $ctx) = @_;
$ctx->response->status(406);
$ctx->response->body('error_not_accepted');
}
__PACKAGE__->meta->make_immutable;
In the given example, a GET
request to http://www.myapp.com/chained
will
match for Accept
values of HTML and JSON, and will return a status 406 error
to all other requests.
John Napiorkowski email:[email protected]
Shout out to Florian Ragwitz [email protected] for providing such a great example in Catalyst::ActionRole::MatchRequestMethod. Source code and tests are pretty much copied from his stuff.
I also cargo culted a chuck of code from Catalyst::TraitFor::Request::REST which let me parse HTTP Accept lines.
Catalyst::ActionRole::MatchRequestMethod, Catalyst::Action::REST, Catalyst, Catalyst::Controller::ActionRole, Moose.
Copyright 2011, John Napiorkowski email:[email protected]
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.