File Coverage

blib/lib/Catalyst/Plugin/DetachIfNotModified.pm
Criterion Covered Total %
statement 29 29 100.0
branch 5 6 83.3
condition n/a
subroutine 7 7 100.0
pod 1 1 100.0
total 42 43 97.6


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