Perl CGI Framework

Al met al heb ik ondertussen een beetje een standaard structuur in al mijn CGI scripts, om snel andere scripts op te zetten (zoals voor mijn Sinterklaaslijstjes). Hier wat uitleg over de delen die de basis vormen.

Begin van het CGI script

Hier wordt het script opgezet. Een aantal zaken zijn hier van belang:

  • Het zetten van de -T optie, vanuit het punt van veiligheid (zie mijn vorige CGI pagina, sectie over veiligheid)
  • Ook zet ik waarschuwingen altijd aan, om programmeerfoutjes snel te vinden
  • Dan volgt er een stuk commentaar, dat ook als helptekst gebruikt kan worden (bij uitvoer van het programma met de -help of -? optie, waar dan ook vervolgens op getest wordt). Dit kan normaal alleen via de command line, niet als cgi in een web page.

#!/usr/bin/perl -T
# Framework for new CGI scripts, Kees Moerman (c) 2004-2010

# set some security and programming mistake preventions
# (note also -T option in line 1)

use warnings; # comparable to -w option
use strict; # don't forget to prefix vars with my

my 1 = "v2.0.0019"; # version number tracking
my $help_text = <<HELP; # help text is placed below, up to HELP

# $0 version 1, Kees Moerman (c) 2004-2010
#
# Syntax: run as cgi script on web server, not via command line
#
# Put a more extensive desription here...
#
# Structure of the program is a kind of state machine,
# with the current state (plus machine state) encoded into the forms
# (as the cgi protocol is stateless).
#

HELP
$help_text =~ s/^# ?//gm; # clean up comment characters

#
# Change list:
# ....

#
# To do:
# ....


# call from command line possible, e.g. 'script.pl a=1 b=2'
# run from command line with help request?

if (defined $ARGV[0] && ($ARGV[0] eq "-?" || $ARGV[0] eq "-h" || $ARGV[0] eq "-help"))
{ print $help_text;
exit 1;
}

Opzetten van de CGI module

Het opzetten van de CGI module, ook weer met wat veiligheidsinstellingen, maar ook dusdanig dat foutmeldingen ook in de browser terecht komen, in plaats van in een of andere duistere server log file.

# set up CGI module and supporting items, esp for error handling
# This way, we do not have to look through server logs to find warnings

use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use CGI qw/:standard -no_xhtml/; # load standard CGI routines
$CGI::POST_MAX=1024 * 10; # max 10K size posts
$CGI::DISABLE_UPLOADS = 1; # no uploads allowed

warningsToBrowser(1);
BEGIN { warningsToBrowser(1); } # want warning as HTML comments

# if you need only one CGI object, you can use the function form instead
my $cgi = new CGI;

# cgi caller error handler
my $error = $cgi->cgi_error;
if ($error) # any error already while opening script?
{ # then try to make the best of it
print $cgi->header(-status=>$error),
$cgi->start_html('Problems'),
$cgi->h2('Request not processed'),
$cgi->strong($error);
exit 1;
}

Foutafhandeling in het programma

Een routine om ook zelf duidelijke foutmeldingen te kunnen genereren (met name de routine die_html). Wel even aanpassen aan je eigen email adres.

my $html_header_sent = 0; # no cgi header send yet? (for die_html)

sub die_html # fatal error: generate HTML error message
{
my $message = shift || "no message passed";
my $program = $0; $program =~ s@.*/@@; # remove path
my CGI framework = "Error in '$program'";

if(!$html_header_sent)
{
$html_header_sent = 1;
print header(); # CGI header: simple document type
print start_html(-title => CGI framework, -BGCOLOR=>'#F0FFFF');
}
print
h1(CGI framework), "\n",
p("Error message: $message"), "\n",
p("Please contact the web master of these web pages:\n",
a({href=>("mailto:yourmailaddress\@yourdomain.nl")})
), "\n",
end_html(); # end of HTML page

if(open LOGFILE, ">>logfile.txt")
{ print LOGFILE "Fatal error detected in '$program' at " . localtime()
. " line $., message is '$message', \$\@ = '$@', \$! = '$!'\n";
close LOGFILE;
}

exit(0);
}

De dispatcher

Complexe CGI scripts (bijvoorbeeld met veel forms) worden vaak aangeroepen vanuit verschillende fases, bijvoorbeeld het initiële scherm met de vraag om in te loggen, een scherm met een invulformulier, en een scherm met de resultaten van het opsturen van een formulier. Dit kunnen ieder verschillende CGI programma's zijn, maar dan moet je je programma verdelen over verschillende scripts.

In plaats hiervan programmeer ik mijn complexere CGI functies vaak als een soort state machine, bijvoorbeeld met states 'login' (het inlogscherm met login form), 'select' (met de main form), en 'add' (bevestigingsboodschap). De state wordt meegegeven in de cgi aanroep als een GET/POST argument (zie de vorige pagina's), in de vorm van action=nextstate. De dispatcher zorgt er vervolgens voor dat de juiste routines worden aangeroepen. Hier een voorbeeld uit mijn Sinterklaaslijstjes programma.

print $cgi->header(); # CGI header: simple document type
print my_html_header(); # does the title, etc
$html_header_sent = 1;

# State machine core state diagram:
# Next state is passed through the action parameter in the calling form
# ....


my $cgi_action = $cgi->param('action') || 'unknown';
if ($cgi_action eq 'unknown'){ sint_login(); }
elsif ($cgi_action eq 'login') { sint_login(); }
elsif ($cgi_action eq 'select') { sint_select(0); }
elsif ($cgi_action eq 'add') { sint_own(0); }
elsif ($cgi_action eq 'buy') { sint_buy(0); }
else
{ die_html("Unknown command '$cgi_action'"); }

Verdere functies

Daarnaast heb ik nog standaard routines om HTML uit een template te plukken, configuratiefiles te lezen en te schrijven, en dergelijke, om snel nieuwe scripts in elkaar te kunnen draaien. Maar, dat heeft niets meer te maken met de CGI problematiek, is meer algemene Perl vaardigheden, dus die ga ik hier niet verder beschrijven.

Meer informatie