File Coverage

inc/Test/HTTP/AnyEvent/Server.pm
Criterion Covered Total %
statement 64 164 39.0
branch 2 16 12.5
condition 0 6 0.0
subroutine 21 28 75.0
pod 2 4 50.0
total 89 218 40.8


line stmt bran cond sub pod time code
1             package Test::HTTP::AnyEvent::Server;
2             # ABSTRACT: the async counterpart to Test::HTTP::Server
3              
4              
5 11     11   83660 use feature qw(state switch);
  11         22  
  11         1123  
6 11     11   61 use strict;
  11         19  
  11         338  
7 11     11   57 use utf8;
  11         14  
  11         84  
8 11     11   276 use warnings qw(all);
  11         19  
  11         397  
9              
10 11     11   7682 use AnyEvent;
  11         23533  
  11         315  
11 11     11   13994 use AnyEvent::Handle;
  11         265553  
  11         646  
12 11     11   13907 use AnyEvent::Log;
  11         160092  
  11         519  
13 11     11   12444 use AnyEvent::Socket;
  11         206388  
  11         1974  
14 11     11   150 use AnyEvent::Util;
  11         27  
  11         800  
15 11     11   5362 use HTTP::Headers;
  11         47209  
  11         446  
16 11     11   10601 use HTTP::Request;
  11         84119  
  11         385  
17 11     11   5354 use HTTP::Response;
  11         38411  
  11         375  
18 11     11   3103 use Moo;
  11         54956  
  11         98  
19 11     11   16323 use MooX::Types::MooseLike::Base qw(:all);
  11         21893  
  11         12081  
20 11     11   90 use POSIX;
  11         33  
  11         119  
21              
22 11     11   53869 no if ($] >= 5.017010), warnings => q(experimental);
  11         114  
  11         97  
23              
24             #$AnyEvent::Log::FILTER->level('debug');
25              
26             our $VERSION = '0.007'; # VERSION
27              
28             my %pool;
29              
30              
31             has address => (is => 'ro', isa => Str, default => sub { '127.0.0.1' }, writer => 'set_address');
32              
33              
34             has port => (is => 'ro', isa => Int, writer => 'set_port');
35              
36              
37             has maxconn => (is => 'ro', isa => Int, default => sub { 10 });
38              
39              
40             has timeout => (is => 'ro', isa => Int, default => sub { 60 });
41              
42              
43             has disable_proxy => (is => 'ro', isa => Bool, default => sub { 1 });
44              
45              
46             has forked => (is => 'ro', isa => Bool, default => sub { 0 });
47              
48              
49             has forked_pid => (is => 'ro', isa => Int, writer => 'set_forked_pid');
50              
51              
52             has server => (is => 'ro', isa => Ref, writer => 'set_server');
53              
54              
55             sub BUILD {
56 8     8 0 429 my ($self) = @_;
57              
58             ## no critic (RequireLocalizedPunctuationVars)
59 8 50       214 @ENV{qw(no_proxy http_proxy ftp_proxy all_proxy)} = (q(localhost,127.0.0.1), (q()) x 3)
60             if $self->disable_proxy;
61              
62 8 50       72 unless ($self->forked) {
63             $self->set_server(
64             $self->start_server(sub {
65 8     8   1946 my (undef, $address, $port) = @_;
66 8         52 $self->set_address($address);
67 8         5090 $self->set_port($port);
68 8         4893 AE::log info =>
69             "bound to http://$address:$port/";
70             })
71 8         91 );
72             } else {
73 0         0 my ($rh, $wh) = portable_pipe;
74              
75 0         0 given (fork) {
76 0         0 when (undef) {
77 0         0 AE::log fatal =>
78             "couldn't fork(): $!";
79 0         0 } when (0) {
80             # child
81 0         0 close $rh;
82              
83             my $h = AnyEvent::Handle->new(
84             fh => $wh,
85             on_error => sub {
86 0     0   0 AE::log fatal =>
87             "couldn't syswrite() to pipe: $!";
88             },
89 0         0 );
90              
91             $self->set_server(
92             $self->start_server(sub {
93 0     0   0 my (undef, $address, $port) = @_;
94             # have to postpone so the address/port gets actually bound
95 0         0 AE::postpone { $h->push_write(join("\t", $address, $port)) };
  0         0  
96             })
97 0         0 );
98              
99 0         0 AE::cv->wait;
100 0         0 POSIX::_exit(0);
101 0         0 exit 1;
102 0         0 } default {
103             # parent
104 0         0 my $pid = $_;
105 0         0 close $wh;
106              
107 0         0 my $buf;
108 0         0 my $len = sysread $rh, $buf, 65536;
109 0 0 0     0 AE::log fatal =>
110             "couldn't sysread() from pipe: $!"
111             if not defined $len or not $len;
112              
113 0         0 my ($address, $port) = split m{\t}x, $buf;
114 0         0 $self->set_address($address);
115 0         0 $self->set_port($port);
116 0         0 $self->set_forked_pid($pid);
117 0         0 AE::log info =>
118             "forked as $pid and bound to http://$address:$port/";
119             }
120             }
121             }
122              
123 8         59915 return;
124             }
125              
126             sub DEMOLISH {
127 0     0 0 0 my ($self) = @_;
128              
129 0 0       0 if ($self->forked) {
130 0         0 my $pid = $self->forked_pid;
131 0         0 kill 9 => $pid;
132 0         0 AE::log info =>
133             "killed $pid";
134             }
135              
136 0         0 return;
137             }
138              
139              
140             sub uri {
141 3     3 1 8111 my ($self) = @_;
142 3         51 return sprintf('http://%s:%d/', $self->address, $self->port);
143             }
144              
145              
146             sub start_server {
147 8     8 1 22 my ($self, $cb) = @_;
148              
149             return tcp_server(
150             $self->address => $self->port,
151             sub {
152 0     0     my ($fh, $host, $port) = @_;
153 0 0         if (scalar keys %pool > $self->maxconn) {
154 0           AE::log error =>
155             "deny connection from $host:$port (too many connections)\n";
156 0           return;
157             } else {
158 0           AE::log warn =>
159             "new connection from $host:$port\n";
160             }
161              
162 0           my $h = AnyEvent::Handle->new(
163             fh => $fh,
164             on_eof => \&_cleanup,
165             on_error => \&_cleanup,
166             timeout => $self->timeout,
167             );
168              
169 0           $pool{fileno($fh)} = $h;
170 0           AE::log debug =>
171             sprintf "%d connection(s) in pool\n", scalar keys %pool;
172              
173 0           my ($req, $hdr);
174              
175             $h->push_read(regex => qr{\015?\012}x, sub {
176             #my ($h, $data) = @_;
177 0           my (undef, $data) = @_;
178 0           $data =~ s/\s+$//sx;
179 0           $req = $data;
180 0           AE::log debug => "request: [$req]\n";
181 0           });
182              
183             $h->push_read(regex => qr{(\015?\012){2}}x, sub {
184 0           my ($_h, $data) = @_;
185 0           $hdr = $data;
186 0           AE::log debug => "got headers\n";
187 0 0         if ($hdr =~ m{\bContent-length:\s*(\d+)\b}isx) {
188 0           AE::log debug => "expecting content\n";
189             $_h->push_read(chunk => int($1), sub {
190 0           my ($__h, $__data) = @_;
191 0           _reply($__h, $req, $hdr, $__data);
192 0           });
193             } else {
194 0           _reply($_h, $req, $hdr);
195             }
196 0           });
197 8         146 } => $cb
198             );
199             }
200              
201              
202             sub _cleanup {
203             #my ($h, $fatal, $msg) = @_;
204 0     0     my ($h) = @_;
205 0           AE::log debug => "closing connection\n";
206 0           my $r = eval {
207             ## no critic (ProhibitNoWarnings)
208 11     11   21560 no warnings;
  11         54  
  11         10291  
209              
210 0           my $id = fileno($h->{fh});
211 0           delete $pool{$id};
212 0           shutdown $h->{fh}, 2;
213              
214 0           return 1;
215             };
216 0 0 0       AE::log warn => "shutdown() aborted\n"
217             if not defined $r or $@;
218 0           $h->destroy;
219 0           return;
220             }
221              
222              
223             sub _reply {
224 0     0     my ($h, $req, $hdr, $content) = @_;
225 0           state $timer = {};
226              
227 0           my $res = HTTP::Response->new(
228             200 => 'OK',
229             HTTP::Headers->new(
230             Connection => 'close',
231             Content_Type => 'text/plain',
232             Server => __PACKAGE__ . "/$Test::HTTP::AnyEvent::Server::VERSION AnyEvent/$AE::VERSION Perl/$] ($^O)",
233             )
234             );
235 0           $res->date(time);
236 0           $res->protocol('HTTP/1.0');
237              
238 0 0         if ($req =~ m{^(GET|POST)\s+(.+)\s+(HTTP/1\.[01])$}ix) {
239 0           my ($method, $uri, $protocol) = ($1, $2, $3);
240 0           AE::log debug => "sending response to $method ($protocol)\n";
241 0           for ($uri) {
242 0           when (m{^/repeat/(\d+)/(.+)}x) {
243 0           $res->content($2 x $1);
244 0           } when (m{^/echo/head$}x) {
245 0           $res->content(
246             join(
247             "\015\012",
248             $req,
249             $hdr,
250             )
251             );
252 0           } when (m{^/echo/body$}x) {
253 0           $res->content($content);
254 0           } when (m{^/delay/(\d+)$}x) {
255 0           $res->content(sprintf(qq(issued %s\n), scalar gmtime));
256             $timer->{$h} = AE::timer $1, 0, sub {
257 0     0     delete $timer->{$h};
258 0           AE::log debug => "delayed response\n";
259 0           $h->push_write($res->as_string("\015\012"));
260 0           _cleanup($h);
261 0           };
262 0           return;
263 0           } default {
264 0           $res->code(404);
265 0           $res->message('Not Found');
266 0           $res->content('Not Found');
267             }
268             }
269             } else {
270 0           AE::log error => "bad request\n";
271 0           $res->code(400);
272 0           $res->message('Bad Request');
273 0           $res->content('Bad Request');
274             }
275              
276 0           $h->push_write($res->as_string("\015\012"));
277 0           _cleanup($h);
278 0           return;
279             }
280              
281              
282             1;
283              
284             __END__