File Coverage

blib/lib/Starlight/Server.pm
Criterion Covered Total %
statement 351 485 72.3
branch 136 312 43.5
condition 54 115 46.9
subroutine 53 58 91.3
pod 0 10 0.0
total 594 980 60.6


line stmt bran cond sub pod time code
1             package Starlight::Server;
2              
3             =head1 NAME
4              
5             Starlight::Server - Core class for a Starlight server
6              
7             =head1 SYNOPSIS
8              
9             =for markdown ```perl
10              
11             use Starlight::Server;
12              
13             my $server = Starlight::Server->new(port => $port);
14             $server->run(sub { [200, ['Content-Type', 'text/plain'], ['PSGI app']] });
15              
16             =for markdown ```
17              
18             =head1 DESCRIPTION
19              
20             This is a core class for a Starlight server. It should be used by a
21             L handler when started with `plackup` command.
22              
23             =cut
24              
25 104     104   1562404 use strict;
  104         938  
  104         5054  
26 104     104   1840 use warnings;
  104         657  
  104         9473  
27              
28             our $VERSION = '0.0501';
29              
30 104     104   1122 use Config;
  104         1488  
  104         14945  
31              
32 104     104   80819 use English '-no_match_vars';
  104         292731  
  104         1149  
33 104     104   77552 use Errno ();
  104         549  
  104         5519  
34 104     104   821 use File::Spec;
  104         465  
  104         8699  
35 104     104   58507 use Plack;
  104         21967  
  104         5800  
36 104     104   53819 use Plack::HTTPParser qw( parse_http_request );
  104         308785  
  104         11625  
37 104     104   1392 use IO::Socket::INET;
  104         20271  
  104         8091  
38 104     104   162360 use HTTP::Date;
  104         374240  
  104         10876  
39 104     104   7522 use HTTP::Status;
  104         72937  
  104         61854  
40 104     104   927 use List::Util qw(max sum);
  104         200  
  104         23240  
41 104     104   5180 use Plack::Util;
  104         26418  
  104         3015  
42 104     104   48132 use Plack::TempBuffer;
  104         750667  
  104         4593  
43 104     104   817 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  104         240  
  104         11195  
44              
45 104     104   796 use Try::Tiny;
  104         224  
  104         10326  
46              
47             BEGIN {
48 104         7147 try { require Time::HiRes; Time::HiRes->import(qw(time)) }
  104         14379  
49 104     104   1059 }
50              
51 104     104   45985 use constant DEBUG => $ENV{PERL_STARLIGHT_DEBUG};
  104         238  
  104         11071  
52 104     104   749 use constant CHUNKSIZE => 64 * 1024;
  104         271  
  104         5214  
53 104     104   596 use constant MAX_REQUEST_SIZE => 131072;
  104         191  
  104         9233  
54              
55 104   50 104   718 use constant HAS_INET6 => eval { AF_INET6 && socket my $ipv6_socket, AF_INET6, SOCK_DGRAM, 0 } && 1;
  104         597  
  104         277  
56 104   50 104   857 use constant HAS_IO_SOCKET_IP => eval { require IO::Socket::IP; 1 } && 1;
  104         440  
  104         303  
57              
58 104 50   104   797 use constant EINTR => exists &Errno::EINTR ? &Errno::EINTR : -1; ## no critic
  104         278  
  104         10723  
59 104 50   104   788 use constant EAGAIN => exists &Errno::EAGAIN ? &Errno::EAGAIN : -1; ## no critic
  104         244  
  104         9811  
60 104 50   104   698 use constant EWOULDBLOCK => exists &Errno::EWOULDBLOCK ? &Errno::EWOULDBLOCK : -1; ## no critic
  104         254  
  104         252970  
61              
62             ## no critic(InputOutput::RequireBriefOpen InputOutput::RequireCheckedOpen)
63 104     104   905 my $null_io = do { open my $io, "<", \""; $io };
  104         222  
  104         4598  
64              
65             sub new {
66 98     98 0 11948 my ($class, %args) = @_;
67              
68             my $self = bless {
69             host => $args{host},
70             port => $args{port},
71             socket => $args{socket},
72             listen => $args{listen},
73             listen_sock => $args{listen_sock},
74             timeout => $args{timeout} || 300,
75             keepalive_timeout => $args{keepalive_timeout} || 2,
76             max_keepalive_reqs => $args{max_keepalive_reqs} || 1,
77             server_software => $args{server_software} || "Starlight/$VERSION ($^O)",
78       97     server_ready => $args{server_ready} || sub { },
79             ssl => $args{ssl},
80             ipv6 => $args{ipv6} || HAS_IO_SOCKET_IP,
81             ssl_key_file => $args{ssl_key_file},
82             ssl_cert_file => $args{ssl_cert_file},
83             ssl_ca_file => $args{ssl_ca_file},
84             ssl_client_ca_file => $args{ssl_client_ca_file},
85             ssl_verify_mode => $args{ssl_verify_mode},
86             user => $args{user},
87             group => $args{group},
88             umask => $args{umask},
89             daemonize => $args{daemonize},
90             pid => $args{pid},
91             error_log => $args{error_log},
92             quiet => $args{quiet} || $args{q} || $ENV{PLACK_QUIET},
93             min_reqs_per_child => (
94             defined $args{min_reqs_per_child} ? $args{min_reqs_per_child} : undef,
95             ),
96             max_reqs_per_child => (
97             $args{max_reqs_per_child} || $args{max_requests} || 1000,
98             ),
99             spawn_interval => $args{spawn_interval} || 0,
100             err_respawn_interval => (
101             defined $args{err_respawn_interval} ? $args{err_respawn_interval} : undef,
102             ),
103 98 100 50     9738 main_process_delay => $args{main_process_delay} || 0.1,
    50 50        
      50        
      33        
      50        
      50        
      33        
      50        
      50        
      50        
104             is_multithread => Plack::Util::FALSE,
105             is_multiprocess => Plack::Util::FALSE,
106             _using_defer_accept => undef,
107             _unlink => [],
108             _sigint => 'INT',
109             }, $class;
110              
111             # Windows 7 and previous have bad SIGINT handling
112 98 50       747 if ($^O eq 'MSWin32') {
113 0         0 require Win32;
114 0         0 my @v = Win32::GetOSVersion();
115 0 0       0 if ($v[1] * 1000 + $v[2] < 6_002) {
116 0         0 $self->{_sigint} = 'TERM';
117             }
118             }
119              
120 98 50 33     461 if ($args{max_workers} && $args{max_workers} > 1) {
121 0         0 die(
122             "Forking in $class is deprecated. Falling back to the single process mode. ",
123             "If you need more workers, use Starlight instead and run like `plackup -s Starlight`\n",
124             );
125             }
126              
127 98         575 $self;
128             }
129              
130             sub run {
131 2     2 0 29 my ($self, $app) = @_;
132 2         53 $self->setup_listener();
133 2         52 $self->accept_loop($app);
134             }
135              
136             sub prepare_socket_class {
137 97     97 0 466 my ($self, $args) = @_;
138              
139 97 50 66     436 if ($self->{socket} and $self->{port}) {
140 0         0 die "UNIX socket and ether IPv4 or IPv6 are not supported at the same time.\n";
141             }
142              
143 97 50 66     520 if ($self->{socket} and $self->{ssl}) {
144 0         0 die "UNIX socket and SSL are not supported at the same time.\n";
145             }
146              
147 97 100       567 if ($self->{socket}) {
148 6     6   324 try { require IO::Socket::UNIX; 1 }
  6         30  
149 6 50       48 or die "UNIX socket suport requires IO::Socket::UNIX\n";
150 6         108 $args->{Local} =~ s/^@/\0/; # abstract socket address
151 6         18 return "IO::Socket::UNIX";
152             }
153              
154 91 50       534 if ($self->{ipv6}) {
155 91         1910 die "IPv6 support requires IO::Socket::IP\n" unless HAS_IO_SOCKET_IP;
156             }
157              
158 91 50       103924 if ($self->{ssl}) {
159 0     0   0 try { require IO::Socket::SSL; 1 }
  0         0  
160 0 0       0 or die "SSL suport requires IO::Socket::SSL\n";
161 0         0 $args->{SSL_key_file} = $self->{ssl_key_file};
162 0         0 $args->{SSL_cert_file} = $self->{ssl_cert_file};
163 0         0 $args->{SSL_ca_file} = $self->{ssl_ca_file};
164 0         0 $args->{SSL_client_ca_file} = $self->{ssl_client_ca_file};
165 0         0 $args->{SSL_startHandshake} = 0;
166             }
167              
168 91 50       384 return "IO::Socket::SSL" if $self->{ssl};
169 91 50       517 return "IO::Socket::IP" if $self->{ipv6};
170 0         0 return "IO::Socket::INET";
171             }
172              
173             sub setup_listener {
174 97     97 0 273 my ($self) = @_;
175              
176             my %args = $self->{socket}
177             ? (
178             Listen => Socket::SOMAXCONN,
179             Local => $self->{socket},
180             )
181             : (
182             Listen => Socket::SOMAXCONN,
183             LocalPort => $self->{port} || 5000,
184             LocalAddr => $self->{host},
185 97 100 50     3216 Proto => 'tcp',
186             ReuseAddr => 1,
187             );
188              
189 97 50       847 my $proto = $self->{ssl} ? 'https' : 'http';
190 97 100       1377 my $listening = $self->{socket} ? "socket $self->{socket}" : "port $self->{port}";
191              
192 97         1502 my $class = $self->prepare_socket_class(\%args);
193             $self->{listen_sock} ||= $class->new(%args)
194 97 50 33     2653 or do {
195 0         0 die "failed to listen to $listening: $!\n";
196             };
197              
198             print STDERR "Starting $self->{server_software} $proto server listening at $listening\n"
199 97 50       88727 unless $self->{quiet};
200              
201 97         1324 my $family = Socket::sockaddr_family(getsockname($self->{listen_sock}));
202 97         424 $self->{_listen_sock_is_unix} = $family == AF_UNIX;
203 97         287 $self->{_listen_sock_is_tcp} = $family != AF_UNIX;
204              
205             # set defer accept
206 97 100 66     865 if ($^O eq 'linux' && $self->{_listen_sock_is_tcp}) {
207             setsockopt($self->{listen_sock}, IPPROTO_TCP, 9, 1)
208 91 50       1658 and $self->{_using_defer_accept} = 1;
209             }
210              
211 97 100 66     506 if ($self->{_listen_sock_is_unix} && not $args{Local} =~ /^\0/) {
212 6         528 $self->_add_to_unlink(File::Spec->rel2abs($args{Local}));
213             }
214              
215 97         2881 $self->{server_ready}->({ %$self, proto => $proto });
216             }
217              
218             sub accept_loop {
219              
220             # TODO handle $max_reqs_per_child
221 89     89 0 14044 my ($self, $app, $max_reqs_per_child) = @_;
222 89         1071 my $proc_req_count = 0;
223              
224 89         2223 $self->{can_exit} = 1;
225 89         863 my $is_keepalive = 0;
226 89         1500 my $sigint = $self->{_sigint};
227             local $SIG{$sigint} = local $SIG{TERM} = sub {
228 66     66   87703216 my ($sig) = @_;
229 66         1339 warn "*** SIG$sig received in process $$" if DEBUG;
230 66 50       18425 exit 0 if $self->{can_exit};
231 0         0 $self->{term_received}++;
232             exit 0
233 0 0 0     0 if ($is_keepalive && $self->{can_exit}) || $self->{term_received} > 1;
      0        
234              
235             # warn "server termination delayed while handling current HTTP request";
236 89         10019 };
237              
238 89         3395 local $SIG{PIPE} = 'IGNORE';
239              
240 89   66     5200 while (!defined $max_reqs_per_child || $proc_req_count < $max_reqs_per_child) {
241 167 50       14224 my ($conn, $peer) = $self->{listen_sock}->accept or do {
242 0         0 warn "failed to accept: $!\n";
243 0         0 next;
244             };
245              
246 101         34929485 my ($peerport, $peerhost, $peeraddr) = (0, undef, undef);
247 101 100       1977 if ($self->{_listen_sock_is_tcp}) {
248 100 50       4157 if (HAS_INET6 && Socket::sockaddr_family(getsockname($conn)) == AF_INET6) {
249 0         0 ($peerport, $peerhost) = Socket::unpack_sockaddr_in6($peer);
250 0         0 $peeraddr = Socket::inet_ntop(AF_INET6, $peerhost);
251             } else {
252 100         1395 ($peerport, $peerhost) = Socket::unpack_sockaddr_in($peer);
253 100         2056 $peeraddr = Socket::inet_ntoa($peerhost);
254             }
255 100 50   100   6422 if (try { TCP_NODELAY }) {
  100         13741  
256             $conn->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
257 100 50       24187 or do {
258 0         0 warn "setsockopt(TCP_NODELAY) failed for $peeraddr:$peerport: $!\n";
259 0         0 next;
260             };
261             }
262             }
263              
264 101 50       28799 if ($conn->isa('IO::Socket::SSL')) {
265 0 0       0 $conn->accept_SSL or do {
266 0         0 my @err = ();
267 0 0       0 push @err, $! if $!;
268 0 0       0 push @err, $IO::Socket::SSL::SSL_ERROR if $IO::Socket::SSL::SSL_ERROR;
269 0         0 warn "failed to ssl handshake with $peeraddr:$peerport: @{[join ': ', @err]}\n";
  0         0  
270 0         0 next;
271             };
272             }
273              
274 101         981 $self->{_is_deferred_accept} = $self->{_using_defer_accept};
275             $conn->blocking(0)
276 101 50       2378 or do {
277 0         0 warn "failed to set socket to nonblocking mode for $peeraddr:$peerport: $!\n";
278 0         0 next;
279             };
280              
281 101         7010 my $req_count = 0;
282 101         824 my $pipelined_buf = '';
283 101         16414 while (1) {
284 101         439 ++$req_count;
285 101         622 ++$proc_req_count;
286             my $env = {
287             SERVER_PORT => $self->{port} || 0,
288             SERVER_NAME => $self->{host} || '*',
289             SCRIPT_NAME => '',
290             REMOTE_ADDR => $peeraddr,
291             REMOTE_PORT => $peerport,
292             'psgi.version' => [1, 1],
293             'psgi.errors' => *STDERR,
294             'psgi.url_scheme' => $self->{ssl} ? 'https' : 'http',
295             'psgi.run_once' => Plack::Util::FALSE,
296             'psgi.multithread' => $self->{is_multithread},
297             'psgi.multiprocess' => $self->{is_multiprocess},
298 101 50 100     7881 'psgi.streaming' => Plack::Util::TRUE,
      100        
299             'psgi.nonblocking' => Plack::Util::FALSE,
300             'psgix.input.buffered' => Plack::Util::TRUE,
301             'psgix.io' => $conn,
302             'psgix.harakiri' => Plack::Util::TRUE,
303             };
304              
305 101         678 my $may_keepalive = $req_count < $self->{max_keepalive_reqs};
306 101 0 33     927 if ($may_keepalive && $max_reqs_per_child && $proc_req_count >= $max_reqs_per_child) {
      33        
307 0         0 $may_keepalive = undef;
308             }
309 101 50       678 $may_keepalive = 1 if length $pipelined_buf;
310 101         311 my $keepalive;
311 101         2254 ($keepalive, $pipelined_buf) = $self->handle_connection(
312             $env, $conn, $app,
313             $may_keepalive, $req_count != 1, $pipelined_buf
314             );
315              
316 101 100       860 if ($env->{'psgix.harakiri.commit'}) {
317 23         401 $conn->close;
318 23         3451 return;
319             }
320 78 50       1339 last unless $keepalive;
321              
322             # TODO add special cases for clients with broken keep-alive support, as well as disabling keep-alive for HTTP/1.0 proxies
323             }
324 78         1151 $conn->close;
325             }
326             }
327              
328             my $bad_response = [400, ['Content-Type' => 'text/plain', 'Connection' => 'close'], ['Bad Request']];
329              
330             sub handle_connection {
331 101     101 0 881 my ($self, $env, $conn, $app, $use_keepalive, $is_keepalive, $prebuf) = @_;
332              
333 101         561 my $buf = '';
334 101         970 my $pipelined_buf = '';
335 101         1254 my $res = $bad_response;
336              
337 101 50       1687 local $self->{can_exit} = (defined $prebuf) ? 0 : 1;
338 101         859 while (1) {
339 101         334 my $rlen;
340 101 50       646 if ($rlen = length $prebuf) {
341 0         0 $buf = $prebuf;
342 0         0 undef $prebuf;
343             } else {
344             $rlen = $self->read_timeout(
345             $conn, \$buf, MAX_REQUEST_SIZE - length($buf), length($buf),
346             $is_keepalive ? $self->{keepalive_timeout} : $self->{timeout},
347 101 50       2011 ) or return;
    100          
348             }
349 92         595 $self->{can_exit} = 0;
350 92         2850 my $reqlen = parse_http_request($buf, $env);
351 92 50       45357 if ($reqlen >= 0) {
352              
353             # handle request
354 92         619 my $protocol = $env->{SERVER_PROTOCOL};
355 92 50       582 if ($use_keepalive) {
356 0 0       0 if ($protocol eq 'HTTP/1.1') {
357 0 0       0 if (my $c = $env->{HTTP_CONNECTION}) {
358 0 0       0 $use_keepalive = undef
359             if $c =~ /^\s*close\s*/i;
360             }
361             } else {
362 0 0       0 if (my $c = $env->{HTTP_CONNECTION}) {
363 0 0       0 $use_keepalive = undef
364             unless $c =~ /^\s*keep-alive\s*/i;
365             } else {
366 0         0 $use_keepalive = undef;
367             }
368             }
369             }
370 92         714 $buf = substr $buf, $reqlen;
371 104     104   1025 my $chunked = do { no warnings 'all'; lc delete $env->{HTTP_TRANSFER_ENCODING} eq 'chunked' };
  104         269  
  104         423768  
  92         350  
  92         795  
372 92 100       740 if (my $cl = $env->{CONTENT_LENGTH}) {
    100          
373 5         265 my $buffer = Plack::TempBuffer->new($cl);
374 5         1226 while ($cl > 0) {
375 6         20 my $chunk;
376 6 100       24 if (length $buf) {
377 4         13 $chunk = $buf;
378 4         20 $buf = '';
379             } else {
380             $self->read_timeout($conn, \$chunk, $cl, 0, $self->{timeout})
381 2 100       35 or return;
382             }
383 5         47 $buffer->print($chunk);
384 5         655 $cl -= length $chunk;
385             }
386 4         41 $env->{'psgi.input'} = $buffer->rewind;
387             } elsif ($chunked) {
388 1         73 my $buffer = Plack::TempBuffer->new;
389 1         620 my $chunk_buffer = '';
390 1         11 my $length;
391 1         5 DECHUNK: while (1) {
392 4         11 my $chunk;
393 4 100       18 if (length $buf) {
394 1         4 $chunk = $buf;
395 1         6 $buf = '';
396             } else {
397             $self->read_timeout($conn, \$chunk, CHUNKSIZE, 0, $self->{timeout})
398 3 50       36 or return;
399             }
400              
401 4         196 $chunk_buffer .= $chunk;
402 4         316 while ($chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)//) {
403 100         444 my $trailer = $1;
404 100         254 my $chunk_len = hex $2;
405 100 100       360 if ($chunk_len == 0) {
    100          
406 1         11 last DECHUNK;
407             } elsif (length $chunk_buffer < $chunk_len + 2) {
408 1         9 $chunk_buffer = $trailer . $chunk_buffer;
409 1         6 last;
410             }
411 98         729 $buffer->print(substr $chunk_buffer, 0, $chunk_len, '');
412 98         3177 $chunk_buffer =~ s/^\015\012//;
413 98         883 $length += $chunk_len;
414             }
415             }
416 1         4 $env->{CONTENT_LENGTH} = $length;
417 1         6 $env->{'psgi.input'} = $buffer->rewind;
418             } else {
419 86 50       591 if ($buf =~ m!^(?:GET|HEAD)!) { #pipeline
420 0         0 $pipelined_buf = $buf;
421 0         0 $use_keepalive = 1; #force keepalive
422             } # else clear buffer
423 86         880 $env->{'psgi.input'} = $null_io;
424             }
425              
426 91 50       1474 if ($env->{HTTP_EXPECT}) {
427 0 0       0 if ($env->{HTTP_EXPECT} eq '100-continue') {
428 0 0       0 $self->write_all($conn, "HTTP/1.1 100 Continue\015\012\015\012")
429             or return;
430             } else {
431 0         0 $res = [417, ['Content-Type' => 'text/plain', 'Connection' => 'close'], ['Expectation Failed']];
432 0         0 last;
433             }
434             }
435              
436 91         2182 $res = Plack::Util::run_app $app, $env;
437 91         23252 last;
438             }
439 0 0       0 if ($reqlen == -2) {
    0          
440              
441             # request is incomplete, do nothing
442             } elsif ($reqlen == -1) {
443              
444             # error, close conn
445 0         0 last;
446             }
447             }
448              
449 91 100       702 if (ref $res eq 'ARRAY') {
    50          
450 88         1769 $self->_handle_response($env->{SERVER_PROTOCOL}, $res, $conn, \$use_keepalive);
451             } elsif (ref $res eq 'CODE') {
452             $res->(
453             sub {
454 3     3   290 $self->_handle_response($env->{SERVER_PROTOCOL}, $_[0], $conn, \$use_keepalive);
455             }
456 3         64 );
457             } else {
458 0         0 die "Bad response $res\n";
459             }
460 91 50       670 if ($self->{term_received}) {
461 0         0 exit 0;
462             }
463              
464 91         902 return ($use_keepalive, $pipelined_buf);
465             }
466              
467             sub _handle_response {
468 91     91   533 my ($self, $protocol, $res, $conn, $use_keepalive_r) = @_;
469 91         616 my $status_code = $res->[0];
470 91         373 my $headers = $res->[1];
471 91         330 my $body = $res->[2];
472              
473 91         392 my @lines;
474             my %send_headers;
475 91         942 for (my $i = 0; $i < @$headers; $i += 2) {
476 104         367 my $k = $headers->[$i];
477 104         459 my $v = $headers->[$i + 1];
478 104 50       549 $v = '' if not defined $v;
479 104         413 my $lck = lc $k;
480 104 50       724 if ($lck eq 'connection') {
481 0 0 0     0 $$use_keepalive_r = undef
482             if $$use_keepalive_r && lc $v ne 'keep-alive';
483             } else {
484 104         764 push @lines, "$k: $v\015\012";
485 104         1288 $send_headers{$lck} = $v;
486             }
487             }
488 91 100       619 if (!exists $send_headers{server}) {
489 90         548 unshift @lines, "Server: $self->{server_software}\015\012";
490             }
491 91 50       457 if (!exists $send_headers{date}) {
492 91         356 unshift @lines, "Date: @{[HTTP::Date::time2str()]}\015\012";
  91         1692  
493             }
494              
495             # try to set content-length when keepalive can be used, or disable it
496 91         5736 my $use_chunked;
497 91 100 66     1621 if (defined $protocol and $protocol eq 'HTTP/1.1') {
498 89 100 66     3275 if ( defined $send_headers{'content-length'}
    100          
499             || defined $send_headers{'transfer-encoding'})
500             {
501             # ok
502             } elsif (!Plack::Util::status_with_no_entity_body($status_code)) {
503 84         1779 push @lines, "Transfer-Encoding: chunked\015\012";
504 84         577 $use_chunked = 1;
505             }
506 89 50       843 push @lines, "Connection: close\015\012" unless $$use_keepalive_r;
507             } else {
508              
509             # HTTP/1.0
510 2 50       30 if ($$use_keepalive_r) {
511 0 0 0     0 if ( defined $send_headers{'content-length'}
    0 0        
512             || defined $send_headers{'transfer-encoding'})
513             {
514             # ok
515             } elsif (!Plack::Util::status_with_no_entity_body($status_code)
516             && defined(my $cl = Plack::Util::content_length($body)))
517             {
518 0         0 push @lines, "Content-Length: $cl\015\012";
519             } else {
520 0         0 $$use_keepalive_r = undef;
521             }
522             }
523 2 50       14 push @lines, "Connection: keep-alive\015\012" if $$use_keepalive_r;
524 2 50       51 push @lines, "Connection: close\015\012" if !$$use_keepalive_r; #fmm..
525             }
526              
527 91   50     483 unshift @lines, "HTTP/1.1 $status_code @{[ HTTP::Status::status_message($status_code) || 'Unknown' ]}\015\012";
  91         2037  
528 91         2512 push @lines, "\015\012";
529              
530 91 100 100     3463 if ( defined $body
      100        
      66        
      100        
531             && ref $body eq 'ARRAY'
532             && @$body == 1
533             && defined $body->[0]
534             && length $body->[0] < 8192)
535             {
536             # combine response header and small request body
537 81         378 my $buf = $body->[0];
538 81 100       396 if ($use_chunked) {
539 78         229 my $len = length $buf;
540 78         597 $buf = sprintf("%x", $len) . "\015\012" . $buf . "\015\012" . '0' . "\015\012\015\012";
541             }
542             $self->write_all(
543             $conn, join('', @lines, $buf), $self->{timeout},
544 81         1962 );
545 81         737 return;
546             }
547             $self->write_all($conn, join('', @lines), $self->{timeout})
548 10 50       138 or return;
549              
550 10 100       52 if (defined $body) {
551 8         20 my $failed;
552             my $completed;
553 8 100       38 my $body_count = (ref $body eq 'ARRAY') ? $#{$body} + 1 : -1;
  3         14  
554             Plack::Util::foreach(
555             $body,
556             sub {
557 10 50   10   1413 unless ($failed) {
558 10         59 my $buf = $_[0];
559 10         20 --$body_count;
560 10 100       28 if ($use_chunked) {
561 7         19 my $len = length $buf;
562 7 50       22 return unless $len;
563 7         425 $buf = sprintf("%x", $len) . "\015\012" . $buf . "\015\012";
564 7 100       42 if ($body_count == 0) {
565 2         25 $buf .= '0' . "\015\012\015\012";
566 2         9 $completed = 1;
567             }
568             }
569             $self->write_all($conn, $buf, $self->{timeout})
570 10 50       58 or $failed = 1;
571             }
572             },
573 8         243 );
574 8 100 100     3703 $self->write_all($conn, '0' . "\015\012\015\012", $self->{timeout}) if $use_chunked && !$completed;
575             } else {
576             return Plack::Util::inline_object write => sub {
577 5     5   454 my $buf = $_[0];
578 5 50       20 if ($use_chunked) {
579 5         12 my $len = length $buf;
580 5 100       25 return unless $len;
581 4         26 $buf = sprintf("%x", $len) . "\015\012" . $buf . "\015\012";
582             }
583 4         37 $self->write_all($conn, $buf, $self->{timeout});
584             },
585             close => sub {
586 2 50   2   68 $self->write_all($conn, '0' . "\015\012\015\012", $self->{timeout}) if $use_chunked;
587 2         120 };
588             }
589             }
590              
591             # returns value returned by $cb, or undef on timeout or network error
592             sub do_io {
593 215     215 0 1471 my ($self, $is_write, $sock, $buf, $len, $off, $timeout) = @_;
594 215         986 my $ret;
595 215 100 100     2230 unless ($is_write || delete $self->{_is_deferred_accept}) {
596 6         76 goto DO_SELECT;
597             }
598             DO_READWRITE:
599              
600             # try to do the IO
601 215 100       961 if ($is_write) {
602 109 50       13677 $ret = syswrite $sock, $buf, $len, $off
603             and return $ret;
604             } else {
605 106 100       5770 $ret = sysread $sock, $$buf, $len, $off
606             and return $ret;
607             }
608 10 50 0     114 if (defined($ret) || ($! != EINTR && $! != EAGAIN && $! != EWOULDBLOCK)) {
      0        
      33        
609 10         164 return;
610             }
611              
612             # wait for data
613             DO_SELECT:
614 6         36 while (1) {
615 6         22 my ($rfd, $wfd);
616 6         85 my $efd = '';
617 6         136 vec($efd, fileno($sock), 1) = 1;
618 6 50       104 if ($is_write) {
619 0         0 ($rfd, $wfd) = ('', $efd);
620             } else {
621 6         39 ($rfd, $wfd) = ($efd, '');
622             }
623 6         139 my $start_at = time;
624 6         434 my $nfound = select($rfd, $wfd, $efd, $timeout);
625 6         73 $timeout -= (time - $start_at);
626 6 50       77 last if $nfound;
627 0 0       0 return if $timeout <= 0;
628             }
629 6         29 goto DO_READWRITE;
630             }
631              
632             # returns (positive) number of bytes read, or undef if the socket is to be closed
633             sub read_timeout {
634 106     106 0 561 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
635 106         2058 $self->do_io(undef, $sock, $buf, $len, $off, $timeout);
636             }
637              
638             # returns (positive) number of bytes written, or undef if the socket is to be closed
639             sub write_timeout {
640 109     109 0 480 my ($self, $sock, $buf, $len, $off, $timeout) = @_;
641 109         562 $self->do_io(1, $sock, $buf, $len, $off, $timeout);
642             }
643              
644             # writes all data in buf and returns number of bytes written or undef if failed
645             sub write_all {
646 109     109 0 858 my ($self, $sock, $buf, $timeout) = @_;
647 109         378 my $off = 0;
648 109         618 while (my $len = length($buf) - $off) {
649 109 50       1288 my $ret = $self->write_timeout($sock, $buf, $len, $off, $timeout)
650             or return;
651 109         833 $off += $ret;
652             }
653 109         597 return length $buf;
654             }
655              
656             sub _add_to_unlink {
657 6     6   24 my ($self, $filename) = @_;
658 6         12 push @{ $self->{_unlink} }, File::Spec->rel2abs($filename);
  6         42  
659             }
660              
661             sub _daemonize {
662 95     95   290 my $self = shift;
663              
664 95 50       448 if ($^O eq 'MSWin32') {
665 0         0 foreach my $arg (qw(daemonize pid)) {
666 0 0       0 die "$arg parameter is not supported on this platform ($^O)\n" if $self->{$arg};
667             }
668             }
669              
670 95         207 my ($pidfh, $pidfile);
671 95 50       368 if ($self->{pid}) {
672 0         0 $pidfile = File::Spec->rel2abs($self->{pid});
673 0 0       0 if (defined *Fcntl::O_EXCL{CODE}) {
674 0 0       0 sysopen $pidfh, $pidfile, Fcntl::O_WRONLY | Fcntl::O_CREAT | Fcntl::O_EXCL
675             or die "Cannot open pid file: $self->{pid}: $!\n";
676             } else {
677 0 0       0 open $pidfh, '>', $pidfile or die "Cannot open pid file: $self->{pid}: $!\n";
678             }
679             }
680              
681 95 50       1132 if (defined $self->{error_log}) {
682 0 0       0 open STDERR, '>>', $self->{error_log} or die "Cannot open error log file: $self->{error_log}: $!\n";
683             }
684              
685 95 50       699 if ($self->{daemonize}) {
686              
687 0 0       0 chdir File::Spec->rootdir or die "Cannot chdir to root directory: $!\n";
688              
689 0 0       0 open my $devnull, '+>', File::Spec->devnull or die "Cannot open null device: $!\n";
690              
691 0 0       0 open STDIN, '>&', $devnull or die "Cannot dup null device: $!\n";
692 0 0       0 open STDOUT, '>&', $devnull or die "Cannot dup null device: $!\n";
693              
694 0 0       0 defined(my $pid = fork) or die "Cannot fork: $!\n";
695 0 0       0 if ($pid) {
696 0 0 0     0 if ($self->{pid} and $pid) {
697 0 0       0 print $pidfh "$pid\n" or die "Cannot write pidfile $self->{pid}: $!\n";
698 0         0 close $pidfh;
699 0 0       0 open STDERR, '>&', $devnull or die "Cannot dup null device: $!\n";
700             }
701 0         0 exit;
702             }
703              
704 0 0       0 close $pidfh if $pidfh;
705              
706 0 0       0 if ($Config::Config{d_setsid}) {
707 0 0       0 POSIX::setsid() or die "Cannot setsid: $!\n";
708             }
709              
710 0 0       0 if (not defined $self->{error_log}) {
711 0 0       0 open STDERR, '>&', $devnull or die "Cannot dup null device: $!\n";
712             }
713             }
714              
715 95 50       840 if ($pidfile) {
716 0         0 $self->_add_to_unlink($pidfile);
717             }
718              
719 95         335 return;
720             }
721              
722             sub _setup_privileges {
723 95     95   302 my ($self) = @_;
724              
725 95 50       291512 if (defined $self->{group}) {
726 0 0       0 if (not $Config::Config{d_setegid}) {
727 0         0 die "group parameter is not supported on this platform ($^O)\n";
728             }
729 0 0       0 if ($self->_get_gid($self->{group}) ne $EGID) {
730 0         0 warn "*** setting group to \"$self->{group}\"" if DEBUG;
731 0         0 $self->_set_gid($self->{group});
732             }
733             }
734              
735 95 50       565 if (defined $self->{user}) {
736 0 0       0 if (not $Config::Config{d_seteuid}) {
737 0         0 die "user parameter is not supported on this platform ($^O)\n";
738             }
739 0 0       0 if ($self->_get_uid($self->{user}) ne $EUID) {
740 0         0 warn "*** setting user to \"$self->{user}\"" if DEBUG;
741 0         0 $self->_set_uid($self->{user});
742             }
743             }
744              
745 95 50       280 if (defined $self->{umask}) {
746 0 0       0 if (not $Config::Config{d_umask}) {
747 0         0 die "umask parameter is not supported on this platform ($^O)\n";
748             }
749 0         0 warn "*** setting umask to \"$self->{umask}\"" if DEBUG;
750 0         0 umask(oct($self->{umask}));
751             }
752              
753 95         509 return;
754             }
755              
756             # Taken from Net::Server::Daemonize
757             sub _get_uid {
758 0     0   0 my ($self, $user) = @_;
759 0         0 my $uid = do {
760 0 0       0 if ($user =~ /^(\d+)$/) {
761 0         0 $1;
762             } else {
763 0         0 getpwnam($user);
764             }
765             };
766              
767 0 0       0 die "No such user \"$user\"\n" unless defined $uid;
768 0         0 return $uid;
769             }
770              
771             # Taken from Net::Server::Daemonize
772             sub _get_gid {
773 0     0   0 my ($self, @groups) = @_;
774 0         0 my @gid;
775              
776 0         0 foreach my $group (split(/[, ]+/, join(" ", @groups))) {
777 0 0       0 if ($group =~ /^\d+$/) {
778 0         0 push @gid, $group;
779             } else {
780 0         0 my $id = getgrnam($group);
781 0 0       0 die "No such group \"$group\"\n" unless defined $id;
782 0         0 push @gid, $id;
783             }
784             }
785              
786 0 0       0 die "No group found in arguments.\n" unless @gid;
787 0         0 return join(" ", $gid[0], @gid);
788             }
789              
790             # Taken from Net::Server::Daemonize
791             sub _set_uid {
792 0     0   0 my ($self, $user) = @_;
793 0         0 my $uid = $self->_get_uid($user);
794              
795 0 0       0 eval { POSIX::setuid($uid) } or 1;
  0         0  
796 0 0 0     0 if ($UID != $uid || $EUID != $uid) { # check $> also (rt #21262)
797 0         0 $UID = $EUID = $uid; # try again - needed by some 5.8.0 linux systems (rt #13450)
798 0 0       0 if ($UID != $uid) {
799 0         0 die "Couldn't become uid \"$uid\": $!\n";
800             }
801             }
802              
803 0         0 return 1;
804             }
805              
806             # Taken from Net::Server::Daemonize
807             sub _set_gid {
808 0     0   0 my ($self, @groups) = @_;
809 0         0 my $gids = $self->_get_gid(@groups);
810 0         0 my $gid = (split /\s+/, $gids)[0];
811 0 0       0 eval { $EGID = $gids } or 1; # store all the gids - this is really sort of optional
  0         0  
812              
813 0 0       0 eval { POSIX::setgid($gid) } or 1;
  0         0  
814 0 0       0 if (!grep { $gid == $_ } split /\s+/, $GID) { # look for any valid id in the list
  0         0  
815 0         0 die "Couldn't become gid \"$gid\": $!\n";
816             }
817              
818 0         0 return 1;
819             }
820              
821             sub _sleep {
822 1607     1607   10429 my ($self, $t) = @_;
823 1607 100       68343656 select undef, undef, undef, $t if $t;
824             }
825              
826             sub _create_process {
827 880     880   1884 my ($self, $app) = @_;
828 880         990270 my $pid = fork;
829 880 50       31952 return warn "cannot fork: $!" unless defined $pid;
830              
831 880 100       20848 if ($pid == 0) {
832 87         26815 warn "*** process $$ starting" if DEBUG;
833 87 50       14112 eval {
834 87         32831 $SIG{CHLD} = 'DEFAULT';
835 87         64450 $self->accept_loop($app, $self->_calc_reqs_per_child());
836             } or 1;
837 23 50       128 warn $@ if $@;
838 23         43 warn "*** process $$ ending" if DEBUG;
839 23         2150 exit 0;
840             } else {
841 793         182254 $self->{processes}->{$pid} = 1;
842             }
843             }
844              
845             sub _calc_reqs_per_child {
846 10087     10087   1059302 my $self = shift;
847 10087         14815 my $max = $self->{max_reqs_per_child};
848 10087 100       17331 if (my $min = $self->{min_reqs_per_child}) {
849 10000         21823 srand((rand() * 2**30) ^ $$ ^ time);
850 10000         21651 return $max - int(($max - $min + 1) * rand);
851             } else {
852 87         17489 return $max;
853             }
854             }
855              
856             sub DESTROY {
857 98     98   4573 my ($self) = @_;
858 98         673 while (my $f = shift @{ $self->{_unlink} }) {
  104         46722  
859 6         841 unlink $f;
860             }
861             }
862              
863             1;
864              
865             __END__