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