File Coverage

blib/lib/Catalyst/Plugin/DetachIfNotModified.pm
Criterion Covered Total %
statement 31 31 100.0
branch 5 6 83.3
condition n/a
subroutine 8 8 100.0
pod 1 1 100.0
total 45 46 97.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::DetachIfNotModified;
2              
3 1     1   1798277 use v5.8;
  1         11  
4              
5             # ABSTRACT: Short-circuit requests with If-Modified-Since headers
6              
7 1     1   5 use Moose::Role;
  1         3  
  1         8  
8              
9 1     1   5141 use HTTP::Headers 5.18;
  1         26  
  1         26  
10 1     1   5 use HTTP::Status qw/ HTTP_NOT_MODIFIED /;
  1         1  
  1         98  
11 1     1   6 use List::Util qw/ max /;
  1         2  
  1         72  
12 1     1   7 use Ref::Util qw/ is_blessed_ref /;
  1         1  
  1         84  
13              
14             # RECOMMEND PREREQ: Plack::Middleware::ConditionalGET
15             # RECOMMEND PREREQ: Ref::Util::XS
16              
17 1     1   7 use namespace::autoclean;
  1         3  
  1         8  
18              
19             our $VERSION = 'v0.2.1';
20              
21              
22             sub detach_if_not_modified_since {
23 4     4 1 99520 my ($c, @times) = @_;
24              
25 4 50       13 my @epochs = grep defined, map { is_blessed_ref($_) ? $_->epoch : $_ } @times;
  4         25  
26 4         32 my $time = max(@epochs);
27 4         16 my $res = $c->res;
28 4         120 $res->headers->last_modified($time);
29              
30 4         872 my $hdr = $c->req->headers;
31 4 100       220 if (my $since = $hdr->if_modified_since) {
32 3 100       291 if ($since >= $time) {
33 2         11 $res->code(HTTP_NOT_MODIFIED);
34 2         193 $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.2.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 SEE ALSO
116              
117             L<Catalyst>
118              
119             L<Catalyst::Plugin::Cache::HTTP::Preempt>
120              
121             L<Plack::Middleware::ConditionalGET>
122              
123             L<RFC 7232 Section 3.3|https://tools.ietf.org/html/rfc7232#section-3.3>
124              
125             =head1 SOURCE
126              
127             The development version is on github at L<https://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified>
128             and may be cloned from L<git://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified.git>
129              
130             =head1 BUGS
131              
132             Please report any bugs or feature requests on the bugtracker website
133             L<https://github.com/robrwo/Catalyst-Plugin-DetachIfNotModified/issues>
134              
135             When submitting a bug or request, please include a test-file or a
136             patch to an existing test-file that illustrates the bug or desired
137             feature.
138              
139             =head1 AUTHOR
140              
141             Robert Rothenberg <rrwo@cpan.org>
142              
143             This module is based on code created for Science Photo Library
144             L<https://www.sciencephoto.com>.
145              
146             =head1 COPYRIGHT AND LICENSE
147              
148             This software is Copyright (c) 2020 by Robert Rothenberg.
149              
150             This is free software, licensed under:
151              
152             The Artistic License 2.0 (GPL Compatible)
153              
154             =cut