HTTP clients with HTTP::Tiny
| A GET request | |
| POST and forms | |
| Reading the response | |
| Timeouts, redirects and TLS |
A GET request
HTTP::Tiny is the HTTP client that ships with Perl. It has no dependencies for plain HTTP, handles redirects, and returns a small hash instead of an object graph.
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $ua = HTTP::Tiny->new(
agent => "perl-course/1.0",
timeout => 10,
);
my $res = $ua->get("https://httpbin.org/json");
die "request failed: $res->{status} $res->{reason}\n"
unless $res->{success};
my $data = JSON::PP->new->utf8->decode($res->{content});
print "keys: ", join(", ", sort keys %$data), "\n";
keys: slideshow
The response is a hash reference with status, reason, content, headers and a boolean success. Check success before reading content: an error page is still valid text, and decoding HTML as JSON produces a confusing message.
Pass a second hash reference to add request headers. The header names are case-insensitive, and values are joined with commas when repeated:
my $res = $ua->get($url, {
headers => {
Accept => "application/json",
Authorization => "Bearer $token",
},
});
POST and forms
post takes the same second argument plus a content key for the raw body. JSON APIs want an explicit content type, otherwise the server may reject or misread the payload:
my $body = JSON::PP->encode_json({
title => "Ship the course",
done => JSON::PP::false,
});
my $res = $ua->post("https://api.example.com/notes", {
headers => { "Content-Type" => "application/json" },
content => $body,
});
HTML forms use a different encoding: keys and values joined with ampersands and escaped. post_form does that encoding for you, so you do not have to remember the rules:
my $res = $ua->post_form("https://example.com/login", {
user => "ada",
pass => $secret,
});
Every method has a generic form as well: $ua->request("DELETE", $url), or "PATCH" with a body. Use it for methods that have no named helper.
Reading the response
Status codes tell you what happened, and they are worth handling by class rather than one by one. A four-hundred series means the request was wrong; a five-hundred series means the server failed; three-hundred series are redirects that HTTP::Tiny follows for you.
if ($res->{status} == 429) {
die "rate limited, retry later\n";
} elsif ($res->{status} >= 500) {
die "server error: $res->{status} $res->{reason}\n";
}
Headers arrive lowercased, which saves you from guessing the server's capitalisation. Content type tells you how to parse the body:
my $type = $res->{headers}{"content-type"} // "";
if ($type =~ m{application/json}) {
$data = JSON::PP->new->utf8->decode($res->{content});
}
When the request never reaches the server — a bad host name, a connection refused, a timeout — the status is 599 and the reason explains why. That is not an HTTP status, it is HTTP::Tiny's way of reporting a transport failure, so test for it explicitly.
Timeouts, redirects and TLS
The default timeout is sixty seconds, which is far too long for a script that talks to an API in a loop. Set a short timeout and decide what a failure means for your program.
HTTPS needs IO::Socket::SSL and Net::SSLeay. They are available from CPAN, and some distributions ship them by default. Certificate verification is on unless you turn it off, and you should not turn it off.
cpanm IO::Socket::SSL
my $ua = HTTP::Tiny->new(
timeout => 5,
max_redirect => 0,
verify_SSL => 1,
);
Setting max_redirect => 0 makes a redirect visible instead of following it, which is useful when you need the Location header. Proxies are read from http_proxy, https_proxy and no_proxy in the environment, so a script that works on your laptop also works behind the office proxy.
For cookies, multipart uploads or connection reuse, move to LWP::UserAgent or Mojo::UserAgent. HTTP::Tiny is deliberately small: it covers simple JSON and form APIs, and it covers them well.
Next: Sockets and IO::Socket
Article author: Arthur Isaev