File Coverage

blib/lib/Arriba/Connection/HTTP.pm
Criterion Covered Total %
statement 112 138 81.1
branch 35 60 58.3
condition 5 12 41.6
subroutine 16 16 100.0
pod 0 3 0.0
total 168 229 73.3


line stmt bran cond sub pod time code
1             package Arriba::Connection::HTTP;
2              
3 3     3   33 use warnings;
  3         27  
  3         414  
4 3     3   183 use strict;
  3         31  
  3         273  
5              
6 3     3   4276 use Data::Dump qw(dump);
  3         28698  
  3         508  
7 3     3   44 use HTTP::Status qw(status_message);
  3         8  
  3         345  
8 3     3   20 use IO::Socket qw(:crlf);
  3         5  
  3         82  
9 3     3   1743 use Plack::Util;
  3         9  
  3         169  
10 3     3   27 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  3         6  
  3         388  
11              
12 3     3   18 use base 'Arriba::Connection';
  3         10  
  3         10961  
13              
14             sub new {
15 3     3 0 10 my $class = shift;
16 3         27 my $self = $class->SUPER::new(@_);
17              
18 3 100       63 if ($self->{client}->NS_proto eq 'TCP') {
19 2 50       59 setsockopt($self->{client}, IPPROTO_TCP, TCP_NODELAY, 1)
20             or die $!;
21             }
22            
23 3         21 $self->{_inputbuf} = '';
24 3         10 $self->{_current_req} = undef;
25 3         7 $self->{_keepalive} = 1;
26              
27 3         18 return $self;
28             }
29              
30             sub read_request {
31 85     85 0 2259542 my $self = shift;
32              
33 85         182 my $req;
34              
35 85 100       931 if ($req = $self->{_current_req}) {
    50          
36             # Partially processed request
37             my $get_chunk = sub {
38 27 50   27   76 if ($self->{_inputbuf}) {
39 0         0 my $chunk = delete $self->{_inputbuf};
40 0         0 return ($chunk, length $chunk);
41             }
42 27         81337 my $read = sysread $self->{client}, my($chunk), $self->{chunk_size};
43 27         89441 return ($chunk, $read);
44 8         57 };
45              
46 8         16 my $chunked = do {
47 3     3   25 no warnings;
  3         17  
  3         4908  
48 8         33 lc delete $req->{env}->{HTTP_TRANSFER_ENCODING} eq 'chunked'
49             };
50              
51 8 50       43 if ((my $cl = $req->{content_length}) >= 0) {
    0          
52 8         160 $req->{body_stream} = Stream::Buffered->new($req->{content_length});
53 8         806 while ($cl > 0) {
54 27         662 my($chunk, $read) = $get_chunk->();
55              
56 27 50 33     179 if (!defined $read || $read == 0) {
57 0         0 die "Read error: $!\n";
58             }
59              
60 27         43 $cl -= $read;
61 27         179 $req->{body_stream}->print($chunk);
62             }
63             }
64             elsif ($chunked) {
65 0         0 $req->{body_stream} = Stream::Buffered->new;
66 0         0 my $chunk_buffer = '';
67 0         0 my $length;
68              
69             DECHUNK:
70 0         0 while (1) {
71 0         0 my($chunk, $read) = $get_chunk->();
72 0         0 $chunk_buffer .= $chunk;
73              
74 0         0 while ($chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)// ) {
75 0         0 my $trailer = $1;
76 0         0 my $chunk_len = hex $2;
77            
78 0 0       0 if ($chunk_len == 0) {
    0          
79 0         0 last DECHUNK;
80             } elsif (length $chunk_buffer < $chunk_len + 2) {
81 0         0 $chunk_buffer = $trailer . $chunk_buffer;
82 0         0 last;
83             }
84            
85 0         0 $req->{body_stream}->print(substr($chunk_buffer, 0,
86             $chunk_len, ''));
87 0         0 $chunk_buffer =~ s/^\015\012//;
88            
89 0         0 $length += $chunk_len;
90             }
91            
92 0 0 0     0 last unless $read && $read > 0;
93             }
94            
95 0         0 $req->{content_length} = $length;
96             }
97              
98 8         137 $req->{complete} = 1;
99 8         82 $self->{_current_req} = undef;
100             }
101             elsif ($self->{_keepalive}) {
102             # New request
103 77         643 $req = Arriba::Request->new($self);
104 77 100       491 $req->{scheme} = $self->{ssl} ? 'https' : 'http';
105              
106 77         138 while (1) {
107 151 100 66     2149 last if defined $self->{_inputbuf} &&
108             $self->{_inputbuf} =~ /$CRLF$CRLF/s;
109            
110 77         1147 my $read = sysread $self->{client}, my $buf, $self->{chunk_size};
111              
112 77 100 66     6280 if (!defined $read || $read == 0) {
113 3         73 die "Read error: $!\n";
114             }
115              
116 74         423 $self->{_inputbuf} .= $buf;
117             }
118              
119 74         732 (my $headers, $self->{_inputbuf}) =
120             split /$CRLF$CRLF/, $self->{_inputbuf}, 2;
121              
122             # Add back two CRLFs, HTTP::Parser's parse_http_requests expects that
123 74         541 $req->{headers} = $headers . $CRLF . $CRLF;
124              
125 74 100       592 if ($req->{headers} =~ /^content-length:\s*(\d+)\015?$/im) {
126 8         48 $req->{content_length} = $1;
127 8         25 $self->{_current_req} = $req;
128             }
129             else {
130             # No "Content-length" header, we have the whole request
131 66         166 $req->{complete} = 1;
132 66         178 $self->{_current_req} = undef;
133             }
134             }
135              
136 82         520 return $req;
137             }
138              
139             sub write_response {
140 74     74 0 120 my $self = shift;
141 74         125 my $req = shift;
142 74         210 my $res = shift;
143              
144 74         215 my $proto = $req->{env}->{SERVER_PROTOCOL};
145 74         117 my $status = $res->[0];
146              
147 74         154 my %headers;
148             my $chunked;
149 74         621 my @header_lines = ("$proto $status " . status_message($status));
150            
151 74         717 my $res_headers = $res->[1];
152              
153 74         268 for (my $i = 0; $i < @$res_headers; $i += 2) {
154 98 50       284 next if $res_headers->[$i] eq 'Connection';
155 98         310 push @header_lines, $res_headers->[$i] . ": " . $res_headers->[$i+1];
156 98         526 $headers{lc $res_headers->[$i]} = $res_headers->[$i+1];
157             }
158              
159 74 50       451 if ($proto eq 'HTTP/1.1') {
160 74 100       3511 if (!exists $headers{'content-length'}) {
    50          
161 66 100       311 if ($status !~ /^1\d\d|[23]04$/) {
162 64         120 push @header_lines, 'Transfer-Encoding: chunked';
163 64         279 $chunked = 1;
164             }
165             }
166             elsif (my $te = $headers{'transfer-encoding'}) {
167 0 0       0 if ($te eq 'chunked') {
168 0         0 $chunked = 1;
169             }
170             }
171             }
172             else {
173 0 0       0 if (!exists $headers{'transfer-encoding'}) {
174 0         0 $self->{_keepalive} = 0;
175             }
176             }
177              
178 74 50       222 if ($self->{_keepalive}) {
179 74         153 push @header_lines, 'Connection: keep-alive';
180             }
181             else {
182 0         0 push @header_lines, 'Connection: close';
183             }
184              
185 74         8164 syswrite $self->{client}, join($CRLF, @header_lines, '') . $CRLF;
186              
187 74 100       5239 if (defined $res->[2]) {
188             Plack::Util::foreach($res->[2], sub {
189 78     78   2052 my $buffer = $_[0];
190 78         126 my ($len, $offset);
191              
192 78 100       218 if ($chunked) {
193 68         88 $len = length $buffer;
194 68 50       178 return unless $len;
195 68         903 $buffer = sprintf( "%x", $len ) . $CRLF . $buffer . $CRLF;
196             }
197            
198 78         117 $len = length $buffer;
199 78         117 $offset = 0;
200 78         219 while ($len) {
201 87         2296 my $written = syswrite $self->{client}, $buffer, $len, $offset;
202             # TODO: Handle errors maybe?
203 87         7974 $len -= $written;
204 87         750 $offset += $written;
205             }
206 72         1182 });
207            
208 72 100       22518 syswrite $self->{client}, "0$CRLF$CRLF" if $chunked;
209             }
210             else {
211             # TODO: Above loop also needed here
212             return Plack::Util::inline_object
213             write => sub {
214 4     4   249 my $buf = $_[0];
215 4 50       17 if ($chunked) {
216 4         10 my $len = length $buf;
217 4 50       11 return unless $len;
218 4         22 $buf = sprintf( "%x", $len ) . $CRLF . $buf . $CRLF;
219             }
220 4         51 syswrite $self->{client}, $buf;
221             },
222             close => sub {
223 2 50   2   116 syswrite $self->{client}, "0$CRLF$CRLF" if $chunked;
224 2         28 };
225             }
226             }
227              
228             1;
229