# NAME

Catalyst::ActionRole::Public - Mount a public url to files in your project directory.

# SYNOPSIS

    package MyApp::Controller::Root;

    use Moose;
    use MooseX::MethodAttributes;

    sub static :Local Does(Public) At(/:actionname/*) { ... }

    __PACKAGE__->config(namespace=>'');

Will create an action that from URL 'localhost/static/a/b/c/d.js' will serve
file $c->config->{root} . '/static' . '/a/b/c/d.js'.  Will also set content type, length
and Last-Modified HTTP headers as needed.  If the file does not exist, will not
match (allowing possibly other actions to match).

# DESCRIPTION

Use this actionrole to map a public facing URL attached to an action to a file
(or files) on the filesystem, off the $c->config->{root} directory.  If the file does
not exist, the action will not match.  No default 'notfound' page is created,
unlike [Plack::App::File](https://metacpan.org/pod/Plack::App::File) or [Catalyst::Plugin::Static::Simple](https://metacpan.org/pod/Catalyst::Plugin::Static::Simple).  The action
method body may be used to modify the response before finalization.

A template may be constructed to determine how we map an incoming request to
a path on the filesystem.  You have extensive control how an incoming HTTP
request maps to a file on the filesystem.  You can even use this action role
in the middle of a chained style action (although its hard to imagine the
use case for that...)

## ACTION METHOD BODY

The body of your action will be executed after we've created a filehandle to
the found file and setup the response.  You may leave it empty, or if you want
to do additional logging or work, you can. Also, you will find a stash key 
`public_file_path` has been populated with a [Path::Class](https://metacpan.org/pod/Path::Class) object which is
pointing to the found file.  The action method body will not be executed if
the file associated with the action does not exist.

# ACTION ATTRIBUTES

Actions the consume this role provide the following subroutine attributes.

## ShowDebugging

Enabled developer debugging output.  Example:

    sub myaction :Local Does(Public) ShowDebugging { ... }

If present do not surpress the extra developer mode debugging information.  Useful
if you have trouble serving files and you can't figure out why.

## At 

Used to set the action class template used to match files on the filesystem to
incoming requests.  Examples:

    package MyApp::Controller::Basic;

    use Moose;
    use MooseX::MethodAttributes;

    extends  'Catalyst::Controller';

    #localhost/basic/css => $c->config->{root} .'/basic/*'
    sub css :Local Does(Public) At(/:namespace/*) { }

    #localhost/basic/static => $c->config->{root} .'/basic/static/*'
    sub static :Local Does(Public) { }

    #localhost/basic/111/aaa/link2/333/444.txt => $c->config->{root} .'/basic/link2/333/444.txt'
    sub chainbase :Chained(/) PathPrefix CaptureArgs(1) { }

      sub link1 :Chained(chainbase) PathPart(aaa) CaptureArgs(0) { }

        sub link2 :Chained(link1) Args(2) Does(Public) { }

    #localhost/chainbase2/111/aaa/222.txt/link4/333 => $c->config->{root} . '/basic/link3/222.txt'
    sub chainbase2 :Chained(/)  CaptureArgs(1) { }

      sub link3 :Chained(chainbase2) PathPart(aaa) CaptureArgs(1) Does(Public) { }

        sub link4 :Chained(link3) Args(1)  { }

    1;

**NOTE:** You're template may be 'relative or absolute' to the $c->config->{root} value
based on if the first character in the template is '/' or not.   If it is '/'
that is an 'absolute' template which will be added to $c->config->{root}.  Generally
if you are making a template this is what you want.  However if you don't have
a '/' prepended to the start of your template (such as in At(file.txt)) we then
make your filesystem lookup relative to the action private path.  So in the
example:

    package MyApp::Controller::Basic;

    sub absolute_path :Path('/example1') Does(Public) At(/example.txt) { }
    sub relative_path :Path('/example2') Does(Public) At(example.txt) { }

Then http://localhost/example1 => $c->config->{root} . '/example.txt' but
http://localhost/example2 => $c->config->{root} . '/basic/relative\_path/example.txt'.
You may find this a useful "DWIW" when an action is linked to a particular file.

**NOTE:** The following expansions are recognized in your `At` declaration:

- :namespace

    The action namespace, determined from the containing controller.  Usually this
    is based on the Controller package name but you can override it via controller
    configuration.  For example:

        package MyApp::Controller::Foo::Bar::Baz;

    Has a namespace of 'foo/bar/baz' by default.

- :privatepath
- :private\_path

    The action private\_path value.  By default this is the namespace + the action
    name.  For example:

        package MyApp::Controller::Foo::Bar::Baz;

        sub myaction :Path('abcd') { ... }

    The action `myaction` has a private\_path of '/foo/bar/baz/myaction'.

    **NOTE:** the expansion `:private_path` is mapped to this value as well.

- actionname
- action\_name

    The name of the action (typically the subroutine name)

        sub static :Local Does(Public) At(/:actionname/*) { ... }

    In this case actionname = 'static'

- :args
- '\*'

    The arguments to the request.  For example:

        Package MyApp::Controller::Static;

        sub myfiles :Path('') Does(Public) At(/:namespace/*) { ... }

    Would map 'http://localhost/static/a/b/c/d.txt' to $c->config->{root} . '/static/a/b/c/d.txt'.

    In this case $args = \['a', 'b', 'c', 'd.txt'\]

## ContentType

Used to set the response Content-Type header and match the file extension. Example:

    sub myaction :Local Does(Public) ContentType(application/javascript) { ... }

By default we inspect the request URL extension and set a content type based on
the extension text (defaulting to 'application/octet' if we cannot determine).  If
you set this to a MIME type, we will always set the response content type based on
this.  Also, we will only match static files on the filesystem whose extensions
match the declared type.

You may declare more than one ContentType, in which case all allowed types are
permitted in the match.

# RESPONSE INFO

If we find a file we serve the filehandle directly to you plack handler, and set
a 'with\_path' value so that you can use this with something like [Plack::Middleware::XSendfile](https://metacpan.org/pod/Plack::Middleware::XSendfile).
We also set the Content-Type, Content-Length and Last-Modified headers.  If you
need to add more information before finalizing the response you may do so with
the matching action metod body.

# COOKBOOK

I often use this in a Root.pm controller like:

    package MyApp::Web::Controller::Root;

    use Moose;
    use MooseX::MethodAttributes;
    use HTTP::Exception;

    extends 'Catalyst::Controller';

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

      sub index :Chained(root) PathPart('') Args(0) {
        my ($self, $c) = @_;
      }

      sub css :Chained(root) Args Does(Public) ContentType(text/css) { } 
      sub js  :Chained(root) Args Does(Public) ContentType(application/javascript) { } 
      sub img :Chained(root) Args Does(Public) { }
      sub html :Chained(root) PathPart('') Args Does(Public) At(/:args) ContentType(text/html,text/plain) { }

    sub default :Default { HTTP::Exception->throw(404) }
    sub end :ActionClass(RenderView) { }

    __PACKAGE__->config(namespace=>'');
    __PACKAGE__->meta->make_immutable;

This sets up to let me mix my templates and static files under $c->config->{root} and in
general prevents non asset types from being accidentally posted.  I might then
have a directory of files like:

    root/
      css/
      js/
      img/
      index.html
      welcome.template

FWIW!

# AUTHOR

John Napiorkowski [email:jjnapiork@cpan.org](email:jjnapiork@cpan.org)

# SEE ALSO

[Catalyst](https://metacpan.org/pod/Catalyst), [Catalyst::Controller](https://metacpan.org/pod/Catalyst::Controller), [Plack::App::Directory](https://metacpan.org/pod/Plack::App::Directory),
[Catalyst::Controller::Assets](https://metacpan.org/pod/Catalyst::Controller::Assets).

# COPYRIGHT & LICENSE

Copyright 2015, John Napiorkowski [email:jjnapiork@cpan.org](email:jjnapiork@cpan.org)

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