ioProgrammo N°7, Settembre 1997 ©Copyright DIEMME Editori
METODI RICHIESTE E CGI-LIB.PL
Entriamo nel vivo della programmazione per Internet analizzando in dettaglio i metodi e le richieste CGI. Concluderemo con un esempio completo di scripting in cui verrà illustrato l'uso di cgi-lib.pl, una delle più famose librerie per gli script di gateway
Quando il server web esegue un programma cgi il passaggio dei dati da elaborare può avvenire in uno dei tre modi seguenti: come argomenti passati sulla riga di comando, come stringa codificata nella variabile d'ambiente QUERY_STRING, o sullo standard input.
Nel primo caso si usa un'interrogazione ISINDEX ovvero una chiamata diretta dal browser al gateway, separando gli argomenti (parole-chiave) con il segno +, così
http://localhost/cgi-bin/isindex.cgi?uno+due+tre
Lo script sarà molto semplice. Si dovrà soltanto estrarre gli argomenti dall'array @ARGV.
#!/usr/bin/perl
print "Content-type: text/html\n\n";
if (@ARGV > 0) {
print "<ol>\n";
foreach $chiave (@ARGV) {
print "<li>$chiave\n";
}
print "</ol>\n";
}
print "<isindex>\n";
Gli ultimi due casi si adattano rispettivamente ai metodi GET e POST.
IL METODO GET
Viene così chiamato perché il browser usa il comando GET del protocollo HTTP per inviare i dati di interrogazione al server. Questi dati vengono prima codificati, poi passati all'interno di un URL ed infine memorizzati nella variabile d'ambiente QUERY_STRING.
La codifica è necessaria perché l'utente può introdurre nel form dati arbitrari e non tutti i caratteri ascii sono leciti in un URL. Per esempio non si possono usare i caratteri < e >, lo spazio viene sostituito con un +, e i caratteri speciali come / + : etc vengono codificati con un simbolo di % seguito da due cifre esadecimali.
Inoltre i dati immessi dall'utente nei campi del modulo vengono separati dal carattere &.
http://localhost/cgi-bin/modulo.cgi?nome=pinco&eta=20&indirizzo=via+roma+1
Ecco perché serve una libreria di parsing che in sostanza decodifica il contenuto di QUERY_STRING.
IL METODO POST
E' il metodo più usato per inviare dati tramite form. Rispetto al GET consente di trasferire le richieste direttamente sullo standard input e l'unica variabile cgi impostata è CONTENT_LENGTH che contiene il numero di dati passati.
L'OUTPUT CGI
Un programma cgi può restituire al client svariati tipi di documenti. Immagini, pagine html, pagine di puro testo, addirittura filmati video e brani musicali. Può inoltre restituire documenti presenti su altri server web sparsi qua e là su Internet. Ovviamente il browser deve sapere quale è il tipo di oggetto restituito, e ad informarlo ci pensa proprio il gateway tramite una richiesta diretta al server.
Questa richiesta è in sostanza una istruzione print che stampa sullo standard output il tipo MIME del documento da restituire seguito da una linea vuota (un \n in più).
In pratica la prima istruzione che apre l'invio dei dati verso il browser è
print "Content-type: tipo/sottotipoMIME\n\n";
Nel caso quindi che il mio cgi restituisca una pagina html, l'istruzione sarà
print "Content-type: text/html\n\n";
seguita da una serie di print di tag html.
Se invece volessi restituire solo testo, dovrei scrivere
print "Content-type: text/plain\n\n";
seguito da alcuni print di testo semplice.
Infine se avessi bisogno di inviare al browser un documento non presente sul server in cui gira il mio programma cgi, dovrei scrivere
print "Location: http://indirizzo/pagina.html\n\n";
Quest'ultima istruzione può essere usata anche per riferirsi a documenti locali usando come indirizzo la stringa localhost o semplicemente il path di una directory locale, purché sia vista dal server web.
Si noti sempre il doppio \n alla fine della prima istruzione print: non ce ne dimentichiamo perché è fondamentale.
CGI-LIB.PL
Scritta da Steven E. Brenner e giunta alla versione 2.14, cgi-lib.pl è la libreria per il parsing cgi più nota.
Gestisce in modo del tutto trasparente e con un'unica funzione di nome ReadParse le richieste ISINDEX, ISMAP ed di metodi GET e POST.
ReadParse per default restituisce l'array associativo %in che contiene le parole-chiave o le coppie indice-valore dei campi del form html. L'indice è il valore del parametro NAME del form, mentre il valore è il contenuto del campo INPUT, SELECT o TEXTAREA. In quest'ultimo caso le linee di testo scritte nel campo vengono separate con il carattere \0. Sarà poi nostro compito sostituire il \0 con dei tag <BR> o con dei \n.
Se a ReadParse viene passata una variabile "glob" (*nomevariabile) i dati saranno memorizzati nell'array associativo identificato dalla variabile stessa e non in %in.
Nel nostro esempio la funzione restituirà l'array %CGI poiché useremo la chiamata &ReadParse(*CGI), ma prima si dovrà includere il file cgi-lib.pl nello script con la funzione require del Perl.
LA RICHIESTA ISINDEX
Poiché la variabile QUERY_STRING è impostata anche per le richieste ISINDEX, è possibile usare cgi-lib per processare gli argomenti passati sulla riga di comando, senza usare l'array @ARGV: comodo no?
Ecco lo script islib.cgi
#!/usr/bin/perl
require "cgi-lib.pl";
&ReadParse;
print "Content-type: text/html\n\n";
if (%in) {
print "<ol>\n";
foreach $chiave (split(' ',(keys(%in)) [0])) {
print "<li>$chiave\n";
}
print "</ol>\n";
}
che verrà caricato da Netscape con http://localhost/cgi-bin/islib.cgi?uno+due+tre
LA RICHIESTA ISMAP
Viene usata quando si vuole utilizzare una immagine cliccabile. Le coordinate del punto su cui si clicca vengono passate al programma cgi separate da un punto e virgola (x;y). Per convenzione il vertice in alto a sinistra dell'immagine ha coordinate 0;0.
Per usare questo tipo di richiesta si usa un tag HREF con all'interno un tag IMG
<A HREF="http://localhost/imgmap.pl">
<IMG SRC="immagine.gif" ISMAP>
</A>
e poi si sfrutta cgi-lib.pl per recuperare i valori di x e y
#!/usr/bin/perl
require "cgi-lib.pl";
&ReadParse;
print "Content-type: text/plain\n\n";
($x,$y)=split(',',(keys(%in)) [0]);
print "$x, $y\n";
Per motivi di spazio l'esempio è volutamente limitato. Le coordinate x e y si dovrebbero usare con delle istruzioni if in modo da ottenere una certa risposta in base al punto dell'immagine su cui si clicca.
LA RICHIESTA FORM
E' la richiesta più usata per consentire l'invio di dati tramite moduli da compilare.
Si inizia col predisporre un form html il cui parametro ACTION punta esattamente al cgi che dovrà elaborare i dati inviati, così
<HTML>
<HEAD>
<TITLE>Modulo</TITLE>
</HEAD>
<BODY>
<FORM METHOD="POST" ACTION="http://localhost/cgi-bin/modulo.cgi">
<FONT SIZE=10><B>Io</B><FONT SIZE=5> e <FONT SIZE=10><B>Linux</B></FONT>
<HR><P>
Nome: <INPUT TYPE="text" NAME="nome" VALUE="" SIZE=20 MAXLENGTH=30>
Eta': <INPUT TYPE="text" NAME="eta" VALUE="" SIZE=2 MAXLENGTH=2><P>
Utilizzo Linux per:
<SELECT NAME="uso">
<OPTION SELECTED>Hobby
<OPTION>Studio
<OPTION>Lavoro
<OPTION>Altro
</SELECT><P>
Cosa penso di Linux?<BR>
<TEXTAREA NAME="penso" VALUE="" COLS=40 ROWS=2></TEXTAREA><P>
Stampo i dati?
<INPUT TYPE="radio" NAME="stampo" VALUE="si" CHECKED><I>Si</I>
<INPUT TYPE="radio" NAME="stampo" VALUE="no"><I>No</I><P>
<INPUT TYPE="submit" NAME="ok" VALUE="OK">
<INPUT TYPE="reset" NAME="cancel" VALUE="Annulla">
</FORM>
</BODY>
</HTML>
Editiamo le righe precedenti nel file modulo.html e lo salviamo nella DocumentRoot (la directory che contiene tutte le pagine html pubbliche) del server. Ora lo possiamo aprire con Netscape.
Successivamente si passa alla scrittura del codice Perl.
Lo script è composto da tre subroutine principali: &Controlli, che esegue un controllo sui principali errori di compilazione del modulo;
&StampaModulo, usata per restituire il modulo compilato al client
in modo che l'utente possa stamparlo; &InviaModulo, che spedisce via e-mail i dati a colui che ha predisposto il form.
#!/usr/bin/perl
require "cgi-lib.pl";
&ReadParse(*CGI);
&Controlli;
if ( $CGI{"stampo"} eq "si" ) {
&StampaModulo;
}
&InviaModulo;
sub Controlli {

Ecco il primo if che controlla se il campo "nome" contiene qualcosa. In caso negativo verrà restituito un messaggio di errore
if ( $CGI{"nome"} eq "" ) {
&Errore("Non hai inserito il tuo nome");
exit;
}

Qui invece con una semplice espressione regolare si verifica se il nome immesso contiene numeri o caratteri speciali
if ( $CGI{"nome"} !~ /^[a-zA-Z' ]*$/ ) {
&Errore("Il nome non può contenere numeri o caratteri speciali");
exit;
}
Poi si prosegue con i controlli sul campo "eta"
if ( $CGI{"eta"} eq "" ) {
&Errore("Non hai inserito la tua eta'");
exit;
}
if ( $CGI{"eta"} !~ /^[0-9]*$/ ) {
&Errore("L'eta' non può contenere lettere o caratteri speciali");
exit;
}
}
La subroutine &Errore riceve come parametro una stringa contenente il tipo di errore commesso e prepara la pagina html da restituire al client
sub Errore {
local ($errore) = @_;
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD><TITLE>Errore</TITLE></HEAD>\n<BODY bgcolor=#ffffff>\n";
print "<H1 align=center>Errore nella compilazione del modulo</H1>\n";
print "<H2 align=center>$errore</H1>\n<P align=center>\n";
print "<A HREF=\"http://localhost/modulo.html\">Ritorna al form</A>\n";
print "</BODY>\n</HTML>\n\n";
}

StampaModulo invece invia a Netscape tutti i dati inseriti nel form, ma solo nel caso in cui l'utente abbia scelto l'opzione "Si" alla richiesta "Stampo i dati?".
sub StampaModulo {
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD>\n";
print "<TITLE>Modulo</TITLE>\n";
print "</HEAD>\n";
print "<BODY bgcolor=#ffffff>\n";
print "<H1 align=center>MODULO INVIATO!</H1>\n
<FONT SIZE=10><B>Io</B><FONT SIZE=5> e <FONT SIZE=10><B>Linux</B></FONT>\n
<HR><P>\n
<B>Nome: </B>$CGI{\"nome\"}<P>\n
<B>Eta': </B>$CGI{\"eta\"}<P>\n
<B>Utilizzo Linux per: </B>$CGI{\"uso\"}<P>\n
<B>Cosa penso di Linux:</B><P>\n";
foreach (split("\0", $CGI{"penso"})) {
s/\n/<br>\n/g;
print "$_\n";
}
print "</BODY>\n</HTML>\n\n";
}

Lo script si conclude con una chiamata al programma sendmail per inviare via posta elettronica i dati del modulo. Supponendo che sia stato root a predisporre il servizio cgi la mail sarà quindi inviata a root@localhost. Per finire si noti il controllo sull'esecuzione del comando sendmail con la funzione die.
sub InviaModulo {
$sendmail = "/usr/bin/sendmail -bs -i";
open (EMAIL, "|$sendmail >/dev/null") || die "Cannot exec mail command";
print EMAIL "mail from: modulo.cgi\n";
print EMAIL "rcpt to: root\@localhost\n";
print EMAIL "data\n";
print EMAIL "From: modulo.cgi\n";
print EMAIL "To: root\@localhost\n";
print EMAIL "Subject: Io e Linux\n\n\n";
print EMAIL "Nome: $CGI{\"nome\"}\n\n";
print EMAIL "Eta': $CGI{\"eta\"}\n\n";
print EMAIL "Utilizzo Linux per: $CGI{\"uso\"}\n\n";
print EMAIL "Cosa penso di Linux:\n";
print EMAIL "$CGI{\"penso\"}\n";
print EMAIL ".\n";
print EMAIL "quit\n";
close (EMAIL);
}
Salviamo lo script modulo.cgi nella directory /cgi-bin del server e con un chmod 755 modulo.cgi daremo la possibilità a tutti gli utenti di eseguirlo.
GESTIRE GLI ERRORI
Uno script di gateway che si rispetti deve essere chiaro, veloce ma soprattutto deve prevedere la maggior parte degli errori, sia di sistema e sia formali.
Gli errori del primo tipo, come quelli dovuti a problemi di I/O su file, di invio di posta elettronica, di chiamate al sistema operativo etc, si controllano con istruzioni die. Lo script si blocca e il server web tiene traccia dell'errore su un file di log (error_log se usiamo Apache).
Per il secondo tipo di errori, commessi durante la compilazione del form, si dovrebbe prima informare l'utente del tipo di errore commesso e poi sospendere l'esecuzione dello script.
E' sufficiente un messaggio (pagina html) restituito dal cgi in cui venga evidenziato dove è stato commesso l'errore e perché (vedi figure 2 e 3).
Non è bello predisporre un form per consentire la registrazione ad un servizio e ricevere nomi fasulli, età assurde e indirizzi e-mail inesistenti. Chiaramente non si può controllare tutto (gli script neurali non esistono ancora) ma con gli strumenti che abbiamo si può fare molto.
Si potrebbe migliorare il nostro esempio scrivendo un po' di codice per restituire oltre al messaggio anche il from con i campi corretti già compilati e quelli errati vuoti: un piccolo esercizio prima delle ferie.
Buone vacanze a tutti.
Data creazione HTML: Febbraio 1998
Autore: Francesco Munaretto
E-mail: NoSpam@thank.you