File Coverage

blib/lib/Test/HTTP/Server.pm
Criterion Covered Total %
statement 175 204 85.7
branch 27 44 61.3
condition 17 30 56.6
subroutine 32 34 94.1
pod 0 4 0.0
total 251 316 79.4


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