The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Catalyst::Plugin::CDN;
{
  $Catalyst::Plugin::CDN::VERSION = '0.7';
}

use Moose::Role;
use HTTP::CDN;
use Path::Class;
use HTTP::Date;

=head3 EXPIRES

Approximately 10 years

=cut

use constant EXPIRES => 315_576_000;

around dispatch => sub {
    my $orig = shift;
    my ($c) = @_;

    my $cdn = $c->config->{'Plugin::CDN'}{cdn};

    # TODO - this path match should be using the configurable 'base' below
    if ( $c->req->path =~ m{ \A cdn/ (.*) \z }xms ) {
        my ($uri, $hash) = $cdn->unhash_uri($1);

        my $info = eval { $cdn->fileinfo($uri) };

        unless ( $info and $info->{hash} eq $hash ) {
            $c->res->status( 404 );
            $c->res->content_type( 'text/html' );
            return;
        }

        $c->res->status( 200 );
        $c->res->content_type( $info->{mime}->type );
        $c->res->headers->header('Last-Modified' => HTTP::Date::time2str($info->{stat}->mtime));
        $c->res->headers->header('Expires' => HTTP::Date::time2str(time + EXPIRES));
        $c->res->headers->header('Cache-Control' => 'max-age=' . EXPIRES . ', public');
        $c->res->body($cdn->filedata($uri));

        # We do this at the very end incase something goes horribly wrong beforehand
        if ( $c->log->can('abort') ) {
            $c->log->abort(1);
        }

        return;
    }
    else {
        return $orig->(@_);
    }
};

before setup_finalize => sub {
    my ($c) = @_;

    my $plugin_config = $c->config->{'Plugin::CDN'} // {};

    my $root = $plugin_config->{root} //= $c->path_to('cdn');
    my $base = $plugin_config->{base} //= '/cdn/';
    my $plugins = $plugin_config->{plugins};
    unless ( UNIVERSAL::isa($plugin_config->{plugins}, 'ARRAY') ) {
        $plugins = $plugin_config->{plugins} = undef;
    }

    $c->config->{'Plugin::CDN'}{cdn} = HTTP::CDN->new(
        root    => $root,
        base    => $base,
        ( $plugins ? (plugins => $plugins) : () ),
    );
};

=head2 cdn

This is how you link to CDN content from your Catalyst application.

  $c->cdn('style.css')

Will generate a nice unique URL for your project's cdn/style.css file based on
a hash of the file content, this URI will automatically be served up correctly.

=cut

sub cdn {
    my ($c, $uri) = @_;

    my $cdn = $c->config->{'Plugin::CDN'}{cdn};

    return $cdn->resolve($uri);
}

1;
__END__