Know when and when not to write networking code.

Even though Perl has support for low-level socket programming, that doesn’t mean that you have to program at the low-level. For common protocols such as FTP, HTTP, POP3, or SMTP, you can use modules that come with Perl to handle the transport details. libnet, the distribution that comprises the basic protocols, comes with Perl since 5.8. Many other protocols, such as SSH, have implementations on CPAN. You can also install from CPAN many higher level libraries, such as LWP and WWW::Mechanize.

There are several reasons to avoid as much low-level work as you can. The foremost reason is that widely-used and heavily-tested libraries have not already made all of the mistakes that you are bound to make, but they’ve almost certainly fixed them. They probably handle all of the edge cases that you don’t even know about, so when your architecture changes, those widely-used modules easily handle the new setup. Is your hand-rolled code to talk to a remote server going to easily handle a proxy or SOCKS setup, or be able to switch from an unencrypted channel to SSL?

Unless you’re the sort of person who likes working in the low-level details, you’re simply wasting your time be reinventing the wheel. If you think your wheel is amazingly better, but you have to remember a lot of people think that. If you have the energy, consider contributing to your enthusiasm to the existing CPAN libraries.

As with everything in The Effective Perl, this isn’t a hard and fast rule. For some things, the higher level libraries don’t give you full access to the protocol, so you do need to work at a lower level.

Enough of that. You may still need some examples. These give you the flavor of what’s available, but that doesn’t mean they are appropriate for your particular situation. As with all modules, use them when they make sense and don’t use them when they don’t.

Simple file downloading

LWP::Simple can handle simple downloads for you:

use LWP::Simple;
my $page = get( 'http://www.perlfoundation.org/' );

That’s simple enough, but what if you need to store that resource somewhere? There’s an app for that:

use LWP::Simple;
getstore( 'http://www.perlfoundation.org/', 'tpf-index.html' );

This also works with FTP and many other protocols. LWP actually handles many other protocols that you might not consider to be part of the World Wide Web. Perhaps you need to fetch some images from a NASA FTP server:

use LWP::Simple;
getstore(
	'ftp://nssdcftp.gsfc.nasa.gov/photo_gallery/hi-res/planetary/moon/gal_moon_color.tiff',
	'awesome-moon.tiff'
	);

Sending email

There are many CPAN modules that can help you send email, and Email::Send is a general interface to many of them. In the constructor, you specify the implementor, in this case, SMTP, and the arguments to the implementor. Email::Send takes care of the rest:

use Email::Send;

my $message = <<'EMAIL';
To: [email protected]
From: [email protected]
Subject: I'm letting Email::Send send this

I did almost nothing to send this email.
EMAIL

my $sender = Email::Send->new({
	mailer      => 'SMTP',
	mailer_args => [ Host   => 'smtp.example.com' ],
	});

my $rc = $sender->send( $message );
unless( $rc ) {
	warn "Problem sending mail! $rc\n";
	}
else {
	print "Send mail just fine.\n";
	}

Unfortunately, Email::Send is superceded by Email::Simple, but here’s the same program:

use Email::Sender::Simple qw(sendmail);
use Email::Simple;
use Email::Simple::Creator; # an Email::Simple mixin 
use Email::Sender::Transport::SMTP;

my $transport = Email::Sender::Transport::SMTP->new({
	host => 'smtp.example.com', # you can also set this in %ENV
	});

my $email = Email::Simple->create(
	header => [
		To      => '"Buster Bean" ',
		From    => '"Mimi Bean" ',
		Subject => "Thanks for all the fish",
	],

	body => "I'm sending this with Email::Simple.\n",
	);

my $rc = eval { sendmail(
	$email,
	{ transport => $transport }
	) };

unless( $rc ) {
	warn "Problem sending mail! $@\n";
	}
else {
	print "Send mail just fine.\n";
	}

This “simpler” version is under active development, but it goes against most of the points we want to stress: you shouldn’t have to think about what most of this is doing or how email actually works, just like you don’t have to understand internal combustion to drive a car.

Programmatic control of a web browser

LWP is sufficient for simple web tasks, but when you have to interact with forms, click through several pages, and handle other actions you might expect from an interactive browser, WWW::Mechanize is the tool you should you. It tracks your path through a website, handles cookies, and lets you select and follow links.

Here’s an example that takes a module name, a starting line number, and an ending line number. It goes through these steps:

  • queries CPAN Search for the module,
  • finds the right link in the search results
  • follows the module link
  • looks for the “Source” link on that page
  • gets the content of that page
  • extract that line range
use WWW::Mechanize;

my( $module, $start_line, $end_line ) = @ARGV;
print "Looking for $module lines $start_line to $end_line\n";

my $mech = WWW::Mechanize->new;

$mech->get( 'http://search.cpan.org' );

$mech->submit_form(
	form_number => 1,
	fields      => {
		query   => $module,
		mode    => 'module',
		}
	);

my $module_links_ref = $mech->find_link( text => $module ) 
	or die "Did not find $module link\n";
$mech->get( $module_links_ref->[0] );

$mech->follow_link( text_regex => qr/\ASource\z/ )

my $content = $mech->content;

open my( $fh ), '<', \ $content;

while( <$fh> ) {
	next unless $. == $start_line .. $. == $end_line;
	printf "%d: %s", $., $_;
	}

You can run this program to get an extract of some lines from HTTP::Size:

$ perl mech HTTP::Size 10 20
Looking for HTTP::Size lines 10 to 20
10: 
11: =head1 SYNOPSIS
12: 
13:     use HTTP::Size
14: 
15:     my $size = HTTP::Size::get_size( $url );
16: 
17:     if( defined $size )
18:             {
19:             print "$url size was $size";
20:             }

This is only a small taste of the sorts of things that WWW::Mechanize can do.

Downloading new files from an FTP server

Sometimes you need to use a lower-level interface. For instance, if you want to sync a local directory with a remote FTP directory, you can use Net::FTP to connect to the remote directory, get the list of files in that directory, compare the modification time of the local file with the modification time, and download files that are newer on the remote side:

use Net::FTP;

my $host = 'nssdcftp.gsfc.nasa.gov';

my $ftp = Net::FTP->new( $host, Debug => 0 )
	or die "Cannot connect to $host: $@";

$ftp->login("anonymous",'-anonymous@')
	or die "Cannot login: ", $ftp->message;

$ftp->cwd( '/photo_gallery/hi-res/planetary/moon' );

foreach my $file ( $ftp->ls ) {
	if( (stat($file))[9] < $ftp->mdtm( $file )  ) {
		print "Fetching $file...\n";
		$ftp->get( $file );
		}
	else {
		print "$file up-to-date\n";
		}

	}

In this case, the Net::FTP module is the appropriate level of coding. You don’t have to deal with the FTP protocol directly, but you have enough flexibility to deal with the features that the protocol provides.

Low-level sockets

Perl has the built-in interface so you can minutely control TCP and UDP sockets if that’s what you need to do. However, since we’re trying to convince you not to do that, we’ll leave those bits as an exercise for the reader.

Things to remember

  • Work at the highest level possible and reasonable
  • Use one of the popular CPAN modules instead of reinventing your own.
  • Don’t create your own low-level implementations

3 thoughts on “Know when and when not to write networking code.”

  1. FYI, the maintainer for Email::Send has decided to replace it with the similarly named module Email::Sender. To quote from the Email::Send documentation, “It has API design problems that make it hard to usefully extend and rather than try to deprecate features and slowly ease in a new interface, we’ve released Email::Sender which fixes these problems and others.”

  2. I’ve added an Email::Simple example, but I think Ricardo took the interface in the wrong direction and that it’s actually harder to use and much more confusing. Most people don’t need to extend the basic interface so Email::Send should be much more straightforward. Really, sending email from within Perl should be as easy as sending it from an email program. You give it some addresses, a subject line, and a message. Something behind the scenes figures out where all the pieces go and how to represent them as objects without dragging the programmer into the weeds.

Comments are closed.