File Coverage

blib/lib/Test/HTTP/MockServer.pm
Criterion Covered Total %
statement 67 69 97.1
branch 12 20 60.0
condition 1 3 33.3
subroutine 15 16 93.7
pod 6 7 85.7
total 101 115 87.8


line stmt bran cond sub pod time code
1             package Test::HTTP::MockServer;
2 7     7   3480 use strict;
  7         7  
  7         154  
3 7     7   14 use warnings;
  7         7  
  7         128  
4 7     7   2600 use HTTP::Parser;
  7         9613  
  7         156  
5 7     7   32 use HTTP::Response;
  7         3  
  7         87  
6 7     7   17 use IO::Handle;
  7         7  
  7         220  
7 7     7   3172 use Socket;
  7         17774  
  7         6528  
8              
9             our $VERSION = '0.0.1';
10              
11             sub new {
12 7     7 1 1757 my ($class) = @_;
13 7   33     43 $class = ref $class || $class;
14 7         22 return bless {}, $class;
15             }
16              
17             sub bind_mock_server {
18 14     14 1 18 my $self = shift;
19 14 100       50 if (!$self->{socket}) {
20 7 50       2837 my $proto = getprotobyname("tcp")
21             or die $!;
22 7 50       196 socket my $s, PF_INET, SOCK_STREAM, $proto
23             or die $!;
24 7         7 my $host_s = '127.0.0.1';
25 7         59 my $host = inet_aton($host_s);
26 7         7 my $port;
27 7         7 while (1) {
28 7         151 $port = int(rand(5000))+10000;
29 7         24 my $addr = sockaddr_in($port, $host);
30 7 50       119 bind($s,$addr)
31             or next;
32 7 50       53 listen($s, 10)
33             or die $!;
34 7         10 last;
35             }
36 7         11 $self->{host} = $host_s;
37 7         23 $self->{port} = $port;
38 7         14 $self->{socket} = $s;
39             }
40 14         14 return 1;
41             }
42              
43             sub host {
44 7     7 1 8 my $self = shift;
45 7         14 $self->bind_mock_server;
46 7         7 return $self->{host};
47             }
48              
49             sub port {
50 7     7 1 7 my $self = shift;
51 7         10 $self->bind_mock_server;
52 7         7 return $self->{port};
53             }
54              
55             sub url_base {
56 7     7 0 25 my $self = shift;
57 7         17 my $host = $self->host;
58 7         47 my $port = $self->port;
59 7         25 return "http://$host:$port";
60             }
61              
62             my $request_handle = sub {
63             my $self = shift;
64             my $rp = shift;
65             my $request = shift;
66             my $response;
67             eval {
68             $response = HTTP::Response->new(200, 'OK');
69             $response->header('Content-type' => 'text/plain');
70             $rp->($request, $response);
71             };
72             if ($@) {
73             my $err = $@;
74             return HTTP::Response->new(
75             500, 'Internal Server Error',
76             HTTP::Headers->new("Content-type" => "text/plain"),
77             $err
78             );
79             } else {
80             return $response;
81             }
82             };
83              
84             my $client_handle = sub {
85             my $self = shift;
86             my $rp = shift;
87             my $client = shift;
88             my $parser = HTTP::Parser->new(request => 1);
89              
90             while (1) {
91             my $buf;
92             # read a byte at a time so we can do a blocking read instead of
93             # having to manage a non-blocking read loop.
94             my $r = read $client, $buf, 1;
95             my $request;
96             my $closed;
97             if (defined $r) {
98             my $p = $parser->add($buf);
99             if ($p == 0) {
100             $request = $parser->object;
101             }
102             $closed = 0;
103             } else {
104             $request = $parser->object;
105             $closed = 1;
106             }
107             if ($request) {
108             my $copy = $request;
109             $request = undef;
110             my $response = $request_handle->($self, $rp, $copy);
111             $response->header('Content-length' => length($response->content));
112             my $strout = "HTTP/1.1 ".($response->as_string("\015\012"));
113             $client->print($strout);
114             # we don't support keep-alive
115             $closed = 1;
116             }
117             last if $closed;
118             }
119             };
120              
121             my $server_loop = sub {
122             my $self = shift;
123             my $rp = shift;
124             while (1) {
125             accept my $client, $self->{socket}
126             or die "Failed to accept new connections: $!";
127             eval {
128             $client_handle->($self, $rp, $client);
129             };
130             close $client;
131             }
132             };
133              
134             sub start_mock_server {
135 14     14 1 7959 my $self = shift;
136 14 50       63 my $rp = shift or die "No request processor";
137              
138             die "There is already a mock server running"
139 14 50       41 if $self->{mock_server_pid};
140              
141 14         8439 $self->{mock_server_pid} = fork;
142 14 100       464 if ($self->{mock_server_pid}) {
143 9         240 return;
144             } else {
145 5         157 $DB::signal = 1;
146 5     5   598 $SIG{INT} = sub { exit 1; };
  5         721  
147 5     0   183 $SIG{TERM} = sub { exit 1; };
  0         0  
148 5         118 $server_loop->($self,$rp);
149 0         0 exit 0;
150             }
151             }
152              
153             sub stop_mock_server {
154 9     9 1 450153 my $self = shift;
155             die "Mock server not started"
156 9 50       48 unless $self->{mock_server_pid};
157 9         158 kill 2, $self->{mock_server_pid};
158 9         1349011 waitpid $self->{mock_server_pid}, 0;
159 9         145 delete $self->{mock_server_pid};
160             }
161              
162             sub DESTROY {
163 7     7   750 my $self = shift;
164 7         17 eval {
165             $self->stop_mock_server
166 7 50       617 if $self->{mock_server_pid};
167             };
168             }
169              
170             1;
171              
172             __END__