| |||||
| © Perl Mongers Italia. Tutti i diritti riservati. | |||||
| |||||
| |||||
| |||||
Uno dei principali inconvenienti nello sviluppo di applicazioni web è che l'architettura del protocollo HTTP è stateless cioè senza memoria di stato. Ad ogni richiesta di un oggetto (documento HTML, immagine, ecc.) il browser deve:
Questo ciclo è assai costoso per pagine che contengono ad esempio molte immagini: ogni URL è gestita infatti da una richiesta separata. Le modifiche apportate al protocollo HTTP con la versione 1.1 cercano di risolvere in parte questo problema. Infatti una connessione può essere mantenuta per un certo periodo di tempo invece che essere subito chiusa e le richieste provenienti da uno stesso browser possono riusare quella connessione invece di crearne una nuova. Tuttavia, una volta terminata la transazione HTTP, il server 'dimentica' tutto senza memorizzare informazione alcuna per le transazioni successive né recuperarne da quelle precedenti (Figura 1). Questo rende assai ardua la realizzazione di web-application che richiedono il mantenimento dello stato come applicazioni di e-commerce (shopping cart), questionari multipli, wizard, pagine personalizzate, ecc. Figura 1. Applicazione senza stato (stateless)
La natura stateless del protocollo HTTP è stata in un certo senso uno stimolo per i Web developer a cercare soluzioni per risolvere il problema del mantenimento dello stato attraverso le richieste HTTP: nasce così il concetto di sessione. In breve la sessione definisce il contesto utente (cioè le variabili di stato, o di sessione appunto, associate a quell'utente) nel quale la richiesta deve essere processata. Le tecniche di gestione delle sessioni possono essere suddivise in due gruppi principali: tecniche client-side e tecniche server-side. Per tecniche client-side intendiamo che le informazioni dello stato utente vengono mantenute nel browser con le seguenti modalità:
Queste tecniche hanno l'indubbio vantaggio di essere semplici da realizzare ed evitano la necessità di dovere memorizzare sul server le informazioni di sessione, cosa che può rivelarsi tediosa, oltreché complessa. Tuttavia ci sono delle controindicazioni. Nel caso si utilizzino i cookies infatti, abbiamo il problema della privacy per cui l'utente potrebbe disabilitarne l'uso. Se poi le informazioni sono tante, c'è il vincolo della lunghezza massima di 4 KB per ogni cookie e un massimo di 20 cookies per dominio (quindi 80 KB di spazio massimo per sessione). Nel secondo caso (campi 'hidden') l'utente è libero di analizzare le informazioni che sono scritte nel sorgente HTML della pagina, e modificarle per tentare un submit 'improprio' della form. In tutti i casi comunque è evidente che se le informazioni relative allo stato dovessero essere numerose si potrebbe incorrere in problematiche di consumo eccessivo di banda dal momento che ad ogni richiesta questi dati vanno e vengono dal client al server e viceversa. Per tecniche server-side si intende che le informazioni dello stato, durante le richieste HTTP, vengono mantenute e salvate sul server in maniera trasparente all'utente. Le informazioni possono essere memorizzate in:
Tipicamente questo avviene creando sul server un oggetto 'sessione' che contiene tutte le informazioni relative alla stato dell'utente. Ad ogni sessione viene univocamente associata una chiave e questa è la sola informazione che dovremo preoccuparci di inviare al browser utilizzando uno dei metodi client-side illustrati. Memorizzare lo stato a livello di web server risolve quindi il problema della dimensione della richiesta HTTP e protegge le informazioni dello stato da accidentali o intenzionali modifiche che l'utente può fare. Per ogni richiesta, l'applicazione deve recuperare la chiave dal browser e ristabilire la relativa sessione ripristinando lo stato associato all'utente. Terminata la richiesta, l'applicazione deve infine preoccuparsi di aggiornare le informazioni di quella sessione nel supporto di memorizzazione utilizzato (Figura 2). Figura 2. Applicazione con mantenimento dello stato (stateful)
| |||||
| |||||
%> perl Makefile.PL %> make %> make test %> make install nonché, se siamo collegati in rete, direttamente dal CPAN: %> perl -MCPAN -e "install 'Apache::Session'"
f64621aeaa43778neafa43b05f6d3858 | |||||
| |||||
Se
Per raggiungere in maniera elegante il nostro obiettivo sfrutteremo la potenza
del Letti i valori delle direttive in httpd.conf tramite le chiamate a
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 Il meccanismo con il quale rendiamo disponibile l'hash
$r->pnotes('SESSION_HANDLE' => \%session );
Il metodo 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 Con le varie direttive 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
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__
| |||||
| |||||
La soluzione di scegliere il file system come datastore è poco scalabile ed è più o meno vincolante all'utilizzo di una sola macchina (a meno di non utilizzare file system distribuiti come NFS): in situazioni di load-balancing avremmo poi difficoltà a garantire la consistenza degli ID di sessione. Per risolvere questo problema, accenniamo brevemente a come modificare il modulo
Una delle caratteristiche più interessanti di use Apache::Session::File; con: use Apache::Session::MySQL; vanno modificate le parti di codice relative alle chiamate della funzione
tie %session, 'Apache::Session::MySQL', $cookie_id, {
DataSource => 'dbi:mysql:sessions',
UserName => $db_user,
Password => $db_pass,
LockDataSource => 'dbi:mysql:sessions',
LockUserName => $db_user,
LockPassword => $db_pass
};
È altresì possibile utilizzare connessioni già aperte specificando come opzione
il relativo handle
tie %session, 'Apache::Session::MySQL', $cookie_id, {
Handle => $dbh,
LockHandle => $dbh
};
Va ricordato infine che
mysql> CREATE DATABASE sessions;
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE TABLE sessions (
> id char(32) not null primary key,
> a_session text );
Query OK, 0 rows affected (0.01 sec)
| |||||
| |||||
In questo articolo abbiamo analizzato la gestione delle sessioni in Gran parte dei concetti illustrati in questo articolo sono alla base del modulo Apache::SessionManager disponibile su CPAN (http://search.cpan.org/~enrys/Apache-SessionManager-1.00) Nella prossima puntata concluderemo la serie sul | |||||
| |||||
Questa appendice illustra le principali classi Le seguenti implementazioni sono incluse nella distribuzione di
Le seguenti implementazioni sono disponibili come moduli aggiuntivi su CPAN:
| |||||
| |||||
Le seguenti convenzioni tipografiche sono stato utilizzate in questo articolo: Corsivo
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 #!/usr/bin/perl Tutti gli esempi che illustrano procedure a linea di comando usano %> perl -e 'print "Hello world\n"'
Hello word
Occasionalmente viene utilizzato #> perl -e 'print "Hello world\n"'
Hello word
| |||||