File Coverage

blib/lib/POE/Declare/HTTP/Online.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package POE::Declare::HTTP::Online;
2              
3             =pod
4              
5             =head1 NAME
6              
7             POE::Declare::HTTP::Online - Does your POE process have access to the web
8              
9             =head1 SYNOPSIS
10              
11             my $online = POE::Declare::HTTP::Online->new(
12             Timeout => 10,
13             OnlineEvent => \&handle_online,
14             OfflineEvent => \&handle_offline,
15             ErrorEvent => \&handle_unknown,
16             );
17            
18             $online->run;
19              
20             =head1 DESCRIPTION
21              
22             This is a port of L to L. It behaves similarly to
23             the original, except that it does not depend on LWP and can execute the HTTP
24             probes in parallel.
25              
26             =cut
27              
28 1     1   757 use 5.008;
  1         3  
  1         37  
29 1     1   4 use strict;
  1         2  
  1         28  
30 1     1   6 use Carp ();
  1         12  
  1         21  
31 1     1   939 use Params::Util 1.00 ();
  1         3013  
  1         32  
32 1     1   591 use POE::Declare::HTTP::Client 0.06 ();
  0            
  0            
33              
34             our $VERSION = '0.02';
35              
36             use POE::Declare 0.54 {
37             Timeout => 'Param',
38             Tests => 'Param',
39             OnlineEvent => 'Message',
40             OfflineEvent => 'Message',
41             ErrorEvent => 'Message',
42             client => 'Internal',
43             result => 'Internal',
44             };
45              
46             # Default test websites, representing major global properties that
47             # should not dissapear often in the future. We can tolerate the
48             # loss of any 3-4 of these before this module stops working.
49             my @DEFAULT = (
50             'http://google.com',
51             'http://yahoo.com/',
52             'http://cnn.com/',
53             'http://microsoft.com/',
54             'http://ibm.com/',
55             'http://amazon.com/',
56             );
57              
58              
59              
60              
61              
62             ######################################################################
63             # Constructor and Accessors
64              
65             =pod
66              
67             =head2 new
68              
69             my $online = POE::Declare::HTTP::Online->new(
70             Timeout => 10,
71             OnlineEvent => \&handle_online,
72             OfflineEvent => \&handle_offline,
73             ErrorEvent => \&handle_unknown,
74             );
75              
76             The C constructor sets up a reusable HTTP online status checker that can
77             be run as often as needed.
78              
79             Unless actively in use, the online detection object will not consume a L
80             session.
81              
82             =cut
83              
84             sub new {
85             my $self = shift->SUPER::new(@_);
86              
87             unless ( defined $self->Timeout ) {
88             $self->{Timeout} = 10;
89             }
90             unless ( defined $self->Tests ) {
91             $self->{Tests} = [ @DEFAULT ];
92             }
93             unless ( Params::Util::_ARRAY($self->Tests) ) {
94             Carp::croak("Missing or invalid 'Test' param");
95             }
96              
97             # Pre-generate a client for each request
98             $self->{client} = [
99             map {
100             POE::Declare::HTTP::Client->new(
101             Timeout => $self->Timeout - 1,
102             MaxRedirect => 0,
103             ResponseEvent => $self->lookback('http_response'),
104             ShutdownEvent => $self->lookback('http_shutdown'),
105             )
106             } $self->urls
107             ];
108              
109             return $self;
110             }
111              
112             sub urls {
113             @{ $_[0]->Tests };
114             }
115              
116             sub clients {
117             @{ $_[0]->{client} }
118             }
119              
120             sub running {
121             grep { $_->running } $_[0]->clients;
122             }
123              
124              
125              
126              
127              
128             ######################################################################
129             # Methods
130              
131             =pod
132              
133             =head2 run
134              
135             The C method starts the online detection process, spawning the L
136             session and initiating HTTP Test to each of the test URLs in parallel.
137              
138             Once a determination has been made as to our online state (positive, negative
139             or unknown) and the reporting event has been fired, the session will be
140             terminated immediately.
141              
142             =cut
143              
144             sub run {
145             my $self = shift;
146             unless ( $self->spawned ) {
147             $self->spawn;
148             }
149             return 1;
150             }
151              
152              
153              
154              
155              
156             ######################################################################
157             # Event Handlers
158              
159             sub _start :Event {
160             $_[SELF]->SUPER::_start(@_[1..$#_]);
161              
162             # Initialise state variables and boot the HTTP clients
163             $_[SELF]->{result} = {
164             online => 0,
165             offline => 0,
166             unknown => scalar($_[SELF]->urls),
167             };
168             foreach my $client ( $_[SELF]->clients ) {
169             $client->start;
170             }
171              
172             $_[SELF]->post('startup');
173             }
174              
175             sub startup :Event {
176             $_[SELF]->timeout_start($_[SELF]->Timeout);
177             my @url = $_[SELF]->urls;
178             my @client = $_[SELF]->clients;
179             foreach ( 0 .. $#client ) {
180             $client[$_]->GET($url[$_]);
181             }
182             }
183              
184             # We're so slow that we should assume we're not online
185             sub timeout :Timeout(10) {
186             $_[SELF]->call( respond => 0 );
187             }
188              
189             sub http_response :Event {
190             my $alias = $_[ARG0];
191             my $response = $_[ARG1];
192             my $result = $_[SELF]->{result};
193              
194             # Do we have a conformant response
195             if ( $_[SELF]->conformant($response) ) {
196             $result->{online}++;
197             } else {
198             $result->{offline}++;
199             }
200              
201             # Are we online?
202             if ( $result->{online} >= 2 ) {
203             return $_[SELF]->call( respond => 1 );
204             }
205              
206             # Are there any active clients left
207             if ( $_[SELF]->running ) {
208             # No definite answer yet
209             return;
210             }
211              
212             # We are not online, so far as we can tell
213             return $_[SELF]->call( respond => 0 );
214             }
215              
216             sub http_shutdown :Event {
217             # Are there any active clients left
218             if ( $_[SELF]->running ) {
219             # No definite answer yet
220             return;
221             }
222              
223             # We are not online, so far as we can tell
224             return $_[SELF]->call( respond => 0 );
225             }
226              
227             sub respond :Event {
228             $_[SELF]->{result} = undef;
229              
230             # Abort any requests still running
231             foreach my $client ( $_[SELF]->clients ) {
232             $client->stop;
233             }
234              
235             # Send the reponse message
236             if ( $_[ARG0] ) {
237             $_[SELF]->OnlineEvent;
238             } elsif ( defined $_[ARG0] ) {
239             $_[SELF]->OfflineEvent;
240             } else {
241             $_[SELF]->ErrorEvent;
242             }
243              
244             # Clean up
245             $_[SELF]->finish;
246             }
247              
248              
249              
250              
251              
252             ######################################################################
253             # Support Methods
254              
255             sub conformant {
256             my $self = shift;
257              
258             # A successful response should result in a redirect.
259             my $response = shift;
260             unless ( Params::Util::_INSTANCE($response, 'HTTP::Response') ) {
261             return 0;
262             }
263             unless ( $response->is_redirect ) {
264             return 0;
265             }
266              
267             # Determine the location we are relocating to
268             my $request = $response->request;
269             my $location = $response->header('Location') or return 0;
270             my $uri = $HTTP::URI_CLASS->new($location);
271             unless ( Params::Util::_INSTANCE($uri, 'URI') and $uri->can('host') ) {
272             return 0;
273             }
274              
275             # It should redirect to the matching www.domain.com for some given domain.com
276             my $original = quotemeta $request->uri->host;
277             unless ( $uri->host =~ /^(?:.+\.)?$original$/ ) {
278             return 0;
279             }
280              
281             return 1;
282             }
283              
284             compile;
285              
286             =pod
287              
288             =head1 SUPPORT
289              
290             Bugs should be always be reported via the CPAN bug tracker at
291              
292             L
293              
294             For other issues, or commercial enhancement or support, contact the author.
295              
296             =head1 AUTHOR
297              
298             Adam Kennedy Eadamk@cpan.orgE
299              
300             =head1 SEE ALSO
301              
302             L
303              
304             =head1 COPYRIGHT
305              
306             Copyright 2011 Adam Kennedy.
307              
308             This program is free software; you can redistribute
309             it and/or modify it under the same terms as Perl itself.
310              
311             The full text of the license can be found in the
312             LICENSE file included with this module.
313              
314             =cut