File Coverage

blib/lib/Catalyst/Plugin/VersionedURI.pm
Criterion Covered Total %
statement 39 41 95.1
branch 6 10 60.0
condition 3 7 42.8
subroutine 9 9 100.0
pod 0 3 0.0
total 57 70 81.4


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