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