Se Apache::Session automatizza tutte le operazioni di memorizzazione e
recupero delle informazioni di stato e di generazione di una chiave, non ci
aiuta molto nel setup e tracking di una sessione. La soluzione che adottiamo
prevede la realizzazione di un semplice modulo mod_perl che, utilizzando
Apache::Session, automatizzi le seguenti azioni:
-
- recuperare l'ID di sessione dal cookie del browser e ripristinare la sessione;
-
- generare e assegnare un nuovo ID di sessione per tutti gli utenti (browser) che non ne abbiano;
-
- rendere disponibile l'oggetto sessione a tutti gli altri moduli che implementino una fase (in particolare la Response Phase) della transazione HTTP;
-
- implementare un semplice meccanismo di garbage collection per le sessioni scadute.
Per raggiungere in maniera elegante il nostro obiettivo sfrutteremo la potenza
del mod_perl. Tramite il meccanismo della ridefinizione degli handler della
Request Loop di Apache (che abbiamo visto nel n 23) installeremo il nostro
modulo nella fase di Header Parsing, con la direttiva
PerlHeaderParserHandler. Ciò renderà trasparente la gestione delle sessioni:
infatti il nostro modulo creerà un oggetto sessione per noi e lo renderà
disponibile a tutti gli altri handler con il meccanismo delle pnotes().
L'Esempio 1 illustra la nostra semplice implementazione di quanto detto fino ad
ora.
Letti i valori delle direttive in httpd.conf tramite le chiamate a
$r->dir_config(), recuperiamo l'ID di sessione nel cookie inviato dal
browser. Notiamo che si utilizzano i metodi che mod_perl ci mette a
disposizione per la gestione dei cookies tramite l'utilizzo della classe
Apache::Cookie:
my %cookies = Apache::Cookie->fetch;
$cookies{'cookie_name'}->value;
per la lettura dei cookies e:
my $cookie = new Apache::Cookie ($r, %args );
$cookie->bake;
per settare un cookie al browser.
Una nuova sessione viene creata con:
tie %session, 'Apache::Session::File', $cookie_id, {
Directory => $session_config{'SessionDirectory'},
LockDirectory => $session_config{'SessionLockDirectory'}
};
questo ci consente direttamente di salvare e leggere informazioni dalla sessione
corrente semplicemente accedendo all'hash legato %session (in realtà ci pensa
poi la classe Apache::Session::File a leggere e a scrivere su file, ma questo
avviene in maniera a noi trasparente!).
Il meccanismo con il quale rendiamo disponibile l'hash %session a tutte le
successive fasi della richiesta HTTP è:
$r->pnotes('SESSION_HANDLE' => \%session );
Il metodo pnotes() ci mette a disposizione un potente strumento per
memorizzare un qualsiasi scalare Perl e renderlo disponibile a tutti gli altri
handler: per strutture dati complesse passiamo un riferimento ad esse. Non ci
dovremo infine preoccupare di deallocare tutte le variabili che abbiamo definito
con pnotes() poiché alla fine di ogni richiesta mod_perl farà questo per
noi.
Esempio 1. Apache/SessionManager.pm
package Apache::SessionManager;
use strict;
use Apache::Constants qw(:common);
use Apache::Cookie ();
use Apache::Session::File;
sub setup {
my $r = shift;
my %session_config;
$session_config{'SessionExpire'} = $r->dir_config("SessionExpire") || 60;
$session_config{'SessionName'} = $r->dir_config("SessionName") || 'PERLSESSIONID';
$session_config{'SessionDirectory'} = $r->dir_config("SessionDirectory") || '/tmp';
$session_config{'SessionLockDirectory'} = $r->dir_config("SessionLockDirectory") || '/tmp';
# Fetch cookies
my %cookies = Apache::Cookie->fetch;
my $cookie_id = $cookies{$session_config{'SessionName'}}->value
if defined $cookies{$session_config{'SessionName'}};
my %session;
# recupera l'oggetto sessione a partire dall'ID del cookie di sessione
eval {
tie %session, 'Apache::Session::File', $cookie_id, {
Directory => $session_config{'SessionDirectory'},
LockDirectory => $session_config{'SessionLockDirectory'}
};
};
# Cookie non presente/sessione non valida: crea un nuovo oggetto sessione
if ($@) {
tie %session, 'Apache::Session::File', undef, {
Directory => $session_config{'SessionDirectory'},
LockDirectory => $session_config{'SessionLockDirectory'}
};
$cookie_id = undef;
$session{'start'} = time;
}
# check dell'expiration sessione (garbage collection)
else {
# Sessione scaduta: viene creato un nuovo oggetto sessione
if ( (time - $session{'start'}) > $session_config{'SessionExpire'} ) {
tied(%session)->delete;
tie %session, 'Apache::Session::File', undef, {
Directory => $session_config{'SessionDirectory'},
LockDirectory => $session_config{'SessionLockDirectory'}
};
$cookie_id = undef;
$session{'start'} = time;
}
}
# per ogni nuova sessione, inviamo il cookie al browser
unless ( $cookie_id ) {
my $cookie = Apache::Cookie->new($r,
-name => $session_config{'SessionName'},
-value => $session{_session_id},
-path => '/'
);
$cookie->bake;
}
# memorizza il riferimento all'oggetto sessione per gli altri handler della request
$r->pnotes('SESSION_HANDLE' => \%session );
# registra ed esegue la sub cleanup alla fine della request
$r->register_cleanup(\&cleanup);
return DECLINED;
}
sub cleanup {
my $r = shift;
my $session = ref $r->pnotes('SESSION_HANDLE') ? $r->pnotes('SESSION_HANDLE') : {};
untie %{$session};
return DECLINED;
}
1;
__END__
Salviamo il modulo su file system (ad es. in /home/web/Apache/SessionManager.pm)
e configuriamo in httpd.conf con il seguente blocco di direttive:
<IfModule mod_perl.c>
<Perl>
use lib '/home/web';
</Perl>
PerlModule Apache::SessionManager
PerlModule Apache::SessionTest
<Location /test-session>
SetHandler perl-script
PerlSetVar SessionExpire 60
PerlSetVar SessionName PERLSESSIONID
PerlSetVar SessionDirectory "/tmp/apache_session_data"
PerlSetVar SessionLockDirectory "/tmp/apache_session_data/lock"
PerlHeaderParserHandler Apache::SessionManager
PerlHandler Apache::SessionTest
</Location>
</IfModule>
Commentiamo brevemente la configurazione. Dopo aver aggiunto la directory
home/lib ai path presenti in @INC, precarichiamo i nostri moduli
Apache::SessionManager e Apache::SessionTest (che vedremo più avanti).
Con le varie direttive PerlSetVar abbiamo la possibilità di configurare il
comportamento del modulo di gestione delle sessioni. In particolare
SessioneExpire definisce la durata massima di una sessione (in secondi),
SessioneName definisce il nome del cookie di sessione inviato al browser,
SessionDirectory e SessionLockDirectory specificano le directory su file
system usate da Apache::Session::File per la memorizzazione delle
informazioni relative alle sessioni. Assicuriamoci dunque che l'utente Apache
abbia permessi di lettura e scrittura in queste directory!
Come semplice esempio di applicazione del nostro modulo, scriviamo un altro
modulo Apache/SessionTest.pm (Esempio 2), responsabile della Response Phase per
la generazione dei contenuti che non fa altro che fotografare lo stato corrente
stampando le variabili di sessione istanziate. Possiamo poi, con il submit della
semplice form, definire e salvare nella sessione corrente nuove variabili di
stato (Figura 3).
Figura 3. Output del modulo Apache/SessionTest.pm
L'istruzione tramite la quale recuperiamo l'oggetto sessione (e quindi tutte le
informazioni salvate nello stato durante le successive transazioni HTTP) è:
my $session = ref $r->pnotes('SESSION_HANDLE')
? $r->pnotes('SESSION_HANDLE') : {};
che recupera con pnotes() il riferimento ad un oggetto sessione istanziato
dal modulo Apache::SessionManager nella fase di Header Parsing della
richiesta HTTP. Da questo momento in poi possiamo leggere le variabili di
sessione salvate nelle precedenti transazioni HTTP direttamente con:
print $$session{'old_session_var'};
# equivalente a
print $session->{'old_session_var'};
e crearne di nuove (o modificarle) con:
$$session{'new_session_var'} = 'bla bla';
# equivalente a
$session->{'new_session_var'} = 'bla bla';
esattamente come se lavorassimo con un hash (un riferimento in questo caso). Per
provare il modulo, riavviamo Apache e lanciamo la URL:
http://localhost/test-session (relativa alla URI che abbiamo definito con la
direttiva <Location>). Provate a cambiare e aggiungere variabili di
sessione con l'apposita form e noterete che le informazioni visualizzate
cambieranno di conseguenza. Allo scadere poi della sessione tutte le variabili
precedentemente impostate non saranno stampate in quanto non più disponibili.
Esempio 2. Apache/SessionTest.pm
package Apache::SessionTest;
use strict;
use Apache::Constants qw(:common);
use Apache::Request ();
sub handler {
my $r = shift;
my $str;
# Recupera la sessione
my $session = ref $r->pnotes('SESSION_HANDLE') ? $r->pnotes('SESSION_HANDLE') : {};
$$session{'timestamp'} = time;
# Recupera i parametri della FORM e li assegna alla sessione
my $form = new Apache::Request($r);
$$session{$form->param('name')} = $form->param('value')
if ( $form->param('name') && $form->param('value') );
$str = <<EOM;
<HTML>
<HEAD><TITLE>Session Test</TITLE></HEAD>
<BODY>
<CENTER><H1>Session management with cookies</H1>
<FORM METHOD="POST">
Nuova variabile di sessione: <INPUT TYPE="text" NAME="name" SIZE="10">
Valore: <INPUT TYPE="text" NAME="value" SIZE="10">
<INPUT TYPE="submit" VALUE="Invia">
</FORM>
<TABLE BORDER="1">
<TR ALIGN="center"><TD COLSPAN="2"><B>Variabili di sessione</B></TD></TR>
EOM
foreach (sort keys %{$session}) {
$str .= " <TR><TD>$_</TD><TD>$$session{$_}</TD></TR>\n";
}
$str .= "</TABLE></CENTER>\n</BODY><HTML>";
# Output HTML al client
$r->content_type('text/html');
$r->send_http_header;
$r->print($str);
return OK;
}
1;
__END__
|