[>a2267.html>] [<a2265.html<] [^a2.html^]
In questo capitolo si introduce la programmazione per la realizzazione di programmi gateway in Perl. Il primo problema che si incontra quando si realizzano programmi del genere è l'analisi delle stringhe di richiesta, per arrivare alla loro scomposizione in modo da poterne gestire i dati. Per questo si utilizzano frequentemente librerie già pronte e ben collaudate, ma in questo capitolo si vuole mostrare come lavorare partendo da zero.
Prima di iniziare a realizzare programmi CGI, occorre fare mente locale alla situazione in cui si trova il programma, specialmente per la verifica del funzionamento dello stesso. Il programma viene eseguito attraverso una forma di intermediazione: è il servente a metterlo in funzione, ed è il servente a ricevere l'output che poi viene restituito al cliente.
In questa situazione, lo standard error del programma viene perduto, e con esso le eventuali segnalazioni di errore di qualunque tipo.
Prima di provare il funzionamento di un programma del genere, per quanto banale sia, occorre averlo analizzato sintatticamente attraverso gli strumenti che mette a disposizione il compilatore o l'interprete. L'utilizzo di Perl come linguaggio di programmazione, non richiedendo una fase di compilazione, tende a fare dimenticare che è necessaria un'analisi sintattica. Se non si verifica il programma, magari solo per un punto e virgola fuori posto, ci si trova di fronte al solito messaggio: «500 Errore interno del servente».
Nello stesso modo, sarebbe bene che il programma che si realizza sia in grado di funzionare in qualche modo anche al di fuori dell'ambiente creato dal servente HTTP.
È il caso di ricordare che il controllo sintattico di un programma Perl si ottiene nel modo seguente:
perl -c programma_perl
Si è accennato al fatto che un programma gateway non può fare a meno di occuparsi della decodifica delle stringhe di richiesta. Questo problema si scompone almeno nelle fasi seguenti:
la suddivisione delle coppie nome=valore;
la separazione delle coppie;
la decodifica URI.
I dati provenienti da un modulo FORM sono uniti assieme attraverso l'uso del simbolo e-commerciale (&). Per suddividerli si può creare un array dei vari elementi utilizzando la funzione split
@coppia = split( '&', $richiesta );
Le coppie nome=valore sono stringhe unite assieme attraverso il simbolo di assegnamento (=). La suddivisione avviene agevolmente attraverso la scomposizione in un array di due soli elementi. Solitamente si utilizza la scorciatoia seguente:
( $nome, $valore ) = split( '=', $coppia[$i] );
In pratica, si scompone il contenuto di un elemento dell'array @coppia, visto nella sezione precedente.
La decodifica URI si compone di due fasi:
sostituzione del simbolo + con lo spazio;
sostituzione dei codici %hh con il carattere corrispondente.
$valore =~ tr/+/ /; $nome =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge; $valore =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge;
Quello che segue è un esempio molto semplificato di due subroutine in grado, rispettivamente, di estrapolare le informazioni da una richiesta in modalità GET e in modalità POST. Le due subroutine restituiscono un hash (l'array associativo di Perl) corrispondente alle coppie di dati.
#====================================================================== # mini-lib.pl # Routine Perl utilizzabili da un programma gateway. #====================================================================== #====================================================================== # &Decodifica_GET () # Decodifica il contenuto della variabile $QUERY_STRING e lo # restituisce in un hash. #---------------------------------------------------------------------- sub Decodifica_GET { local ( $richiesta ) = $ENV{'QUERY_STRING'}; local ( @coppia ) = (); local ( $elemento ) = ""; local ( $nome ) = ""; local ( $valore ) = ""; local ( %DATI ) = (); #------------------------------------------------------------------ # Suddivide la richiesta in un array di coppie «nome=valore». #------------------------------------------------------------------ @coppia = split( '&', $richiesta ); #------------------------------------------------------------------ # Elabora ogni coppia contenuta nell'array. #------------------------------------------------------------------ foreach $elemento ( @coppia ) { #-------------------------------------------------------------- # Scompone la coppia. #-------------------------------------------------------------- ( $nome, $valore ) = split( '=', $elemento ); #-------------------------------------------------------------- # Trasforma «+» in spazio. #-------------------------------------------------------------- $valore =~ tr/+/ /; #-------------------------------------------------------------- # Trasforma «%hh» nel carattere corrispondente. #-------------------------------------------------------------- $nome =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge; $valore =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge; #-------------------------------------------------------------- # Aggiunge la coppia decodificata in un hash. #-------------------------------------------------------------- $DATI{$nome} = $valore; } #------------------------------------------------------------------ # Restituisce l'hash delle coppie ( nome => valore ). #------------------------------------------------------------------ return %DATI; } #====================================================================== # &Decodifica_POST () # Decodifica quanto proveniente dallo standard input e lo # restituisce in un hash. #---------------------------------------------------------------------- sub Decodifica_POST { local ( $richiesta ) = ""; local ( @coppia ) = (); local ( $elemento ) = ""; local ( $nome ) = ""; local ( $valore ) = ""; local ( %DATI ) = (); #------------------------------------------------------------------ # Legge lo standard input. #------------------------------------------------------------------ read( STDIN, $richiesta, $ENV{CONTENT_LENGTH} ); #------------------------------------------------------------------ # Suddivide la richiesta in un array di coppie «nome=valore». #------------------------------------------------------------------ @coppia = split( '&', $richiesta ); #------------------------------------------------------------------ # Elabora ogni coppia contenuta nell'array. #------------------------------------------------------------------ foreach $elemento ( @coppia ) { #-------------------------------------------------------------- # Scompone la coppia. #-------------------------------------------------------------- ( $nome, $valore ) = split( '=', $elemento ); #-------------------------------------------------------------- # Trasforma «+» in spazio. #-------------------------------------------------------------- $valore =~ tr/+/ /; #-------------------------------------------------------------- # Trasforma «%hh» nel carattere corrispondente. #-------------------------------------------------------------- $nome =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge; $valore =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack('c',hex($1))/ge; #-------------------------------------------------------------- # Aggiunge la coppia decodificata in un hash. #-------------------------------------------------------------- $DATI{$nome} = $valore; } #------------------------------------------------------------------ # Restituisce l'hash delle coppie ( nome => valore ). #------------------------------------------------------------------ return %DATI; } #====================================================================== # Trattandosi di una libreria, l'ultima riga deve restituire un # valore equiparabile a TRUE. #---------------------------------------------------------------------- 1; #======================================================================
Un programma banale che potrebbe fare uso di questa libreria, è il seguente. Si occupa solo di restituire i dati ottenuti dall'hash contenente le coppie nome=>valore.
#!/usr/bin/perl #====================================================================== # form.pl #====================================================================== require ('mini-lib.pl'); print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Metodo $ENV{REQUEST_METHOD}</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Metodo $ENV{REQUEST_METHOD}</H1>\n"; print "<PRE>\n"; if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { print "Il metodo della richiesta non è gestibile.\n"; } @nomi = keys( %DATI ); foreach $nome ( @nomi ) { print "$nome = $DATI{$nome}\n"; } print "</PRE>\n"; print "</BODY>\n"; print "</HTML>\n"; #======================================================================
Il programma form.pl, appena mostrato, incorpora inizialmente la libreria presentata prima, mini-lib.pl, quindi, a seconda del metodo utilizzato per la richiesta, chiama la subroutine adatta. Al termine, restituisce semplicemente l'elenco dei dati ottenuti.
In questa sezione si vogliono mostrare alcuni esempi elementari di applicazioni CGI. Si tratta dell'accesso pubblico alla documentazione interna del sistema operativo attraverso apropos, whatis e man.
Per questi tre tipi di interrogazioni si prepara un solo file HTML di partenza, contenente tre FORM distinti, ognuno dei quali invia una richiesta a un diverso programma gateway specializzato.
Segue il sorgente del file manuali.html
contenente i tre FORM necessari per richiamare i programmi gateway in grado di fornire documentazione interna.
<!-- manuali.html --> <HTML> <HEAD> <TITLE>Manualistica</TITLE> </HEAD> <BODY> <H1>Manualistica</H1> <FORM ACTION="/cgi-bin/apropos.pl" METHOD="GET"> apropos <INPUT NAME="apropos" SIZE=30> <INPUT TYPE=submit VALUE="Invio"> </FORM> <FORM ACTION="/cgi-bin/whatis.pl" METHOD="GET"> whatis <INPUT NAME="whatis" SIZE=30> <INPUT TYPE=submit VALUE="Invio"> </FORM> <FORM ACTION="/cgi-bin/man.pl" METHOD="GET"> man <SELECT NAME="sezione"> <OPTION VALUE="" SELECTED>predefinito <OPTION VALUE=1 >comandi utente <OPTION VALUE=2 >chiamate di sistema <OPTION VALUE=3 >chiamate di libreria <OPTION VALUE=4 >dispositivi <OPTION VALUE=5 >formati dei file <OPTION VALUE=6 >giochi <OPTION VALUE=7 >varie <OPTION VALUE=8 >comandi di sistema <OPTION VALUE=9 >routine del kernel </SELECT> <INPUT NAME="man" SIZE=30> <INPUT TYPE=submit VALUE="Invio"> </FORM> </BODY> </HTML>
La figura 205.1 mostra in che modo appaia questo modulo.
Figura 205.1. Il modulo |
Ognuno dei tre FORM permette di indicare una stringa da utilizzare per ottenere informazioni. Ogni FORM ha il proprio tasto di invio indipendente con il quale si decide implicitamente il tipo di informazione che si vuole avere: apropos, whatis o man. Dei tre tipi di FORM, quello della richiesta per i file delle pagine di manuale è un po' diverso, dal momento che potrebbe essere necessario indicare la sezione.
Segue il sorgente del programma apropos.pl, che si occupa di interrogare il sistema attraverso il comando apropos e di restituire un file HTML con la risposta.
#!/usr/bin/perl #====================================================================== # apropos.pl #====================================================================== #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); local ( $risposta ) = ""; #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Rinvia la richiesta a apropos e ne restituisce l'esito. #---------------------------------------------------------------------- if ( open( APROPOS, "apropos $DATI{apropos} |" ) ) { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>apropos $DATI{apropos}</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>apropos $DATI{apropos}</H1>\n"; print "<PRE>\n"; while ( $risposta = <APROPOS> ) { print $risposta; } print "</PRE>\n"; print "</BODY>\n"; print "</HTML>\n"; } else { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Errore</H1>\n"; print "Si è manifestato un errore durante l'inoltro "; print "della richiesta.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== 1; #======================================================================
Il programma è molto semplice: interpreta la richiesta ottenuta e ne estrae solo il valore abbinato all'informazione apropos; quindi esegue il comando apropos leggendone l'output che viene restituito in una pagina HTML molto semplice. Il punto più delicato di questo programma sta quindi nell'istruzione seguente:
open( APROPOS, "apropos $DATI{apropos} |" )
Con questa viene abbinato un flusso di file a un comando il cui standard output verrà letto successivamente, e riemesso all'interno di una pagina HTML con il ciclo seguente:
while ( $risposta = <APROPOS> ) { print $risposta; }
Figura 205.2. Il risultato di un'interrogazione apropos per la parola «manual». |
Segue il sorgente del programma whatis.pl, che si occupa di interrogare il sistema attraverso il comando whatis e di restituire un file HTML con la risposta. È molto simile a apropos.pl appena mostrato, per cui qui alcune parti vengono tralasciate (in corrispondenza dei puntini di sospensione).
#!/usr/bin/perl #====================================================================== # whatis.pl #====================================================================== #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { ... } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); local ( $risposta ) = ""; #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Rinvia la richiesta a man e ne restituisce l'esito. #---------------------------------------------------------------------- if ( open( WHATIS, "whatis $DATI{whatis} |" ) ) { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>whatis $DATI{whatis}</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>whatis $DATI{whatis}</H1>\n"; print "<PRE>\n"; while ( $risposta = <WHATIS> ) { print $risposta; } print "</PRE>\n"; print "</BODY>\n"; print "</HTML>\n"; } else { ... } #====================================================================== 1; #======================================================================
Come si vede, si tratta della stessa cosa già vista nell'altro programma, con la differenza che la richiesta viene fatta al comando whatis invece che a apropos.
Figura 205.3. Il risultato di un'interrogazione whatis per la parola «man». |
Segue il sorgente del programma man.pl, che si occupa di interrogare il sistema operativo attraverso il comando man e di restituire un file HTML con la risposta. È molto simile agli altri due appena mostrati, per cui, anche in questo caso, alcune parti vengono tralasciate.
#!/usr/bin/perl #====================================================================== # man.pl #====================================================================== #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { ... } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); local ( $risposta ) = ""; #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Rinvia la richiesta a man e ne restituisce l'esito. #---------------------------------------------------------------------- if ( open( MAN, "man $DATI{sezione} $DATI{man} | col -bx |" ) ) { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>man $DATI{sezione} $DATI{man}</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>man $DATI{sezione} $DATI{man}</H1>\n"; print "<PRE>\n"; while ( $risposta = <MAN> ) { print $risposta; } print "</PRE>\n"; print "</BODY>\n"; print "</HTML>\n"; } else { ... } #====================================================================== 1; #======================================================================
La differenza fondamentale sta nel fatto che qui si utilizzano due informazioni: il nome del comando di cui si vuole ottenere la pagina di manuale, e il numero della sezione. Un'altra cosa da osservare è il modo in cui è stato predisposto il comando: attraverso una pipeline necessaria a eliminare i caratteri di controllo che non potrebbero essere visualizzati nella pagina HTML.
open( MAN, "man $DATI{sezione} $DATI{man} | col -bx |" )
Figura 205.4. Il risultato di un'interrogazione man per il comando man, senza specificare la sezione. |
La situazione più comune in cui sono utili i moduli HTML, è quella in cui si vuole guidare l'inserimento di dati che poi generano un messaggio di posta elettronica: l'utente potrebbe scrivere un messaggio senza passare per la compilazione del modulo, ma in tal modo non ci sarebbe nessun controllo interattivo.
Viene mostrato un sistema molto semplice attraverso cui un utente può ordinare un prodotto, detto Articolo x, indicando il proprio recapito e i dati della propria carta di credito. Tutto quanto viene mostrato semplificando il procedimento al massimo, per esempio si presume che venga ordinata una sola unità dell'articolo prescelto. Le fasi dell'ordinazione possono distinguersi nel modo seguente:
invio del modulo compilato da parte dell'utente;
verifica da parte del programma gateway e richiesta di conferma dei dati introdotti;
conferma da parte dell'utente;
invio dei dati in forma di messaggio di posta elettronica all'utente root;
avviso del completamento dell'operazione.
La prima fase viene svolta utilizzando un file HTML, ordine.html
, che richiama il programma ordine.pl; tutte le altre fasi sono svolte direttamente dal programma.
Segue il sorgente del file ordine.html
.
<!-- ordine.html --> <HTML> <HEAD> <TITLE>Ordine attraverso FORM</TITLE> </HEAD> <BODY> <H1>Ordine attraverso FORM</H1> <FORM ACTION="/cgi-bin/ordine.pl" METHOD="POST"> <INPUT TYPE=hidden NAME="modulo" VALUE="ordine base"> <P> Articolo ordinato: <SELECT NAME="articolo"> <OPTION VALUE=0 SELECTED>Nessuno <OPTION VALUE=A >Articolo A <OPTION VALUE=B >Articolo B <OPTION VALUE=C >Articolo C <OPTION VALUE=D >Articolo D </SELECT> </P> <H2>Dati dell'ordinante</H2> <P> nome: <INPUT NAME="nome" SIZE=25> cognome: <INPUT NAME="cognome" SIZE=25> </P> <P> via: <INPUT NAME="via" SIZE=20> n.: <INPUT NAME="n" SIZE=5><BR> c.a.p.: <INPUT NAME="cap" SIZE=5> città: <INPUT NAME="citta" SIZE=15><BR> e-mail: <INPUT NAME="email" SIZE=30> </P> <P> carta: VISA <INPUT TYPE=radio NAME="carta" VALUE="VISA" CHECKED> American Express <INPUT TYPE=radio NAME="carta" VALUE="American Express"> <INPUT NAME="carta_num" SIZE=20 MAXLENGTH=19> </P> <P> <INPUT TYPE=submit VALUE="Invio dell'ordine"> </P> </FORM> </BODY> </HTML>
La figura 205.5 mostra un esempio di compilazione del modulo.
Figura 205.5. Un esempio di compilazione del modulo contenuto nel file |
Segue il sorgente del programma ordine.pl che svolge tutte le fasi di controllo, invio e conferma dell'ordine inserito a partire dal file ordine.html
. La descrizione del suo comportamento è inserita nei commenti del sorgente stesso. In particolare, all'inizio sono riportate le subroutine, mentre l'inizio vero e proprio del programma è nella parte finale.
#!/usr/bin/perl #====================================================================== # ordine.pl #====================================================================== #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Verifica_dati () #---------------------------------------------------------------------- sub Verifica_dati { if ( $DATI{articolo} eq "0" ) { return 0 } if ( $DATI{nome} eq "" ) { return 0 } if ( $DATI{cognome} eq "" ) { return 0 } if ( $DATI{via} eq "" ) { return 0 } if ( $DATI{cap} eq "" ) { return 0 } if ( $DATI{citta} eq "" ) { return 0 } if ( $DATI{email} eq "" ) { return 0 } if ( $DATI{carta_num} eq "" ) { return 0 } return 1; } #====================================================================== # &Dati_nascosti () #---------------------------------------------------------------------- sub Dati_nascosti { print "<INPUT TYPE=hidden NAME=\"articolo\" VALUE=\"$DATI{articolo}\">\n"; print "<INPUT TYPE=hidden NAME=\"nome\" VALUE=\"$DATI{nome}\">\n"; print "<INPUT TYPE=hidden NAME=\"cognome\" VALUE=\"$DATI{cognome}\">\n"; print "<INPUT TYPE=hidden NAME=\"via\" VALUE=\"$DATI{via}\">\n"; print "<INPUT TYPE=hidden NAME=\"n\" VALUE=\"$DATI{n}\">\n"; print "<INPUT TYPE=hidden NAME=\"cap\" VALUE=\"$DATI{cap}\">\n"; print "<INPUT TYPE=hidden NAME=\"citta\" VALUE=\"$DATI{citta}\">\n"; print "<INPUT TYPE=hidden NAME=\"email\" VALUE=\"$DATI{email}\">\n"; print "<INPUT TYPE=hidden NAME=\"carta\" VALUE=\"$DATI{carta}\">\n"; print "<INPUT TYPE=hidden NAME=\"carta_num\" VALUE=\"$DATI{carta_num}\">\n"; } #====================================================================== # &Richiedi_conferma () #---------------------------------------------------------------------- sub Richiedi_conferma { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Conferma</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Conferma dati dell'ordinazione.</H1>\n"; print "Si prega di controllare i dati e di confermare se tutto "; print "appare in ordine.\n"; print "<PRE>"; print "Nominativo: $DATI{nome} $DATI{cognome}\n"; print "Indirizzo: $DATI{via} $DATI{n}\n" ; print " $DATI{cap} $DATI{citta}\n" ; print " $DATI{email}\n" ; print "Carta: $DATI{carta} $DATI{carta_num}\n"; print "Articolo ordinato: $DATI{articolo}\n"; print "</PRE>"; print "<FORM ACTION=\"/cgi-bin/ordine.pl\" METHOD=\"POST\">\n"; print "<INPUT TYPE=hidden NAME=\"modulo\" VALUE=\"ordine conferma\">\n"; &Dati_nascosti; print "<INPUT TYPE=submit VALUE=\"Conferma i dati e l'ordine\">\n"; print "</FORM>\n"; print "Se i dati non sono come desiderato, si prega di ritornare\n"; print "alla <A HREF=\"/ordine.html\">compilazione del modulo</A>.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Dati_insufficienti () #---------------------------------------------------------------------- sub Dati_insufficienti { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>I dati inseriti nel modello sono insufficienti.</H1>\n"; print "Si prega di controllare e aggiungere i dati mancanti.\n"; print "<P><A HREF=\"/ordine.html\">"; print "Ritorna al modulo di ordinazione</A>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Modulo_errato () #---------------------------------------------------------------------- sub Modulo_errato { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Il modulo inviato non è previsto.</H1>\n"; print "Si prega di utilizzare il \n"; print "<A HREF=\"/ordine.html\">"; print "modulo d'ordine standard</A>.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &E_mail_errore () #---------------------------------------------------------------------- sub E_mail_errore { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Impossibile inviare l'ordine</H1>\n"; print "Si prega di scusare l'inconveniente.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &E_mail ( <destinatario>, <oggetto>, <contenuto> ) #---------------------------------------------------------------------- sub E_mail { local ( $destinatario ) = $_[0]; local ( $oggetto ) = $_[1]; local ( $contenuto ) = $_[2]; local ( $sendmail ) = "/bin/mail $destinatario"; unless ( open( EMAIL, "| $sendmail " ) ) { return 0; } print EMAIL "$oggetto\n"; print EMAIL "\n\n"; print EMAIL "$contenuto\n"; print EMAIL ".\n"; close( EMAIL ); } #====================================================================== # &Invio_ordine () #---------------------------------------------------------------------- sub Invio_ordine { local ( $ordine ) = ""; $ordine = $ordine . "Nominativo: $DATI{nome} $DATI{cognome}\n" ; $ordine = $ordine . "Indirizzo: $DATI{via} $DATI{n}\n" ; $ordine = $ordine . " $DATI{cap} $DATI{citta}\n"; $ordine = $ordine . " $DATI{email}\n" ; $ordine = $ordine . "Carta: $DATI{carta} $DATI{carta_num}\n" ; $ordine = $ordine . "Articolo ordinato: $DATI{articolo}\n" ; if ( &E_mail ( 'root@localhost', 'Ordine da modulo FORM ordine.pl', $ordine ) ) { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Conferma invio</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Conferma invio</H1>\n"; print "Il Vostro ordine è stato inviato.\n"; print "Grazie.\n"; print "</BODY>\n"; print "</HTML>\n"; } else { &E_mail_errore; } } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Attraverso il dato memorizzato con il nome «modulo» si determina # a che punto sia la compilazione. # «ordine base» è il modulo di partenza, mentre «ordine conferma» # è quello generato da questo programma per conferma. #---------------------------------------------------------------------- if ( $DATI{modulo} eq 'ordine base' ) { #------------------------------------------------------------------ # Prima fase: si verificano i dati e si chiede conferma all'utente. #------------------------------------------------------------------ if ( &Verifica_dati ) { &Richiedi_conferma; } else { &Dati_insufficienti; } } elsif ( $DATI{modulo} eq 'ordine conferma' ) { #------------------------------------------------------------------ # Seconda fase: si verificano i dati e si invia l'ordine. #------------------------------------------------------------------ if ( &Verifica_dati ) { &Invio_ordine; } else { &Dati_insufficienti; } } else { #------------------------------------------------------------------ # È stato indicato un modulo non previsto. #------------------------------------------------------------------ &Modulo_errato; } #====================================================================== 1; #======================================================================
La figura 205.6 mostra in che modo viene richiesta la conferma dei dati inseriti come dall'esempio della figura precedente.
Figura 205.6. La richiesta di conferma a seguito dell'invio del modulo di ordinazione. |
Lo scopo di questo programma è generare e inviare un messaggio di posta elettronica all'utente root. Quello che segue è il messaggio generato dall'esempio mostrato sopra.
Date: Sun, 1 Feb 1998 08:04:30 +0100 From: Nobody <nobody@localhost> Message-Id: <199802010704.IAA00463@localhost> To: root@localhost Ordine da modulo FORM ordine.pl Nominativo: Pinco Pallino Indirizzo: Biglie 1 99999 Sferopoli ppinco@palloni.com Carta: VISA 1234-5678-9012-3456 Articolo ordinato: A
Il programma ordine.pl si occupa solo di registrare un ordine attraverso l'invio di un messaggio di posta elettronica. Lo si potrebbe modificare in modo da aggiungere una registrazione su un file. Basta modificare la subroutine Invio_ordine().
#====================================================================== # ordine2.pl #====================================================================== use Fcntl ':flock'; # Importa le costanti di gestione dei file. #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); ... ... #====================================================================== # &Invio_ordine () #---------------------------------------------------------------------- sub Invio_ordine { local ( $ordine ) = ""; $ordine = $ordine . "Nominativo: $DATI{nome} $DATI{cognome}\n" ; $ordine = $ordine . "Indirizzo: $DATI{via} $DATI{n}\n" ; $ordine = $ordine . " $DATI{cap} $DATI{citta}\n"; $ordine = $ordine . " $DATI{email}\n" ; $ordine = $ordine . "Carta: $DATI{carta} $DATI{carta_num}\n" ; $ordine = $ordine . "Articolo ordinato: $DATI{articolo}\n" ; if ( &E_mail ( 'root@localhost', 'Ordine da modulo FORM ordine.pl', $ordine ) ) { #-------------------------------------------------------------- # Memorizza l'ordine. #-------------------------------------------------------------- if ( open ( ORDINI, ">> /var/log/ordini" ) ) { if ( flock ( ORDINI, LOCK_EX ) ) { seek( ORDINI, 0, 2 ); print ORDINI ( "$ordine\n" ); } close ( ORDINI ); } #-------------------------------------------------------------- # Avvisa l'utente. #-------------------------------------------------------------- print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Conferma invio</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Conferma invio</H1>\n"; print "Il Vostro ordine è stato inviato.\n"; print "Grazie.\n"; print "</BODY>\n"; print "</HTML>\n"; } else { &E_mail_errore; } } ... ...
In pratica, l'ordine viene registrato nel file /var/log/ordini
, che viene bloccato (lock) in modo esclusivo per evitare sovrascritture simultanee da parte di altri processi.
open ( ORDINI, ">> /var/log/ordini" ); if ( flock ( ORDINI, LOCK_EX ) ) { seek( ORDINI, 0, 2 ); print ORDINI ( "$ordine\n" ); } close ( ORDINI );
Per poter utilizzare la costante LOCK_EX, all'inizio del programma è stata inserita l'istruzione seguente:
use Fcntl ':flock';
Il programma proposto per la gestione di ordini a distanza è troppo semplice per poter essere utilizzato come esempio reale di un sistema del genere. Il punto debole più grave è l'assenza di controlli dettagliati sui dati. Per renderlo più efficace occorrerebbe modificare la gestione degli errori, in modo da informare l'utente in modo più preciso di un eventuale errore commesso nella compilazione di un modulo.
In pratica, occorre entrare nella logica della programmazione di procedure aziendali vere e proprie, con tutta la cura che è necessario dare alle maschere di inserimento dei dati e alle segnalazioni di errore relative, in modo da guidare facilmente l'utente nel loro utilizzo.
Il problema che si avverte immediatamente dopo aver compreso il meccanismo della programmazione CGI è quello dell'interfacciamento con una base di dati. A partire dal capitolo 196 è descritto PostgreSQL e a questo DBMS si vuole fare riferimento negli esempi di questa sezione.
Un programma CGI che debba accedere a dati attraverso un DBMS deve essere predisposto per un certo protocollo di comunicazione con il DBMS stesso. Generalmente si tratta di incorporare una libreria adatta e di utilizzare le sue funzioni. Nel caso di Perl si tratta di utilizzare un modulo adatto, e per la connessione con PostgreSQL si usa il modulo Pg.
Se si intendono eseguire solo delle interrogazioni elementari, può darsi che basti utilizzare un cliente elementare attraverso una pipeline. PostgreSQL offre il cliente psql che può essere usato anche per questo scopo.
Per introdurre il problema con un esempio pratico, si suppone di disporre di una base di dati con una tabella contenente il listino di alcuni prodotti. Il programma che si vuole scrivere deve essere in grado di ricevere una stringa di ricerca e di passarla al cliente psql, in modo che questo restituisca gli articoli che corrispondono al modello.
Dalla descrizione fatta, potrebbe sembrare che dal punto di vista della programmazione il problema sia molto semplice. In realtà, tutto il lavoro lo deve fare il programma psql.
È bene ricordare che un DBMS deve gestire in proprio gli utenti per poter definire le politiche di accesso ai dati che vengono amministrati. I programmi CGI che vengono proposti interagiscono con un servente PostgreSQL locale, utilizzando i privilegi dell'utente nobody, ovvero l'utente anonimo del sistema operativo.
Perché tali programmi possano funzionare occorre che questo utente sia aggiunto anche nel DBMS, e nel caso di PostgreSQL si tratta di usare il programma createuser (vedere 196.2.3). Inoltre, è necessario che le tabelle che si utilizzano permettano l'accesso da parte di questo utente, attraverso una politica opportuna di REVOKE e GRANT.
Per facilitare il lettore, vengono riassunte di seguito le azioni da compiere per aggiungere l'utente nobody attraverso il programma createuser.
#
su postgres
[Invio]
postgres$
createuser
[Invio]
Enter name of user to add--->
nobody
[Invio]
Enter user's postgres ID or RETURN to use unix user ID: 99 ->
[Invio]
Is user "nobody" allowed to create databases (y/n)
n
[Invio]
Is user "nobody" allowed to add users? (y/n)
n
[Invio]
createuser: nobody was successfully added
La tabella contenente il listino da interrogare deve essere costruita attraverso gli strumenti di PostgreSQL. Dovendo realizzare qualcosa che deve essere accessibile al tutti gli utenti HTTP, occorre organizzare le cose opportunamente. Si procede con la creazione di una base di dati adatta a contenere dati pubblici; si sceglie il nome: pubblico.
#
su postgres
[Invio]
$
createdb pubblico
[Invio]
Per preparare ciò che serve si utilizza psql specificando di voler accedere alla base di dati appena creata.
$
psql pubblico
[Invio]
Attraverso psql si crea la tabella denominata Listino e gli si inseriscono dei dati. Le istruzioni possono essere simili a quelle seguenti.
CREATE TABLE Listino ( Codice char(7), Descrizione varchar(160), Prezzo integer ); INSERT INTO Listino VALUES ( 'resis1k', 'Resistenze 1kOhm', 100 ); INSERT INTO Listino VALUES ( 'resis2k', 'Resistenze 2kOhm', 100 ); INSERT INTO Listino VALUES ( 'resis3k', 'Resistenze 3kOhm', 100 ); ... INSERT INTO Listino VALUES ( 'con10kp', 'Condensatore 10000 pf', 200 ); INSERT INTO Listino VALUES ( 'con20kp', 'Condensatore 20000 pf', 200 ); INSERT INTO Listino VALUES ( 'con30kp', 'Condensatore 30000 pf', 200 ); ... INSERT INTO Listino VALUES ( 'mo09pm', 'Monitor mono 9 pollici', 200000 ); ... INSERT INTO Listino VALUES ( 'mo09pc', 'Monitor colore 9 pollici', 400000 ); ... REVOKE ALL ON Listino FROM PUBLIC; GRANT ALL ON Listino TO postgres; GRANT SELECT ON Listino TO PUBLIC;
Come si può osservare, prima viene creata la tabella con sole tre colonne: codice, descrizione e prezzo. Successivamente vengono inserite le varie righe contenenti ognuna l'informazione di un certo articolo. Infine, anche se potrebbe non essere indispensabile, è il caso di regolare i permessi di utilizzo di questa tabella: vengono revocati tutti i privilegi; quindi viene permesso qualunque intervento da parte dell'utente postgres (il DBA predefinito); infine viene concessa la lettura a tutti.
pubblico=>
\q
[Invio]
La soluzione proposta del problema è molto semplice: il programma listino.pl fa tutto da solo. Se viene avviato senza informazioni, restituisce un modulo da compilare, e dal quel punto in poi è comunque tutto sotto il suo controllo.
#!/usr/bin/perl #====================================================================== # listino.pl #====================================================================== #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Verifica_dati () #---------------------------------------------------------------------- sub Verifica_dati { if ( $DATI{ricerca} eq "" ) { return 0 } return 1; } #====================================================================== # &Ricerca_listino () #---------------------------------------------------------------------- sub Ricerca_listino { local( $query_sql ) = "SELECT * FROM Listino WHERE descrizione LIKE '$DATI{ricerca}';"; local( @risposta ) = (); if ( open ( LISTINO, "psql -d pubblico -H -q -c \"$query_sql\" |" ) ) { @risposta = <LISTINO>; } else { @risposta = { "La stringa richiesta è incomprensibile\n" }; } print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Consultazione del listino</H1>\n"; print "<FORM ACTION=\"/cgi-bin/listino.pl\" METHOD=\"GET\">\n"; print "<P>\n"; print "Inserire una stringa di ricerca per ottenere gli articoli la\n"; print "cui descrizione coincide: ``%'' corrisponde a una stringa\n"; print "indefinita; ``_'' corrisponde a un singolo carattere\n"; print "indefinito.</P>\n"; print "<P>\n"; print "<INPUT NAME=\"ricerca\" SIZE=25>\n"; print "<INPUT TYPE=submit VALUE=\"Cerca\"></P>\n"; print "</FORM>\n"; print "<P><HR></P>\n"; print "<H3>Risultato della ricerca con il modello: ``$DATI{ricerca}''</H3>\n"; print "\n"; print "@risposta"; print "\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Dati_insufficienti () # In pratica, invia il FORM da compilare. #---------------------------------------------------------------------- sub Dati_insufficienti { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Consultazione del listino</H1>\n"; print "<FORM ACTION=\"/cgi-bin/listino.pl\" METHOD=\"GET\">\n"; print "<P>\n"; print "Inserire una stringa di ricerca per ottenere gli articoli la\n"; print "cui descrizione coincide: ``%'' corrisponde a una stringa\n"; print "indefinita; ``_'' corrisponde a un singolo carattere\n"; print "indefinito.</P>\n"; print "<P>\n"; print "<INPUT NAME=\"ricerca\" SIZE=25>\n"; print "<INPUT TYPE=submit VALUE=\"Cerca\"></P>\n"; print "</FORM>\n"; print "\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Prima fase: si verificano i dati. #---------------------------------------------------------------------- if ( &Verifica_dati ) { &Ricerca_listino; } else { &Dati_insufficienti; } #====================================================================== 1; #======================================================================
Vale la pena di analizzare la subroutine Ricerca_listino, in cui si svolge l'interrogazione della tabella del listino. L'istruzione SQL per la richiesta è la seguente:
SELECT * FROM Listino WHERE descrizione LIKE '$DATI{ricerca}';
In pratica, $DATI{ricerca} viene sostituito con una stringa fornita attraverso il modulo HTML.
Per eseguire la richiesta viene utilizzato psql in una pipeline, fornendo l'istruzione di interrogazione attraverso la riga di comando (opzione -c), specificando che si vogliono ottenere tabelle organizzate attraverso la struttura HTML 3.0 (opzione -H).
open ( LISTINO, "psql -d pubblico -H -q -c \"$query_sql\" |" )
La figura 205.7 mostra un possibile risultato di una ricerca fatta con la stringa %sato%, corrispondente a tutto ciò che contiene la sequenza «sato» (per esempio i condensatori).
Figura 205.7. Un esempio del funzionamento del programma listino.pl. |
Quando le esigenze di programmazione diventano più complesse è bene accedere direttamente attraverso il programma che si scrive al servizio di PostgreSQL. Ciò può essere fatto attraverso un programma che incorpori la libreria LIBPQ, e nel caso di Perl si tratta di utilizzare il modulo Pg (che deve essere stato installato opportunamente).
Per iniziare a comprendere l'utilizzo di questo componente di Perl, viene mostrato l'esempio del listino proposto nella sezione precedente, con le dovute modifiche. Qui vengono mostrate solo le differenze.
#!/usr/bin/perl #====================================================================== # listino2.pl #====================================================================== #---------------------------------------------------------------------- # Utilizza il modulo Pg, per l'utilizzo delle librerie LIBPQ di # PostgreSQL. #---------------------------------------------------------------------- use Pg; #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); ...
Nella prima parte deve essere inserita l'istruzione con cui si dichiara l'utilizzo di Pg: use Pg.
... #====================================================================== # &Ricerca_listino () #---------------------------------------------------------------------- sub Ricerca_listino { local( $query_sql ) = "SELECT * FROM Listino WHERE descrizione LIKE '$DATI{ricerca}'"; local( @tabella ) = (); local( $PGconnessione ); local( $i ); local( $j ); #------------------------------------------------------------------ # Apre la connessione con il servente PostgreSQL locale, # utilizzando il database «pubblico». #------------------------------------------------------------------ $PGconnessione = Pg::connectdb("dbname = pubblico"); #------------------------------------------------------------------ # Verifica che la connessione sia avvenuta e quindi esegue # l'interrogazione. #------------------------------------------------------------------ if ( $PGconnessione->status == PGRES_CONNECTION_OK ) { #-------------------------------------------------------------- # Invia la richiesta utilizzando la funzione Pg::doQuery che # fa tutto da sola (non occorre eseguire PQclear). #-------------------------------------------------------------- Pg::doQuery( $PGconnessione, "$query_sql", \@tabella ); } #------------------------------------------------------------------ # La connessione non ha bisogno di essere chiusa. #------------------------------------------------------------------ #------------------------------------------------------------------ # Procede con la restituzione del risultato. #------------------------------------------------------------------ print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Consultazione del listino</H1>\n"; print "<FORM ACTION=\"/cgi-bin/listino2.pl\" METHOD=\"GET\">\n"; print "<P>\n"; print "Inserire una stringa di ricerca per ottenere gli articoli la\n"; print "cui descrizione coincide: ``%'' corrisponde a una stringa\n"; print "indefinita; ``_'' corrisponde a un singolo carattere\n"; print "indefinito.</P>\n"; print "<P>\n"; print "<INPUT NAME=\"ricerca\" SIZE=25>\n"; print "<INPUT TYPE=submit VALUE=\"Cerca\"></P>\n"; print "</FORM>\n"; print "<P><HR></P>\n"; print "<H3>Risultato della ricerca con il modello: ``$DATI{ricerca}''</H3>\n"; print "\n"; print "<table>\n"; print "<TR>"; print "<TH>Codice</TH>"; print "<TH>Descrizione</TH>"; print "<TH>Prezzo unitario</TH>"; print "</TR>\n"; for ( $i = 0; $i <= $#tabella; $i++ ) { print "<TR>"; for ( $j = 0; $j <= $#{$tabella[$i]}; $j++ ) { print "<TD>$tabella[$i][$j]</TD>"; } print "</TR>\n"; } print "</table>\n"; print "\n"; print "</BODY>\n"; print "</HTML>\n"; } ...
Evidentemente, la differenza sostanziale sta nella subroutine Ricerca_listino(), dove al posto di utilizzare psql, si utilizzano le funzioni di Pg.
La prima cosa da fare è instaurare una connessione con il servizio PostgreSQL, specificando la base di dati con cui si intende interagire. Si ottiene questo attraverso Pg::connectdb() che restituisce un riferimento alla connessione instaurata, cosa che rappresenta un canale di comunicazione per l'invio di istruzioni SQL.
$PGconnessione = Pg::connectdb("dbname = pubblico");
L'argomento di questa funzione (o meglio di questo metodo) è una stringa contenente una serie di assegnamenti a delle parole chiave che rappresentano delle opzioni. In questo caso, le opzioni che non sono state indicate, fanno riferimento a valori che vanno bene al loro stato predefinito.
Prima di utilizzare il riferimento alla connessione è bene controllare che questa sia stata instaurata:
if ( $PGconnessione->status == PGRES_CONNECTION_OK ) { ... }
L'istruzione da inviare è un SELECT, ma per questo viene in aiuto una funzione speciale predisposta all'interno di Pg, per facilitare i programmatori. Si tratta di Pg::doQuery() che restituisce un array bidimensionale contenente il risultato dell'interrogazione.
Pg::doQuery( $PGconnessione, "$query_sql", \@tabella );
Come si può osservare, la funzione utilizza il riferimento alla connessione, rappresentato dalla variabile $PGconnessione, una stringa contenente l'istruzione SELECT opportuna, e un riferimento all'array che verrà riempito con i dati del risultato.
Il risultato dell'interrogazione viene quindi tradotto in modo da poter essere incluso nella pagina HTML. Dall'esempio si può osservare che la tabella ottenuta dall'interrogazione non contiene le intestazioni, per cui queste vengono inserite prima della sua scansione.
print "<table>\n"; print "<TR>"; print "<TH>Codice</TH>"; print "<TH>Descrizione</TH>"; print "<TH>Prezzo unitario</TH>"; print "</TR>\n"; for ( $i = 0; $i <= $#tabella; $i++ ) { print "<TR>"; for ( $j = 0; $j <= $#{$tabella[$i]}; $j++ ) { print "<TD>$tabella[$i][$j]</TD>"; } print "</TR>\n"; } print "</table>\n";
Nelle sezioni seguenti viene proposto un esempio attraverso cui gli utenti possono eseguire sia inserimenti che interrogazioni dalla stessa tabella. Si tratta di un sistema elementare per la gestione di annunci (gratuiti), senza controlli umani di alcun tipo (probabilmente si tratta di qualcosa giuridicamente sconsigliabile).
Il sistema in questione viene realizzato con un solo programma Perl, senza pagine iniziali di ingresso. Quando possibile vengono utilizzati metodi GET, in modo da permettere agli utenti di registrare le posizioni nel segnalibro del loro navigatore.
La tabella utilizzata per memorizzare gli annunci deve essere costruita attraverso gli strumenti di PostgreSQL. Negli esempi precedenti è già stato mostrato in che modo intervenire per creare una base di dati. Qui si intende utilizzare la stessa base di dati, pubblico, aggiungendo la tabella necessaria.
Attraverso psql si crea la tabella denominata Annunci senza bisogno di aggiungerci dati. Le istruzioni possono essere simili alle seguenti.
CREATE TABLE Annunci ( Data integer, Cognome varchar(60), Nome varchar(60), Telefono varchar(40), Email varchar(60), Rubrica integer, Annuncio varchar(1000) ); REVOKE ALL ON Annunci FROM PUBLIC; GRANT ALL ON Annunci TO postgres; GRANT INSERT ON Annunci TO PUBLIC; GRANT SELECT ON Annunci TO PUBLIC;
È da osservare il fatto che per la data viene utilizzato il tipo integer. Ciò è necessario perché nel programma Perl si utilizzerà la funzione time() per riempire questo campo, e questa funzione restituisce un numero intero che rappresenta il numero di secondi trascorsi da una data di riferimento.
Gli utenti che vogliono aggiungere un'inserzione attraverso il programma CGI, dovranno fornire tutti i dati, a esclusione della data che viene fornita dal sistema operativo. Durante l'interrogazione verranno mostrati solo il testo dell'inserzione e l'indirizzo di posta elettronica di chi lo ha fatto.
Il programma attraverso cui si gestisce tutto è annunci.pl. Questo organizza un sistema molto semplice, e con pochi controlli di sicurezza. Nonostante questo si tratta comunque di un esempio molto lungo. Come al solito, l'inizio si trova verso la fine del sorgente.
#!/usr/bin/perl #====================================================================== # annunci.pl #====================================================================== #---------------------------------------------------------------------- # Utilizza il modulo Pg, per l'utilizzo delle librerie LIBPQ di # PostgreSQL. #---------------------------------------------------------------------- use Pg; #---------------------------------------------------------------------- # Incorpora la libreria di decodifica dei dati. #---------------------------------------------------------------------- require ('mini-lib.pl'); #====================================================================== # &Metodo_non_gestibile () #---------------------------------------------------------------------- sub Metodo_non_gestibile { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Verifica_dati_annuncio () #---------------------------------------------------------------------- sub Verifica_dati_annuncio { if ( $DATI{rubrica} eq "0" ) { return 0; } if ( $DATI{testo} eq "" ) { return 0; } if ( $DATI{email} eq "" ) { return 0; } if ( $DATI{cognome} eq "" ) { return 0; } if ( $DATI{nome} eq "" ) { return 0; } if ( $DATI{telefono} eq "" ) { return 0; } return 1; } #====================================================================== # &Verifica_dati_consultazione () #---------------------------------------------------------------------- sub Verifica_dati_consultazione { if ( $DATI{rubrica} eq "0" ) { return 0; } if ( $DATI{modello} eq "" ) { $DATI{modello} = "%"; } return 1; } #====================================================================== # &Dati_insufficienti () #---------------------------------------------------------------------- sub Dati_insufficienti { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>I dati inseriti nel modello sono insufficienti.</H1>\n"; print "Si prega di controllare e aggiungere i dati mancanti.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Modulo_iniziale () #---------------------------------------------------------------------- sub Modulo_iniziale { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Annunci on-line</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Annunci on-line</H1>\n"; print "<P>\n"; print "Selezionare una delle due voci seguenti:</P>\n"; print "<P>\n"; print "<A HREF=\"/cgi-bin/annunci.pl?modulo=preannuncio\">"; print "inserimento di un nuovo annuncio</A></P>\n"; print "<P>\n"; print "<A HREF=\"/cgi-bin/annunci.pl?modulo=prericerca\">"; print "ricerca tra gli annunci</A></P>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Modulo_nuovo_annuncio () #---------------------------------------------------------------------- sub Modulo_nuovo_annuncio { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Annunci on-line: inserimento</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Inserimento di un annuncio</H1>\n"; print "<P>\n"; print "Si prega di inserire tutti i dati richiesti.</P>\n"; print "<P>\n"; print "<FORM ACTION=\"/cgi-bin/annunci.pl\" METHOD=\"POST\">\n"; print "<INPUT TYPE=hidden NAME=\"modulo\" VALUE=\"annuncio\">\n"; print "<P>\n"; print "Rubrica: <SELECT NAME=\"rubrica\">\n"; print " <OPTION VALUE=0 SELECTED>Nessuna\n"; print " <OPTION VALUE=1 >Compro\n"; print " <OPTION VALUE=2 >Vendo\n"; print " <OPTION VALUE=3 >Messaggi\n"; print " <OPTION VALUE=4 >Varie\n"; print "</SELECT></P>\n"; print "<P>\n"; print "Testo dell'annuncio:<BR>\n"; print " <INPUT NAME=\"testo\" SIZE=80></P>\n"; print "<P>\n"; print "e-mail: <INPUT NAME=\"email\" SIZE=40></P>\n"; print "\n"; print "<H2>Dati che non vengono pubblicati</H2>\n"; print "<P>\n"; print "Cognome: <INPUT NAME=\"cognome\" SIZE=25></P>\n"; print "<P>\n"; print "Nome: <INPUT NAME=\"nome\" SIZE=25></P>\n"; print "<P>\n"; print "Telefono: <INPUT NAME=\"telefono\" SIZE=25></P>\n"; print "<P>\n"; print "<INPUT TYPE=submit VALUE=\"Invia l'inserzione\"></P>\n"; print "</FORM></P>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Modulo_ricerca () #---------------------------------------------------------------------- sub Modulo_ricerca { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Annunci on-line: ricerca annunci</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Ricerca tra gli annunci</H1>\n"; print "<P>\n"; print "Si deve indicare la rubrica e un modello di ricerca.</P>\n"; print "<P>\n"; print "Il modello è sensibile alla differenza tra maiuscole \n"; print "e minuscole, si può utilizzare il simbolo `%' per \n"; print "indicare una stringa di caratteri indefinita.\n</P>"; print "<P>\n"; print "<FORM ACTION=\"/cgi-bin/annunci.pl\" METHOD=\"GET\">\n"; print "<INPUT TYPE=hidden NAME=\"modulo\" VALUE=\"ricerca\">\n"; print "<P>\n"; print "Rubrica: <SELECT NAME=\"rubrica\">\n"; print " <OPTION VALUE=0 SELECTED>Nessuna\n"; print " <OPTION VALUE=1 >Compro\n"; print " <OPTION VALUE=2 >Vendo\n"; print " <OPTION VALUE=3 >Messaggi\n"; print " <OPTION VALUE=4 >Varie\n"; print "</SELECT></P>\n"; print "<P>\n"; print "Modello di ricerca: "; print "<INPUT NAME=\"modello\" SIZE=30 VALUE=\"%\"></P>\n"; print "<P>\n"; print "<INPUT TYPE=submit VALUE=\"Inizia la ricerca\"></P>\n"; print "</FORM></P>\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Ricerca_annunci () #---------------------------------------------------------------------- sub Ricerca_annunci { local( $PGquery ) = " SELECT Annuncio, Email FROM Annunci WHERE Rubrica = $DATI{rubrica} AND Annuncio LIKE '$DATI{modello}' ORDER BY Annuncio " ; local( @tabella ) = (); local( $PGconnessione ); local( $i ); local( $j ); #------------------------------------------------------------------ # Apre la connessione con il servente PostgreSQL locale, # utilizzando la base di dati «pubblico». #------------------------------------------------------------------ $PGconnessione = Pg::connectdb("dbname = pubblico"); #------------------------------------------------------------------ # Verifica che la connessione sia avvenuta e quindi esegue # l'interrogazione. #------------------------------------------------------------------ if ( $PGconnessione->status == PGRES_CONNECTION_OK ) { #-------------------------------------------------------------- # Invia la richiesta utilizzando la funzione Pg::doQuery che # fa tutto da sola (non occorre eseguire PQclear). #-------------------------------------------------------------- Pg::doQuery( $PGconnessione, $PGquery, \@tabella ); } else { #-------------------------------------------------------------- # Per qualche motivo la connessione con la base di dati non # funziona e si avvisa l'utente di conseguenza. #-------------------------------------------------------------- &Database_inaccessibile(); } print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Annunci on-line: rubrica n. $DATI{rubrica}</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Consultazione della rubrica n. $DATI{rubrica}</H1>\n"; for ( $i = 0; $i <= $#tabella; $i++ ) { #-------------------------------------------------------------- # Emette il testo dell'annuncio. #-------------------------------------------------------------- print "<P>\n"; print "$tabella[$i][0]</P>\n"; #-------------------------------------------------------------- # Emette l'indirizzo e-mail dello scrivente. #-------------------------------------------------------------- print "<P>\n"; print "<A HREF=\"mailto:$tabella[$i][1]\">$tabella[$i][1]</A></P>\n"; print "<HR>\n"; } print "\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Database_inaccessibile () #---------------------------------------------------------------------- sub Modulo_errato { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Problemi di accesso alla base di dati.</H1>\n"; print "Per qualche motivo non è possibile accedere alla base di dati "; print "degli annunci. Si prega di perdonare l'inconveniente.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Istruzione_errata () #---------------------------------------------------------------------- sub Istruzione_errata { local( $errore ) = $_[0]; local( $istruzione ) = $_[1]; print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Errore</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Problemi di accesso alla base di dati.</H1>\n"; print "<P>\n"; print "Il comando richiesto ha generato l'errore seguente,</P>"; print "<P>\n"; print "<CODE>${errore}</CODE></P>"; print "<P>\n"; print "a seguito di questa istruzione SQL:</P>"; print "<P>\n"; print "<CODE>${istruzione}</CODE></P>"; print "Si prega di avvisare l'amministratore del servizio."; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Annuncio_memorizzato () #---------------------------------------------------------------------- sub Annuncio_memorizzato { print "Content-type: text/html\n"; print "\n"; print "<HTML>\n"; print "<HEAD>\n"; print "<TITLE>Annuncio memorizzato</TITLE>\n"; print "</HEAD>\n"; print "<BODY>\n"; print "<H1>Annuncio memorizzato</H1>\n"; print "L'annuncio è stato memorizzato. Grazie.\n"; print "</BODY>\n"; print "</HTML>\n"; } #====================================================================== # &Memorizza_annuncio () #---------------------------------------------------------------------- sub Memorizza_annuncio { local( $PGconnessione ); local( $PGrisultato ); local( $PGistruzione ); local( $PGstatus ); local( $data ) = time(); #------------------------------------------------------------------ # Apre la connessione con il servente PostgreSQL locale, # utilizzando la base di dati «pubblico». #------------------------------------------------------------------ $PGconnessione = Pg::connectdb("dbname = pubblico"); #------------------------------------------------------------------ # Verifica che la connessione sia avvenuta. #------------------------------------------------------------------ if ( $PGconnessione->status == PGRES_CONNECTION_OK ) { #-------------------------------------------------------------- # La connessione è avvenuta, e si procede con l'inserimento # dell'annuncio. #-------------------------------------------------------------- $PGistruzione = " INSERT INTO Annunci ( Data, Cognome, Nome, Telefono, Email, Rubrica, Annuncio ) VALUES ( ${data}, '$DATI{cognome}', '$DATI{nome}', '$DATI{telefono}', '$DATI{email}', $DATI{rubrica}, '$DATI{testo}' ) "; $PGrisultato = $PGconnessione->exec("$PGistruzione"); #-------------------------------------------------------------- # Verifica il risultato dell'esecuzione dell'istruzione. # Attualmente sembra che il valore dello stato restituito # sia invertito. #-------------------------------------------------------------- #$PGstatus = $PGconnessione->status; # #if ($PGstatus == PGRES_COMMAND_BAD) { # # &Istruzione_errata( $PGconnessione->errorMessage, $PGistruzione ); # #} else { &Annuncio_memorizzato(); #} } else { #-------------------------------------------------------------- # Per qualche motivo la connessione con la base di dati non # funziona e si avvisa l'utente di conseguenza. #-------------------------------------------------------------- &Database_inaccessibile(); } } #====================================================================== # Inizio del programma. #====================================================================== local ( %DATI ) = (); #---------------------------------------------------------------------- # Decodifica i dati in funzione del tipo di metodo della richiesta. #---------------------------------------------------------------------- if ( $ENV{REQUEST_METHOD} eq 'GET' ) { %DATI = &Decodifica_GET; } elsif ( $ENV{REQUEST_METHOD} eq 'POST' ) { %DATI = &Decodifica_POST; } else { &Metodo_non_gestibile; } #---------------------------------------------------------------------- # Attraverso il dato memorizzato con il nome «modulo» si determina # l'operazione da compiere. #---------------------------------------------------------------------- if ( $DATI{modulo} eq 'preannuncio' ) { &Modulo_nuovo_annuncio(); } elsif ( $DATI{modulo} eq 'prericerca' ) { &Modulo_ricerca(); } elsif ( $DATI{modulo} eq 'annuncio' ) { #------------------------------------------------------------------ # L'utente ha inviato un annuncio. #------------------------------------------------------------------ if ( &Verifica_dati_annuncio ) { &Memorizza_annuncio; } else { &Dati_insufficienti; } } elsif ( $DATI{modulo} eq 'ricerca' ) { #------------------------------------------------------------------ # L'utente ha eseguito una ricerca tra gli annunci. #------------------------------------------------------------------ if ( &Verifica_dati_consultazione ) { &Ricerca_annunci; } else { &Dati_insufficienti; } } else { #------------------------------------------------------------------ # Si comincia dalla presentazione. #------------------------------------------------------------------ &Modulo_iniziale; } #====================================================================== 1; #======================================================================
Il programma contiene dentro di se tutte le pagine HTML e i moduli FORM necessari per interagire. Per distinguere il contesto per il quale vengono eseguite le richieste del protocollo HTTP si utilizzano dei FORM contenenti un campo nascosto: modulo. Quando questo campo contiene un valore non previsto, oppure è assente del tutto, viene presentata la pagina di ingresso, attraverso cui si deve specificare l'azione che si vuole compiere.
Figura 205.8. La pagina di ingresso al servizio viene ottenuta con la funzione Modulo_iniziale(). |
La pagina di ingresso, generata dalla funzione Modulo_iniziale(), contiene due riferimenti che puntano allo stesso programma, ma che incorporano una richiesta con il metodo GET, in modo da distinguere l'azione da compiere.
<A HREF="/cgi-bin/annunci.pl?modulo=preannuncio"> inserimento di un nuovo annuncio</A> <A HREF="/cgi-bin/annunci.pl?modulo=prericerca"> ricerca tra gli annunci</A>
Selezionando la funzione di inserimento di un nuovo annuncio, si ottiene un modulo attraverso cui poterlo inserire, generato dalla funzione Modulo_nuovo_annuncio().
Figura 205.9. Modulo per l'inserimento di un'inserzione, già compilato e pronto per essere trasmesso, ottenuto con la funzione Modulo_nuovo_annuncio(). |
Se invece si seleziona la funzione di ricerca, si ottiene un modulo attraverso cui si può specificare la rubrica e una stringa di ricerca. Questo modulo è generato dalla funzione Ricerca_annunci().
Figura 205.10. Modulo per ricercare gli annunci nella base di dati, ottenuto con la funzione Ricerca_annunci(). |
Per completare in modo ragionevole l'esempio proposto di gestione di inserzioni automatiche, bisogna prevedere anche un meccanismo di eliminazione automatica degli annunci dopo un certo tempo. Per questo si può usare un programma separato, che utilizzi privilegi maggiori di quelli dell'utente nobody, eseguito periodicamente dal sistema Cron.
#!/usr/bin/perl #====================================================================== # annunci-elimina.pl #====================================================================== #---------------------------------------------------------------------- # Utilizza il modulo Pg, per l'utilizzo delle librerie LIBPQ di # PostgreSQL. #---------------------------------------------------------------------- use Pg; #====================================================================== # Inizio del programma. #====================================================================== if ($#ARGV >= 0) { $giorni = $ARGV[0]; } else { $giorni = 7; } $adesso = time(); $data_minima = $adesso - ( $giorni * 24 * 60 * 60 ); #---------------------------------------------------------------------- # Apre la connessione con il servente PostgreSQL locale, utilizzando # la base di dati «pubblico». #---------------------------------------------------------------------- $PGconnessione = Pg::connectdb("dbname = pubblico"); #---------------------------------------------------------------------- # Verifica che la connessione sia avvenuta. #---------------------------------------------------------------------- if ( $PGconnessione->status == PGRES_CONNECTION_OK ) { #------------------------------------------------------------------ # La connessione è avvenuta, e si procede con l'eliminazione # degli annunci vecchi. #------------------------------------------------------------------ $PGistruzione = " DELETE FROM Annunci WHERE Data < $data_minima "; $PGrisultato = $PGconnessione->exec("$PGistruzione"); #------------------------------------------------------------------ # Verifica il risultato dell'esecuzione dell'istruzione. # Attualmente sembra che il valore dello stato restituito # sia invertito. #------------------------------------------------------------------ $PGstatus = $PGconnessione->status; if ($PGstatus == PGRES_COMMAND_BAD) { print "$PGerrore\n"; } } else { #------------------------------------------------------------------ # Per qualche motivo la connessione con la base di dati non # funziona e si avvisa l'utente di conseguenza. #-------------------------------------------------------------- print "La base di dati ``pubblico'' è inaccessibile.\n"; } #====================================================================== 1; #======================================================================
Per avviare questo programma conviene ottenere i privilegi dell'utente postgres. Si può inserire il suo avvio all'interno del file /etc/crontab
come nell'esempio seguente:
... 30 1 * * * postgres /usr/sbin/annunci-elimina.pl 10
L'esempio mostra l'avvio del programma ogni giorno alle ore 1:30 (della notte), per eliminare le inserzioni più vecchie di 10 giorni.
Di solito, quando si parte da zero, conviene evitare di reinventarsi le subroutine necessarie a gestire i moduli HTML. Attraverso la rete si possono ottenere molti validi esempi già pronti e collaudati da più tempo.
Tra tutte, la libreria di subroutine Perl più diffusa per la gestione di moduli HTML sembra essere cgi-lib.pl di Steven Brenner.
Ian Graham, Web/HTML Documentation and Developer's Resource
Jacqueline D. Hamilton, CGI Programming 101
Christian Neuss, Johan Vromans, Perl, guida pratica, Apogeo, 1996
perlWWW development, raccolta di riferimenti a librerie Perl per la programmazione CGI
Steven Brenner, cgi-lib.pl, libreria standard per la creazione di script CGI in Perl