File Coverage

blib/lib/Test/HTTP/Server.pm
Criterion Covered Total %
statement 140 200 70.0
branch 20 44 45.4
condition 5 30 16.6
subroutine 27 33 81.8
pod 0 4 0.0
total 192 311 61.7


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