File Coverage

blib/lib/Arriba/Server.pm
Criterion Covered Total %
statement 132 177 74.5
branch 42 76 55.2
condition 19 53 35.8
subroutine 25 30 83.3
pod 8 10 80.0
total 226 346 65.3


line stmt bran cond sub pod time code
1             package Arriba::Server;
2              
3 16     16   184 use warnings;
  16         56  
  16         1928  
4 16     16   280 use strict;
  16         48  
  16         1360  
5              
6 16     16   200 use base 'Net::Server::PreFork';
  16         1200  
  16         28248  
7              
8 16     16   752600 use HTTP::Date;
  16         105744  
  16         1112  
9 16     16   144 use HTTP::Status qw(status_message);
  16         32  
  16         26176  
10 16     16   16760 use HTTP::Parser::XS qw(parse_http_request);
  16         28344  
  16         1192  
11 16     16   25368 use IO::Socket::SSL;
  16         1318800  
  16         224  
12              
13 16     16   4376 use Plack::Util;
  16         32  
  16         504  
14 16     16   14728 use Plack::TempBuffer;
  16         5408  
  16         536  
15              
16 16     16   104 use constant DEBUG => $ENV{ARRIBA_DEBUG};
  16         24  
  16         1112  
17 16     16   80 use constant CHUNKSIZE => 64 * 1024;
  16         32  
  16         1400  
18              
19 16     16   112 my $null_io = do { open my $io, "<", \""; $io };
  16         32  
  16         440  
20              
21 16     16   88 use Net::Server::SIG qw(register_sig);
  16         32  
  16         14608  
22              
23             # Override Net::Server's HUP handling - just restart all the workers and that's
24             # 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 16     16 1 432 my ($self, $app, $options) = @_;
32              
33 16         616 $self->{app} = $app;
34 16         40 $self->{options} = $options;
35              
36 16         48 my %extra = ();
37 16 50       80 if ($options->{pid}) {
38 0         0 $extra{pid_file} = $options->{pid};
39             }
40 16 50       56 if ($options->{daemonize}) {
41 0         0 $extra{setsid} = $extra{background} = 1;
42             }
43 16 100       152 if ($options->{ssl_cert}) {
44 8         16 $extra{SSL_cert_file} = $options->{ssl_cert};
45             }
46 16 100       48 if ($options->{ssl_key}) {
47 8         16 $extra{SSL_key_file} = $options->{ssl_key};
48             }
49 16 50       64 if (!exists $options->{keepalive}) {
50 16         40 $options->{keepalive} = 1;
51             }
52 16 50       48 if (!exists $options->{keepalive_timeout}) {
53 16         32 $options->{keepalive_timeout} = 1;
54             }
55              
56 16         24 my @port;
57 16 50       24 for my $listen (@{$options->{listen} || [ "$options->{host}:$options->{port}" ]}) {
  16         360  
58 16         32 my %listen;
59 16 50       88 if ($listen =~ /:/) {
60 16         56 my($h, $p, $opt) = split /:/, $listen, 3;
61 16 50       80 $listen{host} = $h if $h;
62 16         40 $listen{port} = $p;
63 16 50       2456 $listen{proto} = 'ssl' if 'ssl' eq lc $opt;
64             }
65             else {
66 0         0 %listen = (
67             host => 'localhost',
68             port => $listen,
69             proto => 'unix',
70             );
71             }
72 16         80 push @port, \%listen;
73             }
74              
75 16   50     112 my $workers = $options->{workers} || 5;
76              
77 16         40 local @ARGV = ();
78              
79 16 100 33     928 $self->SUPER::run(
    50 33        
      33        
      33        
      50        
      33        
      33        
      50        
80             port => \@port,
81             host => '*',
82             proto => $options->{ssl} ? 'ssl' : 'tcp',
83             serialize => 'flock',
84             log_level => DEBUG ? 4 : 2,
85             ($options->{error_log} ? ( log_file => $options->{error_log} ) : () ),
86             min_servers => $options->{min_servers} || $workers,
87             min_spare_servers => $options->{min_spare_servers} || $workers - 1,
88             max_spare_servers => $options->{max_spare_servers} || $workers - 1,
89             max_servers => $options->{max_servers} || $workers,
90             max_requests => $options->{max_requests} || 1000,
91             user => $options->{user} || $>,
92             group => $options->{group} || $),
93             listen => $options->{backlog} || 1024,
94             check_for_waiting => 1,
95             no_client_stdout => 1,
96             %extra
97             );
98             }
99              
100             sub configure_hook {
101 16     16 1 1536 my $self = shift;
102              
103             # FIXME: Is this (configure_hook) the best place for this?
104            
105 16 50       120 if ($self->{options}->{listen_ssl}) {
106 0         0 $self->{server}->{ssl_args}->{SSL_key_file} =
107             $self->{options}->{ssl_key_file};
108 0         0 $self->{server}->{ssl_args}->{SSL_cert_file} =
109             $self->{options}->{ssl_cert_file};
110             }
111              
112 16         200 $self->SUPER::configure_hook(@_);
113             }
114              
115             sub pre_bind {
116 16     16 1 10384 my $self = shift;
117              
118 16         144 $self->SUPER::pre_bind(@_);
119              
120 16 50       58424 if ($self->{options}->{spdy}) {
121             # Enable SPDY on SSL sockets
122 0         0 for my $sock (@{$self->{server}->{sock}}) {
  0         0  
123 0 0       0 if ($sock->NS_proto eq 'SSL') {
124 0         0 $sock->SSL_npn_protocols(['spdy/3']);
125             }
126             }
127             }
128             }
129              
130             sub pre_loop_hook {
131 16     16 1 185344 my $self = shift;
132            
133 16         64 my $port = $self->{server}->{port}->[0];
134 16 50       96 my $proto = $port->{proto} eq 'ssl' ? 'https' :
    100          
135             $port->{proto} eq 'unix' ? 'unix' : 'http';
136              
137 16 50       96 $self->{options}{server_ready}->({
138             host => $port->{host},
139             port => $port->{port},
140             proto => $proto,
141             server_software => 'Arriba',
142             }) if $self->{options}{server_ready};
143            
144             register_sig(
145 0     0   0 TTIN => sub { $self->{server}->{$_}++ for qw( min_servers max_servers ) },
146 0     0   0 TTOU => sub { $self->{server}->{$_}-- for qw( min_servers max_servers ) },
147 0     0   0 QUIT => sub { $self->server_close(1) },
148 16         184 );
149             }
150              
151             sub server_close {
152 2     2 1 3680106 my($self, $quit) = @_;
153            
154 2 50       56 if ($quit) {
155 0         0 $self->log(2, $self->log_time . " Received QUIT. Running a graceful shutdown\n");
156 0         0 $self->{server}->{$_} = 0 for qw( min_servers max_servers );
157 0         0 $self->hup_children;
158 0         0 while (1) {
159 0         0 Net::Server::SIG::check_sigs();
160 0         0 $self->coordinate_children;
161 0 0       0 last if !keys %{$self->{server}{children}};
  0         0  
162 0         0 sleep 1;
163             }
164 0         0 $self->log(2, $self->log_time . " Worker processes cleaned up\n");
165             }
166            
167 2         177 $self->SUPER::server_close();
168             }
169              
170             sub run_parent {
171 6     6 0 58593 my $self = shift;
172 6 50       111 $0 = "arriba master " . join(" ", @{$self->{options}{argv} || []});
  6         552  
173 16     16   88 no warnings 'redefine';
  16         16  
  16         13520  
174             local *Net::Server::PreFork::register_sig = sub {
175 6     6   1581 my %args = @_;
176 6         72 delete $args{QUIT};
177 6         303 Net::Server::SIG::register_sig(%args);
178 6         459 };
179 6         459 $self->SUPER::run_parent(@_);
180             }
181              
182             sub child_init_hook {
183 14     14 1 3201065 my $self = shift;
184 14         1871 srand();
185 14 50       322 if ($self->{options}->{psgi_app_builder}) {
186 0         0 $self->{app} = $self->{options}->{psgi_app_builder}->();
187             }
188 14 50       679 $0 = "arriba worker " . join(" ", @{$self->{options}{argv} || []});
  14         2364  
189             }
190            
191             sub post_accept_hook {
192 3     3 1 422803 my $self = shift;
193            
194 3         54 $self->{client} = { };
195             }
196              
197             sub process_request {
198 3     3 1 230 my $self = shift;
199              
200 3         26 my $client = $self->{server}->{client};
201              
202             # Is this an SSL connection?
203 3         54 my $ssl = $client->NS_proto eq 'SSL';
204              
205 3 50 66     161 if ($ssl && $client->next_proto_negotiated &&
      33        
206             $client->next_proto_negotiated eq 'spdy/3')
207             {
208             # SPDY connection
209 0         0 require Arriba::Connection::SPDY;
210 0         0 $self->{client}->{connection} =
211             Arriba::Connection::SPDY->new($client);
212             }
213             else {
214             # HTTP(S) connection
215 3         5936 require Arriba::Connection::HTTP;
216 3         58 $self->{client}->{connection} =
217             Arriba::Connection::HTTP->new($client, ssl => $ssl,
218             chunk_size => CHUNKSIZE);
219             }
220              
221 3         12 my $connection = $self->{client}->{connection};
222              
223 3         15 while (my $req = $connection->read_request) {
224 82         162 my $env;
225             my $conn_header;
226              
227 82 100       455 if ($req->{env}) {
228             # Headers already parsed
229 8         20 $env = $req->{env};
230             }
231             else {
232 74   33     4859 $env = {
      50        
      50        
      50        
233             REMOTE_ADDR => $self->{server}->{peeraddr},
234             REMOTE_HOST => $self->{server}->{peerhost} || $self->{server}->{peeraddr},
235             REMOTE_PORT => $self->{server}->{peerport} || 0,
236             SERVER_NAME => $self->{server}->{sockaddr} || 0, # XXX: needs to be resolved?
237             SERVER_PORT => $self->{server}->{sockport} || 0,
238             SCRIPT_NAME => '',
239             'psgi.version' => [ 1, 1 ],
240             'psgi.errors' => *STDERR,
241             'psgi.url_scheme' => $req->{scheme},
242             'psgi.nonblocking' => Plack::Util::FALSE,
243             'psgi.streaming' => Plack::Util::TRUE,
244             'psgi.run_once' => Plack::Util::FALSE,
245             'psgi.multithread' => Plack::Util::FALSE,
246             'psgi.multiprocess' => Plack::Util::TRUE,
247             'psgix.io' => $client,
248             'psgix.input.buffered' => Plack::Util::TRUE,
249             'psgix.harakiri' => Plack::Util::TRUE,
250             };
251              
252 74         2379 my $reqlen = parse_http_request($req->{headers}, $env);
253              
254 74 50       225 if ($reqlen < 0) {
255             # Bad request
256 0         0 $self->_http_error($req, 400);
257 0         0 last;
258             }
259              
260 74         196 $conn_header = delete $env->{HTTP_CONNECTION};
261 74         173 my $proto = $env->{SERVER_PROTOCOL};
262              
263 74 50 33     759 if ($proto && $proto eq 'HTTP/1.0' ) {
    50 33        
264 0 0 0     0 if ($conn_header && $conn_header =~ /^keep-alive$/i) {
265             # Keep-alive only with explicit header in HTTP/1.0
266 0         0 $connection->{_keepalive} = 1;
267             }
268             else {
269 0         0 $connection->{_keepalive} = 0;
270             }
271             }
272             elsif ($proto && $proto eq 'HTTP/1.1') {
273 74 50 33     294 if ($conn_header && $conn_header =~ /^close$/i ) {
274 0         0 $connection->{_keepalive} = 0;
275             }
276             else {
277             # Keep-alive assumed in HTTP/1.1
278 74         173 $connection->{_keepalive} = 1;
279             }
280            
281             # Do we need to send 100 Continue?
282 74 50       212 if ($env->{HTTP_EXPECT}) {
283 0 0       0 if ($env->{HTTP_EXPECT} eq '100-continue') {
284             # FIXME:
285             #syswrite $client, 'HTTP/1.1 100 Continue' . $CRLF . $CRLF;
286             }
287             else {
288 0         0 $self->_http_error(417, $env);
289 0         0 last;
290             }
291             }
292            
293 74 50       247 unless ($env->{HTTP_HOST}) {
294             # No host, bad request
295 0         0 $self->_http_error(400, $env);
296 0         0 last;
297             }
298             }
299              
300 74 50       334 unless ($self->{options}->{keepalive}) {
301 0         0 $connection->{_keepalive} = 0;
302             }
303              
304 74         209 $req->{env} = $env;
305             }
306            
307             # Process this request later if it's not ready yet
308 82 100       361 next if !$req->{complete};
309              
310 74 100       203 if ($req->{body_stream}) {
311 8         125 $env->{'psgi.input'} = $req->{body_stream}->rewind;
312             }
313             else {
314 66         355 $env->{'psgi.input'} = $null_io;
315             }
316              
317 74         1393 my $res = Plack::Util::run_app($self->{app}, $env);
318              
319 74 100       23323 if (ref $res eq 'CODE') {
320 4     4   51 $res->(sub { $connection->write_response($req, $_[0]) });
  4         449  
321             }
322             else {
323 70         350 $connection->write_response($req, $res);
324             }
325              
326 74         2229 my $sel = IO::Select->new($client);
327 74 50       8594 last unless $sel->can_read($self->{options}->{keepalive_timeout});
328             }
329             }
330              
331             sub _http_error {
332 0     0     my ($self, $req, $code) = @_;
333            
334 0   0       my $status = $code || 500;
335 0           my $msg = status_message($status);
336            
337 0           my $res = [
338             $status,
339             [ 'Content-Type' => 'text/plain', 'Content-Length' => length($msg) ],
340             [ $msg ],
341             ];
342              
343 0           $self->{client}->{connection}->{_keepalive} = 0;
344 0           $self->{client}->{connection}->write_response($req, $res);
345             }
346              
347             1;
348