File Coverage

blib/lib/Starman/Server.pm
Criterion Covered Total %
statement 258 326 79.1
branch 90 158 56.9
condition 26 64 40.6
subroutine 37 43 86.0
pod 7 10 70.0
total 418 601 69.5


line stmt bran cond sub pod time code
1             package Starman::Server;
2 62     62   66672 use strict;
  62         497  
  62         3911  
3 62     62   553 use base 'Net::Server::PreFork';
  62         298  
  62         44216  
4              
5 62     62   1794949 use Data::Dump qw(dump);
  62         311269  
  62         4346  
6 62     62   531 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  62         164  
  62         4993  
7 62     62   475 use IO::Socket qw(:crlf);
  62         294  
  62         1555  
8 62     62   43537 use HTTP::Parser::XS qw(parse_http_request);
  62         71837  
  62         4734  
9 62     62   920 use HTTP::Status qw(status_message);
  62         4488  
  62         24550  
10 62     62   31333 use HTTP::Date qw(time2str);
  62         224114  
  62         4342  
11 62     62   484 use POSIX qw(EINTR EPIPE ECONNRESET);
  62         118  
  62         483  
12 62     62   7918 use Symbol;
  62         162  
  62         8722  
13              
14 62     62   1065 use Plack::Util;
  62         3154  
  62         1647  
15 62     62   25993 use Plack::TempBuffer;
  62         340851  
  62         54363  
16              
17 62   50 62   477 use constant DEBUG => $ENV{STARMAN_DEBUG} || 0;
  62         130  
  62         4817  
18 62     62   373 use constant CHUNKSIZE => 64 * 1024;
  62         143  
  62         4174  
19              
20 62     62   447 my $null_io = do { open my $io, "<", \""; $io };
  62         118  
  62         1807  
21              
22 62     62   380 use Net::Server::SIG qw(register_sig);
  62         130  
  62         58395  
23              
24             # Override Net::Server's HUP handling - just restart all the workers and that's about it
25             sub sig_hup {
26 0     0 0 0 my $self = shift;
27 0         0 $self->hup_children;
28             }
29              
30             sub run {
31 61     61 1 916 my($self, $app, $options) = @_;
32              
33 61         348 $self->{app} = $app;
34 61         122 $self->{options} = $options;
35              
36 61         135 my %extra = ();
37              
38 61 50       189 if ($options->{net_server_args}) {
39 0         0 %extra = %{ $options->{net_server_args} };
  0         0  
40             }
41              
42 61 50       452 if ( $options->{pid} ) {
43 0         0 $extra{pid_file} = $options->{pid};
44             }
45 61 50       256 if ( $options->{daemonize} ) {
46 0         0 $extra{setsid} = $extra{background} = 1;
47             }
48 61 50       165 if ( $options->{error_log} ) {
49 0         0 $extra{log_file} = $options->{error_log};
50             }
51 61         116 if ( DEBUG ) {
52             $extra{log_level} = 4;
53             }
54 61 50       158 if ( $options->{ssl_cert} ) {
55 0         0 $extra{SSL_cert_file} = $options->{ssl_cert};
56             }
57 61 50       152 if ( $options->{ssl_key} ) {
58 0         0 $extra{SSL_key_file} = $options->{ssl_key};
59             }
60 61 50       202 if (! exists $options->{keepalive}) {
61 61         128 $options->{keepalive} = 1;
62             }
63 61 50       190 if (! exists $options->{keepalive_timeout}) {
64 61         116 $options->{keepalive_timeout} = 1;
65             }
66 61 50       171 if (! exists $options->{read_timeout}) {
67 61         109 $options->{read_timeout} = 5;
68             }
69 61 50       196 if (! exists $options->{proctitle}) {
70 61         103 $options->{proctitle} = 1;
71             }
72              
73 61         104 my @port;
74 61 50       79 for my $listen (@{$options->{listen} || [ "$options->{host}:$options->{port}" ]}) {
  61         495  
75 61         134 my %listen;
76 61 50       287 if ($listen =~ /:/) {
77 61         269 my($h, $p, $opt) = split /:/, $listen, 3;
78 61 50       250 $listen{host} = $h if $h;
79 61         134 $listen{port} = $p;
80 61 50       400 $listen{proto} = 'ssl' if 'ssl' eq lc $opt;
81             } else {
82 0         0 %listen = (
83             host => 'localhost',
84             port => $listen,
85             proto => 'unix',
86             );
87             }
88 61         219 push @port, \%listen;
89             }
90              
91 61   50     403 my $workers = $options->{workers} || 5;
92 61         152 local @ARGV = ();
93              
94             $self->SUPER::run(
95             port => \@port,
96             host => '*', # default host
97             proto => $options->{ssl} ? 'ssl' : 'tcp', # default proto
98             serialize => ( $^O =~ m!(linux|darwin|bsd|cygwin)$! ) ? 'none' : 'flock',
99             min_servers => $options->{min_servers} || $workers,
100             min_spare_servers => $options->{min_spare_servers} || $workers - 1,
101             max_spare_servers => $options->{max_spare_servers} || $workers - 1,
102             max_servers => $options->{max_servers} || $workers,
103             max_requests => $options->{max_requests} || 1000,
104             user => $options->{user} || $>,
105             group => $options->{group} || $),
106 61 50 33     4299 listen => $options->{backlog} || 1024,
    50 33        
      33        
      33        
      50        
      33        
      33        
      50        
107             check_for_waiting => 1,
108             no_client_stdout => 1,
109             %extra
110             );
111             }
112              
113             sub pre_loop_hook {
114 61     61 1 379194 my $self = shift;
115              
116 61         196 my $port = $self->{server}->{port}->[0];
117             my $proto = $port->{proto} eq 'ssl' ? 'https' :
118 61 50       317 $port->{proto} eq 'unix' ? 'unix' :
    50          
119             'http';
120              
121             $self->{options}{server_ready}->({
122             host => $port->{host},
123             port => $port->{port},
124             proto => $proto,
125             server_software => 'Starman',
126 61 50       263 }) if $self->{options}{server_ready};
127              
128             register_sig(
129 0     0   0 TTIN => sub { $self->{server}->{$_}++ for qw( min_servers max_servers ) },
130 0     0   0 TTOU => sub { $self->{server}->{$_}-- for qw( min_servers max_servers ) },
131 0     0   0 QUIT => sub { $self->server_close(1) },
132 61         585 );
133             }
134              
135             sub server_close {
136 8     8 1 2132318 my($self, $quit) = @_;
137              
138 8 50       124 if ($quit) {
139 0         0 $self->log(2, $self->log_time . " Received QUIT. Running a graceful shutdown\n");
140 0         0 $self->{server}->{$_} = 0 for qw( min_servers max_servers );
141 0         0 $self->hup_children;
142 0         0 while (1) {
143 0         0 Net::Server::SIG::check_sigs();
144 0         0 $self->coordinate_children;
145 0 0       0 last if !keys %{$self->{server}{children}};
  0         0  
146 0         0 sleep 1;
147             }
148 0         0 $self->log(2, $self->log_time . " Worker processes cleaned up\n");
149             }
150              
151 8         316 $self->SUPER::server_close();
152             }
153              
154             sub run_parent {
155 21     21 0 222243 my $self = shift;
156 21 50       1938 $0 = "starman master " . join(" ", @{$self->{options}{argv} || []})
157 21 50       706 if $self->{options}{proctitle};
158 62     62   477 no warnings 'redefine';
  62         130  
  62         99741  
159             local *Net::Server::PreFork::register_sig = sub {
160 21     21   5184 my %args = @_;
161 21         274 delete $args{QUIT};
162 21         134244 Net::Server::SIG::register_sig(%args);
163 21         2083 };
164 21         901 $self->SUPER::run_parent(@_);
165             }
166              
167             # The below methods run in the child process
168              
169             sub child_init_hook {
170 53     53 1 9486771 my $self = shift;
171 53         2361 srand();
172 53 50       1512 if ($self->{options}->{psgi_app_builder}) {
173 0         0 DEBUG && warn "[$$] Initializing the PSGI app\n";
174 0         0 $self->{app} = $self->{options}->{psgi_app_builder}->();
175             }
176 53 50       5498 $0 = "starman worker " . join(" ", @{$self->{options}{argv} || []})
177 53 50       903 if $self->{options}{proctitle};
178              
179             }
180              
181             sub post_accept_hook {
182 55     55 1 1038618 my $self = shift;
183              
184             $self->{client} = {
185 55         1397 headerbuf => '',
186             inputbuf => '',
187             keepalive => 1,
188             };
189             }
190              
191             sub dispatch_request {
192 83     83 0 434 my ($self, $env) = @_;
193              
194             # Run PSGI apps
195 83         975 my $res = Plack::Util::run_app($self->{app}, $env);
196              
197 83 100       14861 if (ref $res eq 'CODE') {
198 5     5   46 $res->(sub { $self->_finalize_response($env, $_[0]) });
  5         198  
199             } else {
200 78         538 $self->_finalize_response($env, $res);
201             }
202             }
203              
204             sub process_request {
205 55     55 1 3167 my $self = shift;
206 55         334 my $conn = $self->{server}->{client};
207              
208 55 50       680 if ($conn->NS_proto eq 'TCP') {
209 55 50       50525 setsockopt($conn, IPPROTO_TCP, TCP_NODELAY, 1)
210             or die $!;
211             }
212              
213 55         436 while ( $self->{client}->{keepalive} ) {
214 120 50       134848 last if !$conn->connected;
215              
216             # Read until we see all headers
217 120 100       2534 last if !$self->_read_headers;
218              
219             my $env = {
220             REMOTE_ADDR => $self->{server}->{peeraddr},
221             REMOTE_HOST => $self->{server}->{peerhost} || $self->{server}->{peeraddr},
222             REMOTE_PORT => $self->{server}->{peerport} || 0,
223             SERVER_NAME => $self->{server}->{sockaddr} || 0, # XXX: needs to be resolved?
224 83 50 33     11094 SERVER_PORT => $self->{server}->{sockport} || 0,
      50        
      50        
      50        
225             SCRIPT_NAME => '',
226             'psgi.version' => [ 1, 1 ],
227             'psgi.errors' => *STDERR,
228             'psgi.url_scheme' => ($conn->NS_proto eq 'SSL' ? 'https' : 'http'),
229             'psgi.nonblocking' => Plack::Util::FALSE,
230             'psgi.streaming' => Plack::Util::TRUE,
231             'psgi.run_once' => Plack::Util::FALSE,
232             'psgi.multithread' => Plack::Util::FALSE,
233             'psgi.multiprocess' => Plack::Util::TRUE,
234             'psgix.io' => $conn,
235             'psgix.input.buffered' => Plack::Util::TRUE,
236             'psgix.harakiri' => Plack::Util::TRUE,
237             };
238              
239             # Parse headers
240 83         45114 my $reqlen = parse_http_request(delete $self->{client}->{headerbuf}, $env);
241 83 50       9062 if ( $reqlen == -1 ) {
242             # Bad request
243 0         0 DEBUG && warn "[$$] Bad request\n";
244 0         0 $self->_http_error(400, { SERVER_PROTOCOL => "HTTP/1.0" });
245 0         0 last;
246             }
247              
248             # Initialize PSGI environment
249             # Determine whether we will keep the connection open after the request
250 83         189 my $connection = delete $env->{HTTP_CONNECTION};
251 83         281 my $proto = $env->{SERVER_PROTOCOL};
252 83 50 33     12911 if ( $proto && $proto eq 'HTTP/1.0' ) {
    50 33        
253 0 0 0     0 if ( $connection && $connection =~ /^keep-alive$/i ) {
254             # Keep-alive only with explicit header in HTTP/1.0
255 0         0 $self->{client}->{keepalive} = 1;
256             }
257             else {
258 0         0 $self->{client}->{keepalive} = 0;
259             }
260             }
261             elsif ( $proto && $proto eq 'HTTP/1.1' ) {
262 83 50 33     304 if ( $connection && $connection =~ /^close$/i ) {
263 0         0 $self->{client}->{keepalive} = 0;
264             }
265             else {
266             # Keep-alive assumed in HTTP/1.1
267 83         262 $self->{client}->{keepalive} = 1;
268             }
269              
270             # Do we need to send 100 Continue?
271 83 50       284 if ( $env->{HTTP_EXPECT} ) {
272 0 0       0 if ( lc $env->{HTTP_EXPECT} eq '100-continue' ) {
273 0         0 _syswrite($conn, \('HTTP/1.1 100 Continue' . $CRLF . $CRLF));
274 0         0 DEBUG && warn "[$$] Sent 100 Continue response\n";
275             }
276             else {
277 0         0 DEBUG && warn "[$$] Invalid Expect header, returning 417\n";
278 0         0 $self->_http_error( 417, $env );
279 0         0 last;
280             }
281             }
282              
283 83 50       276 unless ($env->{HTTP_HOST}) {
284             # No host, bad request
285 0         0 DEBUG && warn "[$$] Bad request, HTTP/1.1 without Host header\n";
286 0         0 $self->_http_error( 400, $env );
287 0         0 last;
288             }
289             }
290              
291 83 50       477 unless ($self->{options}->{keepalive}) {
292 0         0 DEBUG && warn "[$$] keep-alive is disabled. Closing the connection after this request\n";
293 0         0 $self->{client}->{keepalive} = 0;
294             }
295              
296 83         1810 $self->_prepare_env($env);
297              
298 83         899 $self->dispatch_request($env);
299              
300 83         171 DEBUG && warn "[$$] Request done\n";
301              
302 83 100       347 if ( $self->{client}->{keepalive} ) {
303             # If we still have data in the input buffer it may be a pipelined request
304 65 50       180 if ( $self->{client}->{inputbuf} ne '' ) {
305 0 0       0 if ( $self->{client}->{inputbuf} =~ /^(?:GET|HEAD)/ ) {
306 0         0 if ( DEBUG ) {
307             warn "Pipelined GET/HEAD request in input buffer: "
308             . dump( $self->{client}->{inputbuf} ) . "\n";
309             }
310              
311             # Continue processing the input buffer
312 0         0 next;
313             }
314             else {
315             # Input buffer just has junk, clear it
316 0         0 if ( DEBUG ) {
317             warn "Clearing junk from input buffer: "
318             . dump( $self->{client}->{inputbuf} ) . "\n";
319             }
320              
321 0         0 $self->{client}->{inputbuf} = '';
322             }
323             }
324              
325 65         74 DEBUG && warn "[$$] Waiting on previous connection for keep-alive request...\n";
326              
327 65         619 my $sel = IO::Select->new($conn);
328 65 50       4262 last unless $sel->can_read($self->{options}->{keepalive_timeout});
329             }
330             }
331              
332 55         196 DEBUG && warn "[$$] Closing connection\n";
333             }
334              
335             sub _read_headers {
336 120     120   423 my $self = shift;
337              
338 120         247 eval {
339 120     0   2943 local $SIG{ALRM} = sub { die "Timed out\n"; };
  0         0  
340              
341 120         1252 alarm( $self->{options}->{read_timeout} );
342              
343 120         446 while (1) {
344             # Do we have a full header in the buffer?
345             # This is before sysread so we don't read if we have a pipelined request
346             # waiting in the buffer
347 203 100 66     4300 last if $self->{client}->{inputbuf} ne '' && $self->{client}->{inputbuf} =~ /$CR?$LF$CR?$LF/s;
348              
349             # If not, read some data
350 120         6608 my $read = sysread $self->{server}->{client}, my $buf, CHUNKSIZE;
351              
352 120 100 66     1179 if ( !defined $read || $read == 0 ) {
353 37         813 die "Read error: $!\n";
354             }
355              
356 83         288 if ( DEBUG ) {
357             warn "[$$] Read $read bytes: " . dump($buf) . "\n";
358             }
359              
360 83         407 $self->{client}->{inputbuf} .= $buf;
361             }
362             };
363              
364 120         812 alarm(0);
365              
366 120 100       663 if ( $@ ) {
367 37 50       370 if ( $@ =~ /Timed out/ ) {
368 0         0 DEBUG && warn "[$$] Client connection timed out\n";
369 0         0 return;
370             }
371              
372 37 50       265 if ( $@ =~ /Read error/ ) {
373 37         58 DEBUG && warn "[$$] Read error: $!\n";
374 37         158 return;
375             }
376             }
377              
378             # Pull out the complete header into a new buffer
379 83         280 $self->{client}->{headerbuf} = $self->{client}->{inputbuf};
380              
381             # Save any left-over data, possibly body data or pipelined requests
382 83         1579 $self->{client}->{inputbuf} =~ s/.*?$CR?$LF$CR?$LF//s;
383              
384 83         357 return 1;
385             }
386              
387             sub _http_error {
388 0     0   0 my ( $self, $code, $env ) = @_;
389              
390 0   0     0 my $status = $code || 500;
391 0         0 my $msg = status_message($status);
392              
393 0         0 my $res = [
394             $status,
395             [ 'Content-Type' => 'text/plain', 'Content-Length' => length($msg) ],
396             [ $msg ],
397             ];
398              
399 0         0 $self->{client}->{keepalive} = 0;
400 0         0 $self->_finalize_response($env, $res);
401             }
402              
403             sub _prepare_env {
404 84     84   336 my($self, $env) = @_;
405              
406             my $get_chunk = sub {
407 12 100   12   68 if ($self->{client}->{inputbuf} ne '') {
408 2         85 my $chunk = delete $self->{client}->{inputbuf};
409 2         15 return ($chunk, length $chunk);
410             }
411 10         111346 my $read = sysread $self->{server}->{client}, my($chunk), CHUNKSIZE;
412 10         216 return ($chunk, $read);
413 84         729 };
414              
415 62     62   498 my $chunked = do { no warnings; lc delete $env->{HTTP_TRANSFER_ENCODING} eq 'chunked' };
  62         270  
  62         78707  
  84         246  
  84         308  
416              
417 84 100       304 if (my $cl = $env->{CONTENT_LENGTH}) {
    100          
418 4         92 my $buf = Plack::TempBuffer->new($cl);
419 4         432 while ($cl > 0) {
420 6         238 my($chunk, $read) = $get_chunk->();
421              
422 6 50 33     90 if ( !defined $read || $read == 0 ) {
423 0         0 die "Read error: $!\n";
424             }
425              
426 6         16 $cl -= $read;
427 6         71 $buf->print($chunk);
428             }
429 4         133 $env->{'psgi.input'} = $buf->rewind;
430             } elsif ($chunked) {
431 2         63 my $buf = Plack::TempBuffer->new;
432 2         408 my $chunk_buffer = '';
433 2         6 my $length;
434              
435             DECHUNK:
436 2         3 while (1) {
437 6         136 my($chunk, $read) = $get_chunk->();
438 6         82 $chunk_buffer .= $chunk;
439              
440 6         98 while ( $chunk_buffer =~ s/^(([0-9a-fA-F]+).*\015\012)// ) {
441 20         64 my $trailer = $1;
442 20         54 my $chunk_len = hex $2;
443              
444 20 100       53 if ($chunk_len == 0) {
    100          
445 2         8 last DECHUNK;
446             } elsif (length $chunk_buffer < $chunk_len + 2) {
447 1         2 $chunk_buffer = $trailer . $chunk_buffer;
448 1         3 last;
449             }
450              
451 17         78 $buf->print(substr $chunk_buffer, 0, $chunk_len, '');
452 17         342 $chunk_buffer =~ s/^\015\012//;
453              
454 17         75 $length += $chunk_len;
455             }
456              
457 4 50 33     31 last unless $read && $read > 0;
458             }
459              
460 2         14 $env->{CONTENT_LENGTH} = $length;
461 2         10 $env->{'psgi.input'} = $buf->rewind;
462             } else {
463 78         989 $env->{'psgi.input'} = $null_io;
464             }
465             }
466              
467             sub _finalize_response {
468 83     83   208 my($self, $env, $res) = @_;
469              
470 83 100       306 if ($env->{'psgix.harakiri.commit'}) {
471 18         137 $self->{client}->{keepalive} = 0;
472 18         53 $self->{client}->{harakiri} = 1;
473             }
474              
475 83         336 my $protocol = $env->{SERVER_PROTOCOL};
476 83         147 my $status = $res->[0];
477 83         789 my $message = status_message($status);
478              
479 83         686 my(@headers, %headers);
480 83         295 push @headers, "$protocol $status $message";
481              
482             # Switch on Transfer-Encoding: chunked if we don't know Content-Length.
483 83         120 my $chunked;
484 83         233 my $headers = $res->[1];
485 83         316 for (my $i = 0; $i < @$headers; $i += 2) {
486 95         164 my $k = $headers->[$i];
487 95         179 my $v = $headers->[$i + 1];
488 95 50       214 next if $k eq 'Connection';
489 95         259 push @headers, "$k: $v";
490 95         567 $headers{lc $k} = $v;
491             }
492              
493 83 50       244 if ( $protocol eq 'HTTP/1.1' ) {
494 83 100       237 if ( !exists $headers{'content-length'} ) {
    50          
495 79 100 100     912 if ( $status !~ /^1\d\d|[23]04$/ && $env->{REQUEST_METHOD} ne 'HEAD' ) {
496 77         117 DEBUG && warn "[$$] Using chunked transfer-encoding to send unknown length body\n";
497 77         252 push @headers, 'Transfer-Encoding: chunked';
498 77         128 $chunked = 1;
499             }
500             }
501             elsif ( my $te = $headers{'transfer-encoding'} ) {
502 0 0       0 if ( $te eq 'chunked' ) {
503 0         0 DEBUG && warn "[$$] Chunked transfer-encoding set for response\n";
504 0         0 $chunked = 1;
505             }
506             }
507             } else {
508 0 0       0 if ( !exists $headers{'content-length'} ) {
509 0         0 DEBUG && warn "[$$] Disabling keep-alive after sending unknown length body on $protocol\n";
510 0         0 $self->{client}->{keepalive} = 0;
511             }
512             }
513              
514 83 50       240 if ( ! $headers{date} ) {
515 83         837 push @headers, "Date: " . time2str( time() );
516             }
517              
518             # Should we keep the connection open?
519 83 100       2717 if ( $self->{client}->{keepalive} ) {
520 65         132 push @headers, 'Connection: keep-alive';
521             } else {
522 18         179 push @headers, 'Connection: close';
523             }
524              
525 83         163 my $conn = $self->{server}->{client};
526              
527             # Buffer the headers so they are sent with the first write() call
528             # This reduces the number of TCP packets we are sending
529 83         637 _syswrite($conn, \(join( $CRLF, @headers, '' ) . $CRLF));
530              
531 83 100       317 if (defined $res->[2]) {
532             Plack::Util::foreach($res->[2], sub {
533 80     80   1361 my $buffer = $_[0];
534 80 100       175 if ($chunked) {
535 76         106 my $len = length $buffer;
536 76 50       161 return unless $len;
537 76         499 $buffer = sprintf( "%x", $len ) . $CRLF . $buffer . $CRLF;
538             }
539 80         164 _syswrite($conn, \$buffer);
540 79         1335 });
541 79 100       3272 _syswrite($conn, \"0$CRLF$CRLF") if $chunked;
542             } else {
543             return Plack::Util::inline_object
544             write => sub {
545 6     6   411 my $buffer = $_[0];
546 6 50       19 if ($chunked) {
547 6         12 my $len = length $buffer;
548 6 100       14 return unless $len;
549 5         26 $buffer = sprintf( "%x", $len ) . $CRLF . $buffer . $CRLF;
550             }
551 5         16 _syswrite($conn, \$buffer);
552             },
553             close => sub {
554 4 50   4   165 _syswrite($conn, \"0$CRLF$CRLF") if $chunked;
555 4         132 };
556             }
557             }
558              
559             sub _syswrite {
560 245     245   432 my ($conn, $buffer_ref) = @_;
561              
562 245         383 my $amount = length $$buffer_ref;
563 245         288 my $offset = 0;
564              
565 245         503 while ($amount > 0) {
566 245         10099 my $len = syswrite($conn, $$buffer_ref, $amount, $offset);
567              
568 245 50       918 if (not defined $len) {
569 0 0       0 return if $! == EPIPE;
570 0 0       0 return if $! == ECONNRESET;
571 0 0       0 redo if $! == EINTR;
572 0         0 die "write error: $!";
573             }
574              
575 245         324 $amount -= $len;
576 245         290 $offset += $len;
577              
578 245         1256 DEBUG && warn "[$$] Wrote $len byte", ($len == 1 ? '' : 's'), "\n";
579             }
580             }
581              
582             sub post_client_connection_hook {
583 55     55 1 7186 my $self = shift;
584 55 100       306 if ($self->{client}->{harakiri}) {
585 18         3472 exit;
586             }
587             }
588              
589             1;