File Coverage

blib/lib/WWW/Mechanize/Cached.pm
Criterion Covered Total %
statement 72 101 71.2
branch 25 56 44.6
condition 7 15 46.6
subroutine 17 19 89.4
pod 1 2 50.0
total 122 193 63.2


line stmt bran cond sub pod time code
1 12     12   1653088 use strict;
  12         140  
  12         445  
2 12     12   70 use warnings FATAL => 'all';
  12         25  
  12         736  
3              
4             package WWW::Mechanize::Cached;
5              
6 12     12   342 use 5.006;
  12         52  
7              
8 12     12   6073 use Module::Runtime 'use_module';
  12         19462  
  12         81  
9 12     12   6965 use Moo 1.004005;
  12         100233  
  12         83  
10 12     12   23046 use MooX::Types::MooseLike::Base qw(AnyOf Bool Enum Maybe);
  12         79233  
  12         1210  
11 12     12   7059 use namespace::clean;
  12         137983  
  12         86  
12             extends 'WWW::Mechanize';
13              
14 12     12   4055 use Carp qw( carp croak );
  12         30  
  12         676  
15 12     12   6383 use Data::Dump qw( dump );
  12         77364  
  12         941  
16 12     12   7957 use Storable qw( nfreeze thaw );
  12         36083  
  12         15809  
17              
18             our $VERSION = '1.55';
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 196351 my ( $class, %args ) = @_;
39              
40             # WWW::Mechanize/LWP::UserAgent would complain about these
41 22         84 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         285 delete $args{$attribute};
54             }
55              
56 22         207 return %args;
57             }
58              
59             sub _isa_warn_cache {
60             return
61 10 50 66 10   151346 if 'HASH' ne ref $_[0]
      66        
62             and $_[0]->can('get')
63             and $_[0]->can('set');
64 1         29 carp 'The cache param must be an initialized cache object';
65 1         810 $_[0] = undef;
66             }
67              
68             sub _build_cache {
69 2     2   14046 my $self = shift;
70              
71             return Cache::FileCache->new(
72             {
73             default_expires_in => '1d',
74             namespace => 'www-mechanize-cached',
75             }
76 2 50       6 ) if eval { use_module('Cache::FileCache') };
  2         12  
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 1     1 1 610 my $self = shift;
138 1 50       23 return unless $self->is_cached;
139              
140 0         0 my $request = $self->_last_request;
141 0 0       0 return unless $request;
142              
143 0         0 $self->cache->remove($request);
144 0 0       0 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 22     22   14545 my $self = shift;
180 22         37 my $response = shift;
181 22         39 my $headers = shift;
182              
183 22 50       69 return 0 if !$response;
184 22 50       561 return 1 if !$self->positive_cache;
185              
186 22 50       231 return 0 if $response->code < 200;
187 22 100       304 return 0 if $response->code > 301;
188              
189 13         120 my $size;
190             {
191 13 100       21 if ( $headers->header('Client-Transfer-Encoding') ) {
  13         31  
192 3         92 my @cte = $headers->header('Client-Transfer-Encoding');
193 3         78 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       14 return 1 if $cte eq 'chunked';
202             }
203             }
204              
205 11         474 $size = $headers->{'content-length'};
206             }
207              
208 11 100       32 if ( not defined $size ) {
209 1 50       18 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 1 50       75 if ( $self->cache_undef_content_length == 0 ) {
218 1         13 return 0;
219             }
220             }
221              
222 10 50 33     46 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 10 50 33     54 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 10         196 return 1;
251             }
252              
253             sub _cache_ok {
254              
255 43     43   14355 my $self = shift;
256 43         67 my $response = shift;
257              
258 43 100       135 return 0 if !$response;
259 24 100       462 return 1 if !$self->positive_cache;
260              
261 22 50       205 return 0 if $response->code < 200;
262 22 100       263 return 0 if $response->code > 301;
263              
264 21         216 return 1;
265             }
266              
267 12     12   122 no warnings;
  12         27  
  12         724  
268             "We miss you, Spoon"; ## no critic
269              
270             # ABSTRACT: Cache response to be polite
271              
272             __END__