File Coverage

blib/lib/Plack/Middleware/Deflater.pm
Criterion Covered Total %
statement 98 110 89.0
branch 27 58 46.5
condition 8 25 32.0
subroutine 15 16 93.7
pod 2 2 100.0
total 150 211 71.0


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