This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Upgrade HTTP::Tiny to 0.017.
[perl5.git] / cpan / HTTP-Tiny / lib / HTTP / Tiny.pm
CommitLineData
a3ab329f 1# vim: ts=4 sts=4 sw=4 et:
a3ab329f 2package HTTP::Tiny;
a3ab329f
DG
3use strict;
4use warnings;
35265876 5# ABSTRACT: A small, simple, correct HTTP/1.1 client
4984624c 6our $VERSION = '0.017'; # VERSION
a3ab329f
DG
7
8use Carp ();
9
10
11my @attributes;
12BEGIN {
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
22sub 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
48for 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 }
58HERE
59}
60
61
62sub 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
85sub 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
114my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/;
115
116sub 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
146sub 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
175my %DefaultPort = (
176 http => 80,
177 https => 443,
178);
179
180sub _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
230sub _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
268sub _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
288sub _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
304sub _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
326my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat";
327my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
328sub _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
337sub _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
359my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255;
360$escapes{' '}="+";
361my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/;
362
363sub _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
377package
378 HTTP::Tiny::Handle; # hide from PAUSE/indexers
379use strict;
380use warnings;
381
a3ab329f
DG
382use Errno qw[EINTR EPIPE];
383use IO::Socket qw[SOCK_STREAM];
384
4984624c 385sub BUFSIZE () { 32768 } ## no critic
a3ab329f
DG
386
387my $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
396my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
397
398sub 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
409my $ssl_verify_args = {
410 check_cn => "when_only",
411 wildcards_in_alt => "anywhere",
412 wildcards_in_cn => "anywhere"
413};
414
a3ab329f 415sub 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
454sub 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
461sub 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
494sub 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
525sub 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
549sub 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
592sub 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
600my %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
608sub 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
634sub 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
647sub 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
658sub 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
679sub 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
704sub 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
726sub 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
757sub 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
779sub 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
787sub _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
818sub 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
824sub 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
8301;
831
832
833
834__END__
835=pod
836
837=head1 NAME
838
839HTTP::Tiny - A small, simple, correct HTTP/1.1 client
840
841=head1 VERSION
842
4984624c 843version 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 865This is a very simple HTTP/1.1 client, designed for doing simple GET
a3ab329f
DG
866requests without the overhead of a large framework like L<LWP::UserAgent>.
867
868It is more correct and more complete than L<HTTP::Lite>. It supports
869proxies (currently only non-authenticating ones) and redirection. It
870also correctly resumes after EINTR.
871
872=head1 METHODS
873
874=head2 new
875
876 $http = HTTP::Tiny->new( %attributes );
877
878This constructor returns a new HTTP::Tiny object. Valid attributes include:
879
880=over 4
881
882=item *
883
4984624c 884C<agent>
a3ab329f
DG
885
886A user-agent string (defaults to 'HTTP::Tiny/$VERSION')
887
888=item *
889
4984624c 890C<default_headers>
a3ab329f
DG
891
892A hashref of default headers to apply to requests
893
894=item *
895
4984624c 896C<max_redirect>
a3ab329f
DG
897
898Maximum number of redirects allowed (defaults to 5)
899
900=item *
901
4984624c 902C<max_size>
a3ab329f
DG
903
904Maximum response size (only when not using a data callback). If defined,
4984624c 905responses larger than this will return an exception.
a3ab329f
DG
906
907=item *
908
4984624c 909C<proxy>
a3ab329f 910
77ccfaeb 911URL of a proxy server to use (default is C<$ENV{http_proxy}> if set)
a3ab329f
DG
912
913=item *
914
4984624c 915C<timeout>
a3ab329f
DG
916
917Request timeout in seconds (default is 60)
918
919=back
920
4984624c
A
921Exceptions from C<max_size>, C<timeout> or other errors will result in a
922pseudo-HTTP status code of 599 and a reason of "Internal Exception". The
923content 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
931These methods are shorthand for calling C<request()> for the given method. The
932URL must have unsafe characters escaped and international domain names encoded.
933See C<request()> for valid options and a description of the response.
934
4984624c
A
935The 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
942This method executes a C<POST> request and sends the key/value pairs from a
943form data hash or array reference to the given URL with a C<content-type> of
944C<application/x-www-form-urlencoded>. See documentation for the
945C<www_form_urlencode> method for details on the encoding.
946
947The URL must have unsafe characters escaped and international domain names
948encoded. See C<request()> for valid options and a description of the response.
949Any C<content-type> header or content in the options hashref will be ignored.
a3ab329f 950
4984624c
A
951The 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
960Executes a C<GET> request for the URL and saves the response body to the file
961name provided. The URL must have unsafe characters escaped and international
962domain names encoded. If the file already exists, the request will includes an
963C<If-Modified-Since> header with the modification timestamp of the file. You
4984624c 964may specify a different C<If-Modified-Since> header yourself in the C<<
a3ab329f
DG
965$options->{headers} >> hash.
966
967The C<success> field of the response will be true if the status code is 2XX
4984624c 968or if the status code is 304 (unmodified).
a3ab329f
DG
969
970If the file was modified and the server response includes a properly
971formatted C<Last-Modified> header, the file modification time will
972be updated accordingly.
973
974=head2 request
975
976 $response = $http->request($method, $url);
977 $response = $http->request($method, $url, \%options);
978
435aa301
DG
979Executes 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
981international domain names encoded. A hashref of options may be appended to
982modify the request.
983
984Valid options are:
985
986=over 4
987
988=item *
989
990headers
991
992A hashref containing headers to include with the request. If the value for
993a header is an array reference, the header will be output multiple times with
994each value in the array. These headers over-write any default headers.
995
996=item *
997
998content
999
1000A scalar to include as the body of the request OR a code reference
1001that will be called iteratively to produce the body of the response
1002
1003=item *
1004
1005trailer_callback
1006
1007A code reference that will be called if it exists to provide a hashref
1008of trailing headers (only used with chunked transfer-encoding)
1009
1010=item *
1011
1012data_callback
1013
1014A code reference that will be called for each chunks of the response
1015body received.
1016
1017=back
1018
1019If the C<content> option is a code reference, it will be called iteratively
1020to provide the content body of the request. It should return the empty
1021string or undef when the iterator is exhausted.
1022
1023If the C<data_callback> option is provided, it will be called iteratively until
1024the entire response body is received. The first argument will be a string
1025containing a chunk of the response body, the second argument will be the
1026in-progress response hash reference, as described below. (This allows
1027customizing the action of the callback based on the C<status> or C<headers>
1028received prior to the content body.)
1029
1030The C<request> method returns a hashref containing the response. The hashref
1031will have the following keys:
1032
1033=over 4
1034
1035=item *
1036
1037success
1038
1039Boolean indicating whether the operation returned a 2XX status code
1040
1041=item *
1042
1043status
1044
1045The HTTP status code of the response
1046
1047=item *
1048
1049reason
1050
1051The response phrase returned by the server
1052
1053=item *
1054
1055content
1056
1057The body of the response. If the response does not have any content
1058or if a data callback is provided to consume the response body,
1059this will be the empty string
1060
1061=item *
1062
1063headers
1064
1065A hashref of header fields. All header field names will be normalized
1066to be lower case. If a header is repeated, the value will be an arrayref;
1067it will otherwise be a scalar string containing the value
1068
1069=back
1070
1071On an exception during the execution of the request, the C<status> field will
1072contain 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
1079This method converts the key/value pairs from a data hash or array reference
1080into a C<x-www-form-urlencoded> string. The keys and values from the data
1081reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an
1082array reference, the key will be repeated with each of the values of the array
1083reference. The key/value pairs in the resulting string will be sorted by key
1084and value.
1085
a3ab329f
DG
1086=for Pod::Coverage agent
1087default_headers
1088max_redirect
1089max_size
1090proxy
1091timeout
1092
1093=head1 LIMITATIONS
1094
1095HTTP::Tiny is I<conditionally compliant> with the
1096L<HTTP/1.1 specification|http://www.w3.org/Protocols/rfc2616/rfc2616.html>.
1097It attempts to meet all "MUST" requirements of the specification, but does not
1098implement all "SHOULD" requirements.
1099
1100Some particular limitations of note include:
1101
1102=over
1103
1104=item *
1105
1106HTTP::Tiny focuses on correct transport. Users are responsible for ensuring
1107that user-defined headers and content are compliant with the HTTP/1.1
1108specification.
1109
1110=item *
1111
1112Users must ensure that URLs are properly escaped for unsafe characters and that
1113international domain names are properly encoded to ASCII. See L<URI::Escape>,
1114L<URI::_punycode> and L<Net::IDN::Encode>.
1115
1116=item *
1117
1118Redirection is very strict against the specification. Redirection is only
1119automatic 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
1121mandated by the specification. There is no automatic support for status 305
1122("Use proxy") redirections.
1123
1124=item *
1125
293dcbbb 1126Persistent connections are not supported. The C<Connection> header will
a3ab329f
DG
1127always be set to C<close>.
1128
1129=item *
1130
1131Direct C<https> connections are supported only if L<IO::Socket::SSL> is
1132installed. There is no support for C<https> connections via proxy.
b06ddfb0
DG
1133Any SSL certificate that matches the host is accepted -- SSL certificates
1134are not verified against certificate authorities.
a3ab329f
DG
1135
1136=item *
1137
1138Cookies are not directly supported. Users that set a C<Cookie> header
1139should also set C<max_redirect> to zero to ensure cookies are not
1140inappropriately re-transmitted.
1141
1142=item *
1143
77ccfaeb
DG
1144Only the C<http_proxy> environment variable is supported in the format
1145C<http://HOST:PORT/>. If a C<proxy> argument is passed to C<new> (including
1146undef), then the C<http_proxy> environment variable is ignored.
a3ab329f
DG
1147
1148=item *
1149
1150There is no provision for delaying a request body using an C<Expect> header.
1151Unexpected C<1XX> responses are silently ignored as per the specification.
1152
1153=item *
1154
1155Only 'chunked' C<Transfer-Encoding> is supported.
1156
1157=item *
1158
1159There 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
1169L<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
1179Please report any bugs or feature requests through the issue tracker
1180at L<http://rt.cpan.org/Public/Dist/Display.html?Name=HTTP-Tiny>.
1181You will be notified automatically of any progress on your issue.
44de791a
DG
1182
1183=head2 Source Code
1184
1185This is open source software. The code repository is available for
1186public review and contribution under the terms of the license.
1187
35265876 1188L<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
1198Christian Hansen <chansen@cpan.org>
1199
1200=item *
1201
1202David Golden <dagolden@cpan.org>
1203
1204=back
1205
1206=head1 COPYRIGHT AND LICENSE
1207
4984624c 1208This software is copyright (c) 2012 by Christian Hansen.
a3ab329f
DG
1209
1210This is free software; you can redistribute it and/or modify it under
1211the same terms as the Perl 5 programming language system itself.
1212
1213=cut
1214