File Coverage

blib/lib/IO/Stream/HTTP/Persistent.pm
Criterion Covered Total %
statement 114 132 86.3
branch 19 40 47.5
condition 8 20 40.0
subroutine 18 18 100.0
pod 1 3 33.3
total 160 213 75.1


line stmt bran cond sub pod time code
1             package IO::Stream::HTTP::Persistent;
2 6     6   570578 use 5.010001;
  6         59  
3 6     6   32 use warnings;
  6         23  
  6         181  
4 6     6   28 use strict;
  6         14  
  6         131  
5 6     6   953 use utf8;
  6         32  
  6         33  
6 6     6   127 use Carp;
  6         10  
  6         415  
7              
8             our $VERSION = 'v0.2.2';
9              
10 6     6   46 use Scalar::Util qw( dualvar );
  6         17  
  6         299  
11 6     6   2299 use Data::Alias 0.08;
  6         3871  
  6         281  
12 6     6   761 use IO::Stream::const;
  6         13343  
  6         29  
13              
14 6     6   911 use constant HTTP_SENT => 1<<16;
  6         12  
  6         268  
15 6     6   33 use constant HTTP_RECV => 1<<17;
  6         10  
  6         298  
16              
17 6     6   29 use constant HTTP_EREQINCOMPLETE => dualvar(-10000, 'incomplete HTTP request headers');
  6         11  
  6         308  
18 6     6   29 use constant HTTP_ERESINCOMPLETE => dualvar(-10001, 'incomplete HTTP response');
  6         12  
  6         995  
19              
20              
21             #
22             # Export constants.
23             #
24             # Usage: use IO::Stream::HTTP::Persistent qw( :ALL :DEFAULT :Event :Error HTTP_RECV ... )
25             #
26             my %TAGS = (
27             Event => [ qw( HTTP_SENT HTTP_RECV ) ],
28             Error => [ qw( HTTP_EREQINCOMPLETE HTTP_ERESINCOMPLETE ) ],
29             );
30             $TAGS{ALL} = $TAGS{DEFAULT} = [ map { @{$_} } values %TAGS ];
31             my %KNOWN = map { $_ => 1 } @{ $TAGS{ALL} };
32              
33             sub import {
34 6     6   41 my (undef, @p) = @_;
35 6 50       31 if (!@p) {
36 6         23 @p = (':DEFAULT');
37             }
38 6 50       10 @p = map { /\A:(\w+)\z/xms ? @{ $TAGS{$1} || [] } : $_ } @p;
  6 50       36  
  6         46  
39 6         15 my $pkg = caller;
40 6     6   36 no strict 'refs';
  6         9  
  6         7462  
41 6         13 for my $const (@p) {
42 24 50       51 next if !$KNOWN{$const};
43 24         34 *{"${pkg}::$const"} = \&{$const};
  24         70  
  24         46  
44             }
45 6         1032 return;
46             }
47              
48              
49             sub new {
50 4     4 1 3289 my ($class) = @_;
51 4         44 my $self = bless {
52             out_buf => q{}, # modified on: OUT
53             out_pos => undef, # modified on: OUT
54             out_bytes => 0, # modified on: OUT
55             in_buf => q{}, # modified on: IN
56             in_bytes => 0, # modified on: IN
57             ip => undef, # modified on: RESOLVED
58             is_eof => undef, # modified on: EOF
59             out_sizes => [], # modified on: HTTP_SENT
60             in_sizes => [], # modified on: HTTP_RECV
61             _out_len => 0, # current length of {out_buf}
62             # used to detect how many bytes was added to
63             # {out_buf} in write() and increase {_out_todo}
64             _out_todo => 0, # size of incomplete request at end of {out_buf}
65             # used to find complete requests appended to
66             # {out_buf} and add their sizes to {_out_queue}
67             # can be negative, if we detected size
68             # of next request but it isn't appended to
69             # {out_buf} completely yet
70             _out_queue => [], # sizes of unsent complete requests in {out_buf}
71             # will be moved to {out_sizes} after sending
72             _out_sent => 0, # how many bytes of {_out_queue}[0] already sent
73             # if it become >= {_out_queue}[0] then it's
74             # time to move from {_out_queue} to {out_sizes}
75             _out_broken => 0, # if true, disable HTTP_SENT and {out_sizes} support
76             _in_todo => 0, # size of incomplete response at end of {in_buf}
77             # used to find complete responses appended to
78             # {in_buf} and add their sizes to {in_sizes}
79             _wait_eof => 0, # flag: response end expected on EOF
80             _wait_length=> 0, # expected response length
81             _wait_chunk => 0, # known partial response length before next
82             # chunk header (or end of response sign)
83             }, $class;
84 4         35 return $self;
85             }
86              
87             sub PREPARE {
88 4     4 0 511 my ($self, $fh, $host, $port) = @_;
89 4         11 for (qw( out_buf out_pos in_buf ip is_eof )) {
90 20         52 alias $self->{$_} = $self->{_master}->{$_};
91             }
92 4         25 $self->{_slave}->PREPARE($fh, $host, $port);
93 4         33 return;
94             }
95              
96             sub WRITE {
97 4     4   44 my ($self) = @_;
98 4         16 my $m = $self->{_master};
99              
100 4         10 my $l = length $self->{out_buf};
101 4         8 $self->{_out_todo} += $l - $self->{_out_len};
102 4         7 $self->{_out_len} = $l;
103              
104 4   66     38 while (!$self->{_out_broken} && $self->{_out_todo} > 0) {
105 6         22 pos $self->{out_buf} = $self->{_out_len} - $self->{_out_todo};
106 6 50       50 if ($self->{out_buf} =~ /\G((?:[^\r\n]+\r?\n)+\r?\n)/xms) {
107 6         29 my $h = $1;
108 6 50       26 my $c_len = $h =~ /^Content-Length:\s*(\d+)\s*\n/ixms ? $1 : 0;
109 6         14 my $size = length($h) + $c_len;
110 6         8 push @{ $self->{_out_queue} }, $size;
  6         13  
111 6         24 $self->{_out_todo} -= $size;
112             }
113             else {
114 0         0 $self->{_out_broken} = 1;
115 0         0 $m->EVENT(0, HTTP_EREQINCOMPLETE);
116 0         0 last;
117             }
118             }
119              
120 4         22 $self->{_slave}->WRITE();
121 4         29 return;
122             }
123              
124             sub EVENT { ## no critic (ProhibitExcessComplexity)
125 13     13 0 2358 my ($self, $e, $err) = @_;
126 13         25 my $m = $self->{_master};
127              
128 13 100       38 if ($e & OUT) {
129 4         10 $self->{_out_len} = length $self->{out_buf};
130              
131 4         8 $m->{out_bytes} += $self->{out_bytes};
132 4         32 $self->{_out_sent} += $self->{out_bytes};
133 4         7 $self->{out_bytes} = 0;
134              
135 4   66     6 while (@{ $self->{_out_queue} } && $self->{_out_sent} >= $self->{_out_queue}[0]) {
  10         38  
136 6         10 $e |= HTTP_SENT;
137 6         11 $self->{_out_sent} -= $self->{_out_queue}[0];
138 6         14 push @{ $self->{out_sizes} }, shift @{ $self->{_out_queue} };
  6         11  
  6         12  
139             }
140             }
141              
142 13 100       28 if ($e & IN) {
143 4         10 $m->{in_bytes} += $self->{in_bytes};
144 4         8 $self->{_in_todo} += $self->{in_bytes};
145 4         8 $self->{in_bytes} = 0;
146             }
147              
148 13         32 while ($self->{_in_todo} > 0) {
149 6 50       21 if ($e & IN) {
150 6 50 33     57 if (!$self->{_wait_eof} && !$self->{_wait_length} && !$self->{_wait_chunk}) {
      33        
151 6         21 pos $self->{in_buf} = length($self->{in_buf}) - $self->{_in_todo};
152 6 50       48 if ($self->{in_buf} =~ /\G((?:[^\r\n]+\r?\n)+\r?\n)/xms) {
153 6         16 my $h = $1;
154 6 50       30 if ($h =~ /^Content-Length:\s*(\d+)\s*\n/ixms) {
    0          
155 6         19 $self->{_wait_length} = length($h) + $1;
156             }
157             elsif ($h =~ /^Transfer-Encoding:\s*chunked\s*\n/ixms) {
158 0         0 $self->{_wait_chunk} = length $h;
159             }
160             else {
161 0         0 $self->{_wait_eof} = 1;
162             }
163             }
164             }
165 6   33     26 while ($self->{_wait_chunk} && $self->{_in_todo} > $self->{_wait_chunk}) {
166 0         0 pos $self->{in_buf} = length($self->{in_buf}) - $self->{_in_todo} + $self->{_wait_chunk};
167 0 0       0 if ($self->{in_buf} =~ /\G((?:\r?\n)?([\dA-Fa-f]+)[ \t]*\r?\n)/xms) {
168 0         0 my $chunk = hex $2;
169 0         0 $self->{_wait_chunk} += length($1) + $chunk;
170 0 0       0 next if $chunk > 0;
171 0         0 $self->{_wait_length} = $self->{_wait_chunk};
172 0         0 $self->{_wait_chunk} = 0;
173             }
174 0         0 last;
175             }
176             }
177 6 50       15 if ($e & EOF) {
178 0 0       0 if ($self->{_wait_eof}) {
179 0         0 $self->{_wait_length} = $self->{_in_todo};
180 0         0 $self->{_wait_eof} = 0;
181             }
182             }
183 6 50 33     25 if ($self->{_wait_length} && $self->{_in_todo} >= $self->{_wait_length}) {
184 6         9 $self->{_in_todo} -= $self->{_wait_length};
185 6         8 push @{ $self->{in_sizes} }, $self->{_wait_length};
  6         11  
186 6         24 $self->{_wait_length} = 0;
187 6         14 $e |= HTTP_RECV;
188 6         13 next;
189             }
190 0         0 last;
191             }
192              
193 13 100       44 if ($e & EOF) {
194 1 50       3 if ($self->{_in_todo}) {
195 0   0     0 $err ||= HTTP_ERESINCOMPLETE;
196             }
197             }
198              
199 13         44 $m->EVENT($e, $err);
200 13         6613 return;
201             }
202              
203              
204             1; # Magic true value required at end of module
205             __END__