File Coverage

blib/lib/Plack/Middleware/Cache/CHI.pm
Criterion Covered Total %
statement 103 110 93.6
branch 20 32 62.5
condition 12 26 46.1
subroutine 20 21 95.2
pod 1 11 9.0
total 156 200 78.0


line stmt bran cond sub pod time code
1 2     2   153302 use strict;
  2         6  
  2         121  
2             package Plack::Middleware::Cache::CHI;
3             our $AUTHORITY = 'cpan:PNU';
4             # ABSTRACT: Caching Reverse Proxy for Plack
5              
6 2     2   12 use warnings;
  2         5  
  2         74  
7 2     2   664 use parent qw/Plack::Middleware/;
  2         311  
  2         16  
8              
9 2     2   16453 use Plack::Util::Accessor qw( chi rules scrub cachequeries trace );
  2         6  
  2         15  
10 2     2   1087 use Data::Dumper;
  2         6973  
  2         137  
11 2     2   1692 use Plack::Request;
  2         2382933  
  2         63  
12 2     2   1278 use Plack::Response;
  2         5817  
  2         54  
13 2     2   814 use Time::HiRes qw( gettimeofday );
  2         1443  
  2         16  
14              
15             our $VERSION = '0.102'; # VERSION
16              
17             our @trace;
18             our $timer_call;
19             our $timer_pass;
20              
21             sub _uinterval {
22 8     8   25 my ( $t0, $t1 ) = ( @_, [gettimeofday] );
23 8         29 ($t1->[0] - $t0->[0]) * 1_000_000 + $t1->[1] - $t0->[1];
24             }
25              
26             sub call {
27 5     5 1 43764 my ($self,$env) = @_;
28              
29             ## Pass-thru streaming responses
30 5 50       21 return $self->app->($env)
31             if ( ref $env eq 'CODE' );
32              
33             ## Localize trace for this request
34 5         12 local @trace = ();
35 5         7 local $timer_pass = undef;
36 5         24 local $timer_call = [gettimeofday];
37              
38 5         32 my $req = Plack::Request->new($env);
39 5         47 my $r = $self->handle($req);
40 5         36 my $res = Plack::Response->new(@$r);
41              
42             ## Add trace and cache key to response headers
43 5         428 $timer_call = _uinterval($timer_call);
44 5         17 my $trace = join q{, }, @trace;
45 5         15 my $key = $self->cachekey($req);
46              
47             ## The subrequest is timed separately
48 5 100       29 if ( $timer_pass ) {
49 3         4 $timer_call -= $timer_pass;
50 3         9 $res->headers->push_header(
51             'X-Plack-Cache-Time-Pass' => "$timer_pass us",
52             );
53             }
54              
55             $res->headers->push_header(
56 5         112 'X-Plack-Cache' => $trace,
57             'X-Plack-Cache-Key' => $key,
58             'X-Plack-Cache-Time' => "$timer_call us",
59             );
60              
61 5         324 $res->finalize;
62             }
63              
64             sub handle {
65 5     5 0 6 my ($self,$req) = @_;
66              
67 5 50 33     18 if ( $req->method eq 'GET' or $req->method eq 'HEAD' ) {
68 5 50       54 if ( $req->headers->header('Expect') ) {
69 0         0 push @trace, 'expect';
70 0         0 $self->pass($req);
71             } else {
72 5         638 $self->lookup($req);
73             }
74             } else {
75 0         0 $self->invalidate($req);
76             }
77             }
78              
79             sub pass {
80 3     3 0 4 my ($self,$req) = @_;
81 3         7 push @trace, 'pass';
82 3         14 $timer_pass = [gettimeofday];
83              
84 3         10 my $res = $self->app->($req->env);
85              
86 3         691 $timer_pass = _uinterval($timer_pass);
87 3         8 return $res;
88             }
89              
90             sub invalidate {
91 0     0 0 0 my ($self,$req) = @_;
92 0         0 push @trace, 'invalidate';
93 0         0 $self->chi->remove( $self->cachekey($req) );
94 0         0 $self->pass($req);
95             }
96              
97             sub match {
98 5     5 0 7 my ($self, $req) = @_;
99              
100 5         6 my $path;
101             my $opts;
102              
103 5 50       6 my @rules = @{ $self->rules || [] };
  5         19  
104 5   100     112 while ( @rules || return ) {
105 10         16 my $match = shift @rules;
106 10         15 $opts = shift @rules;
107 10         27 $path = $req->path_info;
108 10 50       110 last if 'CODE' eq ref $match ? $match->($path) : $path =~ $match;
    100          
109             }
110 4         8 return $opts;
111             }
112              
113             sub lookup {
114 5     5 0 9 my ($self, $req) = @_;
115 5         15 push @trace, 'lookup';
116              
117 5         13 my $opts = $self->match($req);
118              
119 5 100       17 return $self->pass($req)
120             if not defined $opts;
121              
122 4 50 33     16 return $self->invalidate($req)
123             if ( $req->param and not $self->cachequeries );
124              
125 4         708 my $entry = $self->fetch( $req );
126 4         508 my $res = [ 500, ['Content-Type','text/plain'], ['ISE'] ];
127              
128 4 100       11 if ( defined $entry ) {
129 2         4 push @trace, 'hit';
130 2         4 $res = $entry->[1];
131 2 50       7 return $self->invalidate($req)
132             if not $self->valid($req,$res);
133             } else {
134 2         5 push @trace, 'miss';
135 2         6 $res = $self->delegate($req);
136 2 50       9 $self->store($req,$res,$opts)
137             if $self->valid($req,$res);
138             }
139 4         1269 return $res;
140             }
141              
142             sub valid {
143 4     4 0 7 my ($self, $req, $res) = @_;
144              
145 4         7 my $res_status = $res->[0];
146              
147             return
148             unless (
149 4 50 66     44 $res_status == 200 or
      66        
      33        
      33        
      33        
      33        
150             $res_status == 203 or
151             $res_status == 300 or
152             $res_status == 301 or
153             $res_status == 302 or
154             $res_status == 404 or
155             $res_status == 410
156             );
157              
158 4         17 return 1;
159             }
160              
161             sub cachekey {
162 11     11 0 13 my ($self, $req) = @_;
163              
164 11         29 my $uri = $req->uri->canonical;
165              
166 11 50       2757 $uri->query(undef)
167             if not $self->cachequeries;
168              
169 11         74 $uri->as_string;
170             }
171              
172             sub fetch {
173 4     4 0 8 my ($self, $req) = @_;
174 4         8 push @trace, 'fetch';
175              
176 4         9 my $key = $self->cachekey($req);
177 4         24 $self->chi->get( $key );
178             }
179              
180             sub store {
181 2     2 0 3 my ($self, $req, $res, $opts) = @_;
182 2         5 push @trace, 'store';
183              
184 2         5 my $key = $self->cachekey($req);
185 2         12 $self->chi->set( $key, [$req->headers,$res], $opts );
186             }
187              
188             sub delegate {
189 2     2 0 4 my ($self, $req, $opts) = @_;
190 2         3 push @trace, 'delegate';
191              
192 2         7 my $res = $self->pass($req);
193 2 50       4 foreach ( @{ $self->scrub || [] } ) {
  2         7  
194 2         22 Plack::Util::header_remove( $res->[1], $_ );
195             }
196              
197 2         43 my $body;
198             Plack::Util::foreach( $res->[2], sub {
199 2 50   2   135 $body .= $_[0] if $_[0];
200 2         13 });
201              
202 2         64 return [ $res->[0], $res->[1], [$body] ];
203             }
204              
205             1;
206              
207             __END__