line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Catalyst::Plugin::VersionedURI; |
2
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:YANICK'; |
3
|
|
|
|
|
|
|
# ABSTRACT: add version component to uris |
4
|
|
|
|
|
|
|
$Catalyst::Plugin::VersionedURI::VERSION = '1.2.0'; |
5
|
|
|
|
|
|
|
|
6
|
4
|
|
|
4
|
|
2702544
|
use 5.10.0; |
|
4
|
|
|
|
|
9
|
|
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
15
|
use strict; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
77
|
|
9
|
4
|
|
|
4
|
|
11
|
use warnings; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
94
|
|
10
|
|
|
|
|
|
|
|
11
|
4
|
|
|
4
|
|
13
|
use Moose::Role; |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
33
|
|
12
|
4
|
|
|
4
|
|
13877
|
use URI::QueryParam; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
81
|
|
13
|
4
|
|
|
4
|
|
2818
|
use Path::Tiny; |
|
4
|
|
|
|
|
29002
|
|
|
4
|
|
|
|
|
1868
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our @uris; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
sub initialize_uri_regex { |
18
|
4
|
|
|
4
|
0
|
5
|
my $self = shift; |
19
|
|
|
|
|
|
|
|
20
|
4
|
0
|
33
|
|
|
15
|
if ( not exists $self->config->{'Plugin::VersionedURI'} |
21
|
|
|
|
|
|
|
and exists $self->config->{'VersionedURI'} ) { |
22
|
0
|
|
|
|
|
0
|
warn <<'END_DEPRECATION'; |
23
|
|
|
|
|
|
|
Catalyst::Plugin::VersionedURI configuration set under 'VersionedURI' is deprecated |
24
|
|
|
|
|
|
|
Please move your configuration to 'Plugin::VersionedURI' |
25
|
|
|
|
|
|
|
END_DEPRECATION |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
$self->config->{'Plugin::VersionedURI'} |
28
|
0
|
|
|
|
|
0
|
= $self->config->{'VersionedURI'}; |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my $conf = $self->config->{'Plugin::VersionedURI'}{uri} |
33
|
4
|
|
50
|
|
|
237
|
|| '/static'; |
34
|
|
|
|
|
|
|
|
35
|
4
|
50
|
|
|
|
180
|
@uris = ref($conf) ? @$conf : ( $conf ); |
36
|
4
|
|
|
|
|
42
|
s#^/## for @uris; |
37
|
4
|
|
|
|
|
26
|
s#(?<!/)$#/# for @uris; |
38
|
|
|
|
|
|
|
|
39
|
4
|
|
|
|
|
14
|
return join '|', @uris; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub versioned_uri_regex { |
43
|
13
|
|
|
13
|
0
|
20
|
my $self = shift; |
44
|
13
|
|
|
|
|
31
|
state $uris_re = $self->initialize_uri_regex; |
45
|
13
|
|
|
|
|
54
|
return $uris_re; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub uri_version { |
49
|
8
|
|
|
8
|
0
|
14
|
my ( $self, $uri ) = @_; |
50
|
|
|
|
|
|
|
|
51
|
8
|
|
|
|
|
58
|
state $app_version = $self->VERSION; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
return $app_version |
54
|
8
|
100
|
|
|
|
36
|
unless state $mtime = $self->config->{'Plugin::VersionedURI'}{mtime}; |
55
|
|
|
|
|
|
|
|
56
|
2
|
|
|
|
|
51
|
state %cache; # Would be nice to make this shared across processes |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# Return the cached value if there is one |
59
|
2
|
50
|
|
|
|
9
|
return $cache{$uri} if defined $cache{$uri}; |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# Strip off the request base, so we can find the file referenced |
62
|
2
|
|
|
|
|
9
|
( my $file = $uri ) =~ s/^\Q@{[ $self->req->base ]}\E//; |
|
2
|
|
|
|
|
5
|
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Search the include_path(s) provided in config or the |
65
|
|
|
|
|
|
|
# project root if no include_path was specified |
66
|
|
|
|
|
|
|
state $include_paths = |
67
|
|
|
|
|
|
|
$self->config->{'Plugin::VersionedURI'}{include_path} // |
68
|
2
|
|
50
|
|
|
126
|
[ $self->config->{root} ]; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Return/cache the file's mtime |
71
|
2
|
|
|
|
|
92
|
for my $path ( map { path( $_, $file ) } @$include_paths ) { |
|
2
|
|
|
|
|
8
|
|
72
|
2
|
100
|
|
|
|
144
|
return $cache{$uri} = $path->stat->mtime if -f $path; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# No file was found. Store and return the application's version as |
76
|
|
|
|
|
|
|
# a fallback. |
77
|
1
|
|
|
|
|
40
|
return $cache{$uri} = $app_version; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
around uri_for => sub { |
81
|
|
|
|
|
|
|
my ( $code, $self, @args ) = @_; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
my $uri = $self->$code(@args); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
my $uris_re = $self->versioned_uri_regex |
86
|
|
|
|
|
|
|
or return $uri; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
return $uri unless $uri->path =~ m#^/($uris_re)#; |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
my $version = $self->uri_version( $uri, @args ); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
if ( state $in_path = $self->config->{'Plugin::VersionedURI'}{in_path} ) { |
93
|
|
|
|
|
|
|
my $path = $uri->path; |
94
|
|
|
|
|
|
|
$path =~ s#^/($uris_re)#${1}v$version/#; |
95
|
|
|
|
|
|
|
$uri->path( $path ); |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
else { |
98
|
|
|
|
|
|
|
state $version_name = $self->config->{'Plugin::VersionedURI'}{param} || 'v'; |
99
|
|
|
|
|
|
|
$uri->query_param( $version_name => $version ); |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
return $uri; |
103
|
|
|
|
|
|
|
}; |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
1; |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
__END__ |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=pod |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=encoding UTF-8 |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head1 NAME |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Catalyst::Plugin::VersionedURI - add version component to uris |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head1 VERSION |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
version 1.2.0 |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head1 SYNOPSIS |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
In your config file: |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
<Plugin::VersionedURI> |
126
|
|
|
|
|
|
|
uri static/ |
127
|
|
|
|
|
|
|
mtime 0 |
128
|
|
|
|
|
|
|
</Plugin::VersionedURI> |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
In C<MyApp.pm>: |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
package MyApp; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
use Catalyst qw/ VersionedURI /; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
In the Apache config: |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
<Directory /home/myapp/static> |
139
|
|
|
|
|
|
|
ExpiresActive on |
140
|
|
|
|
|
|
|
ExpiresDefault "access plus 1 year" |
141
|
|
|
|
|
|
|
</Directory> |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head1 DESCRIPTION |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
C<Catalyst::Plugin::VersionedURI> adds a versioned component |
146
|
|
|
|
|
|
|
to uris returned by C<uri_for()> matching a given set of regular expressions provided in |
147
|
|
|
|
|
|
|
the configuration file. E.g., |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$c->uri_for( '/static/images/foo.png' ); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
will, with the configuration used in the L<SYNOPSIS> return |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
/static/images/foo.png?v=1.2.3 |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
This can be useful, mainly, to have the |
156
|
|
|
|
|
|
|
static files of a site magically point to a new location upon new |
157
|
|
|
|
|
|
|
releases of the application, and thus bypass previously set expiration times. |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
The versioned component of the uri resolves to the version of the application. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head1 CONFIGURATION |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 uri |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
The plugin's accepts any number of C<uri> configuration elements, which are |
166
|
|
|
|
|
|
|
taken as regular expressions to be matched against the uris. The regular |
167
|
|
|
|
|
|
|
expressions are implicitly anchored at the beginning of the uri, and at the |
168
|
|
|
|
|
|
|
end by a '/'. If not given, defaults to C</static>. |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head2 mtime |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
If set to a true value, the plugin will use the file's modification time for |
173
|
|
|
|
|
|
|
versioning instead of the application's version. The modification time is |
174
|
|
|
|
|
|
|
checked only once for each file. If a file is changed after the application is |
175
|
|
|
|
|
|
|
started, the old version number will continue to be used. Checking the |
176
|
|
|
|
|
|
|
modification time on each uri, each time it is served, would result in |
177
|
|
|
|
|
|
|
considerable additional overhead. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head2 include_path |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
A list of directories to search for files if you specify the C<mtime> flag. |
182
|
|
|
|
|
|
|
If no file is found, the application version is used. Defaults to |
183
|
|
|
|
|
|
|
C<MyApp->config->{root}>. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=head2 in_path |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
If true, add the versioned element as part of the path (right after the |
188
|
|
|
|
|
|
|
matched uri). If false, the versioned element is added as a query parameter. |
189
|
|
|
|
|
|
|
For example, if we match on '/static', the base uri '/static/foo.png' will resolve to |
190
|
|
|
|
|
|
|
'/static/v1.2.3/foo.png' if 'in_path' is I<true>, and '/static/foo.png?v=1.2.3' |
191
|
|
|
|
|
|
|
if I<false>. |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
Defaults to false. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head2 param |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
Name of the parameter to be used for the versioned element. Defaults to 'v'. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
Not used if I<in_path> is set to I<true>. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=head1 WEB SERVER-SIDE CONFIGURATION |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Of course, the redirection to a versioned uri is a sham |
204
|
|
|
|
|
|
|
to fool the browsers into refreshing their cache. If the path is |
205
|
|
|
|
|
|
|
modified because I<in_path> is set to I<true>, it's typical to |
206
|
|
|
|
|
|
|
configure the front-facing web server to point back to |
207
|
|
|
|
|
|
|
the same back-end directory. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=head2 Apache |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
To munge the paths back to the base directory, the Apache |
212
|
|
|
|
|
|
|
configuration can look like: |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
<Directory /home/myapp/static> |
215
|
|
|
|
|
|
|
RewriteEngine on |
216
|
|
|
|
|
|
|
RewriteRule ^v[0123456789._]+/(.*)$ /myapp/static/$1 [PT] |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
ExpiresActive on |
219
|
|
|
|
|
|
|
ExpiresDefault "access plus 1 year" |
220
|
|
|
|
|
|
|
</Directory> |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 YOU BROKE MY DEVELOPMENT SERVER, YOU INSENSITIVE CLOD! |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
If I<in_path> is set to I<true>, while the plugin is working fine with a web-server front-end, it's going to seriously cramp |
225
|
|
|
|
|
|
|
your style if you use, for example, the application's standalone server, as |
226
|
|
|
|
|
|
|
now all the newly-versioned uris are not going to resolve to anything. |
227
|
|
|
|
|
|
|
The obvious solution is, well, fairly obvious: remove the VersionedURI |
228
|
|
|
|
|
|
|
configuration stanza from your development configuration file. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
If, for whatever reason, you absolutly want your application to deal with the versioned |
231
|
|
|
|
|
|
|
paths with or without the web server front-end, you can use |
232
|
|
|
|
|
|
|
L<Catalyst::Controller::VersionedURI>, which will undo what |
233
|
|
|
|
|
|
|
C<Catalyst::Plugin::VersionedURI> toiled to shoe-horn in. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=head1 THANKS |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
Mark Grimes, Alexander Hartmaier. |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head1 SEE ALSO |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=over |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=item Blog entry introducing the module: L<http://babyl.dyndns.org/techblog/entry/versioned-uri>. |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=item L<Catalyst::Controller::VersionedURI> |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=back |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=head1 AUTHOR |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
Yanick Champoux <yanick@babyl.dyndns.org> |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
This software is copyright (c) 2011 by Yanick Champoux. |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
258
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=cut |