File Coverage

blib/lib/Dancer/Plugin/CDN.pm
Criterion Covered Total %
statement 45 45 100.0
branch 4 6 66.6
condition 7 13 53.8
subroutine 10 10 100.0
pod n/a
total 66 74 89.1


line stmt bran cond sub pod time code
1             package Dancer::Plugin::CDN;
2             $Dancer::Plugin::CDN::VERSION = '1.002';
3 2     2   55976 use warnings;
  2         3  
  2         64  
4 2     2   7 use strict;
  2         3  
  2         37  
5              
6 2     2   610 use Dancer ':syntax';
  2         176970  
  2         10  
7 2     2   1491 use Dancer::Plugin;
  2         2177  
  2         110  
8 2     2   788 use HTTP::CDN;
  2         775035  
  2         69  
9 2     2   14 use HTTP::Date;
  2         2  
  2         128  
10              
11              
12 2     2   8 use constant EXPIRES => 315_576_000; # approx 10 years
  2         2  
  2         959  
13              
14              
15             my $cdn;
16              
17              
18             register cdn_url => sub {
19 2     2   12937 my($path) = @_;
20              
21 2   66     8 $cdn ||= _init_cdn();
22 2         6219 return $cdn->resolve($path);
23             };
24              
25              
26             sub _send_cdn_content {
27 3   33 3   12397 $cdn ||= _init_cdn();
28 3         9 my ($uri, $hash) = $cdn->unhash_uri(splat);
29              
30 3         515 my $info = eval { $cdn->fileinfo($uri) };
  3         8  
31              
32 3 100 66     1687 unless ( $info and $info->{hash} eq $hash ) {
33 1         4 status 'not_found';
34 1         23 return 'Not Found';
35             }
36              
37 2         9 status( 200 );
38 2         49 content_type( $info->{mime}->type );
39 2         182 header('Last-Modified' => HTTP::Date::time2str($info->{stat}->mtime));
40 2         105 header('Expires' => HTTP::Date::time2str(time + EXPIRES));
41 2         69 header('Cache-Control' => 'max-age=' . EXPIRES . ', public');
42 2         52 return $cdn->filedata($uri);
43              
44             }
45              
46              
47             sub _init_cdn {
48 1     1   4 my $setting = plugin_setting();
49              
50 1   50     15 my $base = $setting->{base} || '/cdn/';
51 1   50     5 my $root = $setting->{root} || setting('public') || 'public';
52              
53 1 50       16 die "CDN root directory does not exist: '$root'\n" unless -d $root;
54              
55 1         4 my %args = (
56             root => $root,
57             base => $base,
58             );
59              
60 1 50       6 if( my $plugins = $setting->{plugins} ) {
61 1         2 $args{plugins} = $plugins;
62             }
63              
64 1         13 return HTTP::CDN->new( %args );
65             }
66              
67              
68             { # Set up route handler to serve responses to rewritten URLS
69              
70             my $base = plugin_setting->{base} || '/cdn/';
71             my($prefix) = $base =~ m{^(?:https?://[^/]+)?(.*)$};
72             my $route = qr/${prefix}(.*)$/;
73              
74             get $route => \&_send_cdn_content;
75             }
76              
77              
78             hook 'before_template_render' => sub {
79             my $tokens = shift;
80             $tokens->{'cdn_url'} = \&cdn_url;
81             };
82              
83              
84             register_plugin;
85              
86             1;
87              
88              
89             =head1 NAME
90              
91             Dancer::Plugin::CDN - Serve static files with unique URLs and far-future expiry
92              
93              
94             =head1 SYNOPSIS
95              
96             use Dancer::Plugin::CDN;
97              
98             # Generate a CDN URL for a static file
99              
100             my $style_sheet = cdn_url('css/style.css'); # e.g.: "/cdn/css/style.B97EA317759D.css"
101              
102             # Or, in a TT2 template:
103              
104             <link rel="stylesheet" href="[% cdn_url('css/style.css') %]" >
105              
106             =head1 DESCRIPTION
107              
108             This plugin generates URLs for your static files that include a content hash so
109             that the URLs will change when the content changes. The plugin also arranges
110             for the files to be served with cache-control and expiry headers to enable the
111             content to be cached by the browser.
112              
113             The real work is performed by the L<HTTP::CDN> module which can also be
114             configured with plugins to minify CSS/JS on-the-fly and also to render LESS to
115             CSS.
116              
117              
118             =head1 FUNCTIONS
119              
120             A single helper function is exported into the caller's namespace. This
121             function is also made available to be called from within your TT2 templates
122             (probably won't work with other template engines).
123              
124             =head2 cdn_url
125              
126             Takes a pathname to a static file (e.g.: C<css/style.css>) and returns a URL
127             with content-hash and configurable CDN prefix added (e.g.:
128             C</cdn/css/style.B97EA317759D.css>);
129              
130              
131             =head1 CONFIGURATION
132              
133             You do not need to configure this module although you may choose to add a
134             section like this to your Dancer config file:
135              
136             plugins:
137             CDN:
138             root: "static"
139             base: "/cdn/"
140             plugins:
141             - "CSS"
142             - "CSS::Minifier::XS"
143              
144             The C<root> setting defines where the static source files can be found. By
145             default this points to Dancer's standard C<public> directory.
146              
147             The C<base> setting is the prefix which will be added to each URL. The default
148             value is C</cdn/>. The plugin will also use this prefix to set up a route
149             handler for serving the static content. This setting can include a hostname
150             e.g.:
151              
152             base: "http://static.example.com/cdn/"
153              
154             The C<plugins> setting should be an array of HTTP::CDN plugin names. The
155             default setting is to enable only the HTTP::CDN::CSS plugin which rewrites
156             URLs (e.g.: for image files) to the CDN scheme.
157              
158              
159             =head1 SUPPORT
160              
161             =over 4
162              
163             =item * Bug reports and feature requests
164              
165             L<https://github.com/grantm/Dancer-Plugin-CDN/issues>
166              
167             =item * Source Code Repository
168              
169             L<http://github.com/grantm/Dancer-Plugin-CDN/>
170              
171             =back
172              
173              
174             =head1 COPYRIGHT AND LICENSE
175              
176             Copyright 2012 Grant McLean C<< <grantm@cpan.org> >>
177              
178             This program is free software; you can redistribute it and/or modify it
179             under the terms of either: the GNU General Public License as published
180             by the Free Software Foundation; or the Artistic License.
181              
182             See http://dev.perl.org/licenses/ for more information.
183              
184              
185             =cut
186              
187             1;
188