Helping ordinary people create extraordinary websites!
GET OUR NEWSLETTER
Your Email:
 

Building Perl projects with MakeMaker

By Sean Dague
2005-05-01


Your First MakeMaker Project (make)

This sample MakeMaker project is inspired by postings on the perl5-porters mailing list by Colin McMillen concerning the Net::Ping modules. Ping is a very useful tool for determining whether or not a machine is accepting packets, but often you want to know more than that, like whether a Web server is actually responding on that machine. Also, in an era of ICMP attacks, many hosts have actually disabled returning standard ping packets, so it no longer is a useful indication of network connectivity. This project will be to build a program pingwww, which will look and act a lot like ping, but will actually be sending HTTP requests, and testing whether or not the Web server in question is functioning.

This project will contain a core module, Net::Ping::HTTP, which will implement the guts of what is happening, and pingwww, a program that provides a user interface to Net::Ping::HTTP.

Writing the Makefile.PL
I will begin by writing the Makefile.PL. At first it has only the most basic entries in it.
Makefile.PL for Net::Ping::HTTP, take 1

use ExtUtils::MakeMaker;

WriteMakefile(
'NAME' => 'Net::Ping::HTTP',
'VERSION_FROM' => 'lib/Net/Ping/HTTP.pm', # finds $VERSION
);
This specifies that the name of the project is Net::Ping::HTTP, and that the $VERSION variable contained within lib/Net/Ping/HTTP.pm will be considered the authoritative version of the project.

Now I have a Makefile.PL, and one other file in the project. It is now time to create the MANIFEST, so that the build process will work correctly.

The first pass at a MANIFEST will look as follows:

MANIFEST, take 1

MANIFEST

Makefile.PL
lib/Net/Ping/HTTP.pm
Remember, every time new files are added to the project, an entry for each of them must also be added to the MANIFEST file.Net::Ping::HTTP
The code for this module will be a simple layer on top of LWP::UserAgent, which is included in the libwww-perl package. Many flavors of Linux and UNIX include this library in their distribution by default. Also, if you have installed ActiveState Perl for Windows, you already have LWP::UserAgent. However, if you are unfortunate enough to be on an operating system that doesn't provide this module by default, you may still download it from CPAN (see Resources).

The strategy for Net::Ping::HTTP is simple. The HTTP protocol supports a method HEAD, which is designed to return only HTTP headers, but no content. Net::Ping::HTTP will send an HTTP HEAD request for the root document of the Web server you are attempting to ping, and will return the status code that is returned by that Web server. In the case where the Web server is available, this status code should be 200 (though in rare cases it might be a 30x or 40x code as well).

Here is a first pass at Net::Ping::HTTP, including the POD documentation for the module:

Net::Ping::HTTP library

package Net::Ping::HTTP;


=head1 NAME

Net::Ping::HTTP - An interface for determining whether an HTTP
server is listening

=head1 SYNOPSIS

my $pinger = new Net::Ping::HTTP;
my $rc = $pinger->ping('http://localhost');

my $pinger = new Net::Ping::HTTP(
TIMEOUT => 15,
PROXY => 'http://someproxy.net',
);
my $rc = $pinger-> ping('http://www.ibm.com');

=head1 DESCRIPTION

Net::Ping::HTTP is a simple interface on top of LWP::UserAgent
that lets you ping a Web server to see if it is responding to HTTP
requests. This would make it suitable for monitoring Web applications.

=head1 AUTHOR

Sean Dague <sldague@us.ibm.com>

=head1 SEE ALSO

L<perl>, <LWP::UserAgent>

=cut

use strict;
use LWP::UserAgent;
use HTTP::Request;
use vars qw($VERSION);

$VERSION = '0.02';

sub new {
my $class = shift;
my %this = (
PROXY => undef,
TIMEOUT => 10,
@_,
);

my $ua = new LWP::UserAgent("Net::Ping::HTTP - $VERSION");
$this{UA} = $ua;
$this{UA}->timeout($this{TIMEOUT});
if($this{PROXY}) {
$this{UA}->proxy('http',$this{PROXY});
}

return bless \%this, $class;
}

sub ping {
my ($this, $url) = @_;

my $request = new HTTP::Request('HEAD',"$url");
my $response = $this->{UA}->request($request);

return $response->code();
}

1;
Net::Ping::HTTP uses both LWP::UserAgent, and HTTP::Request, and will not work without them. Instead of having a user install this module, then have it break because the dependencies aren't there, the module can specify which other modules it requires to run.

These modules can be added as requirements to the Makefile.PL. Their existence will be verified before the master Makefile is generated. The format of this new variable for Makefile.PL is as follows:

Makefile.PL prerequisites

'PREREQ_PM' => {

Module::Name => minimum.version.number,
...
},
The code works with the version of LWP::UserAgent and HTTP::Request that I have installed on my workstation, so I will prereq those versions in my module. To determine those versions, I can run the following commands on the command line:

Finding module versions

perl -MLWP::UserAgent -e 'print $LWP::UserAgent::VERSION'

perl -MHTTP::Request -e 'print $HTTP::Request::VERSION'


The -M flag from perl loads that Module, the -e flag runs the following string as the perl script.

With the information collected, the Makefile.PL looks as follows:

Makefile.PL take 2

use ExtUtils::MakeMaker;


WriteMakefile(
'NAME' => 'Net::Ping::HTTP',
'VERSION_FROM' => 'lib/Net/Ping/HTTP.pm', # finds $VERSION
'PREREQ_PM' => {
LWP::UserAgent => 1.73,
HTTP::Request => 1.27,
});
pingwww
The core library for the project is not complete. For it to be useful, there must exist a program that exploits its functionality and provides a user interface. This program will be called pingwww and will be placed in the bin/ directory of the project tree.

Two features of the ping command that would be nice to simulate in pingwww are the ability to specify the number of times ping sends packets out to the server before it exits, and a synchronous display of how long each request took to respond.

To add these features, the program will need two additional modules that were not required for Net::Ping::HTTP. These modules are Getopt::Std, which parses command line options, and Time::HiRes, which provides microsecond resolution timing. Getopt::Std is part of the core Perl distribution, so there is no need to add it as a PREREQ_PM, however Time::HiRes is not (though it appears that it will be part of the standard library as of Perl 5.8). Time::HiRes is hence added to the PREREQ_PM variable in Makefile.PL.

Here is the first pass at pingwww:

pingwww program

#!/usr/bin/perl

#
# This program is licensed under the same terms as Perl itself
#
# Sean Dague <sldague@us.ibm.com>

=head1 NAME

pingwww - program for "pinging" webservers

=head1 SYNOPSIS

pingwww [-c iterations] [-p proxy] [-t timeout] [-h] hostname

=head1 DESCRIPTION

Pingwww uses the Net::Ping::HTTP module to send HEAD requests
to the host specified by hostname.

=head1 AUTHOR

Sean Dague <sldague@us.ibm.com>

=head1 SEE ALSO

L<Net::Ping::HTTP>, L<Time::HiRes>, L<perl>

=cut

use strict;
use Carp;
use Getopt::Std;
use Net::Ping::HTTP;
use Time::HiRes qw(gettimeofday tv_interval);

$| = 1;
my %opts;

getopt('cpth',\%opts) or croak("Couldn't parse options");

if($opts{h}) {
usage();
}

my $max = $opts{c} || 1000;

my %vars = ();

if($opts{p}) {
$vars{PROXY} = $opts{p};
}

if($opts{t}) {
$vars{TIMEOUT} = $opts{t};
}

my $pinger = new Net::Ping::HTTP(%vars);
my $host = $ARGV[0];
my $packedip = gethostbyname($host);
my $ip = inet_ntoa($packedip);

print "PING HTTP $host ($ip):\n";

for(my $i = 0 ; $i < $max; $i++) {
my $start = [gettimeofday()];
my $rc = $pinger->ping("http://$host");
if ($rc < 400) {
print "Response $rc from $ip: ";
my $elapsed = tv_interval ( $start );
print "$elapsed seconds\n";
} else {
print "Response $rc from $ip: ";
print "failed\n";
}
}

sub usage {
print <<USAGE;
usage: $0 [-c number of iterations] [-p proxy] [-t timeout] [-h] hostname
where
-h : this message
-c : number of iterations to ping the host. Defaults to 1000
-p : http proxy to use for the ping
-t : timeout value. Defaults to 10 secs if none is specified

USAGE

exit(1);
}
Because EXE_FILES includes pingwww, some interesting events happen during installation, which I will discuss later. The MANIFEST file must also contain an entry for the new file. The new MANIFEST file is shown below:

Manifest, take 2

MANIFEST

Makefile.PL
lib/Net/Ping/HTTP.pm
bin/pingwww


Tutorial Pages:
» The Module that Makes Makefiles and Much More
» Programming Products vs. Programs
» Anatomy of a MakeMaker Project
» Your First MakeMaker Project (make)
» Building Test Cases (make test)
» Installation (make install)
» Distributing Your Code (make dist)
» Conclusion
» Resources


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» Random subroutines in Perl
» Log Script Use
» Creating Perl Modules for Web Sites
» Bit Vector, Using Perl Vec
» Build a Perl/CGI Voting System
» Perl Range Operator

Advertise with Us!


Tutorials Scripts Web Hosting Developer Manuals
Resources