[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [volume] [parte]


Capitolo 331.   Programmazione CGI in Perl

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.

In questo capitolo vengono usate prevalentemente delle richieste attraverso moduli HTML (elementi FORM) che utilizzano il metodo POST. Un buon programma gateway, tuttavia, dovrebbe essere in grado di gestire, indifferentemente, richieste fatte con i metodi GET e POST. Questo capitolo, infatti, non esaurisce l'argomento della programmazione CGI, ma affronta solo alcuni dei suoi problemi.

331.1   Problemi

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 HTTP a metterlo in funzione ed è sempre il servente a ricevere l'output che poi viene restituito al programma cliente.

In questa situazione, lo standard error del programma viene perduto, assieme alle 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

oppure ancora meglio con:

perl -c -w programma_perl

331.2   Decodifica

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:

I dati provenienti da un modulo HTML (elemento 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:

$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;

331.2.1   Subroutine di decodifica

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. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/mini-lib.pl>.

##
## 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. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/form.pl>.

#!/usr/bin/perl
##
## form.pl
##
#
require ('mini-lib.pl');
#
print STDOUT ("Content-type: text/html\n");
print STDOUT ("\n");
print STDOUT ("<HTML>\n");
print STDOUT ("<HEAD>\n");
print STDOUT ("<TITLE>Metodo $ENV{REQUEST_METHOD}</TITLE>\n");
print STDOUT ("</HEAD>\n");
print STDOUT ("<BODY>\n");
print STDOUT ("<H1>Metodo $ENV{REQUEST_METHOD}</H1>\n");
print STDOUT ("<PRE>\n");
#
if ($ENV{REQUEST_METHOD} eq 'GET')
  {
    %DATI = &Decodifica_GET;
  }
elsif ($ENV{REQUEST_METHOD} eq 'POST')
  {
    %DATI = &Decodifica_POST;
  }
else
  {
    print STDOUT ("Il metodo della richiesta non è gestibile.\n");
  }
#
@nomi = keys (%DATI);
foreach $nome (@nomi)
  {
    print STDOUT ("$nome = $DATI{$nome}\n");
  }
#
print STDOUT ("</PRE>\n");
print STDOUT ("</BODY>\n");
print STDOUT ("</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.

331.3   Alcuni esempi elementari di applicazioni CGI

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 elementi FORM distinti, ognuno dei quali invia una richiesta a un diverso programma gateway specializzato.

331.3.1   File «manuali.html»

Segue il sorgente del file manuali.html contenente i tre elementi FORM necessari per richiamare i programmi gateway in grado di fornire documentazione interna. Una copia di questo file dovrebbe essere disponibile anche qui: <allegati/a2/manuali.html>.

<!DOCTYPE HTML PUBLIC "ISO/IEC 15445:2000//DTD HTML//EN">
<!-- manuali.html -->
<HTML>
<HEAD>
        <TITLE>Manualistica</TITLE>
</HEAD>
<BODY>
<H1>Manualistica</H1>

    <FORM ACTION="/cgi-bin/apropos.pl" METHOD="GET">

        <P>apropos&nbsp;<INPUT NAME="apropos" SIZE="30">
        <INPUT TYPE="submit" VALUE="Invio">

    </FORM>

    <FORM ACTION="/cgi-bin/whatis.pl" METHOD="GET">

        <P>whatis&nbsp;<INPUT NAME="whatis" SIZE="30">
            <INPUT TYPE="submit" VALUE="Invio">

    </FORM>

    <FORM ACTION="/cgi-bin/man.pl" METHOD="GET">

        <P>man&nbsp;
            <SELECT NAME="sezione">
                <OPTION VALUE="" SELECTED="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 331.7 mostra in che modo appaia questo modulo.

Figura 331.7. Il modulo manuali.html.

cgi-manuali

Ognuno dei tre elementi FORM permette di indicare una stringa da utilizzare per ottenere informazioni. Per ogni elementi FORM c'è un 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 modulo, quello della richiesta per i file delle pagine di manuale è un po' diverso, dal momento che potrebbe essere necessario indicare la sezione.

331.3.2   File «apropos.pl»

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. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/apropos.pl>.

#!/usr/bin/perl
##
## apropos.pl
##
#
# Incorpora la libreria di decodifica dei dati.
#
require ('mini-lib.pl');
#
# &Metodo_non_gestibile ()
#
sub Metodo_non_gestibile
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>apropos $DATI{apropos}</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>apropos $DATI{apropos}</H1>\n");
    print STDOUT ("<PRE>\n");
    #
    while ($risposta = <APROPOS>)
      {
        print $risposta;
      }
    #
    print STDOUT ("</PRE>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
  }
else
  {
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Errore</H1>\n");
    print STDOUT ("Si è manifestato un errore durante l'inoltro ");
    print STDOUT ("della richiesta.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 viene letto successivamente e riemesso all'interno di una pagina HTML con il ciclo seguente:

while ($risposta = <APROPOS>)
  {
   print STDOUT ($risposta);
  }

Figura 331.11. Il risultato di un'interrogazione apropos per la parola «manual».

cgi-manuali-apropos

331.3.3   File «whatis.pl»

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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>whatis $DATI{whatis}</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>whatis $DATI{whatis}</H1>\n");
    print STDOUT ("<PRE>\n");
    #
    while ($risposta = <WHATIS>)
      {
       print STDOUT ($risposta);
      }
    #
    print STDOUT ("</PRE>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 331.13. Il risultato di un'interrogazione whatis per la parola «man».

cgi-manuali-whatis

331.3.4   File «man.pl»

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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>man $DATI{sezione} $DATI{man}</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>man $DATI{sezione} $DATI{man}</H1>\n");
    print STDOUT ("<PRE>\n");
    #
    while ($risposta = <MAN>)
      {
       print STDOUT ($risposta);
      }
    #
    print STDOUT ("</PRE>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 un condotto necessario a eliminare i caratteri di controllo che non potrebbero essere visualizzati nella pagina HTML.

open (MAN, "man $DATI{sezione} $DATI{man} | col -bx |")

Figura 331.16. Il risultato di un'interrogazione man per il comando man, senza specificare la sezione.

cgi-manuali-man

331.4   Ordini a distanza

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:

  1. invio del modulo compilato da parte dell'utente;

  2. verifica da parte del programma gateway e richiesta di conferma dei dati introdotti;

  3. conferma da parte dell'utente;

  4. invio dei dati in forma di messaggio di posta elettronica all'utente root;

  5. 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.

331.4.1   File «ordine.html»

Segue il sorgente del file ordine.html. Una copia di questo file dovrebbe essere disponibile anche qui: <allegati/a2/ordine.html>.

<!DOCTYPE HTML PUBLIC "ISO/IEC 15445:2000//DTD HTML//EN">
<!-- ordine.html -->
<HTML>
<HEAD>
        <TITLE>Ordine attraverso FORM</TITLE>
</HEAD>
<BODY>
<H1>Ordine attraverso FORM</H1>

    <FORM ACTION="/cgi-bin/ordine.pl" METHOD="POST">

        <P><INPUT TYPE="hidden" NAME="modulo" VALUE="ordine base">
        Articolo ordinato:
            <SELECT NAME="articolo">
                <OPTION VALUE="0" SELECTED="selected">Nessuno
                <OPTION VALUE="A">Articolo A
                <OPTION VALUE="B">Articolo B
                <OPTION VALUE="C">Articolo C
                <OPTION VALUE="D">Articolo D
            </SELECT>
        <P><STRONG>Dati dell'ordinante</STRONG>
        <P>nome:&nbsp;<INPUT NAME="nome" SIZE="25"> &nbsp;
        cognome:&nbsp;<INPUT NAME="cognome" SIZE="25">
        <P>via:&nbsp;<INPUT NAME="via" SIZE="20"> &nbsp;
        n.:&nbsp;<INPUT NAME="n" SIZE="5"><BR>
        c.a.p.:&nbsp;<INPUT NAME="cap" SIZE="5"> &nbsp;
        città:&nbsp;<INPUT NAME="citta" SIZE="15"><BR>
        e-mail:&nbsp;<INPUT NAME="email" SIZE="30">
        <P>carta: VISA&nbsp;<INPUT TYPE="radio" NAME="carta" VALUE="VISA"
            CHECKED="checked">
        &nbsp;
        American&nbsp;Express&nbsp;<INPUT TYPE="radio" NAME="carta"
            VALUE="American Express"> &nbsp;
        <INPUT NAME="carta_num" SIZE="20" MAXLENGTH="19">
        <INPUT TYPE="submit" VALUE="Invio dell'ordine">

    </FORM>

</BODY>
</HTML>

La figura 331.18 mostra un esempio di compilazione del modulo.

Figura 331.18. Un esempio di compilazione del modulo contenuto nel file ordine.html.

cgi-ordine-inizio

331.4.2   File «ordine.pl»

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. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/ordine.pl>.

#!/usr/bin/perl
##
## ordine.pl
##
#
# Incorpora la libreria di decodifica dei dati.
#
require ('mini-lib.pl');
#
# &Metodo_non_gestibile ()
#
sub Metodo_non_gestibile
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT
      ("<INPUT type=\"hidden\" name=\"articolo\" value=\"$DATI{articolo}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"nome\" value=\"$DATI{nome}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"cognome\" value=\"$DATI{cognome}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"via\" value=\"$DATI{via}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"n\" value=\"$DATI{n}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"cap\" value=\"$DATI{cap}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"citta\" value=\"$DATI{citta}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"email\" value=\"$DATI{email}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"carta\" value=\"$DATI{carta}\">\n");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"carta_num\" value=\"$DATI{carta_num}\">\n");
}
#
# &Richiedi_conferma ()
#
sub Richiedi_conferma
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Conferma</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Conferma dati dell'ordinazione.</H1>\n");
    print STDOUT ("Si prega di controllare i dati e di confermare se tutto ");
    print STDOUT ("appare in ordine.\n");
    print STDOUT ("<PRE>");
    print STDOUT ("Nominativo: $DATI{nome} $DATI{cognome}\n");
    print STDOUT ("Indirizzo: $DATI{via} $DATI{n}\n" );
    print STDOUT ("           $DATI{cap} $DATI{citta}\n" );
    print STDOUT ("           $DATI{email}\n" );
    print STDOUT ("Carta: $DATI{carta} $DATI{carta_num}\n");
    print STDOUT ("Articolo ordinato: $DATI{articolo}\n");
    print STDOUT ("</PRE>");
    print STDOUT ("<FORM action=\"/cgi-bin/ordine.pl\" method=\"POST\">\n");
    print STDOUT ("<P>");
    print STDOUT
      ("<INPUT type=\"hidden\" name=\"modulo\" value=\"ordine conferma\">\n");
    #
    &Dati_nascosti;
    #
    print STDOUT ("<INPUT type=\"submit\" value=\"Conferma i dati e l'ordine\">\n");
    print STDOUT ("</FORM>\n");
    #
    print STDOUT ("Se i dati non sono come desiderato, si prega di ritornare\n");
    print STDOUT ("alla <A href=\"/ordine.html\">compilazione del modulo</A>.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Dati_insufficienti ()
#
sub Dati_insufficienti
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>I dati inseriti nel modello sono insufficienti.</H1>\n");
    print STDOUT ("Si prega di controllare e aggiungere i dati mancanti.\n");
    print STDOUT ("<P><A href=\"/ordine.html\">");
    print STDOUT ("Ritorna al modulo di ordinazione</A>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Modulo_errato ()
#
sub Modulo_errato
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Il modulo inviato non è previsto.</H1>\n");
    print STDOUT ("Si prega di utilizzare il \n");
    print STDOUT ("<A href=\"/ordine.html\">");
    print STDOUT ("modulo d'ordine standard</A>.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &E_mail_errore ()
#
sub E_mail_errore
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Impossibile inviare l'ordine</H1>\n");
    print STDOUT ("Si prega di scusare l'inconveniente.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT ("Content-type: text/html\n");
        print STDOUT ("\n");
        print STDOUT ("<HTML>\n");
        print STDOUT ("<HEAD>\n");
        print STDOUT ("<TITLE>Conferma invio</TITLE>\n");
        print STDOUT ("</HEAD>\n");
        print STDOUT ("<BODY>\n");
        print STDOUT ("<H1>Conferma invio</H1>\n");
        print STDOUT ("Il Vostro ordine è stato inviato.\n");
        print STDOUT ("Grazie.\n");
        print STDOUT ("</BODY>\n");
        print STDOUT ("</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 331.20 mostra in che modo viene richiesta la conferma dei dati inseriti come dall'esempio della figura precedente.

Figura 331.20. La richiesta di conferma a seguito dell'invio del modulo di ordinazione.

cgi-ordine-conferma

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

331.4.3   File «ordine2.pl»

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 STDOUT ("Content-type: text/html\n");
        print STDOUT ("\n");
        print STDOUT ("<HTML>\n");
        print STDOUT ("<HEAD>\n");
        print STDOUT ("<TITLE>Conferma invio</TITLE>\n");
        print STDOUT ("</HEAD>\n");
        print STDOUT ("<BODY>\n");
        print STDOUT ("<H1>Conferma invio</H1>\n");
        print STDOUT ("Il Vostro ordine è stato inviato.\n");
        print STDOUT ("Grazie.\n");
        print STDOUT ("</BODY>\n");
        print STDOUT ("</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';

331.4.4   Sviluppi ulteriori

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, per 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.

331.5   Interfacciamento con una base di dati

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 600 è 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 programma cliente elementare attraverso un condotto. PostgreSQL offre il programma 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 relazione 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.

331.5.1   Utenti del DBMS e utenti anonimi per il sistema operativo

È 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.(1)

Perché tali programmi possano funzionare occorre che questo utente sia aggiunto anche nel DBMS (si veda eventualmente la sezione 597.1); inoltre, è necessario che le relazioni che si utilizzano permettano l'accesso da parte di questo utente, attraverso una politica opportuna di REVOKE e GRANT.

Per facilitare la lettura, 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: 65534 -> [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

331.5.2   Preparazione del listino

La relazione contenente il listino da interrogare deve essere costruita attraverso gli strumenti di PostgreSQL. Dovendo realizzare qualcosa che deve essere accessibile a 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 relazione 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 relazione con soli tre attributi: codice, descrizione e prezzo. Successivamente vengono inserite le varie tuple 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 relazione: 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]

331.5.3   File «listino.pl»

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; quindi, da quel punto in poi è comunque tutto sotto il suo controllo. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/listino.pl>.

#!/usr/bin/perl
##
## listino.pl
##
#
# Incorpora la libreria di decodifica dei dati.
#
require ('mini-lib.pl');
#
# &Metodo_non_gestibile ()
#
sub Metodo_non_gestibile
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT
      ("<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Consultazione del listino</H1>\n");
    print STDOUT ("<FORM action=\"/cgi-bin/listino.pl\" method=\"GET\">\n");
    print STDOUT ("<P>\n");
    print STDOUT
      ("Inserire una stringa di ricerca per ottenere gli articoli la\n");
    print STDOUT ("cui descrizione coincide: ``%'' corrisponde a una stringa\n");
    print STDOUT ("indefinita; ``_'' corrisponde a un singolo carattere\n");
    print STDOUT ("indefinito.</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT name=\"ricerca\" size=\"25\">\n");
    print STDOUT ("<INPUT type=\"submit\" value=\"Cerca\"></P>\n");
    print STDOUT ("</FORM>\n");
    print STDOUT ("<P><HR></P>\n");
    print STDOUT
      ("<H3>Risultato della ricerca con il modello: ``$DATI{ricerca}''</H3>\n");
    print STDOUT ("\n");
    print STDOUT ("@risposta");
    print STDOUT ("\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Dati_insufficienti ()
# In pratica, invia il FORM da compilare.
#
sub Dati_insufficienti
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT
      ("<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Consultazione del listino</H1>\n");
    print STDOUT ("<FORM action=\"/cgi-bin/listino.pl\" method=\"GET\">\n");
    print STDOUT ("<P>\n");
    print STDOUT
      ("Inserire una stringa di ricerca per ottenere gli articoli la\n");
    print STDOUT
      ("cui descrizione coincide: ``%'' corrisponde a una stringa\n");
    print STDOUT ("indefinita; ``_'' corrisponde a un singolo carattere\n");
    print STDOUT ("indefinito.</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT name=\"ricerca\" size=\"25\">\n");
    print STDOUT ("<INPUT type=\"submit\" value=\"Cerca\"></P>\n");
    print STDOUT ("</FORM>\n");
    print STDOUT ("\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 relazione 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 un condotto, 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 331.30 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 331.30. Un esempio del funzionamento del programma listino.pl.

cgi-listino

331.5.4   Componente Perl Pg

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; nel caso di Perl si tratta di utilizzare il modulo Pg (che deve essere stato installato opportunamente).(2)

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 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, "$query_sql", \@tabella);
      }
    #
    # La connessione non ha bisogno di essere chiusa.
    #
    # Procede con la restituzione del risultato.
    #
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT
      ("<TITLE>Consultazione di un listino attraverso PostgreSQL</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Consultazione del listino</H1>\n");
    print STDOUT ("<FORM action=\"/cgi-bin/listino2.pl\" method=\"GET\">\n");
    print STDOUT ("<P>\n");
    print STDOUT
      ("Inserire una stringa di ricerca per ottenere gli articoli la\n");
    print STDOUT
      ("cui descrizione coincide: ``%'' corrisponde a una stringa\n");
    print STDOUT ("indefinita; ``_'' corrisponde a un singolo carattere\n");
    print STDOUT ("indefinito.</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT name=\"ricerca\" size=\"25\">\n");
    print STDOUT ("<INPUT type=\"submit\" value=\"Cerca\"></P>\n");
    print STDOUT ("</FORM>\n");
    print STDOUT ("<P><HR></P>\n");
    print STDOUT
      ("<H3>Risultato della ricerca con il modello: ``$DATI{ricerca}''</H3>\n");
    print STDOUT ("\n");
    #
    print STDOUT ("<object>\n");
    print STDOUT ("<TR>");
    print STDOUT ("<TH>Codice</TH>");
    print STDOUT ("<TH>Descrizione</TH>");
    print STDOUT ("<TH>Prezzo unitario</TH>");
    print STDOUT ("</TR>\n");
    for ($i = 0; $i <= $#tabella; $i++)
      {
        print STDOUT ("<TR>");
        for ($j = 0; $j <= $#{$tabella[$i]}; $j++)
          {
            print STDOUT ("<TD>$tabella[$i][$j]</TD>");
          }
        print STDOUT ("</TR>\n");
      }
    print STDOUT ("</object>\n");
    #
    print STDOUT ("\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
...

Evidentemente, la differenza sostanziale sta nella subroutine Ricerca_listino(), dove al posto di 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 viene poi 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 relazione ottenuta dall'interrogazione non contiene le intestazioni, per cui queste vengono inserite prima della sua scansione.

print STDOUT ("<object>\n");
print STDOUT ("<TR>");
print STDOUT ("<TH>Codice</TH>");
print STDOUT ("<TH>Descrizione</TH>");
print STDOUT ("<TH>Prezzo unitario</TH>");
print STDOUT ("</TR>\n");
for ($i = 0; $i <= $#tabella; $i++)
  {
    print STDOUT ("<TR>");
    for ($j = 0; $j <= $#{$tabella[$i]}; $j++)
      {
        print STDOUT ("<TD>$tabella[$i][$j]</TD>");
      }
    print STDOUT ("</TR>\n");
  }
print STDOUT ("</object>\n");

331.6   Inserimento e interrogazione attraverso il programma di navigazione

Nelle sezioni seguenti viene proposto un esempio attraverso cui gli utenti possono eseguire sia inserimenti che interrogazioni dalla stessa relazione. 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.

331.6.1   Preparazione della relazione

La relazione 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 relazione necessaria.

Attraverso psql si crea la relazione 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 utilizza poi la funzione time() per riempire questo campo, dove la funzione restituisce un numero intero che rappresenta la quantità di secondi trascorsi da una data di riferimento.

Gli utenti che vogliono aggiungere un'inserzione attraverso il programma CGI, devono fornire tutti i dati, a esclusione della data che viene ottenuta dal sistema operativo. Durante l'interrogazione vengono mostrati solo il testo dell'inserzione e l'indirizzo di posta elettronica di chi lo ha fatto.

331.6.2   File «annunci.pl»

Il programma attraverso cui si gestisce tutto è annunci.pl. Questo organizza un sistema molto semplice, con pochi controlli di sicurezza. Nonostante ciò, si tratta comunque di un esempio molto lungo. Come al solito, l'inizio si trova verso la fine del sorgente. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/annunci.pl>.

#!/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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Metodo $ENV{REQUEST_METHOD} non gestibile.</H1>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT
      ("<H1>I dati inseriti nel modello sono insufficienti.</H1>\n");
    print STDOUT ("Si prega di controllare e aggiungere i dati mancanti.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Modulo_iniziale ()
#
sub Modulo_iniziale
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Annunci on-line</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Annunci on-line</H1>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Selezionare una delle due voci seguenti:</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<A href=\"/cgi-bin/annunci.pl?modulo=preannuncio\">");
    print STDOUT ("inserimento di un nuovo annuncio</A></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<A href=\"/cgi-bin/annunci.pl?modulo=prericerca\">");
    print STDOUT ("ricerca tra gli annunci</A></P>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Modulo_nuovo_annuncio ()
#
sub Modulo_nuovo_annuncio
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Annunci on-line: inserimento</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Inserimento di un annuncio</H1>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Si prega di inserire tutti i dati richiesti.</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<FORM action=\"/cgi-bin/annunci.pl\" method=\"POST\">\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT type=\"hidden\" name=\"modulo\" value=\"annuncio\">\n");
    print STDOUT ("Rubrica:&nbsp;<SELECT name=\"rubrica\">\n");
    print STDOUT ("     <OPTION value=\"0\" selected=\"selected\">Nessuna\n");
    print STDOUT ("     <OPTION value=\"1\">Compro\n");
    print STDOUT ("     <OPTION value=\"2\">Vendo\n");
    print STDOUT ("     <OPTION value=\"3\">Messaggi\n");
    print STDOUT ("     <OPTION value=\"4\">Varie\n");
    print STDOUT ("</SELECT></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Testo dell'annuncio:<BR>\n");
    print STDOUT ("     <INPUT name=\"testo\" size=\"80\"></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("e-mail:&nbsp;<INPUT name=\"email\" size=\"40\"></P>\n");
    print STDOUT ("\n");
    print STDOUT ("<H2>Dati che non vengono pubblicati</H2>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Cognome:&nbsp;<INPUT name=\"cognome\" size=\"25\"></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Nome:&nbsp;<INPUT name=\"nome\" size=\"25\"></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Telefono:&nbsp;<INPUT name=\"telefono\" size=\"25\"></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT type=\"submit\" value=\"Invia l'inserzione\"></P>\n");
    print STDOUT ("</FORM></P>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Modulo_ricerca ()
#
sub Modulo_ricerca
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Annunci on-line: ricerca annunci</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Ricerca tra gli annunci</H1>\n");
    print STDOUT ("<P>\n");
    print STDOUT
      ("Si deve indicare la rubrica e un modello di ricerca.</P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Il modello è sensibile alla differenza tra maiuscole \n");
    print STDOUT ("e minuscole, si può utilizzare il simbolo `%' per \n");
    print STDOUT ("indicare una stringa di caratteri indefinita.\n</P>");
    print STDOUT ("<P>\n");
    print STDOUT ("<FORM action=\"/cgi-bin/annunci.pl\" method=\"GET\">\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT type=\"hidden\" name=\"modulo\" value=\"ricerca\">\n");
    print STDOUT ("Rubrica:&nbsp;<SELECT name=\"rubrica\">\n");
    print STDOUT ("     <OPTION value=\"0\" selected=\"selected\">Nessuna\n");
    print STDOUT ("     <OPTION value=\"1\">Compro\n");
    print STDOUT ("     <OPTION value=\"2\">Vendo\n");
    print STDOUT ("     <OPTION value=\"3\">Messaggi\n");
    print STDOUT ("     <OPTION value=\"4\">Varie\n");
    print STDOUT ("</SELECT></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Modello di ricerca:&nbsp;");
    print STDOUT ("<INPUT name=\"modello\" size=\"30\" value=\"%\"></P>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("<INPUT type=\"submit\" value=\"Inizia la ricerca\"></P>\n");
    print STDOUT ("</FORM></P>\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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 STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT
      ("<TITLE>Annunci on-line: rubrica n. $DATI{rubrica}</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Consultazione della rubrica n. $DATI{rubrica}</H1>\n");
    #
    for ($i = 0; $i <= $#tabella; $i++)
      {
        #
        # Emette il testo dell'annuncio.
        #
        print STDOUT ("<P>\n");
        print STDOUT ("$tabella[$i][0]</P>\n");
        #
        # Emette l'indirizzo e-mail dello scrivente.
        #
        print STDOUT ("<P>\n");
        print STDOUT
          ("<A href=\"mailto:$tabella[$i][1]\">$tabella[$i][1]</A></P>\n");
        print STDOUT ("<HR>\n");
      }
    print STDOUT ("\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Database_inaccessibile ()
#
sub Modulo_errato {
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Problemi di accesso alla base di dati.</H1>\n");
    print STDOUT
      ("Per qualche motivo non è possibile accedere alla base di dati ");
    print STDOUT ("degli annunci. Si prega di perdonare l'inconveniente.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Istruzione_errata ()
#
sub Istruzione_errata
{
    local ($errore) = $_[0];
    local ($istruzione) = $_[1];
    #
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Errore</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Problemi di accesso alla base di dati.</H1>\n");
    print STDOUT ("<P>\n");
    print STDOUT ("Il comando richiesto ha generato l'errore seguente,</P>");
    print STDOUT ("<P>\n");
    print STDOUT ("<CODE>${errore}</CODE></P>");
    print STDOUT ("<P>\n");
    print STDOUT ("a seguito di questa istruzione SQL:</P>");
    print STDOUT ("<P>\n");
    print STDOUT ("<CODE>${istruzione}</CODE></P>");
    print STDOUT ("Si prega di avvisare l'amministratore del servizio.");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</HTML>\n");
}
#
# &Annuncio_memorizzato ()
#
sub Annuncio_memorizzato
{
    print STDOUT ("Content-type: text/html\n");
    print STDOUT ("\n");
    print STDOUT ("<HTML>\n");
    print STDOUT ("<HEAD>\n");
    print STDOUT ("<TITLE>Annuncio memorizzato</TITLE>\n");
    print STDOUT ("</HEAD>\n");
    print STDOUT ("<BODY>\n");
    print STDOUT ("<H1>Annuncio memorizzato</H1>\n");
    print STDOUT ("L'annuncio è stato memorizzato. Grazie.\n");
    print STDOUT ("</BODY>\n");
    print STDOUT ("</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.
        #
        $PGstatus = $PGrisultato->resultStatus;
        #
        if (PGRES_COMMAND_OK == $PGstatus )
          {
            &Annuncio_memorizzato();
          }
        else
          {
            &Istruzione_errata( $PGconnessione->errorMessage, $PGistruzione );
          }
      }
    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 (elementi FORM) necessari per interagire. Per distinguere il contesto per il quale vengono eseguite le richieste del protocollo HTTP si utilizzano degli elementi 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 331.39. La pagina di ingresso al servizio viene ottenuta con la funzione Modulo_iniziale().

cgi-postgresql-annunci-inizio

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 331.41. Modulo per l'inserimento di un'inserzione, già compilato e pronto per essere trasmesso, ottenuto con la funzione Modulo_nuovo_annuncio().

cgi-postgresql-annunci-inserimento

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 331.42. Modulo per ricercare gli annunci nella base di dati, ottenuto con la funzione Ricerca_annunci().

cgi-postgresql-annunci-ricerca

331.6.3   Eliminazione periodica degli annunci vecchi

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. Una copia di questo script dovrebbe essere disponibile anche qui: <allegati/a2/annunci-elimina.pl>.

#!/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 STDOUT ("$PGerrore\n");
      }
  }
else
  {
    #
    # Per qualche motivo la connessione con la base di dati non
    # funziona e si avvisa l'utente di conseguenza.
    #
    print STDOUT ("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 01:30 (della notte), per eliminare le inserzioni più vecchie di 10 giorni.

331.7   Librerie CGI già pronte

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.

331.8   Riferimenti

Appunti di informatica libera 2007.02 --- Copyright © 2000-2007 Daniele Giacomini -- <daniele (ad) swlibero·org>


1) Eventualmente, in base alla configurazione del servente HTTP, può trattarsi di un altro utente specifico.

2) Nella distribuzione GNU/Linux Debian, il modulo Pg è contenuto nel pacchetto libpg-perl.


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome programmazione_cgi_in_perl.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico]

Valid ISO-HTML!

CSS validator!