jim.shamlin.com

PERL Script Template

For just about every Perl script I need to write, there are some common tasks that need to be done, so I put together a "core script" that I use as a template to save myself a bit of time.

Oh, and by the way ... the material below explains how it works, but you need a fundamental understanding of PERL to make any sense of it.


What it Does

It's just a template, doesn't do anything, but there are a few thigns that are stubbed out to save some time with these basic tasks:

In most cases, I find myself stripping out some of the functions that aren't needed and adding code to make others more suitable to a specific purpose. It's just a starting point.


Load configuration variables

The top section of the script starts with this marker:

################################################################################
### SERVER SPECIFIC PARAMETERS #################################################
################################################################################

My practice and recommendation is to use that area of the script to store any configuration parameters for the script as well as the path to any file you might need to reference. Having it all in one place, makes it a lot easier if you ever need to move the script to a different server, as opposed to having to comb the code for every reference that needs to be changed.


Display debugging data

While developing a script, or trying to figure out what went wrong, it helps to display information to screen that you don't want the user to see when the script goes live.

  $debugger = 1;
  $debug = "";

The first line turns the debugger "on" or "off" so that you can easily ... well ... turn it on (for development and testing_ and off (for public use) and back on again (for problem-solving) without having to comb the code.

As you're developing the script, append value to $debug to display it to screen, like this:

sub someThing{
  $debug .= "The someThing subroutine executed";
  }

And that will be displayed at the bottom of the screen.


Read in user inputs

In most instances, your script will need to read input from the user (or passed on the query string), and this bit of code does that elegantly:


if($ENV{'CONTENT_LENGTH'}){
  read(STDIN, $in, $ENV{'CONTENT_LENGTH'});
  $debug .= "<P>METHOD: POST</P>";
  }elsif($ENV{'QUERY_STRING'}){
  $in = $ENV{'QUERY_STRING'};
  $debug .= "<P>METHOD: GET</P>";
  }else{
  $debug .= "<P>NO INPUT RECEIVED</P>";
  &startUp();
  }

$debug .= "\n\n<TABLE BORDER>";
@in = split(/[&;]/,$in);
foreach $i (0 .. $#in) {
   $in[$i] =~ s/\+/ /g;
   ($key, $val) = split(/=/,$in[$i],2);
   $key =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
   $val =~ s/%([A-Fa-f0-9]{2})/pack("c",hex($1))/ge;
   $in{$key} .= "\0" if (defined($in{$key}));
   $in{$key} .= $val; 
   $debug .= "<TR><TD>$key</TD><TD>$val</TD></TR>";
   }
$debug .= "</TABLE>\n\n";

After running this, all values provided by the user are stored in an %in hash for easy access.

The script also creates some useful data for debugging - the METHOD by which the data was sent, and a table showing all keys and their respective values.

And as an added bonus, if no data was received, it kicks off a startUp function. You may want to strip that out if it's not needed, but I find it convenient to build an input form (such as a search form) into a script for testing purposes rather than relying on a separate HTML file.

Also, if you want to require a METHOD (to keep the data hidden and discourage abuse), just strip out the one you don't need or add a function reference to something that handles the error elegantly.

One last thing worth mentioning - if you're using an unusual METHOD to send data, or specifying a different ENCTYPE than the default, this won't work .... you'll just have to hack through it yourself


Handle system errors gracefully

There are a few thigns that might happen in the execution of a script that will cause problems if you allow the script to keep running ... or there may just be a condition in which you need to stop immediately rather than allow the script to finish executing. Here's how I handle that:


sub scriptError{
  $o .= "<H1>Error Type $_[0]</H1>\n\n<P>";
  $o .= "EOF error - no data returned." if ($_[0] eq '1');
  $o .= "Invalid authentication paramter." if ($_[0] eq '2');
  $o .= "Missing action paramter" if ($_[0] eq '3');
  $o .= "Invalid paramter $in{'a'}" if ($_[0] eq '4');
  $o .= "" if ($_[0] eq '5');
  $o .= "" if ($_[0] eq '6');
  $o .= "" if ($_[0] eq '7');
  $o .= "" if ($_[0] eq '8');
  $o .= "" if ($_[0] eq '9');
  $o .= "" if ($_[0] eq '10');
  $o .= "</P>\n\n";
  &display($o);
  }

With this function in place, you can throw a numeric error with a brief message by using something like &scriptError(5);. If you make the errors unique, you should be able to jump directly to the place in the code where things are going awry. The example here comes preloaded with a few common errors that I use in just about every script.

Oh, and yes, the user experience crowd are probably gnashing their teeth just now, in anticipation of giving me a lecture to inform me that throwing an error code and a cryptic message makes their precious users cry bitter tears. I already know that - and if the error is something the user can take action to prevent, I recommend returning a user-friendly message to the screen with advice on how not to screw up again. I only use this in instances where it's an error that the user can't do anything to correct, and the only thing I can tell them is that they're hosed. Actually, I sometimes just tell them that they're hosed and there's nothing they can do to correct it.

But for development purposes, I generally shunt any error to this function, and later review them to decide which errors should be handled a bit more gracefully and develop a more user-friendly response.

Depending on how important it is, you might also want to add to this function to write data to a debug log file or send yourself an e-mail ... that's a bit beyond the scope of a template script.


Dissuade abuse from external sources

This may fall into the category of being more cautious than is necessary (or to others, not aggressive enough), but I've had incidents where someone hijacked a script by copying a form from my site and pasting it to theirs (stealing my bandwidth/processing). And so, I've come up with a quick stopper for that kind of thing.

In the config variables, set up an authentication code ...

$code = "lsakjdfh78";

Then, build that code as a hidden value into the HTML form and check it right after parsing user input:

&scriptError(2) unless ($in{'c'} eq $code); 

If you detect an instance of abuse, just change the code to something else and reload the script. If you're even more paranoid than I am, then you can move the code to an external file and use cron to change it more frequently. or do something even more drastic. In my experience, extreme measures haven't been necessary in most instances.


Execute code based on input parameter

There's really no need to organize your code into functions in PERL, but if you're a crusty old C hacker, you probably miss main() - and it's a good way to organize things even if you aren't. And so, I set up a function splitter, like this:

&scriptError(3) unless ($in{'a'});

&doThis if ($in{'a'} eq "one");
&doThat if ($in{'a'} eq "two");

&scriptError(4);

This enables you to pass a hidden parameter in the HTML form called a that will specify which action to take. So you can build a script that performs multiple tasks (or steps in a sequence) and indicate which task should be run by the action parameter that is passed.


Print output to screen in a template

A single function can be used to print any output to the screen, which saves writing print commands into multiple functions - just load all output into a variable and pass it to the display function:

sub display{
  print "Content-type: text/html\n\n";
  $switch = 0;
  open (TMP,"template.html");
  while (<TMP>){
    chomp ($_);
    if ($_ eq '<!-- HERE -->'){
        print $_[0];
        }else{
        print "$_\n";
        }
     }
  close(TMP);
  print "<HR>$debug" if ($debugger); 
  exit;
  }

This function depends on there being an HTML template that contain the design elements of the page with an HTML comment (<!-- HERE -->) on its own line where the content should appear, which saves coding the design elements into the Perl script.


Exit gracefully if nothing happens

I end the script template with this:

################################################################################
######################################################################### EOF ##
&scriptError(1); ###############################################################

It indicates the end of the script for me, and throws a script error if the program somehow got to this point and didn't end with returning something to screen. That should never happen ... but "should" is the word that keeps us up nights, so better to be safe than sorry.


And finally

This is online for my own use and reference, but feel to snag it if you think it would be useful. It's a trifle and I don't expect to be credited or compensated in any way ... but nor does it come with any sort of guarantee.