File Coverage

blib/lib/LWP/UserAgent/Role/CHICaching.pm
Criterion Covered Total %
statement 14 14 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 19 19 100.0


line stmt bran cond sub pod time code
1             package LWP::UserAgent::Role::CHICaching;
2              
3 3     3   8455 use 5.006000;
  3         12  
4 3     3   511 use CHI;
  3         32028  
  3         55  
5 3     3   12 use Moo::Role;
  3         4  
  3         23  
6 3     3   1706 use Types::Standard qw(Str Bool Ref InstanceOf);
  3         99539  
  3         31  
7 3     3   3304 use Types::URI -all;
  3         248019  
  3         52  
8              
9             our $AUTHORITY = 'cpan:KJETILK';
10             our $VERSION = '0.04';
11              
12             =pod
13              
14             =encoding utf-8
15              
16             =head1 NAME
17              
18             LWP::UserAgent::Role::CHICaching - A role to allow LWP::UserAgent to cache with CHI
19              
20             =head1 SYNOPSIS
21              
22             Compose it into a class, e.g.
23              
24             package LWP::UserAgent::MyCacher;
25             use Moo;
26             extends 'LWP::UserAgent';
27             with 'LWP::UserAgent::Role::CHICaching',
28             'LWP::UserAgent::Role::CHICaching::SimpleKeyGen',
29             'LWP::UserAgent::Role::CHICaching::SimpleMungeResponse';
30              
31              
32             =head1 DESCRIPTION
33              
34             This is a role for creating caching user agents. When the client makes
35             a request to the server, sometimes the response should be cached, so
36             that no actual request has to be sent at all, or possibly just a
37             request to validate the cache. HTTP 1.1 defines how to do this. This
38             role makes it possible to use the very flexible L<CHI> module to
39             manage such a cache. See L<LWP::UserAgent::CHICaching> for a finished
40             class you can use.
41              
42              
43             =head2 Attributes and Methods
44              
45             =over
46              
47             =item C<< cache >>
48              
49             Used to set the C<CHI> object to be used as cache in the constructor.
50              
51              
52              
53             =item C<< request_uri >>
54              
55             The Request-URI of the request. When set, it will clear the C<key>,
56             but should probably be left to be used internally for now.
57              
58             =item C<< request >>
59              
60             Wrapping L<LWP::UserAgent>'s request method.
61              
62             =item C<< is_shared >>
63              
64             A boolean value to set whether the cache can be shared. The default is
65             that it is.
66              
67             =item C<< heuristics_opts >>
68              
69             A hashref that is passed to the C<freshness_lifetime> method of
70             L<HTTP::Response>, and used to determine the behaviour of the
71             heuristic lifetime. By default, heuristic freshness lifetime is off,
72             only standards-compliant freshness lifetime (i.e. based on the
73             C<Cache-Control> and C<Expires> headers) are used.
74              
75             =back
76              
77             =head2 Implemented elsewhere
78              
79             The following are required by this role, but implemented
80             elsewhere. See L<LWP::UserAgent::Role::CHICaching::SimpleKeyGen> and
81             L<LWP::UserAgent::Role::CHICaching::SimpleMungeResponse> for further explanations.
82              
83             =over
84              
85             =item C<< key >>, C<< clear_key >>
86              
87             The key to use for a response.
88              
89             =item C<< cache_vary($response) >>
90              
91             A method that returns true if the response may be cached even if it
92             contains a C<Vary> header, false otherwise. The L<HTTP::Response>
93             object will be passed to it as a parameter.
94              
95             =item C<< cache_set($response, $expires_in) >>
96              
97             A method that takes the L<HTTP::Response> from the client and an
98             expires time in seconds and set the actual cache.
99              
100             =item C<< finalize($cached) >>
101              
102             A method that takes the cached entry as an argument, and will return a
103             L<HTTP::Response> to return to the client.
104              
105             =back
106              
107             =cut
108              
109             has cache => (
110             is => 'ro',
111             isa => InstanceOf['CHI::Driver'],
112             required => 1,
113             );
114              
115              
116             requires 'key';
117             requires 'cache_vary';
118             requires 'finalize';
119             requires 'cache_set';
120              
121             has request_uri => (
122             is =>'rw',
123             isa => Uri,
124             coerce => 1,
125             trigger => sub { shift->clear_key },
126             );
127              
128             has is_shared => (
129             is => 'rw',
130             isa => Bool,
131             default => 1);
132              
133             has heuristics_opts => (
134             is => 'rw',
135             isa => Ref['HASH'],
136             default => sub {return {heuristic_expiry => 0}}
137             );
138              
139             around request => sub {
140             my ($orig, $self) = (shift, shift);
141             my @args = @_;
142             my $request = $args[0];
143              
144             return $self->$orig(@args) if $request->method ne 'GET';
145              
146             $self->request_uri($request->uri);
147              
148             my $cached = $self->cache->get($self->key); # CHI will take care of expiration
149              
150             my $expires_in = 0;
151             if (defined($cached)) {
152             ######## Here, we decide whether to reuse a cached response.
153             ######## The standard describing this is:
154             ######## http://tools.ietf.org/html/rfc7234#section-4
155             $cached->header('Age' => $cached->current_age);
156             return $self->finalize($cached); # TODO: Deal with no-transform
157             } else {
158             my $res = $self->$orig(@args);
159              
160             ######## Here, we decide whether to store a response
161             ######## This is defined in:
162             ######## http://tools.ietf.org/html/rfc7234#section-3
163             # Quoting the standard
164              
165             ## A cache MUST NOT store a response to any request, unless:
166            
167             ## o The request method is understood by the cache and defined as being
168             ## cacheable, and
169             # TODO: Ok, only GET supported, see above
170              
171             ## o the response status code is understood by the cache, and
172             if ($res->is_success) { # TODO: Cache only successful responses for now
173              
174             # First, we deal superficially with the Vary header, for the
175             # full complexity see
176             # http://tools.ietf.org/html/rfc7234#section-4.1
177             return $res unless ($self->cache_vary($res));
178              
179             my $cc = join('|',$res->header('Cache-Control')); # Since we only do string matching, this should be ok
180             if (defined($cc)) {
181             ## o the "no-store" cache directive (see Section 5.2) does not appear
182             ## in request or response header fields, and
183             return $res if ($cc =~ m/no-store|no-cache/); # TODO: Improve no-cache use
184             if ($self->is_shared) {
185             ## o the "private" response directive (see Section 5.2.2.6) does not
186             ## appear in the response, if the cache is shared, and
187             return $res if ($cc =~ m/private/);
188             ## o the Authorization header field (see Section 4.2 of [RFC7235]) does
189             ## not appear in the request, if the cache is shared, unless the
190             ## response explicitly allows it (see Section 3.2), and
191             if ($request->header('Authorization')) {
192             return $res unless ($cc =~ m/public|must-revalidate|s-maxage/);
193             }
194             }
195             ## o the response either:
196             ##
197             ## * contains an Expires header field (see Section 5.3), or
198             ## * contains a max-age response directive (see Section 5.2.2.8), or
199             # This is implemented in HTTP::Response, but it relates to the old RFC2616
200             # and doesn't support shared caches.
201             $expires_in = $res->freshness_lifetime(%{$self->heuristics_opts}) || 0;
202              
203             ## * contains a s-maxage response directive (see Section 5.2.2.9)
204             ## and the cache is shared, or
205              
206             if ($self->is_shared && ($cc =~ m/s-maxage\s*=\s*(\d+)/)) {
207             $expires_in = $1;
208             }
209              
210              
211              
212             ## * contains a Cache Control Extension (see Section 5.2.3) that
213             ## allows it to be cached, or
214             # TODO
215              
216             ## * has a status code that is defined as cacheable by default (see
217             ## Section 4.2.2), or
218             # TODO: We only do GET
219              
220             ## * contains a public response directive (see Section 5.2.2.5).
221             # We do not specifically deal with this
222              
223             }
224             if ($expires_in > 0) {
225             $self->cache_set($res, $expires_in);
226             }
227             }
228             return $res;
229             }
230             };
231              
232             1;
233              
234             __END__
235              
236             =head1 LIMITATIONS
237              
238             Will only cache C<GET> requests, and only successful responses.
239              
240             The module does not validate and does not serve stale responses, even
241             when it would be allowed to do so. It nevertheless does most of
242             RFC7234.
243              
244             =head1 BUGS
245              
246             Please report any bugs to
247             L<https://github.com/kjetilk/p5-lwp-useragent-chicaching/issues>.
248              
249              
250             =head1 AUTHOR
251              
252             Kjetil Kjernsmo E<lt>kjetilk@cpan.orgE<gt>.
253              
254             =head1 ACKNOWLEDGEMENTS
255              
256             It was really nice looking at the code of L<LWP::UserAgent::WithCache>, when I wrote this.
257              
258             Thanks to Matt S. Trout for rewriting this to a Role.
259              
260             =head1 COPYRIGHT AND LICENCE
261              
262             This software is copyright (c) 2015, 2016 by Kjetil Kjernsmo.
263              
264             This is free software; you can redistribute it and/or modify it under
265             the same terms as the Perl 5 programming language system itself.
266              
267              
268