| 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__ |