File Coverage

blib/lib/Fault/Delegate/SimpleHttp.pm
Criterion Covered Total %
statement 18 136 13.2
branch 0 52 0.0
condition 0 30 0.0
subroutine 6 19 31.5
pod 4 4 100.0
total 28 241 11.6


line stmt bran cond sub pod time code
1             #============================= SimpleHttp.pm =================================
2             # Filename: SimpleHttp.pm
3             # Description: Logger delegate for simple http logging.
4             # Original Author: Dale M. Amon
5             # Revised by: $Author: amon $
6             # Date: $Date: 2008-08-28 23:20:19 $
7             # Version: $Revision: 1.10 $
8             # License: LGPL 2.1, Perl Artistic or BSD
9             #
10             #=============================================================================
11 1     1   765 use strict;
  1         2  
  1         46  
12 1     1   6 use Fault::Delegate;
  1         3  
  1         22  
13 1     1   7 use Fault::DebugPrinter;
  1         2  
  1         23  
14 1     1   6 use Fault::ErrorHandler;
  1         2  
  1         19  
15 1     1   3857 use Net::HTTP;
  1         71633  
  1         11  
16              
17             package Fault::Delegate::SimpleHttp;
18 1     1   1028 use vars qw{@ISA};
  1         2  
  1         2233  
19             @ISA = qw( Fault::Delegate );
20              
21             #=============================================================================
22             # Family internal methods
23             #=============================================================================
24             # These operations are alert priority: without them we cannot set up the
25             # system for use. But... if they are not working we cannot tell anyone so
26             # we have to print local warnings and hope someone is watching.
27              
28             sub _write ($$) {
29 0     0     my ($self,$msg) = @_;
30 0           my ($stamp,$priority,$type,$p,$m) = $msg->get;
31 0           my $line = "$stamp $priority $type $p $m";
32              
33 0           my ($code,$text,%headers) = $self->_sendcommand($self->{'logfn'},$line);
34 0 0         if ($code ne 200) {
35 0           $self->warn ("Failed log write: ($p: $m) to weblog!");
36 0           return 0;
37             }
38 0           return 1;
39             }
40              
41             #-----------------------------------------------------------------------------
42              
43             sub _connect ($) {
44 0     0     my $self = shift;
45              
46 0 0         return 1 if (defined $self->{'web'});
47              
48 0           $self->{'web'} = Net::HTTP->new(Host => $self->{'host'});
49 0 0         if (!defined $self->{'web'}) {
50 0           $self->warn
51             ("Cannot connect to weblogger at http://$self->{'host'}");
52 0           return 0;
53             }
54 0           return 1;
55             }
56              
57             #-----------------------------------------------------------------------------
58              
59             sub _disconnect ($) {
60 0     0     my $self = shift;
61 0           $self->{'web'} = undef;
62 0           return 1;
63             }
64              
65             #=============================================================================
66             # Local internal methods
67             #=============================================================================
68             # write_request returns true if it succeeded.
69              
70             sub _write_get_request ($$) {
71 0     0     my ($self,$uri) = @_;
72              
73 0           my $flg = $self->{'web'}->write_request
74             ('GET' => $uri, 'User-Agent' => "Mozilla/5.0");
75 0 0         if (!$flg) {
76 0           $self->warn ("Failed to write request to weblogger at " .
77             "http://$self->{'host'}/$uri");
78 0           return 0;
79             }
80 0           return 1;
81             }
82              
83             #-----------------------------------------------------------------------------
84             # write_request returns true if it succeeded.
85              
86             sub _write_post_request ($$$) {
87 0     0     my ($self,$uri,$data) = @_;
88 0           my %headers;
89 0           my $flg = $self->{'web'}->write_request('POST',$uri,%headers,"$data");
90 0 0         if (!$flg) {
91 0           $self->warn ("Failed to write request to weblogger at " .
92             "http://$self->{'host'}/$uri");
93 0           return 0;
94             }
95 0           return 1;
96             }
97              
98             #-----------------------------------------------------------------------------
99             # read_response_headers return values are:
100             # code standard http codes. 200 for success
101             # mess standard http codes. 'OK'
102             # %headers hash of response headers
103             # die if server does not speak proper http or if max_line_length or
104             # max_header_length limits are reached.
105              
106             sub _read_response_headers ($) {
107 0     0     my $self = shift;
108              
109 0           my ($code, $msg, %headers) =
110 0           eval {$self->{'web'}->read_response_headers};
111 0           my $err = $@;
112            
113 0 0         if ($err) {
114 0           $self->warn ("Failed to read response headers from weblogger at " .
115             "http://$self->{'host'}/$self->{'uribase'}: $err");
116 0           return undef;
117             }
118 0 0         if ($code != 200) {
119 0           $self->warn ("Error \'$msg\' ($code) response from Weblogger at " .
120             "http://$self->{'host'}/");
121 0           return undef;
122             }
123 0           return ($code,$msg,%headers);
124             }
125            
126             #-----------------------------------------------------------------------------
127             # read_entity_body return values are:
128             # n is undef on read error
129             # 0 on EOF
130             # -1 if no data could be returned this time
131             # >0 number of bytes returned
132             # die if server does not speak proper http.
133              
134             sub _read_entity_body ($$$) {
135 0     0     my ($self,$buf,$bufsiz) = @_;
136              
137 0           my $n = eval {$self->{'web'}->read_entity_body ($buf,$bufsiz)};
  0            
138 0           my $err = $@;
139              
140 0 0         if ($err) {
141 0           $self->warn ("Failed to read entity body from weblogger at " .
142             "http://$self->{'host'}/$self->{'uribase'}: $err");
143 0           return undef;
144             }
145 0 0         if (!defined $n) {
146 0           $self->warn ("Failed to read entity body from Weblogger " .
147             "http://$self->{'host'}/");
148 0           return undef;
149             }
150 0           return ($n,$buf);
151             }
152              
153             #-----------------------------------------------------------------------------
154             # Download the contents of the fault table and return it as a list of lines.
155             # Returns an empty list if it cannot connect to the remote site.
156              
157             sub _download ($) {
158 0     0     my $self = shift;
159              
160 0 0         $self->_write_get_request ($self->{'syncfn'}) or return ();
161 0 0         $self->_read_response_headers or return ();
162            
163 0           my ($buf,$n);
164 0           my $file = "";
165 0           while (1) {
166 0 0         ($n,$buf) = $self->_read_entity_body($buf,1024) or return ();
167 0 0         last unless $n;
168 0           $file .= $buf;
169             }
170 0           return split /\n/, $file;
171             }
172              
173             #-----------------------------------------------------------------------------
174             # Send a command and check the response code.
175              
176             sub _sendcommand ($$$) {
177 0     0     my ($self,$uri,$data) = @_;
178 0           my ($code,$msg,%headers);
179 0 0         $self->_write_post_request($uri,$data) or return undef;
180 0           return ($code,$msg,%headers) = $self->_read_response_headers;
181             }
182              
183             #=============================================================================
184             # CLASS METHODS
185             #=============================================================================
186             # This is the only user exposed method and thus requires arg checking
187              
188             sub new ($$$$$$$) {
189 0     0 1   my ($class,$host,$loguri,$raiseuri,$clearuri,$syncuri) = @_;
190 0           my $self = bless {}, $class;
191            
192 0 0 0       if (!defined $host or (ref $host) or !POSIX::isprint $host) {
      0        
193 0           $self->warn ("Web logging server name invalid or undefined!");
194 0           return undef;
195             }
196              
197 0 0 0       if (!defined $loguri or (ref $loguri) or !POSIX::isprint $loguri) {
      0        
198 0           $self->warn
199             ("Web logging base log function uri invalid or undefined!");
200 0           return undef;
201             }
202              
203 0 0 0       if (!defined $raiseuri or (ref $raiseuri) or !POSIX::isprint $raiseuri) {
      0        
204 0           $self->warn
205             ("Web logging base fault raise function uri undefined!");
206 0           return undef;
207             }
208              
209 0 0 0       if (!defined $clearuri or (ref $clearuri) or !POSIX::isprint $clearuri) {
      0        
210 0           $self->warn
211             ("Web logging base fault clear function uri undefined!");
212 0           return undef;
213             }
214              
215 0 0 0       if (!defined $syncuri or (ref $syncuri) or !POSIX::isprint $syncuri) {
      0        
216 0           $self->warn
217             ("Web logging base fault sync function uri undefined!");
218 0           return undef;
219             }
220              
221 0           @$self{'host','raisefn','clearfn','syncfn','logfn'} =
222             ($host,"$raiseuri","$clearuri","$syncuri","$loguri");
223              
224 0 0         return ($self->test) ? $self : undef;
225             }
226              
227             #=============================================================================
228             # INSTANCE METHODS
229             #=============================================================================
230             # Logger Internal Hook Callback Methods
231             #=============================================================================
232             # Callback from Logger when it raises a fault on the web logger.
233              
234             sub trans01 ($$) {
235 0     0 1   my ($self,$msg) = @_;
236 0           my ($stamp,$priority,$type,$p,$m) = $msg->get;
237 0           my $line = "$stamp $priority $type $p $m";
238 0           my $val = 0;
239              
240 0 0         if ($self->_connect) {
241 0           $val = $self->_sendcommand($self->{'raisefn'},$line);
242 0 0         $val or $self->warn ("Failed fault raise: ($p: $m) to weblog!");
243             }
244 0           $self->_disconnect;
245 0           return 0;
246             }
247              
248             #-----------------------------------------------------------------------------
249             # Callback from Logger when it clears a fault on the web logger.
250              
251             sub trans10 ($$) {
252 0     0 1   my ($self,$msg) = @_;
253 0           my ($stamp,$priority,$type,$p,$m) = $msg->get;
254 0           my $val = 0;
255              
256 0 0         if ($self->_connect) {
257 0           $val = $self->_sendcommand($self->{'clearfn'},"$p $m");
258 0 0         $val or $self->warn ("Failed fault clear: ($p: $m) to weblog!");
259             }
260 0           $self->_disconnect;
261 0           return 0;
262             }
263              
264             #-----------------------------------------------------------------------------
265             # Callback from Logger when it initializes it's in-memory fault table from
266             # data held on the web logger.
267              
268             sub initfaults ($) {
269 0     0 1   my ($self) = @_;
270 0           my @msglist = ();
271 0           my $p = $self->processname;
272            
273 0           Fault::DebugPrinter->dbg (3, "Dump fault table.");
274              
275 0           my @lines = ();
276 0 0         if ($self->_connect) {@lines = $self->_download;}
  0            
277 0           $self->_disconnect;
278            
279 0           my $cnt = $#lines + 1;
280 0           Fault::DebugPrinter->dbg (3, "Found $cnt faults.");
281            
282 0           foreach my $line (@lines) {
283 0           my ($tdstamp,$priority,$type,$process,$m) = split ' ',$line,5;
284 0 0         ($process eq $p) or next;
285 0           push @msglist, ($m);
286             }
287 0           return @msglist;
288             }
289            
290             #=============================================================================
291             # POD DOCUMENTATION
292             #=============================================================================
293             # You may extract and format the documention section with the 'perldoc' cmd.
294              
295             =head1 NAME
296              
297             Fault::Delegate::SimpleHttp - Logger delegate for simple http logging.
298              
299             =head1 SYNOPSIS
300              
301             use Fault::Delegate::SimpleHttp;
302             $self = Fault::Delegate::SimpleHttp->new ($host,$loguri,
303             $raiseuri,$clearuri,$syncuri);
304             $okay = $self->log ($msg);
305             $zero = $self->trans01 ($msg);
306             $zero = $self->trans10 ($msg);
307             @list = $self->initfaults;
308              
309             =head1 Inheritance
310              
311             UNIVERSAL
312             Fault::Delegate
313             Fault::Delegate::SimpleHttp
314              
315             =head1 Description
316              
317             The Class manages an http remote logging connection. To utilize this class
318             you must impliment a set of cgi scripts according the specs given in the
319             next section.
320              
321             SimpleHttp can act as a template if I at some point want to impliment a
322             Delegate that uses SSL, or that uses xml messages for two way
323             communications.
324              
325             Note: Each method undef's the Net::Http object it creates before exiting.
326             This may or may not assist with re-entrancy in a multi-process environment,
327             but it was a lesson learned with mysql db handles. The problem was that if a
328             handle was created before processes spawned, it could get carried through into
329             the child processes and if they used the handles the remote daemon could
330             become very confused. Whether this would be true of a web daemon connection
331             or not is conjecture, but the undef is a way of playing it safe.
332              
333             =head1 API For Web Logger
334              
335             A web site must supply a fairly modest set of scripts to support a minimal
336             interface with which this module may communicate. Each of these scripts
337             should return a code of 200 and a message of OK in their response header.
338             They all use a common set of fields and field definitions:
339              
340             =over 4
341             The time stamp will be in the form: yyyymmddhhmmss.
342              
343             Priority is the set of priorities defined by Unix syslog.
344              
345             Type is may be any single word uppercase string that your remote application
346             wishes to use to classify messages. At the very least it should support the
347             minimal set described in Fault::Logger.
348              
349             Process is a single word name of the process that generated the log or fault
350             message.
351              
352             Message is a string of arbitrary length, limited only by what the web server
353             is willing to accept or transmit. It may contain contain any printable
354             character other than newline. Other formatting characters, such as formfeed,
355             are also best avoided.
356              
357             =back 4
358              
359             There are four cgi scripts you will need at your web logger url.
360              
361             =over 4
362              
363             =item log cgi script
364              
365             This script must accept a POST with a single text line in the request body.
366              
367             The line will consist of five space delimited fields:
368              
369             TimeStamp Priority Type Process Message
370              
371             Any additional spaces are part of the message field.
372              
373             The script may then do anything it wants with the message, including
374             ignoring it.
375              
376             =item faultraise cgi script
377              
378             Almost exactly like the log cgi script except that it must at the very least
379             remember the process and message portions such that a search may be carried
380             out on either field.
381              
382             If it recieves a message that is exactly the same as of one that is already
383             stored for the process, do nothing.
384              
385             =item faultclear cgi script
386              
387             Similar to the log cgi script, but the body contains only the process and
388             message, delimited by the first space.
389              
390             Process Message
391              
392             If the message is an exact match for a message of an active fault for the
393             same process, it should be deleted. If the message is not from an active
394             fault for the process, it should be ignored and discarded.
395              
396             =item faultsync cgi script
397              
398             When called with a GET, it should dump a list of all fault messages,
399             one to a line in a response body. Each line should be in the format described
400             for log cgi script.
401              
402             TimeStamp Priority Type Process Message
403              
404             If there are no fault messages, it should return an empty list.
405              
406             =back 4
407              
408             =head1 Examples
409              
410             use Fault::Delegate::SimpleHttp;
411             use Fault::Msg;
412             use Fault::Logger;
413              
414             my $msg = Fault::Msg ("Arf!");
415             my $baz = Fault::Delegate::SimpleHttp->new ($host,$loguri,
416             $raiseuri,$clearuri,
417             $syncuri);
418             my $waslogged = $baz->log ($msg);
419              
420             Fault::Logger->new ($baz);
421             my $waslogged = Fault::Logger->log ("Bow! Wow!");
422              
423             [See Fault::Logger for a detailed example.]
424              
425             =head1 Class Variables
426              
427             None.
428              
429             =head1 Instance Variables
430              
431             host Name or ip of the web logger server.
432             logfn URI on the host web server to the log cgi script.
433             raisefn URI on the host web server to the fault raise cgi script.
434             clearfn URI on the host web server to the fault clear cgi script.
435             syncfn URI on the host web server to the fault sync cgi script.
436              
437             =head1 Class Methods
438              
439             =over 4
440              
441             =item B<$self = Fault::Delegate::LogFile-Enew ($host,$loguri,$raiseuri,$clearuri,$syncuri)>
442              
443             Create an object to allow communications with a remote http based logging
444             application. Returns undef on failure.
445              
446             =head1 Instance Methods
447              
448             =over 4
449              
450             =item B<$okay = $self-Elog ($msg)>
451              
452             Send a log message to the web logger of the form:
453              
454             Time Priority Type Process Message
455              
456             and return true if we succeeded in doing so.
457              
458             =item B<$zero = $self-Etrans01 ($msg)>
459              
460             Tell the web logger to raise a fault for the current process by sending
461             a line of the form:
462              
463             Time Priority Type Process Message
464              
465             It always returns 0.
466              
467             =item B<$zero = $self-Etrans10 ($msg)>
468              
469             The the web logger to clear a fault for the current process by sending
470             a line of the form:
471              
472             Process Message
473              
474             It always returns 0.
475              
476             =item B<@list = $self-Einitfaults>
477              
478             Requests a current list of faults from the weblogger when Logger initializes.
479             @list contains a simple list of strings, where each string represents a
480             unique active fault condition belonging to the current process.
481              
482             ("fault message 1", "fault message 2", ...)
483              
484             If it cannot connect to the remote weblogger, an empty list is returned.
485              
486             =back 4
487              
488             =head1 Private Class Method
489              
490             None.
491              
492             =head1 Private Instance Methods
493              
494             =over 4
495              
496             =item B<$bool = $self-E_write ($msg)>
497              
498             =item B<$bool = $self-E_connect>
499              
500             =item B<$bool = $self-E_disconnect>
501              
502             Impliments the above overrides to the internal family protocol utilized by
503             the Fault:Delegate log and test methods.
504              
505             =back 4
506              
507             =head1 Errors and Warnings
508              
509             Local warning messages are issued if the web logger cannot be reached or has
510             any problems whatever. You cannot log to a web logger that is not working!
511              
512             =head1 KNOWN BUGS
513              
514             See TODO.
515              
516             =head1 SEE ALSO
517              
518             Fault::Logger, Fault::Delegate, Fault::Msg, Net::HTTP,
519             Fault::ErrorHandler, Fault::DebugPrinter
520              
521             =head1 AUTHOR
522              
523             Dale Amon
524              
525             =cut
526            
527             #=============================================================================
528             # CVS HISTORY
529             #=============================================================================
530             # $Log: SimpleHttp.pm,v $
531             # Revision 1.10 2008-08-28 23:20:19 amon
532             # perldoc section regularization.
533             #
534             # Revision 1.9 2008-08-17 21:56:37 amon
535             # Make all titles fit CPAN standard.
536             #
537             # Revision 1.8 2008-07-24 21:17:24 amon
538             # Moved all todo notes to elsewhere; made Stderr the default delegate instead of Stdout.
539             #
540             # Revision 1.7 2008-05-09 18:24:55 amon
541             # Bugs and changes due to pre-release testing
542             #
543             # Revision 1.6 2008-05-08 20:22:50 amon
544             # Minor bug fixes; shifted fault table and initfault from Logger to List
545             #
546             # Revision 1.5 2008-05-07 18:14:55 amon
547             # Simplification and standardization. Much more is inherited from Fault::Delegate.
548             #
549             # Revision 1.4 2008-05-05 19:25:49 amon
550             # Catch any small changes before implimenting major changes
551             #
552             # Revision 1.3 2008-05-04 14:45:23 amon
553             # Updates to perl doc; minor code changes.
554             #
555             # Revision 1.2 2008-05-03 00:56:57 amon
556             # Changed standard argument ordering.
557             #
558             # Revision 1.1.1.1 2008-05-02 16:37:14 amon
559             # Fault and Log System. Pared off of DMA base lib.
560             #
561             # Revision 1.1 2008-04-25 10:55:20 amon
562             # Add SimpleHttp module
563             #
564             # 20080415 Dale Amon
565             # Created.
566             #
567             # DONE
568             # * Put the POST's in a fault check subroutine. [DMA20080416-20080422]
569             # * Change the new method to take a URI for each of the four
570             # methods. Perhaps I can combine the API's and allow it
571             # to EITHER default the fn names OR require 4 full paths
572             # but NOT the base uri. [DMA20080416-20080422]
573             # * Must catch error responses. [DMA20080417-20080422]
574             # * Must catch a 'die' from read_response_headers since that can
575             # happen in some cases. [DMA20080416-20080422?]
576             # * My code might have been misled by the example. I have:
577             # $web->write_request('POST',$uri,,$data)
578             # But documentation says the args are:
579             # $web->write_request(method,$uri,%headers,[$data])
580             # (Adding %headers does not change anything; bracketing data
581             # causes Kenny to return an error. [DMA 20080422-20080422]
582             1;
583