File Coverage

blib/lib/WWW/Mechanize/Cached.pm
Criterion Covered Total %
statement 76 101 75.2
branch 27 56 48.2
condition 7 15 46.6
subroutine 17 19 89.4
pod 1 2 50.0
total 128 193 66.3


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