File Coverage

blib/lib/Plack/Middleware/Deflater.pm
Criterion Covered Total %
statement 98 107 91.5
branch 29 58 50.0
condition 9 28 32.1
subroutine 15 16 93.7
pod 2 2 100.0
total 153 211 72.5


line stmt bran cond sub pod time code
1             package Plack::Middleware::Deflater;
2 3     3   599559 use strict;
  3         11  
  3         146  
3 3     3   57 use 5.008001;
  3         10  
4             our $VERSION = '0.14';
5 3     3   549 use parent qw(Plack::Middleware);
  3         383  
  3         23  
6 3     3   36402 use Plack::Util::Accessor qw( content_type vary_user_agent);
  3         7  
  3         21  
7 3     3   182 use Plack::Util;
  3         6  
  3         2475  
8              
9             sub prepare_app {
10 3     3 1 426061 my $self = shift;
11 3 100       23 if (my $match_cts = $self->content_type) {
12 1 50       39 $match_cts = [$match_cts] if !ref $match_cts;
13 1         3 $self->content_type($match_cts);
14             }
15             }
16              
17             sub call {
18 1     1 1 31508 my($self, $env) = @_;
19              
20 1         14 my $res = $self->app->($env);
21              
22             $self->response_cb(
23             $res,
24             sub {
25 1     1   61 my $res = shift;
26              
27             # can't operate on Content-Ranges
28 1 50       5 return if $env->{HTTP_CONTENT_RANGE};
29              
30 1 50       4 return if $env->{"plack.skip-deflater"};
31              
32 1         5 my $h = Plack::Util::headers($res->[1]);
33 1   50     45 my $content_type = $h->get('Content-Type') || '';
34 1         31 $content_type =~ s/(;.*)$//;
35 1 50       14 if (my $match_cts = $self->content_type) {
36 1         6 my $match = 0;
37 1         1 for my $match_ct (@{$match_cts}) {
  1         3  
38 1 50       4 if ($content_type eq $match_ct) {
39 1         8 $match++;
40 1         2 last;
41             }
42             }
43 1 50       3 return unless $match;
44             }
45              
46 1 50 33     4 if (Plack::Util::status_with_no_entity_body($res->[0])
      33        
47             or $h->exists('Cache-Control') && $h->get('Cache-Control') =~ /\bno-transform\b/)
48             {
49 0         0 return;
50             }
51              
52 1   50     34 my @vary = split /\s*,\s*/, ($h->get('Vary') || '');
53 1         43 push @vary, 'Accept-Encoding';
54 1 50       4 push @vary, 'User-Agent' if $self->vary_user_agent;
55 1         11 $h->set('Vary' => join(",", @vary));
56              
57             # Do not clobber already existing encoding
58 1 50 33     26 return if $h->exists('Content-Encoding') && $h->get('Content-Encoding') ne 'identity';
59              
60             # some browsers might have problems, so set no-compress
61 1 50       21 return if $env->{"psgix.no-compress"};
62              
63             # Some browsers might have problems with content types
64             # other than text/html, so set compress-only-text/html
65 1 50       3 if ($env->{"psgix.compress-only-text/html"}) {
66 0 0       0 return if $content_type ne 'text/html';
67             }
68              
69             # TODO check quality
70 1         31 my $encoding = 'identity';
71 1 50       4 if (defined $env->{HTTP_ACCEPT_ENCODING}) {
72 1         3 for my $enc (qw(gzip deflate identity)) {
73 1 50       17 if ($env->{HTTP_ACCEPT_ENCODING} =~ /\b$enc\b/) {
74 1         2 $encoding = $enc;
75 1         2 last;
76             }
77             }
78             }
79              
80 1         2 my $encoder;
81 1 50 33     5 if ($encoding eq 'gzip' || $encoding eq 'deflate') {
82 1         13 $encoder = Plack::Middleware::Deflater::Encoder->new($encoding);
83             }
84              
85 1 50       7 if ($encoder) {
86 1         5 $h->set('Content-Encoding' => $encoding);
87 1         27 $h->remove('Content-Length');
88              
89             # normal response
90 1 50 33     35 if ($res->[2] && ref($res->[2]) && ref($res->[2]) eq 'ARRAY') {
      33        
91 1         1 my $buf = '';
92 1         9 foreach (@{ $res->[2] }) {
  1         9  
93 1 50       6 $buf .= $encoder->print($_) if defined $_;
94             }
95 1         13 $buf .= $encoder->close();
96 1         4 $res->[2] = [$buf];
97 1         67 return;
98             }
99              
100             # delayed or stream
101             return sub {
102 0         0 $encoder->print(shift);
103 0         0 };
104             }
105             }
106 1         27 );
107             }
108              
109             1;
110              
111             package Plack::Middleware::Deflater::Encoder;
112              
113 3     3   23 use strict;
  3         5  
  3         87  
114 3     3   22 use warnings;
  3         7  
  3         177  
115 3     3   2214 use Compress::Zlib;
  3         217759  
  3         952  
116              
117 3     3   29 use constant GZIP_MAGIC => 0x1f8b;
  3         7  
  3         1892  
118              
119             sub new {
120 1     1   3 my $class = shift;
121 1         2 my $encoding = shift;
122 1 50       13 my($encoder, $status) =
123             $encoding eq 'gzip'
124             ? deflateInit(-WindowBits => -MAX_WBITS())
125             : deflateInit(-WindowBits => MAX_WBITS());
126 1 50       461 die 'Cannot create a deflation stream' if $status != Z_OK;
127              
128 1         17 bless {
129             header => 0,
130             closed => 0,
131             encoding => $encoding,
132             encoder => $encoder,
133             crc => crc32(undef),
134             length => 0,
135             }, $class;
136             }
137              
138             sub print : method {
139 2     2   4 my $self = shift;
140 2 50       5 return if $self->{closed};
141 2         3 my $chunk = shift;
142 2 100       10 if (!defined $chunk) {
143 1         4 my($buf, $status) = $self->{encoder}->flush();
144 1 50       61 die "deflate failed: $status" if ($status != Z_OK);
145 1 50 33     9 if (!$self->{header} && $self->{encoding} eq 'gzip') {
146 1         3 $buf = pack("nccVcc", GZIP_MAGIC, Z_DEFLATED, 0, time(), 0, $Compress::Raw::Zlib::gzip_os_code) . $buf;
147             }
148             $buf .= pack("LL", $self->{crc}, $self->{length})
149 1 50       14 if $self->{encoding} eq 'gzip';
150 1         3 $self->{closed} = 1;
151 1         3 return $buf;
152             }
153              
154 1         10 my($buf, $status) = $self->{encoder}->deflate($chunk);
155 1 50       29 die "deflate failed: $status" if ($status != Z_OK);
156 1         7 $self->{length} += length $chunk;
157 1         4 $self->{crc} = crc32($chunk, $self->{crc});
158 1 50       3 if (length $buf) {
159 0 0 0     0 if (!$self->{header} && $self->{encoding} eq 'gzip') {
160 0         0 $buf = pack("nccVcc", GZIP_MAGIC, Z_DEFLATED, 0, time(), 0, $Compress::Raw::Zlib::gzip_os_code) . $buf;
161             }
162 0         0 $self->{header} = 1;
163 0         0 return $buf;
164             }
165 1         4 return '';
166             }
167              
168             sub close : method {
169 1     1   3 $_[0]->print(undef);
170             }
171              
172             sub closed {
173 0     0     $_[0]->{closed};
174             }
175              
176             1;
177              
178             __END__