The Perl Advent Calendar needs more articles for 2022. Submit your idea today!

NAME

Catalyst::ActionRole::ProvidesMedia - Delegate actions based on best mediatype match

SYNOPSIS

    package MyApp;
    use Catalyst;
  
    MyApp->request_class_traits(['Catalyst::TraitFor::Request::ContentNegotiationHelpers']);
    MyApp->setup;

    package MyApp::Controller::Example;

    use Moose;
    use MooseX::MethodAttributes;

    extends 'Catalyst::Controller';

    sub myaction :Chained(/) Does('ProvidesMedia') CaptureArgs(0) {
      my ($self, $c) = @_;
    }

      # Executed if the request accept header like prefers and accepts
      # type 'application/json'.
      sub myaction_JSON :Action { }

      # Executed if the request accept header like prefers and accepts
      # type 'text/html'.
      sub myaction_HTML :Action { }

      # Executed if none of the above types are accepted by the incoming
      # request accept header.

      sub myaction_no_match :Action {
        my ($self, $c, $matches) = @_;
        
        # There's a sane default for this, but you can override as needed.
      }

      sub next_action_in_chain_1 :Chained(myaction) Args(0) { ... }
      sub next_action_in_chain_2 :Chained(myaction) Args(0) { ... }

    __PACKAGE__->meta->make_immutable;

DESCRIPTION

In server side content negotiation you seek the provide response data based on what a client says it can accept (typically ranked in order of what is most acceptable to which is least.) This way a client can tell the server what type of representation of a resource it knows how to handle. A Server can either provide that represention or return an error state explaining that it cannot ( with some optional information about what it can provide).

Classic REST over HTTP allows for server side content negotiation over representation media type, language, encoding and character set. The Catalyst request trait Catalyst::TraitFor::Request::ContentNegotiationHelpers provides methods on the request object for helping a programmer correctly make choices based on what a client is requesting. This Actionrole provides additional sugar by allowing one to delegate to an action based on the matched media type.

NOTE: This actionrole only provides feaures over mediatypes NOT other catagories of content negotiation. However in general practice content negotiation over media types is probably the most common use case. It would be easy to add action roles that clone this one to do the same for encoding, language, etc.

USAGE

Apply the action role to an action, for example:

    sub myaction :Chained(/) Does('ProvidesMedia') CaptureArgs(1) {
      my ($self, $c, $id) = @_;
    }

When the action body completes (as long as it does not detach or generate an error) we then delegate (via $c->forward) to an action that matches the first action's method name, followed by an extension that can be matched to a media type:

    # Executed if the request accept header like prefers and accepts
    # type 'application/json'.
    sub myaction_JSON :Action { }

    # Executed if the request accept header like prefers and accepts
    # type 'text/html'.
    sub myaction_HTML :Action { }

When several possible matches exist, as in the above example, we inspect the HTTP request ACCEPT header and determine the best match. Should no match exist, we return a default 'no match' response which looks like this:

    sub myaction_no_match :Action {
      my ($self, $c, $matches) = @_;
      my $allowed = join(',', keys %{$matches||+{}});
      $c->response->status(406);
      $c->response->content_type('text/plain');
      $c->response->body("No match for Accept header.  Media types we support are: $allowed");
    }

You may override this default action by providing your own. The argument "$matches" is a hashref where the keys are matches allowed and the values are references to the matched action, that way you could for example redelegate to default.

VERSUS Catalyst::Action::REST

Catalyst::TraitFor::Request::REST (which comes with the Catalyst::Action::REST distribution) defines a method 'accepted_content_types' which returns an array of content types that the client accepts, sorted in order by inspecting the ACCEPT header. However for GET requests this is overridden and instead we return the request content-type if one exists. I'm not sure this is exactly correct.

METHODS

This role contains the following methods.

ALSO SEE

Catalyst::TraitFor::Request::ContentNegotiationHelpers, Catalyst

AUTHOR

  John Napiorkowski <jnapiork@cpan.org>
  

COPYRIGHT

Copyright (c) 2015 the above named AUTHOR

LICENSE

You may distribute this code under the same terms as Perl itself.