File Coverage

blib/lib/WWW/Curl/TraceAscii.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 WWW::Curl::TraceAscii;
2 4     4   96883 use strict;
  4         9  
  4         147  
3 4     4   22 use warnings;
  4         8  
  4         281  
4              
5             require 5.8.8;
6 4     4   55 use Carp;
  4         7  
  4         376  
7 4     4   3177 use bytes;
  4         31  
  4         23  
8 4     4   6438 use WWW::Curl::Easy;
  0            
  0            
9             use Time::HiRes qw(gettimeofday);
10              
11             use vars qw($VERSION);
12             $VERSION = '0.05';
13              
14             =head1 NAME
15              
16             WWW::Curl::TraceAscii - Perl extension interface for libcurl
17              
18             =head1 SYNOPSIS
19              
20             # Just like WWW::Curl::Easy, no fancy overrides
21             use WWW::Curl::TraceAscii;
22              
23             # Overrides WWW::Curl::Easy->new
24             use WWW::Curl::TraceAscii qw(:new);
25              
26             # GET Example
27             use WWW::Curl::TraceAscii;
28             my $curl = WWW::Curl::TraceAscii->new;
29             $curl->setopt(CURLOPT_URL, 'http://example.com');
30             $curl->perform;
31             my $response_PTR = $curl->trace_response;
32              
33             # POST Example
34             use WWW::Curl::TraceAscii;
35             my $response;
36             my $post = "some post data";
37             my $curl = WWW::Curl::TraceAscii->new;
38             $curl->setopt(CURLOPT_POST, 1);
39             $curl->setopt(CURLOPT_POSTFIELDS, $post);
40             $curl->setopt(CURLOPT_URL,'http://example.com/');
41             $curl->setopt(CURLOPT_WRITEDATA,\$response);
42             $curl->perform;
43              
44             # These methods only exist in TraceAscii
45             my $response_PTR = $curl->trace_response;
46             my $headers_PTR = $curl->trace_headers;
47             my $trace_ascii_PTR = $curl->trace_ascii;
48              
49             =head1 DESCRIPTION
50              
51             WWW::Curl::TraceAscii adds additional debugging helpers to WWW::Curl::Easy
52              
53             =head1 DOCUMENTATION
54              
55             This module uses WWW::Curl::Easy at it's base. WWW::Curl::TraceAscii gives you the ability to record a log of your curl connection much like the --trace-ascii feature inside the curl binary.
56              
57             =head2 WHY DO I NEED A TRACE?
58              
59             I've been curling pages for decades. Usually in an automatic fashion. And while you can write code that will handle almost all failures. You can't answer the question that will inevitably be asked for a result you didn't expect... What happened??
60              
61             I've seen hundreds of different types of errors come through that without a good trace would have been impossible to get a difinitive answer as to what happened.
62              
63             I've personally gotten into the practice of storing the trace data for all connections. This allows me to review exactly what happened, even if the problem was only temporary. Especially if the problem was fixed before I was able to review it.
64              
65             =head1 ADDITIONAL METHODS
66              
67             New methods added above what is normally in WWW::Curl::Easy.
68              
69             =cut
70              
71             sub import {
72             no strict "refs"; ## no critic
73              
74             *WWW::Curl::Easy::newTraceAscii = \&WWW::Curl::Easy::new;
75             for my $i (reverse 1 .. $#_) {
76             if ($_[$i] eq ':new') {
77             no warnings "redefine"; # We make this a few times
78             *WWW::Curl::Easy::new = sub(;@) { WWW::Curl::TraceAscii->new(@_); };
79             }
80             }
81              
82             my $me = __PACKAGE__.'::';
83             my $easy = 'WWW::Curl::Easy::';
84              
85             # Export all the CURL constants from Easy
86             ${[caller]->[0].'::'}{$_} = ${__PACKAGE__."::"}{$_}
87             foreach @WWW::Curl::Easy::EXPORT;
88              
89             # Make method calls for all Easy methods, redirect to the proper place
90             my @curl_methods;
91             push @curl_methods, $_
92             foreach grep { not /^(|_.*|AUTOLOAD|EXPORT|DESTROY|VERSION|ISA|isa|BEGIN|import|Dumper|newTraceAscii)$/ }
93             keys %{"WWW::Curl::Easy::"};
94             foreach my $method ( @curl_methods ) {
95             my $fullme = $me.$method;
96             my $fulleasy = $easy.$method;
97             if (! defined *$fullme && defined *$fulleasy) {
98             *$fullme = sub { my $self = shift; $self->{'curl'}->$method(@_); }
99             }
100             }
101             }
102              
103             =head2 new
104              
105             Create a new curl object.
106              
107             =cut
108              
109             sub new {
110             my $class = shift;
111             my $curl = WWW::Curl::Easy->newTraceAscii(@_);
112             my $response;
113             $curl->setopt(CURLOPT_WRITEDATA,\$response);
114              
115             my $hash = {
116             curl => $curl,
117             response => \$response,
118             headers => &trace_headers_init($curl),
119             trace_ascii => &trace_ascii_init($curl),
120             };
121              
122             return bless $hash, $class;
123             }
124              
125             =head2 setopt
126              
127             Same as setopt in WWW::Curl::Easy
128              
129             =cut
130              
131             sub setopt {
132             my $self = shift;
133             if ($_[0] eq CURLOPT_WRITEDATA && ref $_[1] eq 'SCALAR') {
134             $self->{'response'} = $_[1];
135             }
136             $self->{'curl'}->setopt(@_);
137             }
138              
139             =head2 trace_response
140              
141             This can get rather lengthy. So to save memory it returns a pointer to the response data.
142              
143             NOTE: You can still set CURLOPT_WRITEDATA yourself if you pefer.
144              
145             =cut
146              
147             sub trace_response {
148             my $self = shift;
149             $self->{'response'};
150             }
151              
152             =head2 trace_ascii
153              
154             Mimic the curl binary when you enable the --trace-ascii and --trace-time command line options. Minus the SSL negotiation data.
155              
156             This can get rather lengthy. So to save memory it returns a pointer to the trace data.
157              
158             =cut
159              
160             sub trace_ascii {
161             my $self = shift;
162             $self->{'trace_ascii'};
163             }
164              
165             =head2 trace_ascii_init
166              
167             The actual method used to produce the trace_ascii output.
168              
169             In WWW::Curl::Easy you would initialize this like so:
170             my $trace_ascii = &trace_ascii_init($curl);
171              
172             =cut
173              
174             sub trace_ascii_init {
175             my ($curl) = @_;
176             my $trace = '';
177             $curl->setopt(CURLOPT_DEBUGFUNCTION,\&_make_trace_ascii);
178             $curl->setopt(CURLOPT_DEBUGDATA,\$trace);
179             $curl->setopt(CURLOPT_HEADERDATA,\$trace);
180             $curl->setopt(CURLOPT_VERBOSE, 1);
181             return \$trace;
182             }
183              
184             =head2 trace_headers
185              
186             Returns an array of headers from your curl call.
187              
188             =cut
189              
190             sub trace_headers {
191             my $self = shift;
192             $self->{'headers'};
193             }
194              
195             =head2 trace_headers_init
196              
197             The actual method used to produce the trace_headers output.
198              
199             In WWW::Curl::Easy you would initialize this like so:
200             $headers = &trace_headers_init($curl);
201              
202             =cut
203              
204             sub trace_headers_init {
205             my ($curl) = @_;
206             my @headers;
207             my $header_func = sub {
208             my ($header) = @_;
209             $header =~ s/[\r\n]?[\r\n]$//g;
210             push @headers, $header if $header ne '';
211             return length($_[0]);
212             };
213             $curl->setopt(CURLOPT_HEADERFUNCTION,$header_func);
214             return \@headers;
215             }
216              
217             sub _make_trace_ascii {
218             my ($data,$tracePTR,$data_type) =@_;
219             my ($seconds, $microseconds) = gettimeofday;
220             my ($sec,$min,$hour,$mday,$mon,$year,$wday)=localtime($seconds);
221              
222             $$tracePTR .= sprintf('%02d:%02d:%02d.%d ',$hour,$min,$sec,$microseconds);
223             my $l = length($data);
224              
225             if ($data_type == 0) {
226             $$tracePTR .= "== Info: ".$data;
227             } elsif ($data_type == 1) {
228             $data =~ s/\r?\n$//;
229             $$tracePTR .= sprintf("<= Recv header, %d bytes (0x%x)\n",$l,$l)._format_debug_data($data);
230             } elsif ($data_type == 2) {
231             $data =~ s/\r?\n$//;
232             $$tracePTR .= sprintf("=> Send header, %d bytes (0x%x)\n",$l,$l)._format_debug_data($data);
233             } elsif ($data_type == 3) {
234             $$tracePTR .= sprintf("<= Recv data, %d bytes (0x%x)\n",$l,$l)._format_debug_data($data,1);
235             } elsif ($data_type == 4) {
236             $$tracePTR .= sprintf("=> Send data, %d bytes (0x%x)\n",$l,$l)._format_debug_data($data,1);
237             } else {
238             # not sure what any of these values would be, but just in case
239             $$tracePTR .= "== Unknown $data_type: ".$data;
240             }
241             return 0;
242             }
243              
244             sub _format_debug_data {
245             my ($data,$mask_returns) = @_;
246             my $c = 0;
247             my $a = $mask_returns ? [$data] : [split /\r\n/, $data, -1];
248             $a->[0] = '' unless scalar(@$a);
249             my $text = '';
250             foreach my $bit ( @$a ) {
251             my @array = unpack '(a64)*', $bit;
252             $array[0] = '' unless scalar(@array);
253             foreach my $line ( @array ) {
254             $line =~ s/[^\ -\~]/./ig;
255             my $len = bytes::length($line);
256             $line = sprintf('%04x: ',$c).$line;
257             $c+=2 unless $mask_returns; # add they \r\n back in
258             $c+=$len;
259             }
260             $text .= (join "\n",@array)."\n";
261             }
262             $text;
263             }
264              
265             1;