File Coverage

blib/lib/Plack/Middleware/Expires.pm
Criterion Covered Total %
statement 83 85 97.6
branch 38 50 76.0
condition 9 12 75.0
subroutine 12 12 100.0
pod 2 3 66.6
total 144 162 88.8


line stmt bran cond sub pod time code
1             package Plack::Middleware::Expires;
2              
3 3     3   158790 use strict;
  3         7  
  3         121  
4 3     3   19 use warnings;
  3         7  
  3         96  
5 3     3   2191 use parent qw/Plack::Middleware/;
  3         1373  
  3         17  
6              
7 3     3   50800 use Plack::Util;
  3         8  
  3         129  
8 3     3   20 use Plack::Util::Accessor qw( content_type expires );
  3         6  
  3         19  
9 3     3   3788 use HTTP::Status qw//;
  3         12556  
  3         94  
10 3     3   3443 use HTTP::Date;
  3         20278  
  3         1234  
11              
12             our $VERSION = '0.06';
13              
14             sub calc_expires {
15 46     46 0 5689 my ( $expires, $modified, $access ) = @_;
16 46 50       121 $access = time if ! defined $access;
17              
18 46         245 my %term = (
19             year => 60*60*24*365,
20             month => 60*60*24*31,
21             week => 60*60*24*7,
22             day => 60*60*24,
23             hour => 60*60,
24             minute => 60,
25             second => 1
26             );
27              
28 46         52 my $expires_sec;
29 46 100 66     523 if ( $expires && $expires =~ m!^(A|M)(\d+)$! ) {
    100 66        
30 9 100       42 my $base = ( $1 eq 'M' ) ? $modified : $access;
31 9 50       36 return if ! defined $base;
32 9         33 $expires_sec = $base + $2;
33             }
34             elsif ( $expires && $expires =~ m!^(access|now|modification)\s(?:plus)\s(.+)$! ) {
35 36 100       83 my $base = ( $1 eq 'modification' ) ? $modified : $access;
36 36 100       76 return if ! defined $base;
37 35         114 my @datetime = split /\s+/,$2;
38 35         45 $expires_sec = $base;
39 35         104 while ( my ($num, $type) = splice( @datetime, 0, 2) ) {
40 38         43 my $term_sec;
41 38         108 (my $sigular_type = lc $type) =~ s/s$//;
42 38 50       72 $type = '' if ! defined $type;
43 38 100       317 Carp::croak "missing type '$type' in '$expires'" if ! exists $term{$sigular_type};
44              
45 37 100       101 if ( $num !~ m!^\d! ) {
46 1         149 Carp::croak "numeric value expected '$num' in '$expires'";
47             }
48 3     3   31 no warnings 'numeric';
  3         6  
  3         1954  
49 36         62 $num = int( $num + 0 );
50              
51 36         148 $expires_sec += $term{$sigular_type} * $num;
52             }
53             }
54             else {
55 1         1670 Carp::croak("unkown expires format: '$expires'");
56             }
57 42 100       89 $expires_sec = 2147483647 if $expires_sec > 2147483647; #year 2039
58 42         203 return $expires_sec;
59             }
60              
61             sub prepare_app {
62 5     5 1 1019100 my $self = shift;
63 5 50       22 if ( my $expires = $self->expires ) {
64             # test run for configuration check
65 5         172 calc_expires( $expires, time, time );
66             }
67             }
68              
69             sub call {
70 4     4 1 65130 my($self, $env) = @_;
71 4         9 my $req_time = time;
72              
73 4         32 my $res = $self->app->($env);
74              
75             $self->response_cb($res, sub {
76 4     4   86 my $res = shift;
77              
78 4 50       18 return if ! $self->expires;
79 4         39 my $type_match = $self->content_type;
80 4 50       28 return if ! defined $type_match;
81 4 100 66     37 my @type_match = (ref $type_match && ref $type_match eq 'ARRAY') ? @{$type_match} : ($type_match);
  1         5  
82              
83             # expires works only for successful response
84 4 50       23 return if HTTP::Status::is_error( $res->[0] );
85              
86             # if already exists Expires header, do no override
87 4 100       37 return if Plack::Util::header_exists($res->[1], 'Expires');
88              
89             #content_type check
90 3         85 my $type = Plack::Util::header_get($res->[1], 'Content-Type');
91 3 50       77 return if ! defined $type;
92 3         4 my $type_check;
93 3         8 for ( @type_match ) {
94 4 100       13 if (my $ref = ref $_) {
95 3 100 100     31 if ($ref eq 'Regexp' && $type =~ m!$_!) {
    100          
96 1         1 $type_check = 1;
97 1         4 last;
98             }
99             elsif ($ref eq 'CODE') {
100 1         5 $type_check = $_->($env);
101 1         7 last;
102             }
103             }
104             else {
105 1 50       6 if ( lc $type eq lc $_ ) {
106 1         26 $type_check = 1;
107 1         5 last;
108             }
109             }
110             }
111 3 50       26 return if ! $type_check;
112              
113 3         5 my $last_modified;
114 3 50       12 if ( $last_modified = Plack::Util::header_get($res->[1], 'Last-Modified') ) {
115 0         0 $last_modified = HTTP::Date::str2time( $last_modified );
116             }
117            
118             # calurate
119 3         73 my $expires_sec = calc_expires( $self->expires, $last_modified, $req_time );
120 3         18 Plack::Util::header_set( $res->[1], 'Expires', HTTP::Date::time2str( $expires_sec ) );
121 3 50       152 if ( my $cc = Plack::Util::header_get($res->[1], 'Cache-Control') ) {
122 0         0 $cc .= sprintf "max-age=%d", $expires_sec - $req_time;
123             }
124             else {
125 3         89 Plack::Util::header_set( $res->[1], 'Cache-Control', sprintf("max-age=%d", $expires_sec - $req_time) );
126             }
127 4         191 });
128              
129             }
130              
131              
132             1;
133             __END__