Enrico Sorcinelli
Apache/mod_perl: un application server open source

Continuiamo il nostro viaggio all'interno dell'architettura Apache/mod_perl analizzando le tecniche e gli strumenti di debug che questo framework ci mette a disposizione.
Parte III

Data di pubblicazione: Linux & C., anno 4, numero 24 (Maggio 2002).

©2003 Linux & C. Tutti i diritti riservati. E' vietata la copia e la riproduzione o ridistribuzione dell'articolo senza l'espresso permesso scritto dell'autore e dell'editore.

© Perl Mongers Italia. Tutti i diritti riservati.


Introduzione

Dopo aver visto nei numeri precedenti come utilizzare mod_perl per incrementare le prestazioni degli script Perl CGI già realizzati e per scrivere moduli Apache veri e propri ci soffermiamo un attimo su un fondamentale aspetto del ciclo di vita del software: il debugging del codice. Un framework che si rispetti deve poter disporre di strumenti di debug che consentano di evidenziare e risolvere gli errori (bug) della nostra applicazione: mod_perl soddisfa queste esigenze mettendoci a disposizione un'innumerevole serie di moduli. Dedichiamo quindi questa puntata all'analisi delle tecniche e di alcuni dei più comuni tool per il debugging in ambiente Apache/mod_perl. Da notare che comunque alcuni degli strumenti che illustreremo non sono prerogativa esclusiva del mod_perl ma possono essere altresì utilizzati nello sviluppo di CGI Perl tradizionali.


Una programmazione 'pulita'

Abbiamo speso molte parole nelle puntate precedenti sul paradigma della programmazione sotto mod_perl in merito alla persistenza degli oggetti globali (variabili, references, handle di connessioni ad un database, ecc.) attraverso successive richieste dello stesso modulo. Se da un lato ciò comporta notevoli vantaggi, come appunto le connessioni permanenti ad un RDBMS, dall'altro rappresenta una delle cause più frequenti di malfunzionamento dei nostri moduli: variabili globali che credevano non inizializzate e che contengono invece il valore impostato nella precedente invocazione del modulo possono causare eccezioni indesiderate al nostro codice. Non mi stancherò mai di ripeterlo ma la migliore soluzione è sempre quella di evitare il più possibile l'uso di variabili globali e di utilizzare sempre il pragma use strict che forza l'interprete Perl, in fase di compilazione e non di esecuzione, a generare un errore ogni qualvolta vengono incontrate variabili (o più in generale oggetti) globali che non siano stati esplicitamente dichiarati con our, use vars, resi locali con my() o che non siano 'fully-qualified' (es. $MyPackage::MySubClass::MyVar).

In aggiunta a ciò, brevemente accenniamo alle più diffuse tecniche di error handling che possiamo utilizzare nello sviluppo dei nostri moduli. La prima rudimentale gestione delle eccezioni prevede l'utilizzo del costrutto eval { BLOCK } e la verifica della variabile speciale $@ nel caso che il blocco di istruzioni BLOCK generi un errore di run time. Ad esempio:

Esempio 1. eval.pl, semplice script per il costrutto Perl eval

   #!/usr/local/bin/perl -w

   use strict;

   eval {
      require StrangeModule;
   };
   if ($@) {
      print "Modulo non presente. Download dal CPAN [y/n]? ";
      my $answer = <STDIN>;
      chomp $answer;
      if ( $answer =~ /y|yes/i) {
         require CPAN;
         CPAN::install StrangeModule;
      }
      else {
         die("[Fatal]: il modulo StrangeModule  necessario!\n");
      }
   }

   # Il programma continua... 

   exit;

Una soluzione più elegante invece ce la fornisce il modulo Perl Error (prelevabile dal CPAN, http://www.cpan.org/modules/by-module/Error/Error-0.15.tar.gz) tramite un'interfaccia ad oggetti per la gestione delle eccezioni con la possibilità di utilizzare blocchi di istruzioni try/catch/except/finally esattamente come in Java (Esempio 2).

Esempio 2. error.pl, semplice script per illustrare l'exception handling con try/catch

   #!/usr/local/bin/perl -w

   use Error qw(:try);
   use strict;
   @Error::Test::ISA = qw(Error);

   my $file = $ARGV[0];
   try {
      open(FILE,"$file") 
         or throw Error::Test(-text => "File '$file' non esistente");
      print "File $file esistente\n";
      close(FILE);
   }
   catch Error::Test with {
      my $e = shift;
      print $e->file,' (linea ',$e->line,') error: ',$e->text,"\n";
   };
   exit;


error_log: il nostro migliore amico

Sembra banale a dirsi, ma l'error_log del server Apache è e rimarrà sempre il nostro migliore amico. Capita infatti spesso di perdere tempo per capire come mai la nostra applicazione non funzioni quando la spiegazione ce l'abbiamo lì, proprio sotto gli occhi! In fase di sviluppo poi è sempre bene avere più informazioni possibili. Lanciamo dunque i nostri script CGI tramite lo switch -w e attiviamo PerlWarn On (in httpd.conf) per i moduli mod_perl: se impostiamo il livello di logging di Apache nel seguente modo:

   LogLevel warn

troveremo nell'error_log anche tutti i warning generati dal codice Perl. Senza contare infine che, utilizzando le API Apache ($r-Egtlog_error>) o anche un banale print STDERR "Errore...", il file error_log è il luogo più indicato dove scrivere le nostre informazioni di debug.


Moduli per il debugging

Un altro strumento utile, utilizzato anche nelle precedenti puntate, è il modulo Data::Dumper che consente il dump e la visualizzazione, in svariati formati, di strutture dati complesse. Esistono tuttavia una serie di moduli mod_perl assai utili nella diagnosi dei problemi.

Apache::StatINC e Apache::Reload

Quando modifichiamo un modulo mod_perl, c'è la necessità di riavviare il server Apache affinché le modifiche abbiano effetto. Questo può risultare abbastanza tedioso durante la fase di sviluppo. Apache::StatINC, incluso nella distribuzione standard mod_perl, consente di aggiornare (cioè ricompilare ed eseguire) in maniera automatica tutti i moduli e i file presenti nella speciale variabile %INC che sono stati modificati senza dover riavviare il server. Per attivarlo, le direttive in httpd.conf sono le seguenti:

   PerlModule Apache::FirstModule
   PerlModule Apache::StatINC
   <Location /linuxandc-1>
      SetHandler perl-script
      PerlHandler Apache::FirstModule
      PerlInitHandler Apache::StatINC
      PerlSetVar StatINCDebug On
   </Location>

In tal modo, ogni modifica al file produrrà anche una linea simile nell'error_log di Apache:

   Apache::StatINC: process 297 reloading Apache/FirtModule.pm

Questo modulo dovrebbe essere utilizzato solo in fase di sviluppo e non in ambiente di produzione poiché comporta un overhead aggiuntivo dovuto al monitoraggio di tutti i file presenti in %INC.

Una valida alternativa è l'utilizzo del modulo mod_perl Apache::Reload (disponibile su CPAN, http://www.cpan.org/modules/by-module/Apache/Apache-Reload-0.07.tar.gz) che consente di ricaricare solo i moduli desiderati riducendo quindi le chiamate di sistema stat(). Dopo averlo scompattato ed installato nella maniera standard per tutti i moduli Perl (perl Makefile.PL && make && make test && make install) configuriamo httpd.conf per attivarlo:

   PerlInitHandler Apache::Reload
   PerlSetVar ReloadAll Off

D'ora in poi, tutti i moduli che conterranno la linea use Apache::Reload; verranno ricaricati e ricompilati automaticamente se modificati. Ad esempio:

   package Apache::MyModule;

   use Apache::Constants qw(:common);
   use Apache::Reload;
   use strict;

   sub handler { ... }

   1;

Vale comunque lo stesso discorso fatto per Apache::StatINC: anche Apache::Reload andrebbe utilizzato solo durante lo sviluppo e non dovrebbe mai essere installato in un ambiente di produzione.

Apache::Status

Questo utile modulo standard mod_perl consente di analizzare lo stato dell'interprete mod_perl. È possibile ottenere informazioni circa i moduli caricati (Figura 1), le dimensioni di tutte le variabili, ottenere il dump delle strutture allocate, di ispezionare i simboli e tante altre cose compresa la visualizzazione grafica delle strutture dati (sono necessari in tal caso moduli aggiuntivi quali Devel::SysDump, Data::Dumper, Apache::Peek, B::Grahp tutti reperibili presso il CPAN). Per attivare il report va configurato httpd.conf:

   PerlModule Apache::Status
   <Location /perl-status>
      SetHandler perl-script
      PerlHandler Apache::Status
      PerlSetVar StatusDumper On
      order deny,allow
      deny from all
      allow from .mydomain.com localhost
   </Location>

Figura 1. Pannello di monitoraggio fornito da Apache::Status: elenco dei moduli caricati

Pannello di monitoraggio fornito da Apache::Status - elenco dei moduli caricati

Dopo aver riavviato il server è possibile accedere all'URL http://localhost/perl-status (Figura 2). Questo report può essere facilmente ampliato run-time dai propri moduli per aggiungere voci di menu all'indice e per stampare informazioni riguardanti lo stato della propria applicazione. Ad esempio il modulo Apache::DBI registra una sua voce di menu ('DBI connections') in cui è possibile monitorare le connessioni permanenti attive.

Figura 2. Pannello di monitoraggio fornito da

Apache::Status

Pannello di 
monitoraggio fornito da Apache::Status

Apache::FakeRequest

Questo modulo mod_perl, incluso nella distribuzione standard mod_perl, consente di simulare l'ambiente mod_perl inizializzando un oggetto request di Apache senza che il server sia attivo. Può dimostrarsi utile per un veloce test senza dover ogni volta configurare l'handler, riavviare il server e lanciare la URL dal browser. A titolo di esempio ecco lo script da lanciare a linea di comando per emulare la richiesta http:://localhost/linuxandc-1 (il nostro primo modulo, vedere la prima puntata):

   #!/usr/local/bin/perl

   use Apache::FakeRequest ();
   use Apache::FirstModule ();

   my $r = Apache::FakeRequest->new();
   Apache::FirstModule::handler($r);

Apache::Debug

Questo modulo mod_perl, incluso nella distribuzione standard mod_perl, consente di effettuare un dump di alcune utili informazioni di debugging. Il suo utilizzo prevede ad esempio la seguente sintassi:

   use Apache::Debug (); 
   Apache::Debug::dump($r, SERVER_ERROR, "Uh Oh!");

Apache::DebugInfo, Apache::VMonitor

Questi moduli, non inclusi nella distribuzione standard ma disponibili su CPAN, costituiscono una valida integrazione degli strumenti fino ad ora visti. In particolare Apache::DebugInfo (http://www.cpan.org/modules/by-module/Apache/Apache-DebugInfo-0.05.tar.gz) fornisce una interfaccia ad oggetti per la raccolta di utili informazioni sulla connessione HTTP corrente. Apache::VMonitor (http://www.cpan.org/modules/by-module/Apache/Apache-VMonitor-0.7.tar.gz) è un'interfaccia Web per le più diffuse utilities (quali top, mount, df, ifconfig, ecc) ed al contempo mostra uno snapshot del sistema in merito alle risorse utilizzate dai processi httpd e dai moduli mod_perl caricati.

mod_status

Concludiamo questa breve carrellata con mod_status, un modulo Apache scritto in C ma incluso nella distribuzione standard di Apache, che fornisce utili informazioni circa lo stato corrente del server: uptime, idle childs, CPU usage, richieste, ecc. In genere viene compilato per default ed è sufficiente attivarlo tramite le direttive in httpd.conf:

   ExtendedStatus On
   <Location /status>
      SetHandler server-status
      order deny,allow
      deny from all
      allow from localhost
   </Location> 

Dopo aver riavviato il server è possibile accedere alle statistiche all'URL http://localhost/status


perldb: il Perl debugger

Non poteva mancare un breve accenno al Perl debugger (per il quale vi rimando comunque a digitare 'perldoc perldebug'), ma è utile illustrare come utilizzare il debugger del Perl per il debug dei nostri script CGI e mod_perl. In Perl, il debugger non è un programma separato (come ad es il gdb) ma è integrato nell'interprete. Lanciando lo script Perl dell'Esempio 1 con lo switch -d con:

   %> perl -d error.pl

abbiamo a disposizione un ambiente interattivo, a linea di comando (Figura 3), dove sono a disposizione tutti i comandi tipici per esaminare il flusso del nostro programma: visione del codice sorgente, settaggio dei breakpoints, lettura e modifica dei valori delle variabili, dump dello stack delle funzioni chiamate, ecc. (vedere tabella A).

Figura 3. Il debugger Perl a linea di comando

Il debugger Perl a linea di comando

;

Vediamo ora come è possibile utilizzare il debug interattivo anche per script CGI e moduli mod_perl. Per il debug di uno script CGI tradizionale è necessario aggiungere lo switch -d cambiando la prima linea dello script da:

   #!/usr/local/bin/perl 

a

   #!/usr/local/bin/perl -d

Eventualmente gli altri switch presenti possono essere lasciati, ad esempio:

   #!/usr/local/bin/perl -Twd

È importante osservare che il server httpd deve essere avviato in non-forking mode tramite l'opzione -X:

   #!/usr/local/apache/bin/httpd -X

A questo punto lanciando col browser l'URL corrispondente al nostro script otterremo nella console da dove abbiamo avviato Apache il prompt del debug pronto per iniziare la nostra sessione di debug e parallelamente il browser si metterà in una situazione di hangup, cioè rimarrà 'appeso' (connesso al server) in attesa di mostrare passo passo l'output prodotto dallo script CGI.


Apache::DB

Sotto mod_perl è necessario installare l'estensione Apache::DB per utilizzare il debugger Perl interattivo. Il modulo è come sempre prelevabile dal CPAN (http://www.cpan.org/modules/by-module/Apache/Apache-DB-0.06.tar.gz) e si installa nella maniera tradizionale. Per abilitare il debug occorre configurare httpd.conf nel seguente modo:

   <IfModule mod_perl.c>
      # Perl Debugger under mod_perl
      <IfDefine PERLDB>
         <Perl>
            use Apache::DB ();
            Apache::DB->init;
         </Perl>
      </IfDefine>
   </IfModule>

È strettamente necessario che questo blocco di direttive <Perl> venga inserito prima di ogni altro codice Perl in httpd.conf per far sì che tutti i simboli di debugging vengano inseriti all'interno del parse tree, (la rappresentazione interna simbolica ad albero di un programma Perl generata durante la fase di compilazione). Quindi abilitiamo il debugger utilizzando la direttiva <Location>:

   <Location />
      <IfDefine PERLDB>
         PerlFixupHandler Apache::DB
      </IfDefine>
   </Location>

In questo modo abbiamo attivato il debugger a partire dalla root (/) del nostro web e quindi per tutte le richieste. Si noti come si utilizzi la direttiva <IfDefine target> che ci permette di abilitare o meno il parsing di blocchi di direttive all'avvio del server e di attivare nel nostro caso il debug tramite lo switch -D a linea di comando:

   %> /usr/local/apache/bin/httpd -X -DPERLDB

senza dover ogni volta modificare il file httpd.conf. Come per il CGI tradizionale, lanciando col browser l'URL http://localhost/linuxandc-1, corrispondente ad uno dei moduli mod_perl realizzato nella puntata scorsa, otterremo nella console da dove abbiamo avviato Apache il prompt del Perl debugger (Figura 4) e parallelamente anche qui il browser rimarrà 'appeso' (connesso al server) in attesa di mostrare l'output prodotto dal modulo.

Figura 4. Il debugger Perl con Apache::DB

Il debugger Perl con Apache::DB

;


Il visual debugger ptkdb

Un package degno di nota è sicuramente il ptkdb debugger basato sulle librerie grafiche PerlTk che quindi devono essere già installate. Utilizzare questo visual debugger in mod_cgi e mod_perl è relativamente semplice. Il modulo è come sempre disponibile su CPAN (http://www.cpan.org/modules/by-module/Devel/Devel-ptkdb-1.1074.tar.gz) e non presenta particolari problemi di installazione. Per avviare il debugger con semplici script Perl è sufficiente lanciare ad esempio:

   %> perl -d:ptkdb error.pl

Per il debug di script CGI è necessario modificare la prima linea dello script per indicare che si desidera usare il debugger ptkdb e quindi si deve definire la variabile di ambiente DISPLAY per settare l'host su cui apparirà la console di debug.

   #!/usr/local/bin/perl -Twd:ptkdb

   BEGIN {
     $ENV{'DISPLAY'} = "debughost:0.0";
   }            

   # lo script CGI inizia qui...

In tal caso nell'host specificato da 'debughost' (è possibile utilizzare sia hostname che IP) si aprirà la finestra grafica del debugger. Per utilizzare questo visual debugger con mod_perl è necessario apportare una piccola modifica al modulo Apache/DB.pm (in genere sotto /usr/lib/perl5/site_perl/i386-linux/Apache/DB.pm) e sostituire la linea:

   require 'Apache/perl5db.pl';

con

   require 'Devel/ptkdb.pm'; 

Quindi riavviamo il server:

   %> /usr/local/apache/bin/apachectl restart 
      [notice] Apache::DB initialized in child 4596   

Lanciando l'URL corrispondente al nostro modulo http://localhost/linuxandc-1 otteniamo l'apertura della finestra del visual debugger (figura 5) .

Nota: in entrambi i casi (CGI e mod_perl) è possibile avviare Apache in forking-mode, cioè senza lo switch -X.

Figura 5. Il debugger visuale ptkdb

Il debugger visuale ptkdb

;


Conclusione

Anche lo sviluppatore più esperto commette errori di programmazione: il debugging del codice si rivela allora una fase fondamentale del suo lavoro. Abbiamo visto che, utilizzando in maniera sistematica gli appositi strumenti, è possibile ridurre notevolmente il tempo di debug per evidenziare quelle circostanze e situazioni in cui si possono verificare errori imprevisti o anomalie di funzionamento dei nostri programmi. Nelle prossime puntate continueremo il nostro viaggio all'interno dello sviluppo in mod_perl analizzando come realizzare i componenti base di una web application quali ad esempio la gestione delle sessioni e dei template.


Appendice A - Principali comandi del debugger Perl

Ecco un elenco dei principali comandi che è possibile dare al prompt del debugger Perl. Tutti i comandi non riconosciuti vengono eseguiti tramite eval come codice Perl nel package corrente. Nel caso che l'output generato da un comando occupi più di una schermata è possibile precederli da pipe ('|'), ad es:

   | x %ENV
  • h [COMMAND]
    Stampa una pagina di help. Se si indica anche COMMAND stampa l'help

    contestuale al comando.

  • s [EXPR]
    Esecuzione per singoli passi. Esegue la successiva istruzione fino a che non

    stata raggiunta la successiva istruzione. Se viene fornita EXPR, viene valutata anch'essa per singoli passi.

  • n [EXPR]
    Esegue le chiamate di subroutine senza procedere per singoli passi all'interno

    di esse fino a che non raggiunge un'altra istruzione di pari livello (ad es. una chiamata ad un'altra subroutine). Se EXPR viene fornita viene valutata.

  • <ENTER>
    Con il tasto invio viene ripetuto il precedente comando s o n.
  • r
    Continua l'esecuzione fino all'uscita della subroutine corrente.
  • b [LINE] [COND]
    Setta un breakpoint (interruzione) alla linea LINE se definita, oppure

    alla riga successiva che sta per essere eseguita. Se viene definita anche una condizione COND, viene valutata (tramite eval) ogni volta che viene eseguita la linea e il breakpoint viene triggerato solo se COND è vera.

  • d [LINE]
    Cancella il breakpoint alla linea che sta per essere eseguita o alla linea

    [LINE] se definita.

  • D
    Cancella tutti i breakpoints precedentemente impostati.
  • a [LINE] COMMAND
    Imposta un'azione prima che venga eseguita la linea [LINE], valutando (con

    eval) il codice specificato in COMMAND.

  • A
    Cancella tutte le azioni precedentemente impostate.
  • p [EXPR]
    Calcola e stampa EXPR utilizzando la funzione built-in Perl print
  • x [EXPR]
    Calcola EXPR in un contesto di lista e lo stampa in un formato leggibile


Convenzioni usate in questo articolo

Le seguenti convenzioni tipografiche sono stato utilizzate in questo articolo:

Corsivo

Utilizzato nelle URL, nei percorsi di file, nelle pagine di manuale, nei titoli di libri e/o articoli. Anche nuovi termini cui si intende dare particolare rilievo sono formattati in corsivo.

Larghezza costante

Utilizzato negli esempi di codice e file di configurazione, nel codice che appare all'interno del testo e negli esempi che illustrano cosa digitare a linea di comando.

Negli articoli ci sono molti esempi di codice Perl: alcuni sono solamente pezzi di codice, altri invece programmi completi che possono essere individuati poiché iniziano tutti con la linea #! (shebang):

   #!/usr/bin/perl

Tutti gli esempi che illustrano procedure a linea di comando usano %> per indicare il generico prompt della shell:

   %> perl -e 'print "Hello world\n"'
      Hello word

Occasionalmente viene utilizzato #> per indicare espressamente la shell dell'utente root:

   #> perl -e 'print "Hello world\n"'
      Hello word