File Coverage

blib/lib/HTTP/Caching.pm
Criterion Covered Total %
statement 288 346 83.2
branch 117 170 68.8
condition 20 28 71.4
subroutine 37 43 86.0
pod 1 2 50.0
total 463 589 78.6


line stmt bran cond sub pod time code
1             package HTTP::Caching;
2              
3             =head1 NAME
4              
5             HTTP::Caching - The RFC 7234 compliant brains to do caching right
6              
7             =head1 VERSION
8              
9             Version 0.13
10              
11             =cut
12              
13             our $VERSION = '0.15';
14              
15 8     8   4958 use strict;
  8         20  
  8         511  
16 8     8   33 use warnings;
  8         24  
  8         158  
17              
18 8     8   29 use Carp;
  8         12  
  8         303  
19 8     8   32 use Digest::MD5;
  8         13  
  8         257  
20 8     8   3194 use HTTP::Method;
  8         6129  
  8         25  
21 8     8   2734 use HTTP::Status ':constants';
  8         25154  
  8         2596  
22 8     8   3693 use List::MoreUtils qw{ any };
  8         79585  
  8         46  
23 8     8   6712 use Time::HiRes;
  8         13  
  8         59  
24 8     8   3592 use URI;
  8         25180  
  8         197  
25              
26 8     8   3384 use Moo;
  8         71157  
  8         35  
27 8     8   13681 use MooX::Types::MooseLike::Base ':all';
  8         42234  
  8         2322  
28              
29             our $DEBUG = 0;
30              
31 8     8   3435 use Readonly;
  8         25481  
  8         2226  
32              
33             Readonly my $REUSE_NO_MATCH => 0; # mismatch of headers etc
34             Readonly my $REUSE_IS_OK => 1;
35             Readonly my $REUSE_IS_STALE => 2;
36             Readonly my $REUSE_REVALIDATE => 4;
37             Readonly my $REUSE_IS_STALE_OK => $REUSE_IS_STALE | $REUSE_IS_OK;
38             Readonly my $REUSE_IS_STALE_REVALIDATE => $REUSE_IS_STALE | $REUSE_REVALIDATE;
39              
40             =head1 SYNOPSIS
41              
42             my $chi_cache = CHI->new(
43             driver => 'File',
44             root_dir => '/tmp/HTTP_Caching',
45             file_extension => '.cache',
46             l1_cache => {
47             driver => 'Memory',
48             global => 1,
49             max_size => 1024*1024
50             }
51             );
52            
53             my $ua = LWP::UserAgent->new();
54            
55             my $http_caching = HTTP::Caching->new(
56             cache => $chi_cache,
57             cache_type => 'private',
58             forwarder => sub { return $ua->request(shift) }
59             );
60            
61             my $rqst = HTTP::Request->new( GET => 'http://example.com' );
62            
63             my $resp = $http_caching->make_request( $rqst );
64            
65             =cut
66              
67             has cache => (
68             is => 'ro',
69             required => 0,
70             isa => Maybe[ HasMethods['set', 'get'] ],
71             builder => sub {
72 0     0   0 warn __PACKAGE__ . " without cache, forwards requests and responses\n";
73             return undef
74 0         0 },
75             );
76              
77             has cache_meta => (
78             is => 'ro',
79             required => 0,
80             isa => Maybe[ HasMethods['set', 'get'] ],
81             lazy => 1,
82             builder => sub {
83             return shift->cache
84 5     5   112 },
85             );
86              
87             has cache_type => (
88             is => 'ro',
89             required => 1,
90             isa => Maybe[ Enum['private', 'public'] ],
91             );
92              
93             has cache_control_request => (
94             is => 'ro',
95             required => 0,
96             isa => Maybe[ Str ],
97             );
98              
99             has cache_control_response => (
100             is => 'ro',
101             required => 0,
102             isa => Maybe[ Str ],
103             );
104              
105             has forwarder => (
106             is => 'ro',
107             required => 1,
108             isa => CodeRef,
109             );
110              
111             sub is_shared {
112 63     63 0 70 my $self = shift;
113            
114 63 100       162 return unless $self->cache_type;
115 32         84 return $self->cache_type eq 'public'
116             }
117              
118             =head1 DEPRECATION WARNING !!!
119              
120             This module is going to be completely redesigned!!!
121              
122             As it was planned, these are the brains, but unfortunately, it has become an
123             implementation.
124              
125             The future version will answer two questions:
126              
127             =over
128              
129             =item may_store
130              
131             =item may_reuse
132              
133             =back
134              
135             Those are currently implemented as private methods.
136              
137             Please contact the author if you rely on this module directly to prevent
138             breakage
139              
140             Sorry for any inconvenience
141              
142             =head1 ADVICE
143              
144             Please use L or .
145              
146             =head1 NOTE
147              
148             You can surpress the message by setting the environment varibale
149             C
150              
151             =cut
152              
153 8     8   442 use HTTP::Caching::DeprecationWarning;
  8         16  
  8         57  
154              
155             =head1 DESCRIPTION
156              
157             This module tries to provide caching for HTTP responses based on
158             L1.1): Caching|
159             http://tools.ietf.org/html/rfc7234>.
160              
161             Basicly it looks like the following steps below:
162              
163             =over
164              
165             =item
166              
167             For a presented request, it will check with the cache if there is a suitable
168             response available AND if it can be served or that it needs to be revalidated
169             with an upstream server.
170              
171             =item
172              
173             If there was no response available at all, or non were suitable, the (modified)
174             request will simply be forwarded.
175              
176             =item
177              
178             Depending on the response it gets back, it will do one of the following
179             dependingon the response status code:
180              
181             =over
182              
183             =item 200 OK
184              
185             it will update the cache and serve the response as is
186              
187             =item 304 Not Modified
188              
189             the cached version is valid, update the cache with new header info and serve the
190             cached response
191              
192             =item 500 Server Error
193              
194             in general, this is an error, and pass that onto the caller, however, in some
195             cases it could be fine to serve a (stale) cached response
196              
197             =back
198              
199             =back
200              
201             The above is a over-simplified version of the RFC
202              
203             =cut
204              
205             =head1 CONSTRUCTORS
206              
207             =head2 new
208              
209             my $http_caching = HTTP::Caching->new(
210             cache => $chi_cache,
211             cache_type => 'private',
212             cache_request => 'max-age=86400, min-fresh=60',
213             forwarder => sub { return $ua->request(shift) }
214             );
215              
216             Constructs a new C object that knows how to find cached responses
217             and will forward if needed.
218              
219             =head1 ATRRIBUTES
220              
221             =head2 cache
222              
223             Cache must be an object that MUST implement two methods
224              
225             =over
226              
227             =item sub set ($key, $data)
228              
229             to store data in the cache
230              
231             =item sub get ($key)
232              
233             to retrieve the data stored under the key
234              
235             =back
236              
237             This can be as simple as a hash, like we use in the tests:
238              
239             use Test::MockObject;
240            
241             my %cache;
242             my $mocked_cache = Test::MockObject->new;
243             $mocked_cache->mock( set => sub { $cache{$_[1]} = $_[2] } );
244             $mocked_cache->mock( get => sub { return $cache{$_[1]} } );
245              
246             But very convenient is to use L, which implements both required methods and
247             also has the option to use a L1 cache to speed things up even more. See the
248             SYNOPSIS for an example
249              
250             =head2 cache_type
251              
252             This must either be C<'private'> or C<'public'>. For most L, it
253             can be C<'private'> as it will probably not be shared with other processes on
254             the same macine. If this module is being used at the serverside in a
255             L then the cache will be used by all other clients connecting
256             to the server, and thus should be set to C<'public'>.
257              
258             Responses to Authenticated request should not be held in public caches and also
259             those responses that specifacally have their cache-control headerfield set to
260             C<'private'>.
261              
262             =head2 cache_control_request
263              
264             A string that contains the Cache-control header-field settings that will be sent
265             as default with the request. So you do not have to set those each time. See
266             RFC 7234 Section 5.2.1 for the list of available cache-control directives.
267              
268             =head2 cache_control_response
269              
270             Like the above, but those will be set for each response. This is useful for
271             server side caching. See RFC 7234 Section 5.2.2.
272              
273             =head2 forwarder
274              
275             This CodeRef must be a callback function that accepts a L and
276             returns a L. Since this module does not know how to do a request
277             it will use the C. It will be used to sent of validation requests
278             with C and/or C header-fields. Or if it does
279             not have a stored response it will send the original full request (with the
280             extra directives from C).
281              
282             Failing to return a C might cause the module to die or generate
283             a response itself with status code C<502 Bad Gateway>.
284              
285             =head1 METHODS
286              
287             =head2 make_request
288              
289             This is the only public provided method and will take a L. Like
290             described above, it might have to forward the (modified) request throug the
291             CodeRef in the C attribute.
292              
293             It will return a L from cache or a new retrieved one. This might
294             be a HTTP respons with a C<500 Error> message.
295              
296             In other cases it might die and let the caller know what was wrong, or send
297             another 5XX Error.
298              
299             =cut
300              
301             sub make_request {
302            
303 18     18 1 65896 HTTP::Caching::DeprecationWarning->show_once();
304              
305 18         27 my $self = shift;
306            
307 18 100       51 croak __PACKAGE__
308             . " missing request"
309             unless defined $_[0];
310 17 100       75 croak __PACKAGE__
311             . " request is not a HTTP::Request [$_[0]]"
312             unless UNIVERSAL::isa($_[0],'HTTP::Request');
313            
314 16         54 my $presented_request = shift->clone;
315            
316 16         2200 my @params = @_;
317              
318             # add the default Cache-Control request header-field
319 16 100       63 $presented_request->headers->push_header(
320             cache_control => $self->cache_control_request,
321             ) if $self->cache_control_request();
322            
323 16         83 my $response;
324            
325 16 50       81 unless ($self->cache) {
    100          
326 5         13 $response = $self->_forward($presented_request, @params);
327 0         0 } elsif ( $self->_non_safe($presented_request) ) {
328            
329             # always forwad requests with unsafe methods
330 0         0 $response = $self->_forward($presented_request, @params);
331            
332             # when returned with a non-err, invalidate the cache
333 0 0 0     0 if ( $response->is_success or $response->is_redirect ) {
334 0         0 $self->_invalidate($presented_request)
335             }
336             } else {
337 11 100       87 if (my $cache_resp =
338             $self->_retrieve($presented_request)
339             ) {
340 5         9 $response = $cache_resp;
341             } else {
342 6         18 $response = $self->_forward($presented_request, @params);
343 6         14 $self->_store($presented_request, $response);
344             }
345             }
346            
347             # add the default Cache-Control response header-field
348 16 100       80 $response->headers->push_header(
349             cache_control => $self->cache_control_response,
350             ) if $self->cache_control_response;
351            
352 16         122 return $response;
353            
354             }
355              
356             sub _forward {
357 14     14   21 my $self = shift;
358            
359 14         18 my $forwarded_rqst = shift;
360            
361 14         121 my $forwarded_resp = $self->forwarder->($forwarded_rqst, @_);
362            
363 14 100       1250 unless ( UNIVERSAL::isa($forwarded_resp,'HTTP::Response') ) {
364 1         19 carp __PACKAGE__
365             . " response is not a HTTP::Response [$forwarded_resp]";
366             # rescue from a failed forwarding, HTTP::Caching should not break
367 1         465 $forwarded_resp = HTTP::Response->new(502); # Bad Gateway
368             }
369            
370 14         54 return $forwarded_resp;
371             }
372              
373              
374             =head1 ABOUT CACHING
375              
376             If one would read the RFC7234 Section 2. Overview of Cache Operation, it becomes
377             clear that a cache can hold multiple responses for the same URI. Caches that
378             conform to CHI and many others, typically use a key / value storage. But this
379             will become a problem as that it can not use the URI as a key to the various
380             responses.
381              
382             The way it is solved is to create an intermediate meta-dictionary. This can be
383             stored by URI as key. Each response will simply be stored with a unique key and
384             these keys will be used as the entries in the dictionary.
385              
386             The meta-dictionary entries will hold (relevant) request and response headers so
387             that it willbe more quick to figure wich entrie can be used. Otherwise we would
388             had to read the entire responses to analyze them.
389              
390             =cut
391              
392             # _store may or may not store the response into the cache
393             #
394             # depending on the response it _may_store_in_cache()
395             #
396             sub _store {
397 9     9   12 my $self = shift;
398 9         10 my $rqst = shift;
399 9         11 my $resp = shift;
400            
401 9 50       23 return unless $self->_may_store_in_cache($rqst, $resp);
402            
403 9 50       21 if ( my $resp_key = $self->_store_response($resp) ) {
404 9         25 my $rqst_key = Digest::MD5::md5_hex($rqst->uri()->as_string);
405 9         98 my $rsqt_stripped = $rqst->clone; $rsqt_stripped->content(undef);
  9         1120  
406 9         151 my $resp_stripped = $resp->clone; $resp_stripped->content(undef);
  9         876  
407 9         144 $self->_insert_meta_dict(
408             $rqst_key,
409             $resp_key,
410             {
411             resp_stripped => $resp_stripped,
412             rqst_stripped => $rsqt_stripped,
413             },
414             );
415 9         17 return $resp_key;
416             }
417            
418             return
419 0         0 }
420              
421             sub _store_response {
422 9     9   12 my $self = shift;
423 9         11 my $resp = shift;
424            
425 9         83 my $resp_key = Digest::MD5::md5_hex(Time::HiRes::time());
426            
427 9         16 eval { $self->cache->set( $resp_key => $resp ) };
  9         51  
428 9 50       593 return $resp_key unless $@;
429            
430 0         0 croak __PACKAGE__
431             . " could not store response in cache with key [$resp_key], $@";
432            
433             return
434 0         0 }
435              
436             sub _insert_meta_dict {
437 9     9   22 my $self = shift;
438 9         41 my $rqst_key = shift;
439 9         13 my $resp_key = shift;
440 9         15 my $meta_data = shift;
441            
442 9   100     192 my $meta_dict = $self->cache_meta->get($rqst_key) || {};
443 9         568 $meta_dict->{$resp_key} = $meta_data;
444 9         162 $self->cache_meta->set( $rqst_key => $meta_dict );
445            
446 9         503 return $meta_dict;
447             }
448              
449             sub _retrieve {
450 11     11   13 my $self = shift;
451 11         16 my $rqst_presented = shift;
452            
453 11         22 my $rqst_key = Digest::MD5::md5_hex($rqst_presented->uri()->as_string);
454 11         149 my $meta_dict = $self->_retrieve_meta_dict($rqst_key);
455            
456 11 100       35 return unless $meta_dict;
457            
458 5         17 my @meta_keys = keys %$meta_dict;
459            
460 5         10 foreach my $meta_key (@meta_keys) {
461             my $reuse_status = $self->_may_reuse_from_cache(
462             $rqst_presented,
463             $meta_dict->{$meta_key}{resp_stripped},
464             $meta_dict->{$meta_key}{rqst_stripped}
465 5         19 );
466 5         36 $meta_dict->{$meta_key}{reuse_status} = $reuse_status
467             }
468            
469             my @okay_keys =
470 5         10 grep { $meta_dict->{$_}{reuse_status} & $REUSE_IS_OK} @meta_keys;
  5         15  
471            
472 5 100       31 if (scalar @okay_keys) {
473             #
474             # TODO: do content negotiation if possible
475             #
476             # TODO: Sort to select lates response
477             #
478 2         5 my ($resp_key) = @okay_keys;
479 2         6 my $resp = $self->_retrieve_response($resp_key);
480 2         8 return $resp
481             }
482            
483             my @vldt_keys =
484 3         5 grep { $meta_dict->{$_}{reuse_status} & $REUSE_REVALIDATE} @meta_keys;
  3         7  
485            
486 3 50       18 if (scalar @vldt_keys) {
487             #
488             # RFC 7234 Section 4.3.1
489             #
490             # Sending a Validation Request
491             #
492 3         5 my ($resp_key) = @vldt_keys;
493 3         5 my $resp_stripped = $meta_dict->{$resp_key}{resp_stripped};
494            
495             # Assume we have validation headers, otherwise we'll need a HEAD request
496             #
497 3         6 my $etag = $resp_stripped->header('ETag');
498 3         159 my $last = $resp_stripped->header('Last-Modified');
499            
500 3         92 my $rqst_forwarded = $rqst_presented->clone;
501 3 100       378 $rqst_forwarded->header('If-None-Match' => $etag) if $etag;
502 3 100       90 $rqst_forwarded->header('If-Modified-Since' => $last) if $last;
503            
504 3         83 my $resp_forwarded = $self->_forward($rqst_forwarded);
505            
506             # RFC 7234 Section 4.3.3.
507             #
508             # Handling a Validation Response
509             #
510             # Cache handling of a response to a conditional request is dependent
511             # upon its status code:
512            
513            
514             # A 304 (Not Modified) response status code indicates that the
515             # stored response can be updated and reused; see Section 4.3.4.
516             #
517 3 50       7 if ($resp_forwarded->code == HTTP_NOT_MODIFIED) {
518 0         0 my $resp = $self->_retrieve_response($resp_key);
519 0         0 return $resp
520             #
521             # TODO: make it all compliant with Section 4.3.4 on how to select
522             # which stored responses need update
523             # TODO: ade 'Age' header
524             }
525            
526            
527             # A full response (i.e., one with a payload body) indicates that
528             # none of the stored responses nominated in the conditional request
529             # is suitable. Instead, the cache MUST use the full response to
530             # satisfy the request and MAY replace the stored response(s).
531             #
532 3 50       30 if ( not HTTP::Status::is_server_error($resp_forwarded->code) ) {
533 3         37 $self->_store($rqst_presented, $resp_forwarded);
534 3         9 return $resp_forwarded;
535             }
536            
537            
538             # However, if a cache receives a 5xx (Server Error) response while
539             # attempting to validate a response, it can either forward this
540             # response to the requesting client, or act as if the server failed
541             # to respond. In the latter case, the cache MAY send a previously
542             # stored response (see Section 4.2.4).
543             #
544 0 0       0 if ( HTTP::Status::is_server_error($resp_forwarded->code) ) {
545 0         0 return $resp_forwarded;
546             }
547             #
548             # TODO: check if we can use a cached stale version
549            
550            
551 0         0 return undef;
552             }
553            
554 0         0 return undef;
555             }
556              
557             sub _retrieve_meta_dict {
558 11     11   16 my $self = shift;
559 11         16 my $rqst_key = shift;
560            
561 11         212 my $meta_dict = $self->cache_meta->get($rqst_key);
562            
563 11         1168 return $meta_dict;
564             }
565              
566             sub _retrieve_response {
567 2     2   4 my $self = shift;
568 2         3 my $resp_key = shift;
569            
570 2 50       3 if (my $resp = eval { $self->cache->get( $resp_key ) } ) {
  2         12  
571 2         119 return $resp
572             }
573            
574 0         0 carp __PACKAGE__
575             . " could not retrieve response from cache with key [$resp_key], $@";
576            
577             return
578 0         0 }
579              
580             sub _invalidate {
581 0     0   0 my $self = shift;
582 0         0 my $rqst_presented = shift;
583            
584 0         0 my $rqst_key = Digest::MD5::md5_hex($rqst_presented->uri()->as_string);
585 0         0 my $meta_dict = $self->_retrieve_meta_dict($rqst_key);
586            
587 0 0       0 return unless $meta_dict;
588            
589 0         0 my @meta_keys = keys %$meta_dict;
590            
591 0         0 foreach my $meta_key (@meta_keys) {
592 0         0 $self->_invalidate_response($meta_key);
593             }
594            
595 0         0 $self->_invalidate_meta_dict($rqst_key);
596            
597 0         0 return;
598             }
599              
600             sub _invalidate_meta_dict {
601 0     0   0 my $self = shift;
602 0         0 my $rqst_key = shift;
603            
604 0         0 $self->cache_meta->remove($rqst_key);
605            
606             return
607 0         0 }
608              
609             sub _invalidate_response {
610 0     0   0 my $self = shift;
611 0         0 my $resp_key = shift;
612            
613 0         0 $self->cache->remove($resp_key);
614            
615             return
616 0         0 }
617              
618             sub _non_safe {
619 11     11   17 my $self = shift;
620 11         15 my $rqst = shift;
621            
622 11         15 my $method = eval { HTTP::Method->new( uc $rqst->method ) };
  11         25  
623 11 50       287 return 1 unless $method; #safety can not be guaranteed
624            
625 11         92 return not $method->is_method_safe
626             }
627              
628             # _may_store_in_cache()
629             #
630             # based on some headers in the request, but mostly on those in the new response
631             # the cache can hold a copy of it or not.
632             #
633             # see RFC 7234 Section 3: Storing Responses in Caches
634             #
635             sub _may_store_in_cache {
636 29     29   37959 my $self = shift;
637 29         36 my $rqst = shift;
638 29         40 my $resp = shift;
639            
640             # $msg->header('cache-control) is supposed to return a list, but only works
641             # if it has been generated as a list, not as string with 'comma'
642             # $msg->header in scalar context gives a ', ' joined string
643             # which we now split and trim whitespace
644             my @rqst_directives =
645 29   100     86 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  2         37  
  2         7  
  2         3  
  2         4  
646             split ',', scalar $rqst->header('cache-control') || '';
647             my @resp_directives =
648 29   100     1203 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  12         349  
  12         28  
  12         22  
  12         26  
649             split ',', scalar $resp->header('cache-control') || '';
650            
651            
652             # RFC 7234 Section 3
653             #
654             # A cache MUST NOT store a response to any request, unless:
655            
656             # RFC 7234 Section 3 #1
657             #
658             # the request method is understood by the cache and defined as being
659             # cacheable
660             #
661 29         601 do {
662 29         65 my $string = $rqst->method;
663 29         231 my $method = eval { HTTP::Method->new($string) };
  29         77  
664            
665 29 100       615 unless ($method) {
666 1 50       11 carp "NO CACHE: method is not understood: '$string'\n"
667             if $DEBUG;
668 1         359 return 0
669             }
670 28 100       193 unless ($method->is_method_cachable) { # XXX Fix cacheable
671 1 50       17 carp "NO CACHE: method is not cacheable: '$string'\n"
672             if $DEBUG;
673 1         386 return 0
674             }
675             };
676            
677             # RFC 7234 Section 3 #2
678             #
679             # the response status code is understood by the cache
680             #
681 27         130 do {
682 27         57 my $code = $resp->code;
683 27         200 my $message = eval { HTTP::Status::status_message($code) };
  27         61  
684            
685 27 100       108 unless ($message) {
686 1 50       12 carp "NO CACHE: response status code is not understood: '$code'\n"
687             if $DEBUG;
688 1         352 return 0
689             }
690             };
691            
692            
693             # RFC 7234 Section 3 #3
694             #
695             # the "no-store" cache directive (see Section 5.2) does not appear
696             # in request or response header fields
697             #
698 26         28 do {
699 26 100   1   143 if (any { lc $_ eq 'no-store' } @rqst_directives) {
  1         3  
700 1 50       10 carp "NO CACHE: 'no-store' appears in request cache directives\n"
701             if $DEBUG;
702 1         380 return 0
703             }
704 25 100   11   111 if (any { lc $_ eq 'no-store' } @resp_directives) {
  11         32  
705 1 50       10 carp "NO CACHE: 'no-store' appears in response cache directives\n"
706             if $DEBUG;
707 1         373 return 0
708             }
709             };
710            
711             # RFC 7234 Section 3 #4
712             #
713             # the "private" response directive (see Section 5.2.2.6) does not
714             # appear in the response, if the cache is shared
715             #
716 24 100       64 if ($self->is_shared) {
717 6 100   5   18 if (any { lc $_ eq 'private' } @resp_directives) {
  5         12  
718 1 50       11 carp "NO CACHE: 'private' appears in cache directives when shared\n"
719             if $DEBUG;
720 1         393 return 0
721             }
722             };
723            
724             # RFC 7234 Section 3 #5
725             #
726             # the Authorization header field (see Section 4.2 of [RFC7235]) does
727             # not appear in the request, if the cache is shared, unless the
728             # response explicitly allows it (see Section 3.2)
729             #
730 23 100       42 if ($self->is_shared) {
731 5 100       10 if ($rqst->header('Authorization')) {
732 4 100   3   135 if (any { lc $_ eq 'must-revalidate' } @resp_directives) {
  3         7  
733 1 50       10 carp "DO CACHE: 'Authorization' appears: must-revalidate\n"
734             if $DEBUG;
735 1         353 return 1
736             }
737 3 100   2   14 if (any { lc $_ eq 'public' } @resp_directives) {
  2         5  
738 1 50       10 carp "DO CACHE: 'Authorization' appears: public\n"
739             if $DEBUG;
740 1         352 return 1
741             }
742 2 100   1   7 if (any { lc $_ =~ m/^s-maxage=\d+$/ } @resp_directives) {
  1         6  
743 1 50       11 carp "DO CACHE: 'Authorization' appears: s-maxage\n"
744             if $DEBUG;
745 1         400 return 1
746             }
747 1 50       10 carp "NO CACHE: 'Authorization' appears in request when shared\n"
748             if $DEBUG;
749 1         418 return 0
750             }
751             };
752            
753            
754             # RFC 7234 Section 3 #6
755             #
756             # the response either:
757             #
758             # - contains an Expires header field (see Section 5.3)
759             #
760 19         67 do {
761 19         37 my $expires_at = $resp->header('Expires');
762            
763 19 100       632 if ($expires_at) {
764 1 50       11 carp "OK CACHE: 'Expires' at: $expires_at\n"
765             if $DEBUG;
766 1         358 return 1
767             }
768             };
769            
770             # - contains a max-age response directive (see Section 5.2.2.8)
771             #
772 18         23 do {
773 18 100   6   66 if (any { lc $_ =~ m/^max-age=\d+$/ } @resp_directives) {
  6         33  
774 2 100       24 carp "DO CACHE: 'max-age' appears in response cache directives\n"
775             if $DEBUG;
776 2         365 return 1
777             }
778             };
779            
780             # - contains a s-maxage response directive (see Section 5.2.2.9)
781             # and the cache is shared
782             #
783 16 100       65 if ($self->is_shared) {
784 1 50   1   4 if (any { lc $_ =~ m/^s-maxage=\d+$/ } @resp_directives) {
  1         5  
785 1 50       11 carp "DO CACHE: 's-maxage' appears in response cache directives\n"
786             if $DEBUG;
787 1         375 return 1
788             }
789             };
790            
791            
792             # - contains a Cache Control Extension (see Section 5.2.3) that
793             # allows it to be cache
794             #
795             # TODO it looks like this is only used for special defined cache-control
796             # directives. As such, those need special treatment.
797             # It does not seem a good idea to hardcode those here, a config would
798             # be a better solution.
799            
800            
801             # - has a status code that is defined as cacheable by default (see
802             # Section 4.2.2)
803             #
804 15         20 do {
805 15         32 my $code = $resp->code;
806            
807 15 100       139 if (HTTP::Status::is_cacheable_by_default($code)) {
808 9 100       51 carp "DO CACHE: status code is cacheable by default: '$code'\n"
809             if $DEBUG;
810 9         493 return 1
811             }
812             };
813            
814             # - contains a public response directive (see Section 5.2.2.5)
815             #
816 6         54 do {
817 6 100   3   19 if (any { lc $_ eq 'public' } @resp_directives) {
  3         7  
818 1 50       11 carp "DO CACHE: 'public' appears in response cache directives\n"
819             if $DEBUG;
820 1         378 return 1
821             }
822             };
823            
824             # Falls trough ... SHOULD NOT store in cache
825             #
826 5 50       51 carp "NO CACHE: Does not match the six criteria above\n"
827             if $DEBUG;
828            
829 5         1975 return undef;
830             }
831              
832              
833             # _may_reuse_from_cache
834             #
835             # my $status = _may_reuse_from_cache (
836             # $presented_request,
837             # $stored_response,
838             # $associated_request,
839             # )
840             #
841             # will return false if the stored response can not be used for this request at
842             # all. In all other cases, it either
843             # - can be used, because it matches all the criteria and os fresh
844             # - is stale and can be used if needed
845             # - needs revalidation
846             #
847             # see RFC 7234 Section 4: Constructing Responses from Caches
848             #
849             sub _may_reuse_from_cache {
850 23     23   37268 my $self = shift;
851 23         30 my $rqst_presented = shift;
852 23         31 my $resp_stored = shift;
853 23         25 my $rqst_associated = shift;
854            
855             # RFC 7234 Section 4
856             #
857             # When presented with a request, a cache MUST NOT reuse a stored
858             # response, unless:
859            
860            
861             # RFC 7234 Section 4 #1
862             #
863             # The presented effective request URI (Section 5.5 of [RFC7230]) and
864             # that of the stored response match
865             #
866 23         28 do {
867 23 100       56 unless ( URI::eq($rqst_presented->uri, $rqst_associated->uri) ) {
868 1 50       175 carp "NO REUSE: URI's do not match\n"
869             if $DEBUG;
870 1         389 return $REUSE_NO_MATCH
871             }
872             };
873            
874            
875             # RFC 7234 Section 4 #2
876             #
877             # the request method associated with the stored response allows it
878             # to be used for the presented request
879             #
880 22         4018 do {
881 22 100       52 unless ( $rqst_presented->method eq $rqst_associated->method ) {
882 2 50       49 carp "NO REUSE: Methods do not match\n"
883             if $DEBUG;
884 2         774 return $REUSE_NO_MATCH
885             }
886             };
887             #
888             # NOTE: We did not make the test case insensitive, according to RFC 7231.
889             #
890             # NOTE: We might want to extend it so that we can serve a chopped response
891             # where the presented request is a HEAD request
892            
893            
894             # RFC 7234 Section 4 #3
895             #
896             # selecting header fields nominated by the stored response (if any)
897             # match those presented (see Section 4.1)
898 20 100       356 if ( $resp_stored->header('Vary') ) {
899            
900 5 100       182 if ( scalar $resp_stored->header('Vary') eq '*' ) {
901 1 50       40 carp "NO REUSE: 'Vary' equals '*'\n"
902             if $DEBUG;
903 1         385 return $REUSE_NO_MATCH
904             }
905            
906             #create an array with nominated headers
907             my @vary_headers =
908 4   50     124 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  4         124  
  4         10  
  4         8  
  4         10  
909             split ',', scalar $resp_stored->header('Vary') || '';
910            
911 4         8 foreach my $header ( @vary_headers ) {
912 4   100     6 my $header_presented = $rqst_presented->header($header) || '';
913 4   100     142 my $header_associated = $rqst_associated->header($header) || '';
914 4 100       128 unless ( $header_presented eq $header_associated ) {
915 2 50       20 carp "NO REUSE: Nominated headers in 'Vary' do not match\n"
916             if $DEBUG;
917 2         788 return $REUSE_NO_MATCH
918             }
919             }
920             };
921             #
922             # TODO: According to Section 4.1, we could do normalization and reordering
923             # This requires further investigation and is worth doing to increase
924             # the hit chance
925            
926            
927             # RFC 7234 Section 4 #4
928             #
929             # the presented request does not contain the no-cache pragma
930             # (Section 5.4), nor the no-cache cache directive (Section 5.2.1),
931             # unless the stored response is successfully validated
932             # (Section 4.3)
933             #
934 17         613 do {
935             # generate an array with cache-control directives
936             my @rqst_directives =
937 17   100     50 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  1         33  
  1         3  
  1         3  
  1         3  
938             split ',', scalar $rqst_presented->header('cache-control') || '';
939            
940 17 100   1   657 if (any { lc $_ eq 'no-cache' } @rqst_directives) {
  1         4  
941 1 50       12 carp "NO REUSE: 'no-cache' appears in request cache directives\n"
942             if $DEBUG;
943 1         395 return $REUSE_REVALIDATE
944             }
945            
946 16 50 100     60 if (
947             $rqst_presented->header('Pragma')
948             and
949             scalar $rqst_presented->header('Pragma') =~ /no-cache/
950             ) {
951 1 50       76 carp "NO REUSE: Pragma: 'no-cache' appears in request\n"
952             if $DEBUG;
953 1         383 return $REUSE_REVALIDATE
954             }
955             };
956            
957            
958             # RFC 7234 Section 4 #5
959             #
960             # the stored response does not contain the no-cache cache directive
961             # (Section 5.2.2.2), unless it is successfully validated
962             # (Section 4.3)
963             #
964 15         489 do {
965             # generate an array with cache-control directives
966             my @resp_directives =
967 15   100     47 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  4         131  
  4         11  
  4         10  
  4         11  
968             split ',', scalar $resp_stored->header('cache-control') || '';
969            
970 15 100   4   406 if (any { lc $_ eq 'no-cache' } @resp_directives) {
  4         14  
971 1 50       12 carp "NO REUSE: 'no-cache' appears in response cache directives\n"
972             if $DEBUG;
973 1         394 return $REUSE_REVALIDATE
974             }
975             };
976            
977             # RFC 7234 Section 4 #6
978             #
979             # the stored response is either:
980             #
981             # - fresh (see Section 4.2), or
982             #
983 14         34 do {
984 14 100       47 if ($resp_stored->is_fresh(heuristic_expiry => undef)) {
985 4 100       7696 carp "DO REUSE: Response is fresh\n"
986             if $DEBUG;
987 4         869 return $REUSE_IS_OK
988             }
989             };
990             #
991             # TODO: heuristic_expiry => undef should be a option, not hardcoded
992            
993             # - allowed to be served stale (see Section 4.2.4), or
994             #
995 10         8894 do {
996             # generate an array with cache-control directives
997             my @resp_directives =
998 10   50     26 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  0         0  
  0         0  
  0         0  
  0         0  
999             split ',', scalar $resp_stored->header('cache-control') || '';
1000            
1001             # RFC 7234 Section 5.2.2.1
1002             #
1003             # must-revalidate
1004             #
1005 10 50   0   399 if (any { lc $_ eq 'must-revalidate' } @resp_directives) {
  0         0  
1006 0 0       0 carp "NO REUSE: Stale but 'must-revalidate'\n"
1007             if $DEBUG;
1008 0         0 return $REUSE_IS_STALE_REVALIDATE
1009             }
1010            
1011             # RFC 7234 Section 5.2.2.7
1012             #
1013             # proxy-revalidate
1014             #
1015 10 50 33     57 if (
1016 0     0   0 any { lc $_ eq 'proxy-revalidate' } @resp_directives
1017             and
1018             $self->is_shared
1019             ) {
1020 0 0       0 carp "NO REUSE: Stale but 'proxy-revalidate'\n"
1021             if $DEBUG;
1022 0         0 return $REUSE_IS_STALE_REVALIDATE
1023             }
1024            
1025            
1026             # RFC 7234 Section 5.2.1.2
1027             #
1028             # max-stale = ...
1029             #
1030             my @rqst_directives =
1031 10   50     43 map { my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//; $str }
  0         0  
  0         0  
  0         0  
  0         0  
1032             split ',', scalar $rqst_presented->header('cache-control') || '';
1033            
1034             my ($directive) =
1035 10         347 grep { $_ =~ /^max-stale\s*=?\s*\d*$/ } @rqst_directives;
  0         0  
1036            
1037 10 50       24 if ($directive) {
1038 0         0 my ($max_stale) = $directive =~ /=(\d+)$/;
1039 0 0       0 unless (defined $max_stale) {
1040 0 0       0 carp "DO REUSE: 'max-stale' for unlimited time\n"
1041             if $DEBUG;
1042 0         0 return $REUSE_IS_STALE_OK
1043             }
1044 0         0 my $freshness = # not fresh!!! so, this is a negative number
1045             $resp_stored->freshness_lifetime(heuristic_expiry => undef);
1046 0 0       0 if ( abs($freshness) < $max_stale ) {
1047 0 0       0 carp "DO REUSE: 'max-stale' not exceeded\n"
1048             if $DEBUG;
1049 0         0 return $REUSE_IS_STALE_OK
1050             }
1051             }
1052            
1053             };
1054            
1055             # - successfully validated (see Section 4.3).
1056             #
1057 10         16 do {
1058 10 100       92 carp "NO REUSE: must successfully validated"
1059             if $DEBUG;
1060 10         2942 return $REUSE_IS_STALE_REVALIDATE
1061             };
1062            
1063             }
1064              
1065             1;