File Coverage

lib/Test/Fake/HTTPD.pm
Criterion Covered Total %
statement 54 86 62.7
branch 3 28 10.7
condition 1 15 6.6
subroutine 20 27 74.0
pod 8 10 80.0
total 86 166 51.8


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