File Coverage

lib/Test/Fake/HTTPD.pm
Criterion Covered Total %
statement 54 85 63.5
branch 2 26 7.6
condition 0 12 0.0
subroutine 20 26 76.9
pod 8 9 88.8
total 84 158 53.1


line stmt bran cond sub pod time code
1             package Test::Fake::HTTPD;
2              
3 3     3   265029 use 5.008_001;
  3         10  
  3         117  
4 3     3   16 use strict;
  3         4  
  3         103  
5 3     3   13 use warnings;
  3         8  
  3         96  
6 3     3   1923 use HTTP::Daemon;
  3         106318  
  3         47  
7 3     3   3600 use HTTP::Message::PSGI qw(res_from_psgi);
  3         17378  
  3         219  
8 3     3   456 use Test::TCP qw(wait_port);
  3         28273  
  3         128  
9 3     3   15 use URI;
  3         4  
  3         55  
10 3     3   11 use Time::HiRes ();
  3         3  
  3         80  
11 3     3   13 use Scalar::Util qw(blessed weaken);
  3         4  
  3         166  
12 3     3   36 use Carp qw(croak);
  3         3  
  3         139  
13 3     3   13 use Exporter qw(import);
  3         4  
  3         350  
14              
15             our $VERSION = '0.07';
16             $VERSION = eval $VERSION;
17              
18             our @EXPORT = qw(run_http_server run_https_server);
19              
20             our $ENABLE_SSL = eval { require HTTP::Daemon::SSL; 1 };
21 1     1 0 5 sub enable_ssl { $ENABLE_SSL }
22              
23             sub run_http_server (&) {
24 1     1 1 951 my $app = shift;
25 1         9 __PACKAGE__->new->run($app);
26             }
27              
28 0     0 1 0 sub run_https_server (&) {} # noop
29             if ($ENABLE_SSL) {
30 3     3   13 no warnings 'redefine';
  3         4  
  3         2115  
31             *run_https_server = sub (&) {
32             my $app = shift;
33             __PACKAGE__->new(scheme => 'https')->run($app);
34             };
35             }
36              
37             sub new {
38 1     1 1 2 my ($class, %args) = @_;
39 1         11 bless { timeout => 5, listen => 5, scheme => 'http', %args }, $class;
40             }
41              
42             our $DAEMON_MAP = {
43             http => 'HTTP::Daemon',
44             https => 'HTTP::Daemon::SSL',
45             };
46              
47             sub _daemon_class {
48 0     0   0 my $self = shift;
49 0         0 return $DAEMON_MAP->{$self->{scheme}};
50             }
51              
52             sub run {
53 1     1 1 2 my ($self, $app) = @_;
54              
55             $self->{server} = Test::TCP->new(
56             code => sub {
57 0     0   0 my $port = shift;
58              
59 0         0 my $d;
60 0         0 for (1..10) {
61 0 0       0 $d = $self->_daemon_class->new(
    0          
62             LocalAddr => '127.0.0.1',
63             LocalPort => $port,
64             Timeout => $self->{timeout},
65             Proto => 'tcp',
66             Listen => $self->{listen},
67             ($self->_is_win32 ? () : (ReuseAddr => 1)),
68             ) and last;
69 0         0 Time::HiRes::sleep(0.1);
70             }
71              
72 0 0       0 croak("Can't accepted on 127.0.0.1:$port") unless $d;
73              
74 0         0 $d->accept; # wait for port check from parent process
75              
76 0         0 while (my $c = $d->accept) {
77 0         0 while (my $req = $c->get_request) {
78 0         0 my $res = $self->_to_http_res($app->($req));
79 0         0 $c->send_response($res);
80             }
81 0         0 $c->close;
82 0         0 undef $c;
83             }
84             },
85 1 50       19 ($self->{port} ? (port => $self->{port}) : ()),
86             );
87              
88 1         3854 weaken($self);
89 1         11 $self;
90             }
91              
92             sub scheme {
93 3     3 1 6 my $self = shift;
94 3         18 return $self->{scheme};
95             }
96              
97             sub port {
98 4     4 1 1427 my $self = shift;
99 4 50       36 return $self->{server} ? $self->{server}->port : 0;
100             }
101              
102             sub host_port {
103 1     1 1 556 my $self = shift;
104 1         6 return $self->endpoint->host_port;
105             }
106              
107             sub endpoint {
108 3     3 1 14288 my $self = shift;
109 3         48 my $url = sprintf '%s://127.0.0.1:%d', $self->scheme, $self->port;
110 3         73 return URI->new($url);
111             }
112              
113 0     0     sub _is_win32 { $^O eq 'MSWin32' }
114              
115             sub _is_psgi_res {
116 0     0     my ($self, $res) = @_;
117 0 0         return unless ref $res eq 'ARRAY';
118 0 0         return unless @$res == 3;
119 0 0 0       return unless $res->[0] && $res->[0] =~ /^\d{3}$/;
120 0 0 0       return unless ref $res->[1] eq 'ARRAY' || ref $res->[1] eq 'HASH';
121 0           return 1;
122             }
123              
124             sub _to_http_res {
125 0     0     my ($self, $res) = @_;
126              
127 0           my $http_res;
128 0 0 0       if (blessed($res) and $res->isa('HTTP::Response')) {
    0 0        
    0          
129 0           $http_res = $res;
130             }
131             elsif (blessed($res) and $res->isa('Plack::Response')) {
132 0           $http_res = res_from_psgi($res->finalize);
133             }
134             elsif ($self->_is_psgi_res($res)) {
135 0           $http_res = res_from_psgi($res);
136             }
137              
138 0 0         croak(sprintf '%s: response must be HTTP::Response or Plack::Response or PSGI', __PACKAGE__)
139             unless $http_res;
140              
141 0           return $http_res;
142             }
143              
144             1;
145              
146             =head1 NAME
147              
148             Test::Fake::HTTPD - a fake HTTP server
149              
150             =head1 SYNOPSIS
151              
152             DSL-style
153              
154             use Test::Fake::HTTPD;
155              
156             my $httpd = run_http_server {
157             my $req = shift;
158             # ...
159              
160             # 1. HTTP::Response ok
161             return $http_response;
162             # 2. Plack::Response ok
163             return $plack_response;
164             # 3. PSGI response ok
165             return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello World' ] ];
166             };
167              
168             printf "You can connect to your server at %s.\n", $httpd->host_port;
169             # or
170             printf "You can connect to your server at 127.0.0.1:%d.\n", $httpd->port;
171              
172             # access to fake HTTP server
173             use LWP::UserAgent;
174             my $res = LWP::UserAgent->new->get($httpd->endpoint); # "http://127.0.0.1:{port}"
175              
176             # Stop http server automatically at destruction time.
177              
178             OO-style
179              
180             use Test::Fake::HTTPD;
181              
182             my $httpd = Test::Fake::HTTPD->new(
183             timeout => 5,
184             );
185              
186             $httpd->run(sub {
187             my $req = shift;
188             # ...
189             [ 200, [ 'Content-Type', 'text/plain' ], [ 'Hello World' ] ];
190             });
191              
192             # Stop http server automatically at destruction time.
193              
194             =head1 DESCRIPTION
195              
196             Test::Fake::HTTPD is a fake HTTP server module for testing.
197              
198             =head1 FUNCTIONS
199              
200             =over 4
201              
202             =item * C
203              
204             Starts HTTP server and returns the guard instance.
205              
206             my $httpd = run_http_server {
207             my $req = shift;
208             # ...
209             return $http_or_plack_or_psgi_res;
210             };
211              
212             # can use $httpd guard object, same as OO-style
213             LWP::UserAgent->new->get($httpd->endpoint);
214              
215             =item * C
216              
217             Starts B server and returns the guard instance.
218              
219             If you use this method, you MUST install L.
220              
221             my $httpd = run_https_server {
222             my $req = shift;
223             # ...
224             return $http_or_plack_or_psgi_res;
225             };
226              
227             # can use $httpd guard object, same as OO-style
228             my $ua = LWP::UserAgent->new(
229             ssl_opts => {
230             SSL_verify_mode => 0,
231             verify_hostname => 0,
232             },
233             );
234             $ua->get($httpd->endpoint);
235              
236             =back
237              
238             =head1 METHODS
239              
240             =over 4
241              
242             =item * C
243              
244             Returns a new instance.
245              
246             my $httpd = Test::Fake::HTTPD->new(%args);
247              
248             C<%args> are:
249              
250             =over 8
251              
252             =item * C
253              
254             timeout value (default: 5)
255              
256             =item * C
257              
258             queue size for listen (default: 5)
259              
260             =item * C
261              
262             local bind port number (default: auto detection)
263              
264             =back
265              
266             my $httpd = Test::Fake::HTTPD->new(
267             timeout => 10,
268             listen => 10,
269             port => 3333,
270             );
271              
272             =item * C
273              
274             Starts this HTTP server.
275              
276             $httpd->run(sub { ... });
277              
278             =item * C
279              
280             Returns a scheme of running, "http" or "https".
281              
282             my $scheme = $httpd->scheme;
283              
284             =item * C
285              
286             Returns a port number of running.
287              
288             my $port = $httpd->port;
289              
290             =item * C
291              
292             Returns a URI host_port of running. ("127.0.0.1:{port}")
293              
294             my $host_port = $httpd->host_port;
295              
296             =item * C
297              
298             Returns an endpoint URI of running. ("http://127.0.0.1:{port}" URI object)
299              
300             use LWP::UserAgent;
301              
302             my $res = LWP::UserAgent->new->get($httpd->endpoint);
303              
304             my $url = $httpd->endpoint;
305             $url->path('/foo/bar');
306             my $res = LWP::UserAgent->new->get($url);
307              
308             =back
309              
310             =head1 AUTHOR
311              
312             NAKAGAWA Masaki Emasaki@cpan.orgE
313              
314             =head1 THANKS TO
315              
316             xaicron
317              
318             =head1 LICENSE
319              
320             This library is free software; you can redistribute it and/or modify
321             it under the same terms as Perl itself.
322              
323             =head1 SEE ALSO
324              
325             L, L, L, L
326              
327             =cut