Commit | Line | Data |
---|---|---|
a3ab329f | 1 | # vim: ts=4 sts=4 sw=4 et: |
a3ab329f | 2 | package HTTP::Tiny; |
a3ab329f DG |
3 | use strict; |
4 | use warnings; | |
35265876 | 5 | # ABSTRACT: A small, simple, correct HTTP/1.1 client |
4984624c | 6 | our $VERSION = '0.017'; # VERSION |
a3ab329f DG |
7 | |
8 | use Carp (); | |
9 | ||
10 | ||
11 | my @attributes; | |
12 | BEGIN { | |
13 | @attributes = qw(agent default_headers max_redirect max_size proxy timeout); | |
14 | no strict 'refs'; | |
15 | for my $accessor ( @attributes ) { | |
16 | *{$accessor} = sub { | |
17 | @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor}; | |
18 | }; | |
19 | } | |
20 | } | |
21 | ||
22 | sub new { | |
23 | my($class, %args) = @_; | |
24 | (my $agent = $class) =~ s{::}{-}g; | |
25 | my $self = { | |
26 | agent => $agent . "/" . ($class->VERSION || 0), | |
27 | max_redirect => 5, | |
28 | timeout => 60, | |
29 | }; | |
30 | for my $key ( @attributes ) { | |
31 | $self->{$key} = $args{$key} if exists $args{$key} | |
32 | } | |
77ccfaeb DG |
33 | |
34 | # Never override proxy argument as this breaks backwards compat. | |
35 | if (!exists $self->{proxy} && (my $http_proxy = $ENV{http_proxy})) { | |
36 | if ($http_proxy =~ m{\Ahttp://[^/?#:@]+:\d+/?\z}) { | |
37 | $self->{proxy} = $http_proxy; | |
38 | } | |
39 | else { | |
40 | Carp::croak(qq{Environment 'http_proxy' must be in format http://<host>:<port>/\n}); | |
41 | } | |
42 | } | |
43 | ||
a3ab329f DG |
44 | return bless $self, $class; |
45 | } | |
46 | ||
47 | ||
35265876 CBW |
48 | for my $sub_name ( qw/get head put post delete/ ) { |
49 | my $req_method = uc $sub_name; | |
50 | no strict 'refs'; | |
4984624c | 51 | eval <<"HERE"; ## no critic |
35265876 CBW |
52 | sub $sub_name { |
53 | my (\$self, \$url, \$args) = \@_; | |
54 | \@_ == 2 || (\@_ == 3 && ref \$args eq 'HASH') | |
55 | or Carp::croak(q/Usage: \$http->$sub_name(URL, [HASHREF])/ . "\n"); | |
56 | return \$self->request('$req_method', \$url, \$args || {}); | |
57 | } | |
58 | HERE | |
59 | } | |
60 | ||
61 | ||
62 | sub post_form { | |
63 | my ($self, $url, $data, $args) = @_; | |
64 | (@_ == 3 || @_ == 4 && ref $args eq 'HASH') | |
65 | or Carp::croak(q/Usage: $http->post_form(URL, DATAREF, [HASHREF])/ . "\n"); | |
66 | ||
67 | my $headers = {}; | |
68 | while ( my ($key, $value) = each %{$args->{headers} || {}} ) { | |
69 | $headers->{lc $key} = $value; | |
70 | } | |
71 | delete $args->{headers}; | |
72 | ||
73 | return $self->request('POST', $url, { | |
74 | %$args, | |
75 | content => $self->www_form_urlencode($data), | |
76 | headers => { | |
77 | %$headers, | |
78 | 'content-type' => 'application/x-www-form-urlencoded' | |
79 | }, | |
80 | } | |
81 | ); | |
a3ab329f DG |
82 | } |
83 | ||
84 | ||
85 | sub mirror { | |
86 | my ($self, $url, $file, $args) = @_; | |
87 | @_ == 3 || (@_ == 4 && ref $args eq 'HASH') | |
77ccfaeb | 88 | or Carp::croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n"); |
a3ab329f DG |
89 | if ( -e $file and my $mtime = (stat($file))[9] ) { |
90 | $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime); | |
91 | } | |
92 | my $tempfile = $file . int(rand(2**31)); | |
93 | open my $fh, ">", $tempfile | |
77ccfaeb | 94 | or Carp::croak(qq/Error: Could not open temporary file $tempfile for downloading: $!\n/); |
b06ddfb0 | 95 | binmode $fh; |
a3ab329f DG |
96 | $args->{data_callback} = sub { print {$fh} $_[0] }; |
97 | my $response = $self->request('GET', $url, $args); | |
98 | close $fh | |
77ccfaeb | 99 | or Carp::croak(qq/Error: Could not close temporary file $tempfile: $!\n/); |
a3ab329f DG |
100 | if ( $response->{success} ) { |
101 | rename $tempfile, $file | |
77ccfaeb | 102 | or Carp::croak(qq/Error replacing $file with $tempfile: $!\n/); |
a3ab329f DG |
103 | my $lm = $response->{headers}{'last-modified'}; |
104 | if ( $lm and my $mtime = $self->_parse_http_date($lm) ) { | |
105 | utime $mtime, $mtime, $file; | |
106 | } | |
107 | } | |
108 | $response->{success} ||= $response->{status} eq '304'; | |
109 | unlink $tempfile; | |
110 | return $response; | |
111 | } | |
112 | ||
113 | ||
114 | my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/; | |
115 | ||
116 | sub request { | |
117 | my ($self, $method, $url, $args) = @_; | |
118 | @_ == 3 || (@_ == 4 && ref $args eq 'HASH') | |
77ccfaeb | 119 | or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/ . "\n"); |
a3ab329f DG |
120 | $args ||= {}; # we keep some state in this during _request |
121 | ||
122 | # RFC 2616 Section 8.1.4 mandates a single retry on broken socket | |
123 | my $response; | |
124 | for ( 0 .. 1 ) { | |
125 | $response = eval { $self->_request($method, $url, $args) }; | |
126 | last unless $@ && $idempotent{$method} | |
127 | && $@ =~ m{^(?:Socket closed|Unexpected end)}; | |
128 | } | |
129 | ||
130 | if (my $e = "$@") { | |
131 | $response = { | |
132 | success => q{}, | |
133 | status => 599, | |
134 | reason => 'Internal Exception', | |
135 | content => $e, | |
136 | headers => { | |
137 | 'content-type' => 'text/plain', | |
138 | 'content-length' => length $e, | |
139 | } | |
140 | }; | |
141 | } | |
142 | return $response; | |
143 | } | |
144 | ||
35265876 CBW |
145 | |
146 | sub www_form_urlencode { | |
147 | my ($self, $data) = @_; | |
148 | (@_ == 2 && ref $data) | |
149 | or Carp::croak(q/Usage: $http->www_form_urlencode(DATAREF)/ . "\n"); | |
150 | (ref $data eq 'HASH' || ref $data eq 'ARRAY') | |
151 | or Carp::croak("form data must be a hash or array reference"); | |
152 | ||
153 | my @params = ref $data eq 'HASH' ? %$data : @$data; | |
154 | @params % 2 == 0 | |
155 | or Carp::croak("form data reference must have an even number of terms\n"); | |
156 | ||
157 | my @terms; | |
158 | while( @params ) { | |
159 | my ($key, $value) = splice(@params, 0, 2); | |
160 | if ( ref $value eq 'ARRAY' ) { | |
161 | unshift @params, map { $key => $_ } @$value; | |
162 | } | |
163 | else { | |
164 | push @terms, join("=", map { $self->_uri_escape($_) } $key, $value); | |
165 | } | |
166 | } | |
167 | ||
168 | return join("&", sort @terms); | |
169 | } | |
170 | ||
171 | #--------------------------------------------------------------------------# | |
172 | # private methods | |
173 | #--------------------------------------------------------------------------# | |
174 | ||
a3ab329f DG |
175 | my %DefaultPort = ( |
176 | http => 80, | |
177 | https => 443, | |
178 | ); | |
179 | ||
180 | sub _request { | |
181 | my ($self, $method, $url, $args) = @_; | |
182 | ||
183 | my ($scheme, $host, $port, $path_query) = $self->_split_url($url); | |
184 | ||
185 | my $request = { | |
186 | method => $method, | |
187 | scheme => $scheme, | |
188 | host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), | |
189 | uri => $path_query, | |
190 | headers => {}, | |
191 | }; | |
192 | ||
193 | my $handle = HTTP::Tiny::Handle->new(timeout => $self->{timeout}); | |
194 | ||
195 | if ($self->{proxy}) { | |
196 | $request->{uri} = "$scheme://$request->{host_port}$path_query"; | |
77ccfaeb | 197 | die(qq/HTTPS via proxy is not supported\n/) |
a3ab329f DG |
198 | if $request->{scheme} eq 'https'; |
199 | $handle->connect(($self->_split_url($self->{proxy}))[0..2]); | |
200 | } | |
201 | else { | |
202 | $handle->connect($scheme, $host, $port); | |
203 | } | |
204 | ||
205 | $self->_prepare_headers_and_cb($request, $args); | |
206 | $handle->write_request($request); | |
207 | ||
208 | my $response; | |
209 | do { $response = $handle->read_response_header } | |
210 | until (substr($response->{status},0,1) ne '1'); | |
211 | ||
212 | if ( my @redir_args = $self->_maybe_redirect($request, $response, $args) ) { | |
213 | $handle->close; | |
214 | return $self->_request(@redir_args, $args); | |
215 | } | |
216 | ||
217 | if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) { | |
218 | # response has no message body | |
219 | } | |
220 | else { | |
221 | my $data_cb = $self->_prepare_data_cb($response, $args); | |
222 | $handle->read_body($data_cb, $response); | |
223 | } | |
224 | ||
225 | $handle->close; | |
226 | $response->{success} = substr($response->{status},0,1) eq '2'; | |
227 | return $response; | |
228 | } | |
229 | ||
230 | sub _prepare_headers_and_cb { | |
231 | my ($self, $request, $args) = @_; | |
232 | ||
233 | for ($self->{default_headers}, $args->{headers}) { | |
234 | next unless defined; | |
235 | while (my ($k, $v) = each %$_) { | |
236 | $request->{headers}{lc $k} = $v; | |
237 | } | |
238 | } | |
239 | $request->{headers}{'host'} = $request->{host_port}; | |
240 | $request->{headers}{'connection'} = "close"; | |
241 | $request->{headers}{'user-agent'} ||= $self->{agent}; | |
242 | ||
243 | if (defined $args->{content}) { | |
244 | $request->{headers}{'content-type'} ||= "application/octet-stream"; | |
245 | if (ref $args->{content} eq 'CODE') { | |
246 | $request->{headers}{'transfer-encoding'} = 'chunked' | |
247 | unless $request->{headers}{'content-length'} | |
248 | || $request->{headers}{'transfer-encoding'}; | |
249 | $request->{cb} = $args->{content}; | |
250 | } | |
251 | else { | |
252 | my $content = $args->{content}; | |
253 | if ( $] ge '5.008' ) { | |
254 | utf8::downgrade($content, 1) | |
77ccfaeb | 255 | or die(qq/Wide character in request message body\n/); |
a3ab329f DG |
256 | } |
257 | $request->{headers}{'content-length'} = length $content | |
258 | unless $request->{headers}{'content-length'} | |
259 | || $request->{headers}{'transfer-encoding'}; | |
260 | $request->{cb} = sub { substr $content, 0, length $content, '' }; | |
261 | } | |
262 | $request->{trailer_cb} = $args->{trailer_callback} | |
263 | if ref $args->{trailer_callback} eq 'CODE'; | |
264 | } | |
265 | return; | |
266 | } | |
267 | ||
268 | sub _prepare_data_cb { | |
269 | my ($self, $response, $args) = @_; | |
270 | my $data_cb = $args->{data_callback}; | |
271 | $response->{content} = ''; | |
272 | ||
273 | if (!$data_cb || $response->{status} !~ /^2/) { | |
274 | if (defined $self->{max_size}) { | |
275 | $data_cb = sub { | |
276 | $_[1]->{content} .= $_[0]; | |
277 | die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/) | |
278 | if length $_[1]->{content} > $self->{max_size}; | |
279 | }; | |
280 | } | |
281 | else { | |
282 | $data_cb = sub { $_[1]->{content} .= $_[0] }; | |
283 | } | |
284 | } | |
285 | return $data_cb; | |
286 | } | |
287 | ||
288 | sub _maybe_redirect { | |
289 | my ($self, $request, $response, $args) = @_; | |
290 | my $headers = $response->{headers}; | |
291 | my ($status, $method) = ($response->{status}, $request->{method}); | |
292 | if (($status eq '303' or ($status =~ /^30[127]/ && $method =~ /^GET|HEAD$/)) | |
293 | and $headers->{location} | |
294 | and ++$args->{redirects} <= $self->{max_redirect} | |
295 | ) { | |
296 | my $location = ($headers->{location} =~ /^\//) | |
297 | ? "$request->{scheme}://$request->{host_port}$headers->{location}" | |
298 | : $headers->{location} ; | |
299 | return (($status eq '303' ? 'GET' : $method), $location); | |
300 | } | |
301 | return; | |
302 | } | |
303 | ||
304 | sub _split_url { | |
305 | my $url = pop; | |
306 | ||
307 | # URI regex adapted from the URI module | |
308 | my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> | |
77ccfaeb | 309 | or die(qq/Cannot parse URL: '$url'\n/); |
a3ab329f DG |
310 | |
311 | $scheme = lc $scheme; | |
312 | $path_query = "/$path_query" unless $path_query =~ m<\A/>; | |
313 | ||
314 | my $host = (length($authority)) ? lc $authority : 'localhost'; | |
315 | $host =~ s/\A[^@]*@//; # userinfo | |
316 | my $port = do { | |
317 | $host =~ s/:([0-9]*)\z// && length $1 | |
318 | ? $1 | |
319 | : ($scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef); | |
320 | }; | |
321 | ||
322 | return ($scheme, $host, $port, $path_query); | |
323 | } | |
324 | ||
325 | # Date conversions adapted from HTTP::Date | |
326 | my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat"; | |
327 | my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; | |
328 | sub _http_date { | |
329 | my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]); | |
330 | return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", | |
331 | substr($DoW,$wday*4,3), | |
332 | $mday, substr($MoY,$mon*4,3), $year+1900, | |
333 | $hour, $min, $sec | |
334 | ); | |
335 | } | |
336 | ||
337 | sub _parse_http_date { | |
338 | my ($self, $str) = @_; | |
339 | require Time::Local; | |
340 | my @tl_parts; | |
341 | if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { | |
342 | @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); | |
343 | } | |
344 | elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { | |
345 | @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); | |
346 | } | |
347 | elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { | |
348 | @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); | |
349 | } | |
350 | return eval { | |
351 | my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; | |
352 | $t < 0 ? undef : $t; | |
353 | }; | |
354 | } | |
355 | ||
35265876 CBW |
356 | # URI escaping adapted from URI::Escape |
357 | # c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 | |
6ce52845 | 358 | # perl 5.6 ready UTF-8 encoding adapted from JSON::PP |
35265876 CBW |
359 | my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255; |
360 | $escapes{' '}="+"; | |
361 | my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/; | |
362 | ||
363 | sub _uri_escape { | |
364 | my ($self, $str) = @_; | |
6ce52845 CBW |
365 | if ( $] ge '5.008' ) { |
366 | utf8::encode($str); | |
367 | } | |
368 | else { | |
369 | $str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string | |
370 | if ( length $str == do { use bytes; length $str } ); | |
371 | $str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag | |
372 | } | |
35265876 CBW |
373 | $str =~ s/($unsafe_char)/$escapes{$1}/ge; |
374 | return $str; | |
375 | } | |
376 | ||
a3ab329f DG |
377 | package |
378 | HTTP::Tiny::Handle; # hide from PAUSE/indexers | |
379 | use strict; | |
380 | use warnings; | |
381 | ||
a3ab329f DG |
382 | use Errno qw[EINTR EPIPE]; |
383 | use IO::Socket qw[SOCK_STREAM]; | |
384 | ||
4984624c | 385 | sub BUFSIZE () { 32768 } ## no critic |
a3ab329f DG |
386 | |
387 | my $Printable = sub { | |
388 | local $_ = shift; | |
389 | s/\r/\\r/g; | |
390 | s/\n/\\n/g; | |
391 | s/\t/\\t/g; | |
392 | s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; | |
393 | $_; | |
394 | }; | |
395 | ||
396 | my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/; | |
397 | ||
398 | sub new { | |
399 | my ($class, %args) = @_; | |
400 | return bless { | |
401 | rbuf => '', | |
402 | timeout => 60, | |
403 | max_line_size => 16384, | |
404 | max_header_lines => 64, | |
405 | %args | |
406 | }, $class; | |
407 | } | |
408 | ||
435aa301 DG |
409 | my $ssl_verify_args = { |
410 | check_cn => "when_only", | |
411 | wildcards_in_alt => "anywhere", | |
412 | wildcards_in_cn => "anywhere" | |
413 | }; | |
414 | ||
a3ab329f | 415 | sub connect { |
77ccfaeb | 416 | @_ == 4 || die(q/Usage: $handle->connect(scheme, host, port)/ . "\n"); |
a3ab329f DG |
417 | my ($self, $scheme, $host, $port) = @_; |
418 | ||
419 | if ( $scheme eq 'https' ) { | |
420 | eval "require IO::Socket::SSL" | |
421 | unless exists $INC{'IO/Socket/SSL.pm'}; | |
77ccfaeb | 422 | die(qq/IO::Socket::SSL must be installed for https support\n/) |
a3ab329f DG |
423 | unless $INC{'IO/Socket/SSL.pm'}; |
424 | } | |
425 | elsif ( $scheme ne 'http' ) { | |
77ccfaeb | 426 | die(qq/Unsupported URL scheme '$scheme'\n/); |
a3ab329f DG |
427 | } |
428 | ||
429 | $self->{fh} = 'IO::Socket::INET'->new( | |
430 | PeerHost => $host, | |
431 | PeerPort => $port, | |
432 | Proto => 'tcp', | |
433 | Type => SOCK_STREAM, | |
434 | Timeout => $self->{timeout} | |
77ccfaeb | 435 | ) or die(qq/Could not connect to '$host:$port': $@\n/); |
a3ab329f DG |
436 | |
437 | binmode($self->{fh}) | |
77ccfaeb | 438 | or die(qq/Could not binmode() socket: '$!'\n/); |
a3ab329f DG |
439 | |
440 | if ( $scheme eq 'https') { | |
441 | IO::Socket::SSL->start_SSL($self->{fh}); | |
442 | ref($self->{fh}) eq 'IO::Socket::SSL' | |
435aa301 DG |
443 | or die(qq/SSL connection failed for $host\n/); |
444 | $self->{fh}->verify_hostname( $host, $ssl_verify_args ) | |
445 | or die(qq/SSL certificate not valid for $host\n/); | |
a3ab329f DG |
446 | } |
447 | ||
448 | $self->{host} = $host; | |
449 | $self->{port} = $port; | |
450 | ||
451 | return $self; | |
452 | } | |
453 | ||
454 | sub close { | |
77ccfaeb | 455 | @_ == 1 || die(q/Usage: $handle->close()/ . "\n"); |
a3ab329f DG |
456 | my ($self) = @_; |
457 | CORE::close($self->{fh}) | |
77ccfaeb | 458 | or die(qq/Could not close socket: '$!'\n/); |
a3ab329f DG |
459 | } |
460 | ||
461 | sub write { | |
77ccfaeb | 462 | @_ == 2 || die(q/Usage: $handle->write(buf)/ . "\n"); |
a3ab329f DG |
463 | my ($self, $buf) = @_; |
464 | ||
465 | if ( $] ge '5.008' ) { | |
466 | utf8::downgrade($buf, 1) | |
77ccfaeb | 467 | or die(qq/Wide character in write()\n/); |
a3ab329f DG |
468 | } |
469 | ||
470 | my $len = length $buf; | |
471 | my $off = 0; | |
472 | ||
473 | local $SIG{PIPE} = 'IGNORE'; | |
474 | ||
475 | while () { | |
476 | $self->can_write | |
77ccfaeb | 477 | or die(qq/Timed out while waiting for socket to become ready for writing\n/); |
a3ab329f DG |
478 | my $r = syswrite($self->{fh}, $buf, $len, $off); |
479 | if (defined $r) { | |
480 | $len -= $r; | |
481 | $off += $r; | |
482 | last unless $len > 0; | |
483 | } | |
484 | elsif ($! == EPIPE) { | |
77ccfaeb | 485 | die(qq/Socket closed by remote server: $!\n/); |
a3ab329f DG |
486 | } |
487 | elsif ($! != EINTR) { | |
77ccfaeb | 488 | die(qq/Could not write to socket: '$!'\n/); |
a3ab329f DG |
489 | } |
490 | } | |
491 | return $off; | |
492 | } | |
493 | ||
494 | sub read { | |
77ccfaeb | 495 | @_ == 2 || @_ == 3 || die(q/Usage: $handle->read(len [, allow_partial])/ . "\n"); |
a3ab329f DG |
496 | my ($self, $len, $allow_partial) = @_; |
497 | ||
498 | my $buf = ''; | |
499 | my $got = length $self->{rbuf}; | |
500 | ||
501 | if ($got) { | |
502 | my $take = ($got < $len) ? $got : $len; | |
503 | $buf = substr($self->{rbuf}, 0, $take, ''); | |
504 | $len -= $take; | |
505 | } | |
506 | ||
507 | while ($len > 0) { | |
508 | $self->can_read | |
77ccfaeb | 509 | or die(q/Timed out while waiting for socket to become ready for reading/ . "\n"); |
a3ab329f DG |
510 | my $r = sysread($self->{fh}, $buf, $len, length $buf); |
511 | if (defined $r) { | |
512 | last unless $r; | |
513 | $len -= $r; | |
514 | } | |
515 | elsif ($! != EINTR) { | |
77ccfaeb | 516 | die(qq/Could not read from socket: '$!'\n/); |
a3ab329f DG |
517 | } |
518 | } | |
519 | if ($len && !$allow_partial) { | |
77ccfaeb | 520 | die(qq/Unexpected end of stream\n/); |
a3ab329f DG |
521 | } |
522 | return $buf; | |
523 | } | |
524 | ||
525 | sub readline { | |
77ccfaeb | 526 | @_ == 1 || die(q/Usage: $handle->readline()/ . "\n"); |
a3ab329f DG |
527 | my ($self) = @_; |
528 | ||
529 | while () { | |
530 | if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { | |
531 | return $1; | |
532 | } | |
533 | if (length $self->{rbuf} >= $self->{max_line_size}) { | |
77ccfaeb | 534 | die(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}\n/); |
a3ab329f DG |
535 | } |
536 | $self->can_read | |
77ccfaeb | 537 | or die(qq/Timed out while waiting for socket to become ready for reading\n/); |
a3ab329f DG |
538 | my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); |
539 | if (defined $r) { | |
540 | last unless $r; | |
541 | } | |
542 | elsif ($! != EINTR) { | |
77ccfaeb | 543 | die(qq/Could not read from socket: '$!'\n/); |
a3ab329f DG |
544 | } |
545 | } | |
77ccfaeb | 546 | die(qq/Unexpected end of stream while looking for line\n/); |
a3ab329f DG |
547 | } |
548 | ||
549 | sub read_header_lines { | |
77ccfaeb | 550 | @_ == 1 || @_ == 2 || die(q/Usage: $handle->read_header_lines([headers])/ . "\n"); |
a3ab329f DG |
551 | my ($self, $headers) = @_; |
552 | $headers ||= {}; | |
553 | my $lines = 0; | |
554 | my $val; | |
555 | ||
556 | while () { | |
557 | my $line = $self->readline; | |
558 | ||
559 | if (++$lines >= $self->{max_header_lines}) { | |
77ccfaeb | 560 | die(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}\n/); |
a3ab329f DG |
561 | } |
562 | elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { | |
563 | my ($field_name) = lc $1; | |
564 | if (exists $headers->{$field_name}) { | |
565 | for ($headers->{$field_name}) { | |
566 | $_ = [$_] unless ref $_ eq "ARRAY"; | |
567 | push @$_, $2; | |
568 | $val = \$_->[-1]; | |
569 | } | |
570 | } | |
571 | else { | |
572 | $val = \($headers->{$field_name} = $2); | |
573 | } | |
574 | } | |
575 | elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { | |
576 | $val | |
77ccfaeb | 577 | or die(qq/Unexpected header continuation line\n/); |
a3ab329f DG |
578 | next unless length $1; |
579 | $$val .= ' ' if length $$val; | |
580 | $$val .= $1; | |
581 | } | |
582 | elsif ($line =~ /\A \x0D?\x0A \z/x) { | |
583 | last; | |
584 | } | |
585 | else { | |
77ccfaeb | 586 | die(q/Malformed header line: / . $Printable->($line) . "\n"); |
a3ab329f DG |
587 | } |
588 | } | |
589 | return $headers; | |
590 | } | |
591 | ||
592 | sub write_request { | |
77ccfaeb | 593 | @_ == 2 || die(q/Usage: $handle->write_request(request)/ . "\n"); |
a3ab329f DG |
594 | my($self, $request) = @_; |
595 | $self->write_request_header(@{$request}{qw/method uri headers/}); | |
596 | $self->write_body($request) if $request->{cb}; | |
597 | return; | |
598 | } | |
599 | ||
600 | my %HeaderCase = ( | |
601 | 'content-md5' => 'Content-MD5', | |
602 | 'etag' => 'ETag', | |
603 | 'te' => 'TE', | |
604 | 'www-authenticate' => 'WWW-Authenticate', | |
605 | 'x-xss-protection' => 'X-XSS-Protection', | |
606 | ); | |
607 | ||
608 | sub write_header_lines { | |
77ccfaeb | 609 | (@_ == 2 && ref $_[1] eq 'HASH') || die(q/Usage: $handle->write_header_lines(headers)/ . "\n"); |
a3ab329f DG |
610 | my($self, $headers) = @_; |
611 | ||
612 | my $buf = ''; | |
613 | while (my ($k, $v) = each %$headers) { | |
614 | my $field_name = lc $k; | |
615 | if (exists $HeaderCase{$field_name}) { | |
616 | $field_name = $HeaderCase{$field_name}; | |
617 | } | |
618 | else { | |
619 | $field_name =~ /\A $Token+ \z/xo | |
77ccfaeb | 620 | or die(q/Invalid HTTP header field name: / . $Printable->($field_name) . "\n"); |
a3ab329f DG |
621 | $field_name =~ s/\b(\w)/\u$1/g; |
622 | $HeaderCase{lc $field_name} = $field_name; | |
623 | } | |
624 | for (ref $v eq 'ARRAY' ? @$v : $v) { | |
625 | /[^\x0D\x0A]/ | |
77ccfaeb | 626 | or die(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_). "\n"); |
a3ab329f DG |
627 | $buf .= "$field_name: $_\x0D\x0A"; |
628 | } | |
629 | } | |
630 | $buf .= "\x0D\x0A"; | |
631 | return $self->write($buf); | |
632 | } | |
633 | ||
634 | sub read_body { | |
77ccfaeb | 635 | @_ == 3 || die(q/Usage: $handle->read_body(callback, response)/ . "\n"); |
a3ab329f DG |
636 | my ($self, $cb, $response) = @_; |
637 | my $te = $response->{headers}{'transfer-encoding'} || ''; | |
638 | if ( grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ) { | |
639 | $self->read_chunked_body($cb, $response); | |
640 | } | |
641 | else { | |
642 | $self->read_content_body($cb, $response); | |
643 | } | |
644 | return; | |
645 | } | |
646 | ||
647 | sub write_body { | |
77ccfaeb | 648 | @_ == 2 || die(q/Usage: $handle->write_body(request)/ . "\n"); |
a3ab329f DG |
649 | my ($self, $request) = @_; |
650 | if ($request->{headers}{'content-length'}) { | |
651 | return $self->write_content_body($request); | |
652 | } | |
653 | else { | |
654 | return $self->write_chunked_body($request); | |
655 | } | |
656 | } | |
657 | ||
658 | sub read_content_body { | |
77ccfaeb | 659 | @_ == 3 || @_ == 4 || die(q/Usage: $handle->read_content_body(callback, response, [read_length])/ . "\n"); |
a3ab329f DG |
660 | my ($self, $cb, $response, $content_length) = @_; |
661 | $content_length ||= $response->{headers}{'content-length'}; | |
662 | ||
663 | if ( $content_length ) { | |
664 | my $len = $content_length; | |
665 | while ($len > 0) { | |
666 | my $read = ($len > BUFSIZE) ? BUFSIZE : $len; | |
667 | $cb->($self->read($read, 0), $response); | |
668 | $len -= $read; | |
669 | } | |
670 | } | |
671 | else { | |
672 | my $chunk; | |
673 | $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) ); | |
674 | } | |
675 | ||
676 | return; | |
677 | } | |
678 | ||
679 | sub write_content_body { | |
77ccfaeb | 680 | @_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n"); |
a3ab329f DG |
681 | my ($self, $request) = @_; |
682 | ||
683 | my ($len, $content_length) = (0, $request->{headers}{'content-length'}); | |
684 | while () { | |
685 | my $data = $request->{cb}->(); | |
686 | ||
687 | defined $data && length $data | |
688 | or last; | |
689 | ||
690 | if ( $] ge '5.008' ) { | |
691 | utf8::downgrade($data, 1) | |
77ccfaeb | 692 | or die(qq/Wide character in write_content()\n/); |
a3ab329f DG |
693 | } |
694 | ||
695 | $len += $self->write($data); | |
696 | } | |
697 | ||
698 | $len == $content_length | |
77ccfaeb | 699 | or die(qq/Content-Length missmatch (got: $len expected: $content_length)\n/); |
a3ab329f DG |
700 | |
701 | return $len; | |
702 | } | |
703 | ||
704 | sub read_chunked_body { | |
77ccfaeb | 705 | @_ == 3 || die(q/Usage: $handle->read_chunked_body(callback, $response)/ . "\n"); |
a3ab329f DG |
706 | my ($self, $cb, $response) = @_; |
707 | ||
708 | while () { | |
709 | my $head = $self->readline; | |
710 | ||
711 | $head =~ /\A ([A-Fa-f0-9]+)/x | |
77ccfaeb | 712 | or die(q/Malformed chunk head: / . $Printable->($head) . "\n"); |
a3ab329f DG |
713 | |
714 | my $len = hex($1) | |
715 | or last; | |
716 | ||
717 | $self->read_content_body($cb, $response, $len); | |
718 | ||
719 | $self->read(2) eq "\x0D\x0A" | |
77ccfaeb | 720 | or die(qq/Malformed chunk: missing CRLF after chunk data\n/); |
a3ab329f DG |
721 | } |
722 | $self->read_header_lines($response->{headers}); | |
723 | return; | |
724 | } | |
725 | ||
726 | sub write_chunked_body { | |
77ccfaeb | 727 | @_ == 2 || die(q/Usage: $handle->write_chunked_body(request)/ . "\n"); |
a3ab329f DG |
728 | my ($self, $request) = @_; |
729 | ||
730 | my $len = 0; | |
731 | while () { | |
732 | my $data = $request->{cb}->(); | |
733 | ||
734 | defined $data && length $data | |
735 | or last; | |
736 | ||
737 | if ( $] ge '5.008' ) { | |
738 | utf8::downgrade($data, 1) | |
77ccfaeb | 739 | or die(qq/Wide character in write_chunked_body()\n/); |
a3ab329f DG |
740 | } |
741 | ||
742 | $len += length $data; | |
743 | ||
744 | my $chunk = sprintf '%X', length $data; | |
745 | $chunk .= "\x0D\x0A"; | |
746 | $chunk .= $data; | |
747 | $chunk .= "\x0D\x0A"; | |
748 | ||
749 | $self->write($chunk); | |
750 | } | |
751 | $self->write("0\x0D\x0A"); | |
752 | $self->write_header_lines($request->{trailer_cb}->()) | |
753 | if ref $request->{trailer_cb} eq 'CODE'; | |
754 | return $len; | |
755 | } | |
756 | ||
757 | sub read_response_header { | |
77ccfaeb | 758 | @_ == 1 || die(q/Usage: $handle->read_response_header()/ . "\n"); |
a3ab329f DG |
759 | my ($self) = @_; |
760 | ||
761 | my $line = $self->readline; | |
762 | ||
763 | $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x | |
77ccfaeb | 764 | or die(q/Malformed Status-Line: / . $Printable->($line). "\n"); |
a3ab329f DG |
765 | |
766 | my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); | |
767 | ||
77ccfaeb | 768 | die (qq/Unsupported HTTP protocol: $protocol\n/) |
a3ab329f DG |
769 | unless $version =~ /0*1\.0*[01]/; |
770 | ||
771 | return { | |
772 | status => $status, | |
773 | reason => $reason, | |
774 | headers => $self->read_header_lines, | |
775 | protocol => $protocol, | |
776 | }; | |
777 | } | |
778 | ||
779 | sub write_request_header { | |
77ccfaeb | 780 | @_ == 4 || die(q/Usage: $handle->write_request_header(method, request_uri, headers)/ . "\n"); |
a3ab329f DG |
781 | my ($self, $method, $request_uri, $headers) = @_; |
782 | ||
783 | return $self->write("$method $request_uri HTTP/1.1\x0D\x0A") | |
784 | + $self->write_header_lines($headers); | |
785 | } | |
786 | ||
787 | sub _do_timeout { | |
788 | my ($self, $type, $timeout) = @_; | |
789 | $timeout = $self->{timeout} | |
790 | unless defined $timeout && $timeout >= 0; | |
791 | ||
792 | my $fd = fileno $self->{fh}; | |
793 | defined $fd && $fd >= 0 | |
77ccfaeb | 794 | or die(qq/select(2): 'Bad file descriptor'\n/); |
a3ab329f DG |
795 | |
796 | my $initial = time; | |
797 | my $pending = $timeout; | |
798 | my $nfound; | |
799 | ||
800 | vec(my $fdset = '', $fd, 1) = 1; | |
801 | ||
802 | while () { | |
803 | $nfound = ($type eq 'read') | |
804 | ? select($fdset, undef, undef, $pending) | |
805 | : select(undef, $fdset, undef, $pending) ; | |
806 | if ($nfound == -1) { | |
807 | $! == EINTR | |
77ccfaeb | 808 | or die(qq/select(2): '$!'\n/); |
a3ab329f DG |
809 | redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; |
810 | $nfound = 0; | |
811 | } | |
812 | last; | |
813 | } | |
814 | $! = 0; | |
815 | return $nfound; | |
816 | } | |
817 | ||
818 | sub can_read { | |
77ccfaeb | 819 | @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_read([timeout])/ . "\n"); |
a3ab329f DG |
820 | my $self = shift; |
821 | return $self->_do_timeout('read', @_) | |
822 | } | |
823 | ||
824 | sub can_write { | |
77ccfaeb | 825 | @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_write([timeout])/ . "\n"); |
a3ab329f DG |
826 | my $self = shift; |
827 | return $self->_do_timeout('write', @_) | |
828 | } | |
829 | ||
830 | 1; | |
831 | ||
832 | ||
833 | ||
834 | __END__ | |
835 | =pod | |
836 | ||
837 | =head1 NAME | |
838 | ||
839 | HTTP::Tiny - A small, simple, correct HTTP/1.1 client | |
840 | ||
841 | =head1 VERSION | |
842 | ||
4984624c | 843 | version 0.017 |
a3ab329f DG |
844 | |
845 | =head1 SYNOPSIS | |
846 | ||
847 | use HTTP::Tiny; | |
848 | ||
849 | my $response = HTTP::Tiny->new->get('http://example.com/'); | |
850 | ||
851 | die "Failed!\n" unless $response->{success}; | |
852 | ||
853 | print "$response->{status} $response->{reason}\n"; | |
854 | ||
855 | while (my ($k, $v) = each %{$response->{headers}}) { | |
856 | for (ref $v eq 'ARRAY' ? @$v : $v) { | |
857 | print "$k: $_\n"; | |
858 | } | |
859 | } | |
860 | ||
861 | print $response->{content} if length $response->{content}; | |
862 | ||
863 | =head1 DESCRIPTION | |
864 | ||
35265876 | 865 | This is a very simple HTTP/1.1 client, designed for doing simple GET |
a3ab329f DG |
866 | requests without the overhead of a large framework like L<LWP::UserAgent>. |
867 | ||
868 | It is more correct and more complete than L<HTTP::Lite>. It supports | |
869 | proxies (currently only non-authenticating ones) and redirection. It | |
870 | also correctly resumes after EINTR. | |
871 | ||
872 | =head1 METHODS | |
873 | ||
874 | =head2 new | |
875 | ||
876 | $http = HTTP::Tiny->new( %attributes ); | |
877 | ||
878 | This constructor returns a new HTTP::Tiny object. Valid attributes include: | |
879 | ||
880 | =over 4 | |
881 | ||
882 | =item * | |
883 | ||
4984624c | 884 | C<agent> |
a3ab329f DG |
885 | |
886 | A user-agent string (defaults to 'HTTP::Tiny/$VERSION') | |
887 | ||
888 | =item * | |
889 | ||
4984624c | 890 | C<default_headers> |
a3ab329f DG |
891 | |
892 | A hashref of default headers to apply to requests | |
893 | ||
894 | =item * | |
895 | ||
4984624c | 896 | C<max_redirect> |
a3ab329f DG |
897 | |
898 | Maximum number of redirects allowed (defaults to 5) | |
899 | ||
900 | =item * | |
901 | ||
4984624c | 902 | C<max_size> |
a3ab329f DG |
903 | |
904 | Maximum response size (only when not using a data callback). If defined, | |
4984624c | 905 | responses larger than this will return an exception. |
a3ab329f DG |
906 | |
907 | =item * | |
908 | ||
4984624c | 909 | C<proxy> |
a3ab329f | 910 | |
77ccfaeb | 911 | URL of a proxy server to use (default is C<$ENV{http_proxy}> if set) |
a3ab329f DG |
912 | |
913 | =item * | |
914 | ||
4984624c | 915 | C<timeout> |
a3ab329f DG |
916 | |
917 | Request timeout in seconds (default is 60) | |
918 | ||
919 | =back | |
920 | ||
4984624c A |
921 | Exceptions from C<max_size>, C<timeout> or other errors will result in a |
922 | pseudo-HTTP status code of 599 and a reason of "Internal Exception". The | |
923 | content field in the response will contain the text of the exception. | |
924 | ||
35265876 | 925 | =head2 get|head|put|post|delete |
a3ab329f DG |
926 | |
927 | $response = $http->get($url); | |
928 | $response = $http->get($url, \%options); | |
35265876 CBW |
929 | $response = $http->head($url); |
930 | ||
931 | These methods are shorthand for calling C<request()> for the given method. The | |
932 | URL must have unsafe characters escaped and international domain names encoded. | |
933 | See C<request()> for valid options and a description of the response. | |
934 | ||
4984624c A |
935 | The C<success> field of the response will be true if the status code is 2XX. |
936 | ||
35265876 CBW |
937 | =head2 post_form |
938 | ||
939 | $response = $http->post_form($url, $form_data); | |
940 | $response = $http->post_form($url, $form_data, \%options); | |
a3ab329f | 941 | |
35265876 CBW |
942 | This method executes a C<POST> request and sends the key/value pairs from a |
943 | form data hash or array reference to the given URL with a C<content-type> of | |
944 | C<application/x-www-form-urlencoded>. See documentation for the | |
945 | C<www_form_urlencode> method for details on the encoding. | |
946 | ||
947 | The URL must have unsafe characters escaped and international domain names | |
948 | encoded. See C<request()> for valid options and a description of the response. | |
949 | Any C<content-type> header or content in the options hashref will be ignored. | |
a3ab329f | 950 | |
4984624c A |
951 | The C<success> field of the response will be true if the status code is 2XX. |
952 | ||
a3ab329f DG |
953 | =head2 mirror |
954 | ||
955 | $response = $http->mirror($url, $file, \%options) | |
956 | if ( $response->{success} ) { | |
957 | print "$file is up to date\n"; | |
958 | } | |
959 | ||
960 | Executes a C<GET> request for the URL and saves the response body to the file | |
961 | name provided. The URL must have unsafe characters escaped and international | |
962 | domain names encoded. If the file already exists, the request will includes an | |
963 | C<If-Modified-Since> header with the modification timestamp of the file. You | |
4984624c | 964 | may specify a different C<If-Modified-Since> header yourself in the C<< |
a3ab329f DG |
965 | $options->{headers} >> hash. |
966 | ||
967 | The C<success> field of the response will be true if the status code is 2XX | |
4984624c | 968 | or if the status code is 304 (unmodified). |
a3ab329f DG |
969 | |
970 | If the file was modified and the server response includes a properly | |
971 | formatted C<Last-Modified> header, the file modification time will | |
972 | be updated accordingly. | |
973 | ||
974 | =head2 request | |
975 | ||
976 | $response = $http->request($method, $url); | |
977 | $response = $http->request($method, $url, \%options); | |
978 | ||
435aa301 DG |
979 | Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST', |
980 | 'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and | |
a3ab329f DG |
981 | international domain names encoded. A hashref of options may be appended to |
982 | modify the request. | |
983 | ||
984 | Valid options are: | |
985 | ||
986 | =over 4 | |
987 | ||
988 | =item * | |
989 | ||
990 | headers | |
991 | ||
992 | A hashref containing headers to include with the request. If the value for | |
993 | a header is an array reference, the header will be output multiple times with | |
994 | each value in the array. These headers over-write any default headers. | |
995 | ||
996 | =item * | |
997 | ||
998 | content | |
999 | ||
1000 | A scalar to include as the body of the request OR a code reference | |
1001 | that will be called iteratively to produce the body of the response | |
1002 | ||
1003 | =item * | |
1004 | ||
1005 | trailer_callback | |
1006 | ||
1007 | A code reference that will be called if it exists to provide a hashref | |
1008 | of trailing headers (only used with chunked transfer-encoding) | |
1009 | ||
1010 | =item * | |
1011 | ||
1012 | data_callback | |
1013 | ||
1014 | A code reference that will be called for each chunks of the response | |
1015 | body received. | |
1016 | ||
1017 | =back | |
1018 | ||
1019 | If the C<content> option is a code reference, it will be called iteratively | |
1020 | to provide the content body of the request. It should return the empty | |
1021 | string or undef when the iterator is exhausted. | |
1022 | ||
1023 | If the C<data_callback> option is provided, it will be called iteratively until | |
1024 | the entire response body is received. The first argument will be a string | |
1025 | containing a chunk of the response body, the second argument will be the | |
1026 | in-progress response hash reference, as described below. (This allows | |
1027 | customizing the action of the callback based on the C<status> or C<headers> | |
1028 | received prior to the content body.) | |
1029 | ||
1030 | The C<request> method returns a hashref containing the response. The hashref | |
1031 | will have the following keys: | |
1032 | ||
1033 | =over 4 | |
1034 | ||
1035 | =item * | |
1036 | ||
1037 | success | |
1038 | ||
1039 | Boolean indicating whether the operation returned a 2XX status code | |
1040 | ||
1041 | =item * | |
1042 | ||
1043 | status | |
1044 | ||
1045 | The HTTP status code of the response | |
1046 | ||
1047 | =item * | |
1048 | ||
1049 | reason | |
1050 | ||
1051 | The response phrase returned by the server | |
1052 | ||
1053 | =item * | |
1054 | ||
1055 | content | |
1056 | ||
1057 | The body of the response. If the response does not have any content | |
1058 | or if a data callback is provided to consume the response body, | |
1059 | this will be the empty string | |
1060 | ||
1061 | =item * | |
1062 | ||
1063 | headers | |
1064 | ||
1065 | A hashref of header fields. All header field names will be normalized | |
1066 | to be lower case. If a header is repeated, the value will be an arrayref; | |
1067 | it will otherwise be a scalar string containing the value | |
1068 | ||
1069 | =back | |
1070 | ||
1071 | On an exception during the execution of the request, the C<status> field will | |
1072 | contain 599, and the C<content> field will contain the text of the exception. | |
1073 | ||
35265876 CBW |
1074 | =head2 www_form_urlencode |
1075 | ||
1076 | $params = $http->www_form_urlencode( $data ); | |
1077 | $response = $http->get("http://example.com/query?$params"); | |
1078 | ||
1079 | This method converts the key/value pairs from a data hash or array reference | |
1080 | into a C<x-www-form-urlencoded> string. The keys and values from the data | |
1081 | reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an | |
1082 | array reference, the key will be repeated with each of the values of the array | |
1083 | reference. The key/value pairs in the resulting string will be sorted by key | |
1084 | and value. | |
1085 | ||
a3ab329f DG |
1086 | =for Pod::Coverage agent |
1087 | default_headers | |
1088 | max_redirect | |
1089 | max_size | |
1090 | proxy | |
1091 | timeout | |
1092 | ||
1093 | =head1 LIMITATIONS | |
1094 | ||
1095 | HTTP::Tiny is I<conditionally compliant> with the | |
1096 | L<HTTP/1.1 specification|http://www.w3.org/Protocols/rfc2616/rfc2616.html>. | |
1097 | It attempts to meet all "MUST" requirements of the specification, but does not | |
1098 | implement all "SHOULD" requirements. | |
1099 | ||
1100 | Some particular limitations of note include: | |
1101 | ||
1102 | =over | |
1103 | ||
1104 | =item * | |
1105 | ||
1106 | HTTP::Tiny focuses on correct transport. Users are responsible for ensuring | |
1107 | that user-defined headers and content are compliant with the HTTP/1.1 | |
1108 | specification. | |
1109 | ||
1110 | =item * | |
1111 | ||
1112 | Users must ensure that URLs are properly escaped for unsafe characters and that | |
1113 | international domain names are properly encoded to ASCII. See L<URI::Escape>, | |
1114 | L<URI::_punycode> and L<Net::IDN::Encode>. | |
1115 | ||
1116 | =item * | |
1117 | ||
1118 | Redirection is very strict against the specification. Redirection is only | |
1119 | automatic for response codes 301, 302 and 307 if the request method is 'GET' or | |
1120 | 'HEAD'. Response code 303 is always converted into a 'GET' redirection, as | |
1121 | mandated by the specification. There is no automatic support for status 305 | |
1122 | ("Use proxy") redirections. | |
1123 | ||
1124 | =item * | |
1125 | ||
293dcbbb | 1126 | Persistent connections are not supported. The C<Connection> header will |
a3ab329f DG |
1127 | always be set to C<close>. |
1128 | ||
1129 | =item * | |
1130 | ||
1131 | Direct C<https> connections are supported only if L<IO::Socket::SSL> is | |
1132 | installed. There is no support for C<https> connections via proxy. | |
b06ddfb0 DG |
1133 | Any SSL certificate that matches the host is accepted -- SSL certificates |
1134 | are not verified against certificate authorities. | |
a3ab329f DG |
1135 | |
1136 | =item * | |
1137 | ||
1138 | Cookies are not directly supported. Users that set a C<Cookie> header | |
1139 | should also set C<max_redirect> to zero to ensure cookies are not | |
1140 | inappropriately re-transmitted. | |
1141 | ||
1142 | =item * | |
1143 | ||
77ccfaeb DG |
1144 | Only the C<http_proxy> environment variable is supported in the format |
1145 | C<http://HOST:PORT/>. If a C<proxy> argument is passed to C<new> (including | |
1146 | undef), then the C<http_proxy> environment variable is ignored. | |
a3ab329f DG |
1147 | |
1148 | =item * | |
1149 | ||
1150 | There is no provision for delaying a request body using an C<Expect> header. | |
1151 | Unexpected C<1XX> responses are silently ignored as per the specification. | |
1152 | ||
1153 | =item * | |
1154 | ||
1155 | Only 'chunked' C<Transfer-Encoding> is supported. | |
1156 | ||
1157 | =item * | |
1158 | ||
1159 | There is no support for a Request-URI of '*' for the 'OPTIONS' request. | |
1160 | ||
1161 | =back | |
1162 | ||
1163 | =head1 SEE ALSO | |
1164 | ||
1165 | =over 4 | |
1166 | ||
1167 | =item * | |
1168 | ||
1169 | L<LWP::UserAgent> | |
1170 | ||
1171 | =back | |
1172 | ||
452d0b70 | 1173 | =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders |
44de791a DG |
1174 | |
1175 | =head1 SUPPORT | |
1176 | ||
1177 | =head2 Bugs / Feature Requests | |
1178 | ||
4984624c A |
1179 | Please report any bugs or feature requests through the issue tracker |
1180 | at L<http://rt.cpan.org/Public/Dist/Display.html?Name=HTTP-Tiny>. | |
1181 | You will be notified automatically of any progress on your issue. | |
44de791a DG |
1182 | |
1183 | =head2 Source Code | |
1184 | ||
1185 | This is open source software. The code repository is available for | |
1186 | public review and contribution under the terms of the license. | |
1187 | ||
35265876 | 1188 | L<https://github.com/dagolden/p5-http-tiny> |
44de791a | 1189 | |
35265876 | 1190 | git clone https://github.com/dagolden/p5-http-tiny.git |
44de791a | 1191 | |
a3ab329f DG |
1192 | =head1 AUTHORS |
1193 | ||
1194 | =over 4 | |
1195 | ||
1196 | =item * | |
1197 | ||
1198 | Christian Hansen <chansen@cpan.org> | |
1199 | ||
1200 | =item * | |
1201 | ||
1202 | David Golden <dagolden@cpan.org> | |
1203 | ||
1204 | =back | |
1205 | ||
1206 | =head1 COPYRIGHT AND LICENSE | |
1207 | ||
4984624c | 1208 | This software is copyright (c) 2012 by Christian Hansen. |
a3ab329f DG |
1209 | |
1210 | This is free software; you can redistribute it and/or modify it under | |
1211 | the same terms as the Perl 5 programming language system itself. | |
1212 | ||
1213 | =cut | |
1214 |