File Coverage

blib/lib/WWW/Mechanize/Cached.pm
Criterion Covered Total %
statement 72 97 74.2
branch 28 58 48.2
condition 7 15 46.6
subroutine 14 16 87.5
pod 1 2 50.0
total 122 188 64.8


line stmt bran cond sub pod time code
1             package WWW::Mechanize::Cached;
2              
3 12     12   2216830 use Moo 1.004005;
  12         94934  
  12         83  
4 12     12   18842 use warnings FATAL => 'all';
  12         31  
  12         955  
5              
6             our $VERSION = '2.00';
7              
8 12     12   92 use Carp qw( carp croak );
  12         26  
  12         824  
9 12     12   5939 use Module::Runtime qw( use_module );
  12         22298  
  12         95  
10 12     12   7555 use MooX::Types::MooseLike::Base qw( AnyOf Bool Enum Maybe );
  12         101580  
  12         1671  
11 12     12   7302 use Storable qw( nfreeze thaw );
  12         50099  
  12         1198  
12              
13 12     12   7095 use namespace::clean;
  12         209621  
  12         128  
14              
15             extends 'WWW::Mechanize';
16              
17             has is_cached => ( is => 'rw', isa => Maybe [Bool], default => undef );
18             has positive_cache => ( is => 'rw', isa => Bool, default => 1 );
19             has ref_in_cache_key => ( is => 'rw', isa => Bool, default => 0 );
20             has _verbose_dwarn => ( is => 'rw', isa => Bool, default => 0 );
21             has _last_request => ( is => 'rw' );
22              
23             has [qw/ cache_undef_content_length cache_zero_content_length /] =>
24             ( is => 'rw', isa => AnyOf [ Bool, Enum ['warn'] ], default => 0 );
25              
26             has cache_mismatch_content_length => (
27             is => 'rw',
28             isa => AnyOf [ Bool, Enum ['warn'] ],
29             default => 'warn',
30             );
31              
32             has cache => ( is => 'lazy', isa => \&_isa_warn_cache );
33              
34             sub FOREIGNBUILDARGS {
35 22     22 0 5801565 my ( $class, %args ) = @_;
36              
37             # WWW::Mechanize/LWP::UserAgent would complain about these
38 22         75 for my $attribute (
39             qw(
40             is_cached
41             positive_cache
42             ref_in_cach_key
43             _verbose_dwarn
44             cache_undef_content_length
45             cache_zero_content_length
46             cache_mismatch_content_length
47             cache
48             )
49             ) {
50 176         299 delete $args{$attribute};
51             }
52              
53 22         221 return %args;
54             }
55              
56             sub _isa_warn_cache {
57             return
58 11 50 66 11   123171 if 'HASH' ne ref $_[0]
      66        
59             and $_[0]->can('get')
60             and $_[0]->can('set');
61 1         29 carp 'The cache param must be an initialized cache object';
62 1         1015 $_[0] = undef;
63             }
64              
65             sub _build_cache {
66 3     3   51054 my $self = shift;
67              
68 3 50       9 if ( eval { use_module('Cache::FileCache') && use_module('File::XDG') } )
  3 50       29  
69             {
70 3         103386 my $cache_root = File::XDG->new(
71             name => 'WWW-Mechanize-Cached',
72             api => 1,
73             )->cache_home;
74 3         41549 $cache_root->mkdir( { mode => 0700 } );
75 3         974 chmod 0700, "$cache_root";
76 3         146 return Cache::FileCache->new(
77             {
78             default_expires_in => '1d',
79             namespace => 'www-mechanize-cached',
80             cache_root => "$cache_root",
81             directory_umask => 077,
82             }
83             );
84             }
85              
86             return CHI->new(
87             driver => 'File',
88             expires_in => '1d',
89             namespace => 'www-mechanize-cached',
90 0 0       0 ) if eval { use_module('CHI') };
  0         0  
91              
92 0         0 croak( 'Could not create a default cache.'
93             . 'Please make sure either CHI or Cache::FileCache are installed or configure manually as appropriate'
94             );
95             }
96              
97             around _make_request => sub {
98             my ( $orig, $self, $request ) = splice @_, 0, 3;
99             my $req = $request;
100              
101             $self->is_cached(0);
102              
103             # An odd line to need.
104             # No idea what purpose this serves? OALDERS
105             $self->{proxy} = {} unless defined $self->{proxy}; ## no critic
106              
107             # RT #56757
108             if ( !$self->ref_in_cache_key ) {
109             my $clone = $request->clone;
110             $clone->header( Referer => undef );
111             $req = $clone->as_string;
112             }
113              
114             my $response = $self->cache->get($req);
115              
116             if ($response) {
117             $response = thaw($response);
118             }
119             if ( $self->_cache_ok($response) ) {
120             $self->is_cached(1);
121             $self->_last_request($req);
122             return $response;
123             }
124              
125             $response = $self->$orig( $request, @_ );
126              
127             # decode strips some important headers.
128             my $headers = $response->headers->clone;
129              
130             my $should_cache = $self->_response_cache_ok( $response, $headers );
131              
132             # http://rt.cpan.org/Public/Bug/Display.html?id=42693
133             $response->decode();
134             delete $response->{handlers}; ## no critic
135              
136             if ($should_cache) {
137             $self->_last_request($req);
138             $self->cache->set( $req, nfreeze($response) );
139             }
140              
141             return $response;
142             };
143              
144             sub invalidate_last_request {
145 4     4 1 4905 my $self = shift;
146 4 100       135 return unless $self->is_cached;
147              
148 2         46 my $request = $self->_last_request;
149 2 50       8 return unless $request;
150              
151 2         57 $self->cache->remove($request);
152 2 50       1002 return $self->is_cached( $self->cache->get($request) ? 1 : 0 );
153             }
154              
155             sub _dwarn_filter {
156             return {
157 0     0   0 hide_keys => [
158             qw( _content cookie content set-cookie handlers cookie_jar cache req res page_stack )
159             ]
160             };
161              
162             }
163              
164             sub _dwarn {
165 0     0   0 my $self = shift;
166 0         0 my $message = shift;
167              
168 0 0       0 return unless my $handler = $self->{onwarn}; ## no critic
169              
170 0 0       0 return if $self->quiet;
171              
172 0 0       0 if ( $self->_verbose_dwarn ) {
173 0         0 my $payload = {
174             self => $self,
175             message => $message,
176             debug => \@_,
177             };
178 0         0 require Data::Dump;
179 0         0 return $handler->( Data::Dump::dumpf( $payload, \&_dwarn_filter ) );
180             }
181             else {
182 0         0 return $handler->($message);
183             }
184             }
185              
186             sub _response_cache_ok {
187 29     29   15492 my $self = shift;
188 29         60 my $response = shift;
189 29         1488 my $headers = shift;
190              
191 29 50       144 return 0 if !$response;
192 29 50       1246 return 1 if !$self->positive_cache;
193              
194 29 50       383 return 0 if $response->code < 200;
195 29 50       484 return 0 if $response->code > 301;
196              
197 29         344 my $size;
198             {
199 29 100       63 if ( $headers->header('Client-Transfer-Encoding') ) {
  29         117  
200 5         171 my @cte = $headers->header('Client-Transfer-Encoding');
201 5         136 for my $cte (@cte) {
202              
203             # Transfer-Encoding = chunked means document consistency
204             # is independent of Content-Length value,
205             # and that Content-Length can be safely ignored.
206             # Its not obvious how the lower levels represent a
207             # failed chunked-transfer yet.
208             # But its safe to say relying on content-length proves pointless.
209 5 100       28 return 1 if $cte eq 'chunked';
210             }
211             }
212              
213 25         1696 $size = $headers->{'content-length'};
214             }
215              
216 25 100       92 if ( not defined $size ) {
217 8 50       241 if ( $self->cache_undef_content_length . q{} eq q{warn} ) {
218 0         0 $self->_dwarn(
219             q[Content-Length header was undefined, not caching]
220             . q[ (E=WWW_MECH_CACHED_CONTENTLENGTH_MISSING)],
221             $headers
222             );
223 0         0 return 0;
224             }
225 8 50       226 if ( $self->cache_undef_content_length == 0 ) {
226 8         62 return 0;
227             }
228             }
229              
230 17 50 33     114 if ( defined $size and $size == 0 ) {
231 0 0       0 if ( $self->cache_zero_content_length . q{} eq q{warn} ) {
232 0         0 $self->_dwarn(
233             q{Content-Length header was 0, not caching}
234             . q{ (E=WWW_MECH_CACHED_CONTENTLENGTH_ZERO)},
235             $headers
236             );
237 0         0 return 0;
238             }
239 0 0       0 if ( $self->cache_zero_content_length == 0 ) {
240 0         0 return 0;
241             }
242             }
243              
244 17 50 33     165 if ( defined $size
      33        
245             and $size != 0
246             and $size != length( $response->content ) ) {
247 0 0       0 if ( $self->cache_mismatch_content_length . "" eq "warn" ) {
248 0         0 $self->_dwarn(
249             q{Content-Length header did not match contents actual length, not caching}
250             . q{ (E=WWW_MECH_CACHED_CONTENTLENGTH_MISSMATCH)} );
251 0         0 return 0;
252             }
253 0 0       0 if ( $self->cache_mismatch_content_length == 0 ) {
254 0         0 return 0;
255             }
256             }
257              
258 17         604 return 1;
259             }
260              
261             sub _cache_ok {
262              
263 62     62   15644 my $self = shift;
264 62         127 my $response = shift;
265              
266 62 100       234 return 0 if !$response;
267 36 100       1188 return 1 if !$self->positive_cache;
268              
269 34 50       462 return 0 if $response->code < 200;
270 34 100       584 return 0 if $response->code > 301;
271              
272 33         473 return 1;
273             }
274              
275 12     12   29389 no warnings;
  12         69  
  12         898  
276             "We miss you, Spoon"; ## no critic
277              
278             # ABSTRACT: Cache response to be polite
279              
280             __END__