Why HTTP::Tiny?

Reading time: 4 minutes

I’ve recently been collaborating with Christian Hansen on HTTP::Tiny, a minimalist, HTTP/1.1 client library for Perl. For basic web client tasks like grabbing a single page or mirroring a file, it does the job in a fraction of the code that would be needed to install LWP::UserAgent. Because it has no non-core dependencies, it is ideal for what I need to get CPAN.pm to bootstrap itself with pure Perl.

Here’s a quick look at how you would use it for the two common tasks I mentioned:

use HTTP::Tiny;
    my $http = HTTP::Tiny->new;
    my $response;

    # get a single page
    $response = $http->get('http://example.com/');
    die "Failed!\n" unless $response->{success};
    print $response->{content};

    # mirror a file
    $response = $http->mirror('http://example.com/file.tar.gz', 'file.tar.gz');
    die "Failed!\n" unless $response->{success};
    print "Unchanged!\n" if $response->{status} eq '304';

The example above is almost the same as you’d get using LWP::UserAgent with one big exception. HTTP::Tiny returns the response as a hash reference rather than as an object. Just like LWP::UserAgent, the mirror method will send an If-Modified-Since header for an existing file to skip downloading if the file is unchanged. HTTP::Tiny doesn’t (yet) handle query parameters – you have to prepare those yourself, but for simple downloads, you don’t generally need those anyway.

Where did HTTP::Tiny come from? When I was working on getting CPAN.pm to support a pure-Perl HTTP bootstrap, I started with HTTP::Lite. When I discussed it on #p5p, Christian Hansen pointed out a number of serious shortcomings and decided that it would be easier for him to write a new, lightweight HTTP/1.1 client from scratch rather than try to redo the plumbing in HTTP::Lite.

The result is HTTP::Tiny and I’ve been collaborating with Christian to get it ready for use by CPAN.pm and ready for the Perl core. Unlike HTTP::Lite, HTTP::Tiny is a conditionally conforming HTTP/1.1 client. It supports both redirection and mirroring, which HTTP::Lite does not, both of which are important features for a CPAN client.

As a “Tiny” module, HTTP::Tiny achieves its HTTP/1.1 conformance in a just a fraction the code required for LWP::UserAgent and its non-core dependents. Don’t get me wrong – LWP::UserAgent is a great piece of software. But sometimes – like for the Perl core or for a fatpacked application – something much smaller and simpler will do just as well.

Let’s see what a difference there is. CPANdeps shows us all of LWP::UserAgent’s dependencies and highlights the ones that are non-core (as of Perl 5.10.1):

I used David A. Wheeler’s SLOCCOUNT to count lines of code in each of the distributions containing these modules as well as for HTTP::Lite and HTTP::Tiny (based on the “soon-to-be” 0.007 release). In all cases, I excluded files in t/ and examples directories. (Lines of Perl includes any programs distributed with the distribution.)

Here are the results:

Distribution        .pm files Lines (Perl) Lines (C)  Total Lines
-----------------   --------- ------------ ----------  -----------
libwww-perl-5.837       52        10258          0       10258
HTML-Parser-3.68         7          883       1972        2855
HTML-Tagset-3.20         1          139          0         139
URI-1.56                52         2715          0        2715
                     -----        -----      -----       -----
TOTAL LWP & friends    112        13995       1972       15967


Distribution        .pm files Lines (Perl) Lines (C)  Total Lines
-----------------   --------- ------------ ----------  -----------
HTTP-Lite-2.3            1          634          0         634
HTTP-Tiny-0.007          1          603          0         603

Wow! Both HTTP::Lite and HTTP::Tiny accomplish simple HTTP tasks with less than 4% of the SLOC of LWP and its non-core dependencies.

What about memory usage? Here’s the “null” case that shows memory usage of just loading the three client modules:

30440  5512 perl -MLWP::UserAgent -e 1 while 1
26700  3960 perl -MHTTP::Tiny -e 1 while 1
26040  3168 perl -MHTTP::Lite -e 1 while 1

HTTP::Lite is smallest, but it also doesn’t have the features or conformance I need.

That’s not much of a real-world test, so let’s try something else – downloading the 950K CPAN 02packages.details.txt.gz file. Here is the test code for HTTP::Tiny and LWP::UserAgent side by side:

# http-tiny-mirror.pl                  # lwp-useragent-mirror.pl
#!/usr/bin/env perl                    #!/usr/bin/env perl                                 
use strict;                            use strict;                                         
use warnings;                          use warnings;                                       
use HTTP::Tiny;                        use LWP::UserAgent;                                 
my ($url, $file) = @ARGV;              my ($url, $file) = @ARGV;                           
die unless $url & $file;               die unless $url & $file;                            
my $http = HTTP::Tiny->new;            my $http = LWP::UserAgent->new;                     
my $res = $http->mirror($url, $file);  my $res = $http->mirror($url, $file);               
die "Failed!\n"                        die "Failed!\n"                                     
  unless $res->{success};                unless $res->is_success;                          
1 while 1;                             1 while 1;                                          

What about HTTP::Lite? It doesn’t have a mirror method and doesn’t do redirection, which I get for free with HTTP::Tiny and LWP::UserAgent. I’d have to write dozens of lines of code to emulate that for a “fair” test, so I skipped it.

Let’s add those two programs (after downloading the 02packages file to files with different names) to our memory benchmarks:

50916  9824 perl ./lwp-useragent-mirror.pl [...]
35376  4348 perl ./http-tiny-mirror.pl [...]
30440  5512 perl -MLWP::UserAgent -e 1 while 1
26700  3960 perl -MHTTP::Tiny -e 1 while 1
26040  3168 perl -MHTTP::Lite -e 1 while 1

HTTP::Tiny wins – not by a huge amount in absolute terms, admittedly, but if you don’t need the extra features that LWP::UserAgent offers, HTTP::Tiny might just be all you need.

Thank you, Christian, for HTTP::Tiny!

•      •      •

If you enjoyed this or have feedback, please let me know by or