File Coverage

blib/lib/HTTP/Server/PSGI.pm
Criterion Covered Total %
statement 164 193 84.9
branch 30 68 44.1
condition 9 26 34.6
subroutine 38 42 90.4
pod 0 10 0.0
total 241 339 71.0


line stmt bran cond sub pod time code
1             package HTTP::Server::PSGI;
2 39     39   22861 use strict;
  39         121  
  39         2867  
3 39     39   343 use warnings;
  39         485  
  39         2374  
4              
5 39     39   268 use Carp ();
  39         78  
  39         1358  
6 39     39   23338 use Plack;
  39         115  
  39         1801  
7 39     39   23683 use Plack::HTTPParser qw( parse_http_request );
  39         115  
  39         3889  
8 39     39   1411 use IO::Socket::INET;
  39         35155  
  39         2568  
9 39     39   74036 use HTTP::Date;
  39         178011  
  39         3355  
10 39     39   1315 use HTTP::Status;
  39         9972  
  39         24328  
11 39     39   387 use List::Util qw(max sum);
  39         115  
  39         9375  
12 39     39   831 use Plack::Util;
  39         79  
  39         1645  
13 39     39   1203 use Stream::Buffered;
  39         13948  
  39         2464  
14 39     39   18608 use Plack::Middleware::ContentLength;
  39         154  
  39         1989  
15 39     39   1356 use POSIX qw(EINTR);
  39         13725  
  39         1898  
16 39     39   11641 use Socket qw(IPPROTO_TCP);
  39         115  
  39         3617  
17              
18 39     39   275 use Try::Tiny;
  39         78  
  39         3119  
19 39     39   1507 use Time::HiRes qw(time);
  39         2901  
  39         1310  
20              
21 39     39   19200 use constant TCP_NODELAY => try { Socket::TCP_NODELAY };
  39         78  
  39         274  
  39         1611  
22              
23             my $alarm_interval;
24             BEGIN {
25 39 50   39   4673 if ($^O eq 'MSWin32') {
26 0         0 $alarm_interval = 1;
27             } else {
28 39         269 Time::HiRes->import('alarm');
29 39         3404 $alarm_interval = 0.1;
30             }
31             }
32              
33 39     39   235 use constant MAX_REQUEST_SIZE => 131072;
  39         78  
  39         2826  
34 39     39   356 use constant MSWin32 => $^O eq 'MSWin32';
  39         78  
  39         91424  
35              
36             sub new {
37 37     37 0 185 my($class, %args) = @_;
38              
39             my $self = bless {
40             ($args{listen_sock} ? (
41             listen_sock => $args{listen_sock},
42             host => $args{listen_sock}->sockhost,
43             port => $args{listen_sock}->sockport,
44             ):(
45             host => $args{host} || 0,
46             port => $args{port} || 8080,
47             )),
48             timeout => $args{timeout} || 300,
49             server_software => $args{server_software} || $class,
50       37     server_ready => $args{server_ready} || sub {},
51             ssl => $args{ssl},
52             ipv6 => $args{ipv6},
53             ssl_key_file => $args{ssl_key_file},
54             ssl_cert_file => $args{ssl_cert_file},
55 37 50 50     1369 }, $class;
      50        
      50        
      33        
      50        
56              
57 37         259 $self;
58             }
59              
60             sub run {
61 37     37 0 74 my($self, $app) = @_;
62 37         111 $self->setup_listener();
63 37         222 $self->accept_loop($app);
64             }
65              
66             sub prepare_socket_class {
67 37     37 0 74 my($self, $args) = @_;
68              
69 37 0 33     111 if ($self->{ssl} && $self->{ipv6}) {
70 0         0 Carp::croak("SSL and IPv6 are not supported at the same time (yet). Choose one.");
71             }
72              
73 37 50       148 if ($self->{ssl}) {
    50          
74 0 0       0 eval { require IO::Socket::SSL; 1 }
  0         0  
  0         0  
75             or Carp::croak("SSL suport requires IO::Socket::SSL");
76 0         0 $args->{SSL_key_file} = $self->{ssl_key_file};
77 0         0 $args->{SSL_cert_file} = $self->{ssl_cert_file};
78 0         0 return "IO::Socket::SSL";
79             } elsif ($self->{ipv6}) {
80 0 0       0 eval { require IO::Socket::IP; 1 }
  0         0  
  0         0  
81             or Carp::croak("IPv6 support requires IO::Socket::IP");
82 0   0     0 $self->{host} ||= '::';
83 0   0     0 $args->{LocalAddr} ||= '::';
84 0         0 return "IO::Socket::IP";
85             }
86              
87 37         111 return "IO::Socket::INET";
88             }
89              
90             sub setup_listener {
91 37     37 0 74 my $self = shift;
92              
93 37   33     259 $self->{listen_sock} ||= do {
94             my %args = (
95             Listen => SOMAXCONN,
96             LocalPort => $self->{port},
97             LocalAddr => $self->{host},
98 37         888 Proto => 'tcp',
99             ReuseAddr => 1,
100             );
101              
102 37         185 my $class = $self->prepare_socket_class(\%args);
103 37 50       814 $class->new(%args)
104             or die "failed to listen to port $self->{port}: $!";
105             };
106              
107 37 50       26603 $self->{server_ready}->({ %$self, proto => $self->{ssl} ? 'https' : 'http' });
108             }
109              
110             sub accept_loop {
111 37     37 0 111 my($self, $app) = @_;
112              
113 37         1258 $app = Plack::Middleware::ContentLength->wrap($app);
114              
115 37         111 while (1) {
116 740         18503 local $SIG{PIPE} = 'IGNORE';
117 740 50       14368 if (my $conn = $self->{listen_sock}->accept) {
118 740 50       5044081 if (defined TCP_NODELAY) {
119 740 50       14342 $conn->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
120             or die "setsockopt(TCP_NODELAY) failed:$!";
121             }
122             my $env = {
123             SERVER_PORT => $self->{port},
124             SERVER_NAME => $self->{host},
125             SCRIPT_NAME => '',
126             REMOTE_ADDR => $conn->peerhost,
127             REMOTE_PORT => $conn->peerport || 0,
128             'psgi.version' => [ 1, 1 ],
129             'psgi.errors' => *STDERR,
130 740 50 50     26238 'psgi.url_scheme' => $self->{ssl} ? 'https' : 'http',
131             'psgi.run_once' => Plack::Util::FALSE,
132             'psgi.multithread' => Plack::Util::FALSE,
133             'psgi.multiprocess' => Plack::Util::FALSE,
134             'psgi.streaming' => Plack::Util::TRUE,
135             'psgi.nonblocking' => Plack::Util::FALSE,
136             'psgix.harakiri' => Plack::Util::TRUE,
137             'psgix.input.buffered' => Plack::Util::TRUE,
138             'psgix.io' => $conn,
139             };
140              
141 740         126499 $self->handle_connection($env, $conn, $app);
142 703         18802 $conn->close;
143 703 50       201817 last if $env->{'psgix.harakiri.commit'};
144             }
145             }
146             }
147              
148             sub handle_connection {
149 740     740 0 3265 my($self, $env, $conn, $app) = @_;
150              
151 740         2615 my $buf = '';
152 740         3793 my $res = [ 400, [ 'Content-Type' => 'text/plain' ], [ 'Bad Request' ] ];
153              
154 740         2122 while (1) {
155             my $rlen = $self->read_timeout(
156             $conn, \$buf, MAX_REQUEST_SIZE - length($buf), length($buf),
157             $self->{timeout},
158 740 100       5688 ) or return;
159 703         13852 my $reqlen = parse_http_request($buf, $env);
160 703 50       3720 if ($reqlen >= 0) {
161 703         4041 $buf = substr $buf, $reqlen;
162 703 100       3112 if (my $cl = $env->{CONTENT_LENGTH}) {
163 74         3812 my $buffer = Stream::Buffered->new($cl);
164 74         7504 while ($cl > 0) {
165 74         393 my $chunk;
166 74 100       890 if (length $buf) {
167 40         395 $chunk = $buf;
168 40         325 $buf = '';
169             } else {
170             $self->read_timeout($conn, \$chunk, $cl, 0, $self->{timeout})
171 34 50       1190 or return;
172             }
173 74         784 $buffer->print($chunk);
174 74         9356 $cl -= length $chunk;
175             }
176 74         1086 $env->{'psgi.input'} = $buffer->rewind;
177             } else {
178 629     37   13775 open my $input, "<", \$buf;
  37         444  
  37         74  
  37         1147  
179 629         45115 $env->{'psgi.input'} = $input;
180             }
181              
182 703         9554 $res = Plack::Util::run_app $app, $env;
183 666         8188 last;
184             }
185 0 0       0 if ($reqlen == -2) {
    0          
186             # request is incomplete, do nothing
187             } elsif ($reqlen == -1) {
188             # error, close conn
189 0         0 last;
190             }
191             }
192              
193 666 50       5658 if (ref $res eq 'ARRAY') {
    0          
194 666         8645 $self->_handle_response($res, $conn);
195             } elsif (ref $res eq 'CODE') {
196             $res->(sub {
197 0     0   0 $self->_handle_response($_[0], $conn);
198 0         0 });
199             } else {
200 0         0 die "Bad response $res";
201             }
202              
203 666         81407 return;
204             }
205              
206             sub _handle_response {
207 666     666   6642 my($self, $res, $conn) = @_;
208              
209 666         2690 my @lines = (
210 666         11139 "Date: @{[HTTP::Date::time2str()]}\015\012",
211             "Server: $self->{server_software}\015\012",
212             );
213              
214             Plack::Util::header_iter($res->[1], sub {
215 1487     1487   3993 my ($k, $v) = @_;
216 1487         10646 push @lines, "$k: $v\015\012";
217 666         51603 });
218              
219 666         5500 unshift @lines, "HTTP/1.0 $res->[0] @{[ HTTP::Status::status_message($res->[0]) ]}\015\012";
  666         16834  
220 666         13490 push @lines, "\015\012";
221              
222             $self->write_all($conn, join('', @lines), $self->{timeout})
223 666 50       9948 or return;
224              
225 666 50       3258 if (defined $res->[2]) {
226 666         2575 my $err;
227             my $done;
228             {
229 666         1447 local $@;
  666         1642  
230 666         2225 eval {
231             Plack::Util::foreach(
232             $res->[2],
233             sub {
234             $self->write_all($conn, $_[0], $self->{timeout})
235 721 50   721   4546 or die "failed to send all data\n";
236             },
237 666         11632 );
238 666         3375 $done = 1;
239             };
240 666         2351 $err = $@;
241             };
242 666 50       3567 unless ($done) {
243 0 0       0 if ($err =~ /^failed to send all data\n/) {
244 0         0 return;
245             } else {
246 0         0 die $err;
247             }
248             }
249             } else {
250             return Plack::Util::inline_object
251 0     0   0 write => sub { $self->write_all($conn, $_[0], $self->{timeout}) },
252 0     0   0 close => sub { };
253             }
254             }
255              
256             # returns 1 if socket is ready, undef on timeout
257             sub do_timeout {
258 2161     2161 0 7086 my ($self, $cb, $timeout) = @_;
259 2161     0   63731 local $SIG{ALRM} = sub {};
260 2161         15805 my $wait_until = time + $timeout;
261 2161         22305 alarm($timeout);
262 2161         5380 my $ret;
263 2161         3951 while (1) {
264 2161 100 33     6641 if ($ret = $cb->()) {
    50          
265 2124         257343 last;
266             } elsif (! (! defined($ret) && $! == EINTR)) {
267 37         1924 undef $ret;
268 37         333 last;
269             }
270             # got EINTR
271 0         0 my $left = $wait_until - time;
272 0 0       0 last if $left <= 0;
273 0         0 alarm($left + $alarm_interval);
274             }
275 2161         20449 alarm(0);
276 2161         50543 $ret;
277             }
278              
279             # returns (positive) number of bytes read, or undef if the socket is to be closed
280             sub read_timeout {
281 774     774 0 3043 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
282 774     774   12075 $self->do_timeout(sub { $sock->sysread($$buf, $len, $off) }, $timeout);
  774         7636  
283             }
284              
285             # returns (positive) number of bytes written, or undef if the socket is to be closed
286             sub write_timeout {
287 1387     1387 0 5175 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
288 1387     1387   17822 $self->do_timeout(sub { $sock->syswrite($buf, $len, $off) }, $timeout);
  1387         28672  
289             }
290              
291             # writes all data in buf and returns number of bytes written or undef if failed
292             sub write_all {
293 1387     1387 0 6246 my ($self, $sock, $buf, $timeout) = @_;
294 1387 50       4254 return 0 unless defined $buf;
295 1387         5217 _encode($buf);
296 1387         3469 my $off = 0;
297 1387         6035 while (my $len = length($buf) - $off) {
298 1387 50       5381 my $ret = $self->write_timeout($sock, $buf, $len, $off, $timeout)
299             or return;
300 1387         8846 $off += $ret;
301             }
302 1387         7288 return length $buf;
303             }
304              
305             # syswrite() will crash when given wide characters
306             sub _encode {
307 1387 50   1387   65290 if ($_[0] =~ /[^\x00-\xff]/) {
308 0         0 Carp::carp("Wide character outside byte range in response. Encoding data as UTF-8");
309 0         0 utf8::encode($_[0]);
310             }
311             }
312              
313             1;
314              
315             __END__