Perl CGI Framework
Dit is een vervolg-pagina op deel 1 en deel 2!
Op deze pagina
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.
- Plus nog misschien wat zaken om de tekenset goed in te stellen, bijvoorbeeld op utf-8, niet
hier maar zie verderop.
#!/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
programmaEen 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'"); }
UTF-8 Unicode
Als je speciale tekens aan wilt kunnen, als smileys, zal je programma
rekening moeten houden met Unicode. Dit is een
uitgebreide karakterset, met meer verschillende tekens dan in de 256 combinaties in een enkel byte
kunnen. Eén manier om dit te coderen is gebruik te maken van de 'UTF-8' encoding, waarbij indien nodig gebruik wordt
gemaakt van meer dan een byte. Zo is een Euro-symbool € het utf-8 teken U+20AC, wat neerkomt op de
bytes 0xE2 0x82 0xAC. Alle normale (7-bit) ASCII-tekens blijven hetzelfde (een ASCII file is dus
automatisch geldig utf-8 gecodeerde Unicode). Speciale tekens worden vervangen door twee tot vier
bytes, waarbij het eerste byte een speciale marker is.
Het mooie is dat Perl intern goed overweg kan met Unicode (en utf-8), en dat je er normaal niets
van merkt dat Unicode utf-8 tekens meerdere bytes zijn. In perl gelden ze gewoon als een teken
(tenzij je expliciet je data als binary benaderd). Wel moet je het duidelijk maken als je utf-8 ook
voor je I/O wilt gebruiken, een paar voorbeelden:
use utf-8; # Dit geeft alléén aan dat de Perl source
Unicode utf-8 gecodeerd is, zegt NIETS over file I/O!!
use open ':encoding(utf-8)'; # geeft aan dat álle files utf-8
zijn
if(open(MIJNFILE, $filename)) # dit is het alternatief per file
{
binmode MIJNFILE, ':encoding(UTF-8)';
print MIJNFILE "Unicode 😀 string ಠ_ಠ";
close MIJNFILE;
}
# Omzetten van ingevulde data in een cgi-form naar netjes escaped HTML met
Unicode:
use HTML::Entities;
# Hier zitten handige HTML - unicode
conversies in
$antwoord = escapeHTML(decode_entities($cgi->param('antwoord')));
Ps: met het Linux file commando kan je snel checken of een file ASCII, Unicode of
een andere codering (als latin-1, 256-bits ASCII) bevat.
Zenden van email
Ook handig: vanuit CGI files email verzenden. Vroeger gebruikte ik hiervoor de module
MIME::Lite, maar deze wordt helaas niet meer ondersteund. Er zijn verschillende alternatieven, maar
die staan niet standard bij mijn web-host geïnstalleerd. Dus, dan maar lekker low-level met behulp
van sendmail... Hier een voorbeeld, waarbij ik voor de eenvoud wat controles verwijderd heb,
zoals het testen op de juiste vorm van een email-adres. Gelijk een voorbeeld van een email in
simpel HTML-formaat:
sub
do_sendmail
# to, subject, HTML content --> error code
{
my $send_to = shift;
my $subject = shift || "Oops, onderwerp mist (systeemfout)...";
my $content = shift || "<p>Oops, boodschap inhoud mist
(systeemfout)...</p>";
my $from = "mijn.email.adres@mijnhost.nl";
$ENV{PATH} = "";
# suppress Insecure $ENV{PATH} while running with -T
my $sendmail= "/usr/sbin/sendmail"; # !! Vervang door het juiste pad !!
$sendmail = "|$sendmail
-t"; # maak er een pipe van
if(open(SENDMAIL,
$sendmail))
# open een pipe naar het sendmail programma
{
binmode SENDMAIL, ':encoding(UTF-8)'; # kijk, daar is de utf-8 weer...
print SENDMAIL "To: $send_to\n";
# eerst de sendmail header maken
print SENDMAIL "From: $from\n";
print SENDMAIL "Subject: $subject\n"; #
En ook de email wordt utf-8 gecodeerd
print SENDMAIL "Content-type: text/html; charset=utf-8\n";
print SENDMAIL "Content-Transfer-Encoding: 8bit\n\n"; # Einde header: 2x newline
print SENDMAIL "<html>\n";
print SENDMAIL '<head><meta http-equiv="content-type"
content="text/html; charset=utf-8"></head>';
print SENDMAIL "\n<body>\n";
print SENDMAIL $content;
print SENDMAIL "<p>Dit is een automatisch gegenereerde email\n";
print SENDMAIL "</p></body></html>\n";
return close(SENDMAIL);
# 1 = OK
}
return
0;
# failed...
}
Zorg wel dat het pad in $sendmail staat naar waar bij jou sendmail te vinden
is.
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.
|