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
|
|
|
|
|
|
|
|