File Coverage

blib/lib/Catalyst/Plugin/DetachIfNotModified.pm
Criterion Covered Total %
statement 31 31 100.0
branch 5 6 83.3
condition 1 2 50.0
subroutine 8 8 100.0
pod 1 1 100.0
total 46 48 95.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::DetachIfNotModified;
2              
3 1     1   2186761 use v5.14;
  1         13  
4              
5             # ABSTRACT: Short-circuit requests with If-Modified-Since headers
6              
7 1     1   7 use Moose::Role;
  1         3  
  1         10  
8              
9 1     1   5989 use HTTP::Headers 5.18;
  1         27  
  1         43  
10 1     1   9 use HTTP::Status qw/ HTTP_NOT_MODIFIED /;
  1         2  
  1         66  
11 1     1   8 use List::Util qw/ max /;
  1         2  
  1         71  
12 1     1   9 use Ref::Util qw/ is_blessed_ref /;
  1         3  
  1         98  
13              
14             # RECOMMEND PREREQ: Plack::Middleware::ConditionalGET
15             # RECOMMEND PREREQ: Ref::Util::XS
16              
17 1     1   8 use namespace::autoclean;
  1         2  
  1         9  
18              
19             our $VERSION = 'v0.3.1';
20              
21              
22             sub detach_if_not_modified_since {
23 4     4 1 123439 my ($c, @times) = @_;
24              
25 4 50       49 my @epochs = grep defined, map { is_blessed_ref($_) ? $_->epoch : $_ } @times;
  4         31  
26 4   50     31 my $time = max(@epochs) // return;
27 4         22 my $res = $c->res;
28 4         172 $res->headers->last_modified($time);
29              
30 4         1156 my $hdr = $c->req->headers;
31 4 100       266 if (my $since = $hdr->if_modified_since) {
32 3 100       387 if ($since >= $time) {
33 2         18 $res->code(HTTP_NOT_MODIFIED);
34 2         266 $c->detach;
35             }
36             }
37             }
38              
39              
40             1;
41              
42             __END__
43              
44             =pod
45              
46             =encoding UTF-8
47              
48             =head1 NAME
49              
50             Catalyst::Plugin::DetachIfNotModified - Short-circuit requests with If-Modified-Since headers
51              
52             =head1 VERSION
53              
54             version v0.3.1
55              
56             =head1 SYNOPSIS
57              
58             In your Catalyst class:
59              
60             use Catalyst qw/
61             DetachIfNotModified
62             /;
63              
64             In a controller method:
65              
66             my $item = ...
67              
68             $c->detach_if_not_modified_since( $item->timestamp );
69              
70             # Do some CPU-intensive stuff or generate response body here.
71              
72             =head1 DESCRIPTION
73              
74             This plugin will allow your L<Catalyst> app to handle requests with
75             C<If-Modified-Since> headers.
76              
77             If the content of a web page has not been modified since a given date,
78             you can quickly bail out and avoid generating a web page that you do
79             not need to.
80              
81             This can improve the performance of your website.
82              
83             This should be used with L<Plack::Middleware::ConditionalGET>.
84              
85             =head1 METHODS
86              
87             =head2 detach_if_not_modified_since
88              
89             $c->detach_if_not_modified_since( @timestamps );
90              
91             This sets the C<Last-Modified> header in the response to the
92             maximum timestamp, and checks if the request contains a
93             C<If-Modified-Since> header that not less than the maximum timestamp. If it
94             does, then it will set the response status code to C<304> (Not
95             Modified) and detach.
96              
97             The C<@timestamps> is a list of unix epochs or objects with an C<epoch>
98             method, such as a L<DateTime> object.
99              
100             This should only be used with GET or HEAD requests.
101              
102             If you later need to reset the C<Last-Modified> header after calling
103             this method, you can use
104              
105             $c->res->headers->remove_header('Last-Modified');
106              
107             =head1 CAVEATS
108              
109             Be careful when aggregating a collection of objects into a single
110             timestamp, e.g. the maximum timestamp from a list. If a member is
111             removed from that collection, then the maximum timestamp won't be
112             affected, and the result is that an outdated web page may be cached by
113             user agents.
114              
115             =head1 SUPPORT FOR OLDER PERL VERSIONS
116              
117             Since v0.3.0, the this module requires Perl v5.14 or later.
118              
119             Future releases may only support Perl versions released in the last ten years.
120              
121             If you need this module on earlier Perls, please use one of the v0.2.x
122             versions of this module. Significant bug or security fixes may be
123             backported to those versions.
124              
125             =head1 SEE ALSO
126              
127             L<Catalyst>
128              
129             L<Catalyst::Plugin::Cache::HTTP::Preempt>
130              
131             L<Plack::Middleware::ConditionalGET>
132              
133             L<RFC 7232 Section 3.3|https://tools.ietf.org/html/rfc7232#section-3.3>
134              
135             =head1 SOURCE
136              
137             The development version is on github at L<https://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified>
138             and may be cloned from L<git://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified.git>
139              
140             =head1 BUGS
141              
142             Please report any bugs or feature requests on the bugtracker website
143             L<https://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified/issues>
144              
145             When submitting a bug or request, please include a test-file or a
146             patch to an existing test-file that illustrates the bug or desired
147             feature.
148              
149             =head1 AUTHOR
150              
151             Robert Rothenberg <rrwo@cpan.org>
152              
153             This module is based on code created for Science Photo Library
154             L<https://www.sciencephoto.com>.
155              
156             =head1 COPYRIGHT AND LICENSE
157              
158             This software is Copyright (c) 2020-2023 by Robert Rothenberg.
159              
160             This is free software, licensed under:
161              
162             The Artistic License 2.0 (GPL Compatible)
163              
164             =cut