The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Catalyst::ActionRole::MatchRequestAccepts - Dispatch actions based on HTTP Accept Header

SYNOPSIS

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') { ... }

DESCRIPTION

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.

EXAMPLE WITH CHAINED ACTIONS

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.

AUTHOR

John Napiorkowski email:jjnapiork@cpan.org

THANKS

Shout out to Florian Ragwitz <rafl@debian.org> 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.

SEE ALSO

Catalyst::ActionRole::MatchRequestMethod, Catalyst::Action::REST, Catalyst, Catalyst::Controller::ActionRole, Moose.

COPYRIGHT & LICENSE

Copyright 2011, John Napiorkowski email:jjnapiork@cpan.org

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.