File Coverage

blib/lib/Perlbal/Test/WebServer.pm
Criterion Covered Total %
statement 162 167 97.0
branch 69 88 78.4
condition 28 42 66.6
subroutine 14 14 100.0
pod 0 3 0.0
total 273 314 86.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Perlbal::Test::WebServer;
4              
5 57     57   49674 use strict;
  57         121  
  57         2047  
6 57     57   274 use IO::Socket::INET;
  57         104  
  57         732  
7 57     57   109919 use HTTP::Request;
  57         56530  
  57         1774  
8 57     57   364 use Socket qw(MSG_NOSIGNAL IPPROTO_TCP TCP_NODELAY SOL_SOCKET);
  57         104  
  57         7882  
9 57     57   306 use Perlbal::Test;
  57         86  
  57         6262  
10              
11 57     57   68561 use Perlbal::Test::WebClient;
  57         154  
  57         122591  
12              
13             require Exporter;
14 57     57   445 use vars qw(@ISA @EXPORT);
  57         120  
  57         204237  
15             @ISA = qw(Exporter);
16             @EXPORT = qw(start_webserver);
17              
18             our @webserver_pids;
19              
20             my $testpid; # of the test suite's main program, the one running the HTTP client
21              
22             END {
23             # ensure we kill off the webserver
24 57 100 100 57   7072074 kill 9, @webserver_pids if $testpid && $testpid == $$;
25             }
26              
27              
28             sub start_webserver {
29 78     78 0 5817 my $port = new_port();
30              
31             # dummy mode
32 78 50       508 if ($ENV{'TEST_PERLBAL_USE_EXISTING'}) {
33 0         0 return $port;
34             }
35              
36 78         793 $testpid = $$;
37              
38 78 100       232499 if (my $child = fork) {
39             # i am parent, wait for child to startup
40 43         1512 push @webserver_pids, $child;
41 43         6881 my $sock = wait_on_child($child, $port);
42 43 50       334 die "Unable to spawn webserver on port $port\n"
43             unless $sock;
44 43         4390 print $sock "GET /reqdecr,status HTTP/1.0\r\n\r\n";
45 43         868879 my $line = <$sock>;
46 43 0 33     3074 die "Didn't get 200 OK: " . (defined $line ? $line : "(undef)")
    50          
47             unless $line && $line =~ /200 OK/;
48 43         1548 return $port;
49             }
50              
51             # i am child, start up
52 35 50       579925 my $ssock = IO::Socket::INET->new(LocalPort => $port, ReuseAddr => 1, Listen => 3)
53             or die "Unable to start socket: $!\n";
54 35         61588 while (my $csock = $ssock->accept) {
55 81 50       70053922 exit 0 unless $csock;
56 81 100       219925 fork and next; # parent starts waiting for next request
57 35 50       5640 setsockopt($csock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die;
58 35         4460 serve_client($csock);
59             }
60             }
61              
62             sub serve_client {
63 35     35 0 1572 my $csock = shift;
64 35         597 my $req_num = 0;
65 35         456 my $did_options = 0;
66 35         424 my @reqs;
67              
68             REQ:
69 35         572 while (1) {
70 156         1381 my $req = '';
71 156         1268 my $clen = undef;
72 156         66295977 while (<$csock>) {
73 695         3266 $req .= $_;
74 695 100       6868 if (/^content-length:\s*(\d+)/i) { $clen = $1; };
  86         815  
75 695 100 66     17828 last if ! $_ || /^\r?\n/;
76             }
77 156 100       2536 exit 0 unless $req;
78              
79             # parse out things we want to have
80 151         750 my @cmds;
81 151         2681 my $httpver = 0; # 0 = 1.0, 1 = 1.1, undef = neither
82 151         709 my $method;
83 151 100       3359 if ($req =~ m!^([A-Z]+) /?(\S+) HTTP/(1\.\d+)\r?\n?!) {
84 150         1526 $method = $1;
85 150         2134 my $cmds = durl($2);
86 150         1716 @cmds = split(/\s*,\s*/, $cmds);
87 150         356 $req_num++;
88 150 0       2630 $httpver = ($3 eq '1.0' ? 0 : ($3 eq '1.1' ? 1 : undef));
    50          
89             }
90 151         5581 my $msg = HTTP::Request->parse($req);
91 151         3551741 my $keeping_alive = undef;
92              
93 151         538 my $body;
94 151 100       795 if ($clen) {
95 85 50       768 die "Can't read a body on a GET or HEAD" if $method =~ /^GET|HEAD$/;
96 85         12040244 my $read = read $csock, $body, $clen;
97 85 50       1135 die "Didn't read $clen bytes. Got $read." if $clen != $read;
98             }
99              
100             my $response = sub {
101 151     151   1229 my %opts = @_;
102 151         1307 my $code = delete $opts{code};
103 151         2062 my $codetext = delete $opts{codetext};
104 151         778 my $content = delete $opts{content};
105 151         527 my $ctype = delete $opts{type};
106 151         358 my $extra_hdr = delete $opts{headers};
107 151 50       870 die "unknown data in opts: %opts" if %opts;
108              
109 151   100     2074 $extra_hdr ||= '';
110 151 100 66     1225 $code ||= $content ? 200 : 200;
111 151   33     4190 $codetext ||= { 200 => 'OK', 500 => 'Internal Server Error', 204 => "No Content" }->{$code};
112 151   100     919 $content ||= "";
113              
114 151         486 my $clen = length $content;
115 151 100 50     1741 $ctype ||= "text/plain" unless $code == 204;
116 151 100       869 $extra_hdr .= "Content-Type: $ctype\r\n" if $ctype;
117              
118 151         347 my $hdr_keepalive = "";
119              
120 151 100       822 unless (defined $keeping_alive) {
121 149   100     1111 my $hdr_connection = $msg->header('Connection') || '';
122 149 50       13643 if ($httpver == 1) {
123 0 0       0 if ($hdr_connection =~ /\bclose\b/i) {
124 0         0 $keeping_alive = 0;
125             } else {
126 0         0 $keeping_alive = "1.1implicit";
127             }
128             }
129 149 100 66     2582 if ($httpver == 0 && $hdr_connection =~ /\bkeep-alive\b/i) {
130 121         311 $keeping_alive = "1.0keepalive";
131             }
132             }
133              
134 151 100       421 if ($keeping_alive) {
135 121         309 $hdr_keepalive = "Connection: keep-alive\n";
136             } else {
137 30         614 $hdr_keepalive = "Connection: close\n";
138             }
139              
140 151         3768 return "HTTP/1.0 $code $codetext\r\n" .
141             $hdr_keepalive .
142             "Content-Length: $clen\r\n" .
143             $extra_hdr .
144             "\r\n" .
145             "$content";
146 151         4893 };
147              
148             my $send = sub {
149 151     151   443 my $res = shift;
150 151         30859 print $csock $res;
151 151 100       44168 exit 0 unless $keeping_alive;
152 151         1074 };
153              
154             # 500 if no commands were given or we don't know their HTTP version
155             # or we didn't parse a proper HTTP request
156 151 50 66     4621 unless (@cmds && defined $httpver && $msg) {
      66        
157 1         40 print STDERR "500 response!\n";
158 1         6 $send->($response->(code => 500));
159 1         21 next REQ;
160             }
161              
162 150 100       968 if ($method eq "OPTIONS") {
163 1         20 $did_options = 1;
164 1         6 $send->($response->(code => 200));
165 1         72 next REQ;
166             }
167              
168             # prepare a simple 200 to send; undef this if you want to control
169             # your own output below
170 149         503 my $to_send;
171              
172 149         874 foreach my $cmd (@cmds) {
173 171         1390 $cmd =~ s/^\s+//;
174 171         656 $cmd =~ s/\s+$//;
175              
176 171 100       836 if ($cmd =~ /^sleep:([\d\.]+)$/i) {
177 4         25 my $sleeptime = $1;
178             #print "I, $$, should sleep for $sleeptime.\n";
179 57     57   64641 use Time::HiRes;
  57         124752  
  57         289  
180 4         32 my $t1 = Time::HiRes::time();
181 4         1406074 select undef, undef, undef, $1;
182 4         40 my $t2 = Time::HiRes::time();
183 4         34 my $td = $t2 - $t1;
184             #print "I, $$, slept for $td\n";
185             }
186              
187 171 100       2070 if ($cmd =~ /^keepalive:([01])$/i) {
188 2         13 $keeping_alive = $1;
189             }
190              
191 171 100       645 if ($cmd eq "status") {
192 128   100     3608 my $len = $clen || 0;
193 128   100     6241 my $bu = $msg->header('X-PERLBAL-BUFFERED-UPLOAD-REASON') || '';
194 128         33801 $to_send = $response->(content =>
195             "pid = $$\nreqnum = $req_num\nmethod = $method\n".
196             "length = $len\nbuffered = $bu\noptions = $did_options\n");
197             }
198              
199 171 100       1352 if ($cmd eq "reqdecr") {
200 15         52 $req_num--;
201             }
202              
203 171 100       592 if ($cmd =~ /^kill:(\d+):(\w+)$/) {
204 1         183 kill $2, $1;
205             }
206              
207 171 100       1387 if ($cmd =~ /^reproxy_url:(.+)/i) {
208 8         161 $to_send = $response->(headers => "X-Reproxy-URL: $1\r\n",
209             code => 204,
210             );
211             }
212              
213 171 100       1206 if ($cmd =~ /^reproxy_url204:(.+)/i) {
214 2         14 $to_send = $response->(headers => "X-Reproxy-URL: $1\r\n");
215             }
216              
217 171 100       713 if ($cmd =~ /^reproxy_url_cached:(\d+):(.+)/i) {
218 2         69 kill 'USR1', $testpid;
219 2         29 $to_send = $response->(headers =>
220             "X-Reproxy-URL: $2\r\nX-Reproxy-Cache-For: $1; Last-Modified Content-Type\r\nLast-Modified: 199\r\nContent-Type: application/badger\r\n");
221             }
222              
223 171 100       768 if ($cmd =~ /^reproxy_url_multi:((?:\d+:){2,})(\S+)/i) {
224 1         5 my $ports = $1;
225 1         3 my $path = $2;
226 1         2 my @urls;
227 1         5 foreach my $port (split(/:/, $ports)) {
228 2         7 push @urls, "http://127.0.0.1:$port$path";
229             }
230 1         7 $to_send = $response->(headers => "X-Reproxy-URL: @urls\r\n");
231             }
232              
233 171 100       832 if ($cmd =~ /^reproxy_file:(.+)/i) {
234 6         42 $to_send = $response->(headers => "X-Reproxy-File: $1\r\n");
235             }
236              
237 171 100       854 if ($cmd =~ /^subreq:(\d+)$/) {
238 1         22 my $port = $1;
239 1         55 my $wc = Perlbal::Test::WebClient->new;
240 1         9 $wc->server("127.0.0.1:$port");
241 1         172 $wc->keepalive(0);
242 1         6 $wc->http_version('1.0');
243 1         13 my $resp = $wc->request("status");
244 1         3 my $subpid;
245 1 50 33     18 if ($resp && $resp->content =~ /^pid = (\d+)$/m) {
246 1         25 $subpid = $1;
247             }
248 1         14 $to_send = $response->(content => "pid = $$\nsubpid = $subpid\nreqnum = $req_num\n");
249             }
250              
251 171 100       1032 if ($cmd =~ /^reflect_request_headers$/) {
252 1         21 $to_send = $response->(content => $msg->headers->as_string);
253             }
254             }
255              
256 149   33     1740 $send->($to_send || $response->());
257             } # while(1)
258             }
259              
260             # de-url escape
261             sub durl {
262 150     150 0 1183 my ($a) = @_;
263 150         1312 $a =~ tr/+/ /;
264 150         1074 $a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
  0         0  
265 150         746 return $a;
266             }
267              
268             1;