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; |