File Coverage

inc/Test/HTTP/Server.pm
Criterion Covered Total %
statement 22 191 11.5
branch 1 42 2.3
condition 0 27 0.0
subroutine 7 30 23.3
pod 0 2 0.0
total 30 292 10.2


line stmt bran cond sub pod time code
1             package Test::HTTP::Server;
2             #
3 11     11   335620 use strict;
  11         28  
  11         456  
4 11     11   57 use warnings;
  11         22  
  11         850  
5 11     11   12556 use IO::Socket;
  11         388938  
  11         64  
6 11     11   30067 use POSIX ":sys_wait_h";
  11         108932  
  11         81  
7              
8             sub _open_socket
9             {
10 0     0     my $frompid = $$;
11 0           $frompid %= 63 * 1024;
12 0 0         $frompid += 63 * 1024 if $frompid < 1024;
13 0   0       my $port = $ENV{HTTP_PORT} || $frompid;
14 0           foreach ( 0..100 ) {
15 0           my $socket = IO::Socket::INET->new(
16             Proto => 'tcp',
17             LocalPort => $port,
18             Listen => 5,
19             Reuse => 1,
20             Blocking => 1,
21             );
22 0 0         return ( $port, $socket ) if $socket;
23 0           $port = 1024 + int rand 63 * 1024;
24             }
25             }
26              
27             sub new
28             {
29 0     0 0   my $class = shift;
30              
31 0 0         my ( $port, $socket ) = _open_socket()
32             or die "Could not start HTTP server\n";
33              
34 0           my $pid = fork;
35 0 0         die "Could not fork\n"
36             unless defined $pid;
37 0 0         if ( $pid ) {
38 0           my $self = { port => $port, pid => $pid };
39 0           return bless $self, $class;
40             } else {
41 0           $SIG{CHLD} = \&_sigchld;
42 0           HTTP::Server::_main_loop( $socket, @_ );
43 0           exec "true";
44 0           die "Should not be here\n";
45             }
46             }
47              
48             sub uri
49             {
50 0     0 0   my $self = shift;
51 0           return "http://127.0.0.1:$self->{port}/";
52             }
53              
54             sub _sigchld
55             {
56 0     0     my $kid;
57 0           local $?;
58 0           do {
59 0           $kid = waitpid -1, WNOHANG;
60             } while ( $kid > 0 );
61             }
62              
63             sub DESTROY
64             {
65 0     0     my $self = shift;
66 0           my $done = 0;
67 0           local $SIG{CHLD} = \&_sigchld;
68 0           my $cnt = kill 15, $self->{pid};
69 0 0         return unless $cnt;
70 0           foreach my $sig ( 15, 15, 15, 9, 9, 9 ) {
71 0           $cnt = kill $sig, $self->{pid};
72 0 0         last unless $cnt;
73 0           select undef, undef, undef, 0.1;
74             }
75             }
76              
77             package HTTP::Server;
78              
79             sub _term
80             {
81 0     0     exec "true";
82 0           die "Should not be here\n";
83             }
84              
85             sub _main_loop
86             {
87 0     0     my $socket = shift;
88 0           $SIG{TERM} = \&_term;
89              
90 0           for (;;) {
91 0 0         my $client = $socket->accept()
92             or redo;
93 0           my $pid = fork;
94 0 0         die "Could not fork\n" unless defined $pid;
95 0 0         if ( $pid ) {
96 0           close $client;
97             } else {
98 0           HTTP::Server::Request->open( $client, @_ );
99 0           _term();
100             }
101             }
102             }
103              
104             package HTTP::Server::Connection;
105              
106             BEGIN {
107 11     11   26957 eval {
108 11         15573 require URI::Escape;
109 11         19973 URI::Escape->import( qw(uri_unescape) );
110             };
111 11 50       551 if ( $@ ) {
112             *uri_unescape = sub {
113 0         0 local $_ = shift;
114 0         0 s/%(..)/chr hex $1/eg;
  0         0  
115 0         0 return $_;
116 0         0 };
117             }
118             }
119              
120             use constant {
121 11         11511 DNAME => [qw(Sun Mon Tue Wed Thu Fri Sat)],
122             MNAME => [qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)],
123 11     11   85 };
  11         21  
124              
125             sub _http_time
126             {
127 0     0     my $self = shift;
128 0   0       my @t = gmtime( shift || time );
129 0           return sprintf '%s, %02d %s %04d %02d:%02d:%02d GMT',
130             DNAME->[ $t[6] ], $t[3], MNAME->[ $t[4] ], 1900+$t[5],
131             $t[2], $t[1], $t[0];
132             }
133              
134             sub open
135             {
136 0     0     my $class = shift;
137 0           my $socket = shift;
138              
139 0           open STDOUT, '>&', $socket;
140 0           open STDIN, '<&', $socket;
141              
142 0           my $self = {
143             version => "1.0",
144             @_,
145             socket => $socket,
146             };
147 0           bless $self, $class;
148 0           $self->process;
149             }
150              
151             sub process
152             {
153 0     0     my $self = shift;
154 0           $self->in_all;
155 0           $self->out_all;
156 0           close STDIN;
157 0           close STDOUT;
158 0           close $self->{socket};
159             }
160              
161             sub in_all
162             {
163 0     0     my $self = shift;
164 0           $self->{request} = $self->in_request;
165 0           $self->{headers} = $self->in_headers;
166              
167 0 0         if ( $self->{request}->[0] =~ /^(?:POST|PUT)/ ) {
168 0           $self->{body} = $self->in_body;
169             } else {
170 0           delete $self->{body};
171             }
172             }
173              
174             sub in_request
175             {
176 0     0     my $self = shift;
177 0           local $/ = "\r\n";
178 0           $_ = ;
179 0           $self->{head} = $_;
180 0           chomp;
181 0           return [ split /\s+/, $_ ];
182             }
183              
184             sub in_headers
185             {
186 0     0     my $self = shift;
187 0           local $/ = "\r\n";
188 0           my @headers;
189 0           while ( ) {
190 0           $self->{head} .= $_;
191 0           chomp;
192 0 0         last unless length $_;
193 0           s/(\S+):\s*//;
194 0           my $header = $1;
195 0           $header =~ tr/-/_/;
196 0           push @headers, ( lc $header, $_ );
197             }
198              
199 0           return \@headers;
200             }
201              
202             sub in_body
203             {
204 0     0     my $self = shift;
205 0           my %headers = @{ $self->{headers} };
  0            
206              
207 0           $_ = "";
208 0           my $len = $headers{content_length};
209 0 0         $len = 10 * 1024 * 1024 unless defined $len;
210              
211 0           read STDIN, $_, $len;
212 0           return $_;
213             }
214              
215             sub out_response
216             {
217 0     0     my $self = shift;
218 0           my $code = shift;
219 0           print "HTTP/$self->{version} $code\r\n";
220             }
221              
222             sub out_headers
223             {
224 0     0     my $self = shift;
225 0           while ( my ( $name, $value ) = splice @_, 0, 2 ) {
226 0           $name = join "-", map { ucfirst lc $_ } split /[_-]+/, $name;
  0            
227 0 0         if ( ref $value ) {
228             # must be an array
229 0           foreach my $val ( @$value ) {
230 0           print "$name: $val\r\n";
231             }
232             } else {
233 0           print "$name: $value\r\n";
234             }
235             }
236             }
237              
238             sub out_body
239             {
240 0     0     my $self = shift;
241 0           my $body = shift;
242              
243 11     11   12576 use bytes;
  11         110  
  11         53  
244 0           my $len = length $body;
245 0           print "Content-Length: $len\r\n";
246 0           print "\r\n";
247 0           print $body;
248             }
249              
250             sub out_all
251             {
252 0     0     my $self = shift;
253              
254 0           my %default_headers = (
255             content_type => "text/plain",
256             date => $self->_http_time,
257             );
258 0           $self->{out_headers} = { %default_headers };
259              
260 0           my $req = $self->{request}->[1];
261 0           $req =~ s#^/##;
262 0           my @args = map { uri_unescape $_ } split m#/#, $req;
  0            
263 0           my $func = shift @args;
264 0 0         $func = "index" unless length $func;
265              
266 0           my $body;
267 0           eval {
268 0           $body = $self->$func( @args );
269             };
270 0 0         if ( $@ ) {
    0          
271 0           warn "Server error: $@\n";
272 0           $self->out_response( "404 Not Found" );
273 0           $self->out_headers(
274             %default_headers
275             );
276 0           $self->out_body(
277             "Server error: $@\n"
278             );
279             } elsif ( defined $body ) {
280 0   0       $self->out_response( $self->{out_code} || "200 OK" );
281 0           $self->out_headers( %{ $self->{out_headers} } );
  0            
282 0           $self->out_body( $body );
283             }
284             }
285              
286             # default handlers
287             sub index
288             {
289 0     0     my $self = shift;
290 0           my $body = "Available functions:\n";
291             $body .= ( join "", map "- $_\n", sort { $a cmp $b}
292             grep { not __PACKAGE__->can( $_ ) }
293 0   0       grep { HTTP::Server::Request->can( $_ ) }
294             keys %{HTTP::Server::Request::} )
295             || "NONE\n";
296 0           return $body;
297             }
298              
299             sub echo
300             {
301 0     0     my $self = shift;
302 0           my $type = shift;
303 0           my $body = "";
304 0 0 0       if ( not $type or $type eq "head" ) {
305 0           $body .= $self->{head};
306             }
307 0 0 0       if ( ( not $type or $type eq "body" ) and defined $self->{body} ) {
      0        
308 0           $body .= $self->{body};
309             }
310 0           return $body;
311             }
312              
313             sub cookie
314             {
315 0     0     my $self = shift;
316 0   0       my $num = shift || 1;
317 0   0       my $template = shift ||
318             "test_cookie%n=true; expires=%date(+600); path=/";
319              
320             my $expdate = sub {
321 0     0     my $time = shift;
322 0 0         $time += time if $time =~ m/^[+-]/;
323 0           return $self->_http_time( $time );
324 0           };
325 0           my @cookies;
326 0           foreach my $n ( 1..$num ) {
327 0           $_ = $template;
328 0           s/%n/$n/;
329 0           s/%date\(\s*([+-]?\d+)\s*\)/$expdate->( $1 )/e;
  0            
330 0           push @cookies, $_;
331             }
332 0           $self->{out_headers}->{set_cookie} = \@cookies;
333              
334 0           return "Sent $num cookies matching template:\n$template\n";
335             }
336              
337             sub repeat
338             {
339 0     0     my $self = shift;
340 0   0       my $num = shift || 1024;
341 0   0       my $pattern = shift || "=";
342              
343 0           return $pattern x $num;
344             }
345              
346             package HTTP::Server::Request;
347             our @ISA = qw(HTTP::Server::Connection);
348              
349             1;
350              
351             __END__