Irrational Exuberance!

Intro to HTTP::Server::Simple::CGI

December 5, 2008. Filed under perl

Last week I needed to create a persistent Perl service to listening for incoming files and then process them. For the protocol I went with HTTP, because it is more than adequate to meet my humble needs. So all I needed was (A) an implementation of an HTTP server in Perl, and (B) a clue how to use it.

Solving the first half was pretty easy, and I quickly fell upon the HTTP::Server::Simple. From there I realized that it would be easiest to write my service using its CGI specific subclass HTTP::Server::Simple::CGI.

Installation was typically painless:

sudo perl -MCPAN -e shell
install HTTP::Server::Simple::CGI

And then I was ever so slightly stuck. There is documentation on how to create a simple CGI server, but it wasn't quite enough to get me to the service I needed.

The skeleton of creating a server looks like this:

#!/usr/local/bin/perl -w

package MyServer;
use strict;
use HTTP::Server::Simple::CGI;
use base qw(HTTP::Server::Simple::CGI);

my $port = 4569;

sub handle_request {
  my ($self, $cgi) = @_;
  return "Hi.";
}

my $pid = MyServer->new(4116)->background();

This is quite simple and concise, but then again our server isn't actually doing anything useful yet. That's where things get a bit less obviously documented, especially if you're unfamiliar with the CGI module, which is they key that unlocks all mysteries.

Here are a few recipes for writing the handle_request subprocess.

Vary Response On HTTP Method

sub handle_request {
  my ($self, $cgi) = @_;
  my $method = $ENV{REQUEST_METHOD};
  my $timestamp = time();
  if (lc($method) eq 'post') {
    print "Received POST request at $timestamp";
  }
  else {
    print "Received request of type $method at $timestamp";
  }
}

Receive POSTed File

sub handle_request {
  my ($self, $cgi) = @_;
  my $method = $ENV{REQUEST_METHOD};
  if (lc($method) == 'post') {
    my $file = $cgi->param('POSTDATA');
    open OUT '>file.xml' or warn $!;
    print OUT $file;
    close OUT;
    print 'Received data.';
  }
  else {
    print 'Please POST a file.';
  }
}

I've used the above code to handle rather large files (over 100 megs), but I strongly suspect that it wouldn't scale to multiple requests, but for a simple backend service the above code actually--somewhat to my surprise--behaves nicely.

Forking a Process for Requests

sub handle_request {
  my ($self, $cgi) = @_;
  my $pid = fork();
  if ($pid == 0) {
    exec('perl some_other_script.pl');
  }
  print 'Forked additional process.';
}

I think this approach is helpful, because it makes it easier to use a simple Perl server to manage your other processes. For example, you might write an XML parser in Java or C for speed, and then save a posted file (like the above example) and then fork a new process to run the parsing script.

Fin

All in all, HTTP::Server::Simple has been another very positive CPAN experience for me, and it is definitely the CPAN experience that is keeping me learning and playing with Perl. I do find the quality of documentation to be rather uneven (XML::Twig has an insurmountably large amount of documentation), and there are many fewer people randomly writing blog entries about Perl module X than you'd find in the Python or Ruby spheres. Hopefully this entry will turn up in some lost soul's search results.