4. Monitoraggio delle sessioni
4.1. Il problema
Un'applicazione web può consistere in diversi scambi di moduli tra il server e il client. Il processo funziona come segue:
- Passo 1
- Il client C1 apre una connessione con il server ed effettua la sua richiesta iniziale.
- Il server invia il modulo F1 al client C1 e chiude la connessione aperta nel passo 1.
- Fase 2
- Il client C1 lo compila e lo rinvia al server. Per farlo, il browser apre una nuova connessione con il server.
- Il server elabora i dati del modulo 1, ne ricava l'informazione I1, invia il modulo F2 al client C1 e chiude la connessione aperta nel passaggio 3.
- Fase 3
- Il ciclo dei passaggi 3 e 4 si ripete nei passaggi 5 e 6. Al termine del passaggio 6, il server avrà ricevuto due moduli, F1 e F2, e avrà calcolato le informazioni I1 e I2 da essi.
Il problema in questione è: in che modo il server tiene traccia delle informazioni I1 e I2 associate al client C1? Questo problema è chiamato tracciamento della sessione del client C1. Per comprenderne la causa principale, esaminiamo il diagramma di un'applicazione server TCP-IP che serve più client contemporaneamente:
![]() |
In una classica applicazione client-server TCP-IP:
- il client stabilisce una connessione con il server
- scambia dati con il server tramite questa connessione
- la connessione viene chiusa da una delle due parti
I due punti chiave di questo meccanismo sono:
- viene creata una singola connessione per ogni client
- questa connessione viene utilizzata per l'intera durata del dialogo tra il server e il suo client
Ciò che permette al server di sapere in ogni momento con quale client sta interagendo è la connessione — o, in altre parole, il "canale" — che lo collega al suo client. Poiché questo canale è dedicato a un client specifico, tutto ciò che passa attraverso questo canale proviene da quel client, e tutto ciò che viene inviato attraverso questo canale raggiunge il client.
Il meccanismo client-server HTTP segue da vicino il modello precedente, con l'eccezione che il dialogo client-server è limitato a un singolo scambio tra il client e il server:
- il client apre una connessione al server ed effettua la sua richiesta
- il server invia la sua risposta e chiude la connessione
Se al tempo T1, un client C effettua una richiesta al server, ottiene una connessione C1 che verrà utilizzata per il singolo scambio richiesta-risposta. Se, al tempo T2, questo stesso client effettua una seconda richiesta al server, otterrà una connessione C2 diversa dalla connessione C1. Per il server, non vi è quindi alcuna differenza tra questa seconda richiesta dell’utente C e la sua richiesta iniziale: in entrambi i casi, il server tratta il cliente come un nuovo cliente. Affinché vi sia un collegamento tra le diverse connessioni del cliente C al server, il cliente C deve essere “riconosciuto” dal server come un “cliente abituale” e il server deve recuperare le informazioni in suo possesso su tale cliente abituale.
Immaginiamo un sistema che funziona come segue:
- C'è una sola coda
- Ci sono diversi sportelli di servizio. Ciò significa che più clienti possono essere serviti contemporaneamente. Quando uno sportello si libera, un cliente lascia la fila per essere servito a quello sportello
- Se questa è la prima visita del cliente, l’addetto allo sportello gli consegna un biglietto con un numero. Il cliente può porre una sola domanda. Una volta ricevuta la risposta, deve lasciare lo sportello e tornare in fondo alla fila. L’addetto allo sportello registra le informazioni del cliente in un file contrassegnato dal numero.
- Quando è di nuovo il suo turno, il cliente potrebbe essere servito da un addetto diverso rispetto alla volta precedente. L'addetto chiede il suo gettone e recupera il fascicolo con il numero del gettone. Ancora una volta, il cliente fa una richiesta, riceve una risposta e le informazioni vengono aggiunte al suo fascicolo.
- E così via... Nel corso del tempo, il cliente riceverà le risposte a tutte le sue richieste. Il tracciamento tra le diverse richieste avviene tramite il gettone e la scheda ad esso associata.
Il meccanismo di tracciamento delle sessioni in un'applicazione web client-server funziona in modo simile:
- Al momento della prima richiesta, al client viene rilasciato un token dal server web
- presenterà questo token ad ogni richiesta successiva per identificarsi
Il token può assumere varie forme:
- un campo nascosto in un modulo
- il client effettua la sua prima richiesta (il server la riconosce perché il client non ha alcun token)
- Il server invia la sua risposta (un modulo) e inserisce il token in un campo nascosto al suo interno. A questo punto, la connessione viene chiusa (il client esce dalla sessione con il proprio token). Il server potrebbe avere delle informazioni associate a questo token.
- Il client effettua una seconda richiesta inviando nuovamente il modulo. Il server recupera il token dal modulo. Può quindi elaborare la seconda richiesta del client accedendo, tramite il token, alle informazioni calcolate durante la prima richiesta. Nuove informazioni vengono aggiunte al file associato al token, una seconda risposta viene inviata al client e la connessione viene chiusa per la seconda volta. Il token è stato reinserito nel modulo di risposta in modo che l'utente possa presentarlo durante la sua prossima richiesta.
- e così via...
Lo svantaggio principale di questa tecnica è che il token deve essere inserito in un modulo. Se la risposta del server non è un modulo, il metodo del campo nascosto non può più essere utilizzato.
- Il metodo del cookie
- Il client effettua la sua prima richiesta (il server la riconosce perché il client non ha alcun token)
- Il server risponde aggiungendo un cookie alle intestazioni HTTP. Ciò avviene utilizzando il comando HTTP Set-Cookie:
Set-Cookie: param1=valore1;param2=valore2;....
dove param1, param2, ecc. sono i nomi dei parametri e i valori corrispondenti. Tra i parametri ci sarà il token. Molto spesso, nel cookie viene incluso solo il token, mentre il server memorizza le altre informazioni nella cartella associata al token. Il browser che riceve il cookie lo memorizzerà in un file sul disco. Dopo la risposta del server, la connessione viene chiusa (il client esce dalla sessione con il proprio token).
- (continua)
- Il client effettua la sua seconda richiesta al server. Ogni volta che viene effettuata una richiesta a un server, il browser controlla tra tutti i cookie in suo possesso per vedere se ne ha uno proveniente dal server richiesto. In tal caso, lo invia al server, sempre sotto forma di comando HTTP: il comando Cookie, che ha una sintassi simile a quella del comando Set-Cookie utilizzato dal server:
Cookie: param1=valore1;param2=valore2;....
Tra i parametri inviati dal browser, il server troverà il token che gli permette di riconoscere il client e recuperare le informazioni ad esso associate.
Questa è la forma di token più comunemente utilizzata. Presenta un unico svantaggio: un utente può configurare il proprio browser in modo da rifiutare i cookie. Tali utenti non potranno quindi accedere alle applicazioni web che utilizzano i cookie.
- Riscrittura dell'URL
- Il client effettua la sua prima richiesta (il server la riconosce perché il client non ha alcun token)
- Il server invia la sua risposta. Questa risposta contiene dei link che l'utente deve utilizzare per continuare a utilizzare l'applicazione. Nell'URL di ciascuno di questi link, il server aggiunge il token nella forma URL;token=valore.
- Quando l'utente clicca su uno dei link per continuare a utilizzare l'applicazione, il browser invia una richiesta al server web, includendo l'URL richiesto (URL;token=value) nelle intestazioni HTTP. Il server è quindi in grado di recuperare il token.
4.2. L'API Java per il tracciamento delle sessioni
Di seguito presenteremo i principali metodi utili per il tracciamento delle sessioni:
Recupera l'oggetto Session a cui appartiene la richiesta corrente. Se la richiesta non faceva ancora parte di una sessione, ne viene creata una. | |
identificatore della sessione corrente | |
data di creazione della sessione corrente (numero di millisecondi trascorsi dal 1° gennaio 1970, ore 00:00). | |
data dell'ultimo accesso del client alla sessione | |
durata massima in secondi di inattività per una sessione. Dopo questo periodo, la sessione viene invalidata. | |
Imposta la durata massima di inattività per una sessione in secondi. Trascorso questo periodo, la sessione viene invalidata. | |
true se la sessione è stata appena creata | |
associa un valore a un parametro in una determinata sessione. Questo meccanismo consente di memorizzare informazioni che rimarranno disponibili per tutta la durata della sessione. | |
Rimuove il parametro dai dati della sessione. | |
restituisce il valore associato al parametro nella sessione. Restituisce null se il parametro non esiste. | |
elenca tutti gli attributi della sessione corrente come enumerazione | |
chiude la sessione corrente. Tutte le informazioni ad essa associate vengono eliminate. |
4.3. Esempio 1
Presentiamo un esempio tratto dall'eccellente libro "Programming with J2EE" pubblicato da Wrox e distribuito da Eyrolles. Questo libro è una miniera di informazioni di alto livello per gli sviluppatori di soluzioni web Java. L'applicazione presentata in questo libro come singolo servlet Java è stata adattata qui come servlet principale che chiama pagine JSP per visualizzare le varie risposte possibili al client.
L'applicazione si chiama "sessions" ed è configurata come segue nel file <tomcat>\conf\server.xml:
Nella cartella docBase sopra indicata, troverete i seguenti elementi:

I file error.jsp, invalid.jsp e valid.jsp sono tutti associati all'applicazione sessions. Nella cartella WEB-INF sopra indicata, troviamo:

Quello mostrato sopra è il file di configurazione web.xml per l'applicazione sessions. Nella cartella classes troverete il file della classe servlet:

Il file web.xml dell'applicazione è il seguente:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>cycledevie</servlet-name>
<servlet-class>cycledevie</servlet-class>
<init-param>
<param-name>urlSessionValide</param-name>
<param-value>/valide.jsp</param-value>
</init-param>
<init-param>
<param-name>urlSessionInvalide</param-name>
<param-value>/invalide.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>cycledevie</servlet-name>
<url-pattern>/cycledevie</url-pattern>
</servlet-mapping>
</web-app>
Il servlet principale si chiama cycledevie (servlet-name) ed è associato al file cycledevie.class (servlet-class). Ha un alias /cycledevie (servlet-mapping) che permette di richiamarlo tramite l'URL http://localhost:8080/sessions/cycledevie. Ha tre parametri di inizializzazione:
URL della pagina che mostra le proprietà della sessione corrente | |
URL della pagina visualizzata dopo l'invalidazione della sessione corrente | |
URL della pagina visualizzata in caso di errore di inizializzazione del servlet principale cycledevie |
I componenti dell'applicazione delle sessioni sono i seguenti:
servlet principale - analizza la richiesta del client:
| |
| |
visualizzata quando l'utente ha invalidato la sessione corrente. Offre quindi la possibilità di crearne una nuova. | |
visualizzata quando il servlet principale rileva degli errori durante l'inizializzazione. |
Il ciclo di vita del servlet principale è il seguente:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class cycledevie extends HttpServlet{
// instance variables
String msgErreur=null;
String urlSessionInvalide=null;
String urlSessionValide=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// was the initialization successful?
if(msgErreur!=null){
// we hand over to the error page
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// retrieve the current session
HttpSession session=request.getSession();
// analyze the action to be taken
String action=request.getParameter("action");
// invalidate current session
if(action!=null && action.equals("invalider")){
// the current session is invalidated
session.invalidate();
// we hand over to the urlSessionInvalide url
getServletContext().getRequestDispatcher(urlSessionInvalide).forward(request,response);
}
// other cases
// we hand over to the urlSessionInvalide url
getServletContext().getRequestDispatcher(urlSessionValide).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlSessionInvalide=config.getInitParameter("urlSessionInvalide");
urlSessionValide=config.getInitParameter("urlSessionValide");
urlErreur=config.getInitParameter("urlErreur");
// parameters ok?
if(urlSessionValide==null || urlSessionInvalide==null){
msgErreur="Configuration incorrecte";
}
}
}
Si notino i seguenti punti:
- Nel suo metodo di inizializzazione, il servlet recupera i suoi tre parametri
- Durante l'elaborazione (doGet) di una richiesta, il servlet:
- verifica innanzitutto che non si siano verificati errori durante l'inizializzazione. Se ce ne sono stati, reindirizza alla pagina error.jsp.
- verifica il valore del parametro action. Se questo parametro ha il valore "invalid", il servlet reindirizza alla pagina invalid.jsp; in caso contrario, alla pagina valid.jsp.
La pagina JSP **valide.jsp** visualizza le caratteristiche della sessione corrente:
<%@ page import="java.util.*" %>
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
%>
<!-- top of page HTML -->
<html>
<meta http-equiv="pragma" content="no-cache">
<head>
<title>Cycle de vie d'une session</title>
</head>
<body>
<h3>Cycle de vie d'une session</h3>
<hr>
<br>Etat session : <%= etat %>
<br>ID session : <%= session.getId() %>
<br>Heure de création : <%= new Date(session.getCreationTime()) %>
<br>Heure du dernier accès : <%= new Date(session.getLastAccessedTime()) %>
<br>Intervalle maximum d'inactivité : <%= session.getMaxInactiveInterval() %>
<br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
<br><a href="/sessions/cycledevie">Recharger la page</a>
<body>
</html>
Si noti che nella riga
utilizziamo un oggetto session che sembra apparire dal nulla. In realtà, questo oggetto è uno degli oggetti impliciti messi a disposizione delle pagine JSP, proprio come gli oggetti request, response, out, config (ServletConfig) e context (ServletContext) che abbiamo già incontrato. I due link presenti nella pagina fanno riferimento al servlet lifecycle presentato in precedenza:
<br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
<br><a href="/sessions/cycledevie">Recharger la page</a>
Il link per invalidare la sessione include il parametro action=invalidate, che permette al servlet del ciclo di vita di riconoscere che l'utente vuole invalidare la sessione corrente. L'altro link ricarica la pagina. Per impedire al browser di recuperare la pagina dalla cache, la direttiva HTML:
. Essa indica al browser di non utilizzare la cache per la pagina ricevuta.
La pagina invalidate.jsp è la seguente:
<!-- top of page HTML -->
<html>
<head>
<title>Cycle de vie d'une session</title>
</head>
<body>
<h3>Cycle de vie d'une session</h3>
<hr>
Votre session a été invalidée
<a href="/sessions/cycledevie">Créer une nouvelle session</a>
</body>
</html>
Fornisce un collegamento al servlet cycledevie senza il parametro action. Questo collegamento farà sì che il servlet cycledevie crei una nuova sessione.
La pagina error.jsp è la seguente:
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String msgErreur= request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée)";
%>
<!-- top of page HTML -->
<html>
<head>
<title>Cycle de vie d'une session</title>
</head>
<body>
<h3>Cycle de vie d'une session</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
Il suo ruolo è quello di visualizzare il messaggio di errore che gli viene inviato dal servlet del ciclo di vita. Vediamo ora alcuni esempi di esecuzione. Il servlet viene richiesto per la prima volta:

La pagina sopra indica che siamo in una nuova sessione. Utilizziamo il link "Ricarica pagina":

Il risultato precedente indica che siamo ancora nella stessa sessione della pagina precedente (stesso ID). Si noti che l'ora dell'ultimo accesso a questa sessione è cambiata. Ora utilizziamo il link “Invalida sessione”:

Si noti l'URL di questa nuova pagina con il parametro action=invalidate. Utilizziamo il link "Crea una nuova sessione" per creare una nuova sessione:

Possiamo vedere che è stata avviata una nuova sessione. Negli esempi precedenti, la sessione si basa sul meccanismo dei cookie. Disattiviamo ora i cookie nel nostro browser e ripetiamo i test. Gli esempi seguenti sono stati eseguiti utilizzando Netscape Communicator. Per qualche motivo inspiegabile, i test eseguiti con IE6 hanno prodotto risultati inaspettati, come se IE6 continuasse a utilizzare i cookie anche se erano stati disattivati. Il servlet cycledevie viene richiesto per la prima volta:

Ora utilizziamo il link "Ricarica pagina":

Possiamo notare due cose:
- l'ID della sessione è cambiato
- il servlet rileva la sessione come una nuova sessione
Il server Tomcat fornisce una soluzione al problema degli utenti che disabilitano i cookie nel proprio browser. Utilizza due meccanismi per implementare il token menzionato all'inizio di questo paragrafo: i cookie e la riscrittura degli URL. Se il cookie di sessione non è disponibile, tenterà di recuperare il token dall'URL richiesto dal client. Affinché ciò funzioni, l'URL deve contenere il token. In generale, tutti i link generati in un documento HTML che puntano all'applicazione web devono contenere il token dell'applicazione. Ciò può essere fatto utilizzando il metodo encodeURL:
aggiunge il token della sessione corrente all'URL passato come parametro nella forma URL;jsessionid=xxxx |
Modifichiamo la nostra applicazione come segue:
- Nel servlet cycledevie.java, gli URL sono codificati:
// we hand over to the error page
getServletContext().getRequestDispatcher(response.encodeURL(urlErreur)).forward(request,response);
....
// we hand over to the urlSessionInvalide url
getServletContext().getRequestDispatcher(response.encodeURL(urlSessionInvalide)).forward(request,response);
....
// we hand over to the urlSessionInvalide url
getServletContext().getRequestDispatcher(response.encodeURL(urlSessionValide)).forward(request,response);
- Nella pagina valide.jsp, gli URL sono codificati:
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
// encodage URL cycledevie
String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>
............
<br><a href="<%= URLcycledevie %>?action=invalider">Invalider la session</a>
<br><a href="<%= URLcycledevie %>">Recharger la page</a>
- Nella pagina invalide.jsp, gli URL sono codificati:
<%
// jspservice - on invalide la session en cours
session.invalidate();
// encodage URL cycledevie
String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>
..........
<a href="<%= URLcycledevie %>">Créer une nouvelle session</a>
Ora siamo pronti per il test. Stiamo utilizzando Netscape 4.5 e i cookie sono stati disabilitati. Richiediamo il servlet cycledevie per la prima volta:

e ricarichiamo la pagina utilizzando il link "Ricarica pagina":

Possiamo vedere che:
- la sessione non è cambiata (stesso ID)
- l'URL del servlet cycledevie contiene effettivamente il token, come mostrato nel campo Indirizzo sopra
- il server Tomcat recupera quindi il token di sessione dall'URL richiesto (se lo sviluppatore ha avuto cura di codificarlo).
4.4. Esempio 2
Presentiamo ora un esempio che mostra come memorizzare informazioni nella sessione di un client. In questo caso, l'unica informazione sarà un contatore che viene incrementato ogni volta che l'utente richiama l'URL del servlet. Quando viene richiamato per la prima volta, appare la seguente pagina:

Se si fa clic sul link "Ricarica pagina" in alto, si ottiene la seguente nuova pagina:

L'applicazione è composta da tre elementi:
- un servlet che elabora la richiesta del client
- una pagina JSP che visualizza il valore del contatore
- una pagina JSP che visualizza eventuali errori
Questi tre componenti sono installati nell'applicazione web "sessions" già in uso. Il suo file web.xml è stato modificato per configurare i nuovi servlet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
...
<servlet>
<servlet-name>compteur</servlet-name>
<servlet-class>compteur</servlet-class>
<init-param>
<param-name>urlAffichageCompteur</param-name>
<param-value>/compteur.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreurcompteur.jsp</param-value>
</init-param>
</servlet>
...
<servlet-mapping>
<servlet-name>compteur</servlet-name>
<url-pattern>/compteur</url-pattern>
</servlet-mapping>
</web-app>
- Il servlet si chiama counter (servlet-name) ed è collegato al file di classe counter.class (servlet-class)
- Ha due parametri di inizializzazione:
- displayCounterURL: URL della pagina JSP che visualizza il contatore
- urlErreur: URL della pagina JSP che visualizza eventuali errori
- e un alias /compteur, il che significa che verrà richiamato tramite l'URL http://localhost:8080/sessions/compteur
Il servlet compteur.java è il seguente:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class compteur extends HttpServlet{
// instance variables
String msgErreur=null;
String urlAffichageCompteur=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// was the initialization successful?
if(msgErreur!=null){
// we hand over to the error page
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// retrieve the current session
HttpSession session=request.getSession();
// and the
String compteur=(String)session.getAttribute("compteur");
if(compteur==null) compteur="0";
// counter incrementation
try{
compteur=""+(Integer.parseInt(compteur)+1);
}catch(Exception ex){}
// save counter in session
session.setAttribute("compteur",compteur);
// and in the query
request.setAttribute("compteur",compteur);
// hand over to counter display url
getServletContext().getRequestDispatcher(urlAffichageCompteur).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlAffichageCompteur=config.getInitParameter("urlAffichageCompteur");
urlErreur=config.getInitParameter("urlErreur");
// parameters ok?
if(urlAffichageCompteur==null){
msgErreur="Configuration incorrecte";
}
}
}
Questo servlet ha la stessa struttura dei servlet che abbiamo già visto. Da notare la gestione del contatore:
- la sessione viene recuperata tramite request.getSession()
- il contatore viene recuperato da questa sessione tramite session.getAttribute("counter")
- se viene recuperato un valore nullo, significa che la sessione è appena iniziata. Il contatore viene quindi impostato a 0.
- il contatore viene incrementato, salvato nuovamente nella sessione (session.setAttribute("counter", counter)) e inserito nella richiesta che verrà passata al servlet di visualizzazione (request.setAttribute("counter", counter)).
La pagina di visualizzazione, compteur.jsp, è la seguente:
<%
// jspService
// on récupère le compteur
String compteur= (String) request.getAttribute("compteur");
if(compteur==null) compteur="inconnu";
%>
<!-- top of page HTML -->
<html>
<head>
<title>Comptage au fil d'une session</title>
</head>
<body>
<h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
<hr>
compteur = (<%= compteur %>)
<br><a href="/sessions/compteur">Recharger la page</a>
</body>
</html>
La pagina sopra riportata recupera semplicemente l'attributo contatore (request.getAttribute("counter")) che le viene passato dal servlet principale e lo visualizza.
La pagina di errore, *erreurcompteur.jsp,* è la seguente:
<%
// jspService
// une erreur s'est produite
String msgErreur= request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
<head>
<title>Comptage au fil d'une session</title>
</head>
<body>
<h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
4.5. Esempio 3
Proponiamo di scrivere un'applicazione Java che funga da client per la precedente applicazione contatore. La chiamerebbe N volte di seguito, dove N viene passato come parametro. Il nostro obiettivo è dimostrare un client web programmato e come gestire i cookie. Il nostro punto di partenza sarà un client web generico presentato nella dispensa Java dello stesso autore. Viene chiamato come segue:
webclient URL GET/HEAD
- URL: URL richiesto
- GET/HEAD: GET per richiedere il codice HTML della pagina, HEAD per limitare la risposta alle sole intestazioni HTTP
Ecco un esempio che utilizza l'URL http://localhost:8080/sessions/compteur:
E:\data\serge\JAVA\SOCKETS\client web>java clientweb http://localhost:8080/sessions/compteur GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 14:21:18 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=B8A9076E552945009215C34A97A0EC5D;Path=/sessions
<!-- top of page HTML -->
<html>
<head>
<title>Comptage au fil d'une session</title>
</head>
<body>
<h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
<hr>
compteur = (1)
<br><a href="/sessions/compteur">Recharger la page</a>
</body>
</html>
Il programma clientweb visualizza tutto ciò che riceve dal server. Sopra, vediamo il comando HTTP Set-cookie, che il server utilizza per inviare un cookie al proprio client. In questo caso, il cookie contiene due informazioni:
- JSESSIONID, che è il token di sessione
- Path, che definisce l'URL a cui appartiene il cookie. Path=/sessions indica al browser che deve inviare il cookie al server ogni volta che richiede un URL che inizia con /sessions. Nell'applicazione sessions abbiamo utilizzato vari servlet, tra cui i servlet /sessions/lifecycle e /sessions/counter. Se chiamiamo il servlet /sessions/cycledevie, il browser riceverà un token J. Se, utilizzando lo stesso browser, chiamiamo poi il servlet /sessions/compteur, il browser invierà il token J al server perché si applica a tutti gli URL che iniziano con /sessions. Nel nostro esempio, i servlet cycledevie e compteur non hanno bisogno di condividere lo stesso token di sessione. Non avrebbero quindi dovuto essere inseriti nella stessa applicazione web. Questo è un punto importante da ricordare: tutti i servlet all'interno della stessa applicazione condividono lo stesso token di sessione.
- Un cookie può anche avere una data di scadenza. In questo caso, tale informazione manca. Il cookie verrà quindi cancellato alla chiusura del browser. Un cookie può avere una durata di N giorni, ad esempio. Finché è valido, il browser lo invierà ogni volta che si accede a uno degli URL nel suo dominio (Path). Si consideri un negozio di CD online. Esso può tracciare il percorso di navigazione di un cliente attraverso il proprio catalogo e determinare gradualmente le sue preferenze, ad esempio la musica classica. Queste preferenze possono essere memorizzate in un cookie con una durata di 3 mesi. Se lo stesso cliente torna sul sito dopo un mese, il browser invierà il cookie all'applicazione server. Sulla base delle informazioni contenute nel cookie, l'applicazione server potrà quindi personalizzare le pagine generate in base alle preferenze del cliente.
Di seguito è riportato il codice del client web. In seguito servirà come punto di partenza per un altro client.
// imported packages
import java.io.*;
import java.net.*;
public class clientweb{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URI GET/HEAD";
// number of arguments
if(args.length != 2)
erreur(syntaxe,1);
// note the URI required
String URLString=args[0];
String commande=args[1].toUpperCase();
// URI validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// order verification
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// incorrect order
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// extract useful information from URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
try{
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println();
// we read the answer
while((réponse=IN.readLine())!=null){
// the answer is processed
System.out.println(réponse);
}//while
// it's over
client.close();
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
}//class
Ora creiamo il programma clientCounter, che viene chiamato come segue:
clientCounter URL N [JSESSIONID]
- URL: URL del servlet contatore
- N: numero di chiamate da effettuare a questo servlet
- JSESSIONID: parametro opzionale — token di sessione
Lo scopo del programma è chiamare il servlet contatore N volte gestendo il cookie di sessione e visualizzando ogni volta il valore del contatore restituito dal server. Al termine delle N chiamate, il valore del contatore deve essere N. Ecco un primo esempio di esecuzione:
E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/counter 3
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A;Path=/sessions
cookie trouvÚ : 92DB3808CE8FCB47D47D997C8B52294A
compteur : 1
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 2
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 3
Il programma visualizza:
- le intestazioni HTTP che invia al server nel modulo -->
- le intestazioni HTTP che riceve
- il valore del contatore dopo ogni chiamata
Possiamo vedere che durante la prima chiamata:
- il client non invia un cookie
- il server ne invia uno
Per le richieste successive:
- il client rinvia sistematicamente il cookie ricevuto dal server durante la prima richiesta. Questo è ciò che permette al server di riconoscerlo e di incrementare il proprio contatore.
- il server non invia più un cookie
Eseguiamo nuovamente il programma precedente passando il token sopra indicato come terzo parametro:
E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/compteur 3 92DB3808CE8FCB47D47D997C8B52294A
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 4
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 5
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 6
Qui vediamo che non appena il client effettua la sua prima richiesta, il server riceve un cookie di sessione valido. È importante notare che per Tomcat, il tempo massimo di inattività predefinito per una sessione è di 20 minuti (questo è in realtà configurabile). Se la seconda richiesta del programma invia il cookie ricevuto durante la prima richiesta abbastanza rapidamente, il server la tratterà come la stessa sessione. Ciò evidenzia una potenziale vulnerabilità di sicurezza. Se riesco a intercettare un token di sessione sulla rete, posso quindi impersonare l'utente che ha avviato la sessione. Nel nostro esempio, la prima chiamata rappresenta l'utente che avvia la sessione (forse con un nome utente e una password che gli garantiscono il diritto di ricevere un token), mentre la seconda chiamata rappresenta l'utente che ha "hackerato" il token di sessione dalla prima chiamata. Se l'operazione in corso è una transazione bancaria, ciò può diventare molto problematico...
Il codice client è il seguente:
// imported packages
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class clientCompteur{
// requests a URL
// displays its contents on the screen
public static void main(String[] args){
// syntax
final String syntaxe="pg URL-COMPTEUR N [JSESSIONID]";
// number of arguments
if(args.length !=2 && args.length != 3)
erreur(syntaxe,1);
// note the URL required
String URLString=args[0];
// URL validity check
URL url=null;
try{
url=new URL(URLString);
}catch (Exception ex){
// URI incorrect
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// check number of calls N
int N=0;
try{
N=Integer.parseInt(args[1]);
if(N<=0) throw new Exception();
}catch(Exception ex){
// incorrect N argument
erreur("Le nombre d'appels N doit être un entier >0",3);
}
// has the JSESSIONID token been passed as a parameter?
String JSESSIONID="";
if (args.length==3) JSESSIONID=args[2];
// extract useful information from URL
String path=url.getPath();
if(path.equals("")) path="/";
String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost();
int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// we can work
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
// the model searched for in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the model searched for in the HTML code
Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
// the result of the model comparison
Matcher résultat=null;
// a Boolean giving the result of the counter search
boolean compteurTrouvé;
try{
// we make N calls to the server
for(int i=0;i<N;i++){
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
envoie(OUT,"GET " + path + query + " HTTP/1.1");
envoie(OUT,"Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
}
envoie(OUT,"Connection: close");
envoie(OUT,"");
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// follow-up response
System.out.println(réponse);
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the cookie
JSESSIONID=résultat.group(1);
}
}
}//while
// that's it for HTTP headers - move on to HTML code
compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// does the current line contain the counter?
if (! compteurTrouvé){
résultat=modèleCompteur.matcher(réponse);
if(résultat.find()){
// counter found - displayed
System.out.println("compteur : " + résultat.group(1));
compteurTrouvé=true;
}
}
}//while
// it's over
client.close();
}//for
} catch(Exception e){
// we handle the exception
erreur(e.getMessage(),4);
}//catch
}//hand
// error display
public static void erreur(String msg, int exitCode){
// error display
System.err.println(msg);
// stop with error
System.exit(exitCode);
}//error
// monitoring client-server exchanges
public static void envoie(PrintWriter OUT,String msg){
// sends message to server
OUT.println(msg);
// screen tracking
System.out.println("--> "+msg);
}//error
}//class
Analizziamo i punti chiave di questo programma:
- Dobbiamo eseguire N scambi client-server. Ecco perché sono in un ciclo
- Per ogni scambio, il client apre una connessione TCP/IP con il server. Una volta stabilita la connessione, invia le intestazioni HTTP della sua richiesta al server:
// on demande l'URL - envoi des entêtes HTTP
envoie(OUT,"GET " + path + query + " HTTP/1.1");
envoie(OUT,"Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
}
envoie(OUT,"Connection: close");
envoie(OUT,"");
Se il token JSESSIONID è disponibile, viene inviato come cookie; in caso contrario, non viene inviato.
- Una volta inviata la richiesta, il client attende la risposta del server. Inizia esaminando le intestazioni HTTP di questa risposta per cercare un possibile cookie. Per trovarlo, confronta le righe ricevute con l'espressione regolare del cookie:
// the model searched in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
...........................
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// follow-up response
System.out.println(réponse);
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the cookie
JSESSIONID=résultat.group(1);
}
}
}//while
- Una volta individuato il token per la prima volta, non verrà più cercato nelle successive richieste al server. Una volta elaborate le intestazioni HTTP della risposta, passiamo al codice HTML della stessa risposta. Al suo interno, cerchiamo la riga che fornisce il valore del contatore. Anche questa ricerca viene effettuata utilizzando un'espressione regolare:
// the counter model searched for in the HTML code
Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
..................................
// that's it for HTTP headers - move on to HTML code
compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// does the current line contain the counter?
if (! compteurTrouvé){
résultat=modèleCompteur.matcher(réponse);
if(résultat.find()){
// counter found - displayed
System.out.println("compteur : " + résultat.group(1));
compteurTrouvé=true;
}
}
}//while
4.6. Esempio 4
Nell'esempio precedente, il client web restituisce il token come cookie. Abbiamo visto che potrebbe anche restituirlo all'interno dello stesso URL richiesto nella forma URL;jsessionid=xxx. Verifichiamolo. Il programma clientCompteur.java viene rinominato clientCompteur2.java e modificato come segue:
....
// on demande l'URL - envoi des entêtes HTTP
if(JSESSIONID.equals(""))
envoie(OUT,"GET " + path + query + " HTTP/1.1");
else envoie(OUT,"GET " + path + query + ";jsessionid=" + JSESSIONID + " HTTP/1.1");
envoie(OUT,"Host: " + host + ":" + port);
envoie(OUT,"Connection: close");
envoie(OUT,"");
....
Il client richiede quindi l'URL del contatore tramite GET URL;jsessionid=xx HTTP/1.1 e non invia più un cookie. Questa è l'unica modifica. Ecco i risultati di una chiamata iniziale:
E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur2 http://localhost:8080/sessions/counter 2
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=48A6DBA8357D808EC012AAF3A2AFDA63;Path=/sessions
cookie trouvÚ : 48A6DBA8357D808EC012AAF3A2AFDA63
compteur : 1
--> GET /sessions/compteur;jsessionid=48A6DBA8357D808EC012AAF3A2AFDA63 HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
compteur : 2
Alla prima richiesta, il client richiede l'URL senza un token di sessione. Il server risponde inviando il token. Il client quindi richiede nuovamente lo stesso URL, aggiungendovi il token ricevuto. Possiamo vedere che il contatore viene incrementato, a dimostrazione del fatto che il server ha correttamente riconosciuto che si trattava della stessa sessione.
4.7. Esempio 5
Questo esempio mostra un'applicazione composta da tre pagine, che chiameremo pagina0, pagina1 e pagina2. L'utente deve accedervi in questo ordine:
- pagina0 è un modulo che richiede informazioni: un nome
- pagina1 è un modulo ricevuto in risposta all'invio del modulo su pagina0. Richiede una seconda informazione: un'età
- pagina2 è un documento HTML che visualizza il nome ottenuto da pagina0 e l'età ottenuta da pagina1.
Qui ci sono tre scambi client-server:
- Nel primo scambio, il modulo di pagina0 viene richiesto dal client e inviato dal server
- Nel secondo scambio, il modulo di page1 viene richiesto dal client e inviato dal server. Il client invia il nome al server.
- Nel terzo scambio, il documento page3 viene richiesto dal client e inviato dal server. Il client invia l'età al server. Il documento page3 deve visualizzare il nome e l'età. Il nome è stato ottenuto dal server nel secondo scambio e da allora è stato "dimenticato". Viene utilizzata una sessione per memorizzare il nome dello scambio 2 in modo che sia disponibile durante lo scambio 3.
Il documento page0 ottenuto nel primo scambio è il seguente:

Compiliamo il campo del nome:

Clicchiamo sul pulsante "Avanti" e otteniamo la seguente pagina 1:

Compiliamo il campo relativo all'età:

Clicchiamo sul pulsante "Avanti" e ci viene quindi presentata la seguente pagina 2:

Quando la pagina 0 viene inviata al server, quest'ultimo potrebbe restituirla con un codice di errore se il campo del nome è vuoto:

Quando invii la pagina 1 al server, questo potrebbe restituire un codice di errore se l'età non è valida:

L'applicazione è composta da un servlet e quattro pagine JSP:
visualizza page0 | |
visualizza la pagina 1 | |
visualizza pagina2 | |
visualizza una pagina di errore |
L'applicazione web si chiama suitedepages ed è configurata come segue nel file server.xml di Tomcat:
Il file di configurazione web.xml per l'applicazione suitedepages è il seguente:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>main</servlet-class>
<init-param>
<param-name>urlPage0</param-name>
<param-value>/page0.jsp</param-value>
</init-param>
<init-param>
<param-name>urlPage1</param-name>
<param-value>/page1.jsp</param-value>
</init-param>
<init-param>
<param-name>urlPage2</param-name>
<param-value>/page2.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
</web-app>
Il servlet principale si chiama main e, grazie al suo alias (servlet-mapping), è accessibile tramite l'URL http://localhost:8080/suitedepages/main*. Ha quattro parametri di inizializzazione, che sono gli URL delle quattro pagine JSP utilizzate per le diverse viste. Il codice per il servlet main* è il seguente:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.util.regex.*;
public class main extends HttpServlet{
// instance variables
String msgErreur=null;
String urlPage0=null;
String urlPage1=null;
String urlPage2=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// was the initialization successful?
if(msgErreur!=null){
// we hand over to the error page
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// we retrieve the step parameter
String étape=request.getParameter("etape");
// retrieve the current session
HttpSession session=request.getSession();
// the current step is processed
if(étape==null) étape0(request,response,session);
if(étape.equals("1")) étape1(request,response,session);
if(étape.equals("2")) étape2(request,response,session);
// other cases are invalid
étape0(request,response,session);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlPage0=config.getInitParameter("urlPage0");
urlPage1=config.getInitParameter("urlPage1");
urlPage2=config.getInitParameter("urlPage2");
urlErreur=config.getInitParameter("urlErreur");
// parameters ok?
if(urlPage0==null || urlPage1==null || urlPage2==null){
msgErreur="Configuration incorrecte";
}
}
//-------- step0
public void étape0(HttpServletRequest request, HttpServletResponse response, HttpSession session)
throws IOException, ServletException{
// we set a few attributes
request.setAttribute("nom","");
// we present page 0
request.getRequestDispatcher(urlPage0).forward(request,response);
}
//-------- step1
public void étape1(HttpServletRequest request, HttpServletResponse response, HttpSession session)
throws IOException, ServletException{
// retrieve the name from the query
String nom=request.getParameter("nom");
// name positioned?
if(nom==null) étape0(request,response,session);
// remove any spaces from the name
nom=nom.trim();
// we put it in a query attribute
request.setAttribute("nom",nom);
// empty name?
if(nom.equals("")){
// it's a mistake
ArrayList erreurs=new ArrayList();
erreurs.add("Nous n'avez pas indiqué de nom");
// put the errors in the query
request.setAttribute("erreurs",erreurs);
// back to page 0
étape0(request,response,session);
}
// valid name - stored in the current session
session.setAttribute("nom",nom);
// set the age attribute in the query
request.setAttribute("age","");
// we present page 1
request.getRequestDispatcher(urlPage1).forward(request,response);
}
//-------- step2
public void étape2(HttpServletRequest request, HttpServletResponse response, HttpSession session)
throws IOException, ServletException{
// retrieve the name from the session
String nom=(String)session.getAttribute("nom");
// name positioned?
if(nom==null) étape0(request,response,session);
// we put it in a query attribute
request.setAttribute("nom",nom);
// the age is retrieved from the query
String age=request.getParameter("age");
// age positioned?
if(age==null){
// back to page 1
request.setAttribute("age","");
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// the age is stored in the query
age=age.trim();
request.setAttribute("age",age);
// valid age?
if(! Pattern.matches("^\\s*\\d+\\s*$",age)){
// this is a mistake
ArrayList erreurs=new ArrayList();
erreurs.add("Age invalide");
// put the errors in the query
request.setAttribute("erreurs",erreurs);
// back to page 1
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// age valid - page 2 is presented
request.getRequestDispatcher(urlPage2).forward(request,response);
}
}
- Il metodo init recupera i quattro parametri di inizializzazione e imposta un messaggio di errore se uno di essi è mancante
- Abbiamo visto che la richiesta consiste in tre scambi. Per tenere traccia della fase corrente di questi scambi, i moduli page0 e page1 hanno una variabile nascosta "etape" con un valore pari a 1 (page0) o 2 (page1). Questo numero può essere interpretato come il numero della pagina successiva da visualizzare. Nel metodo doGet, questo parametro viene recuperato dalla richiesta e, a seconda del suo valore, l'elaborazione viene delegata ad altri tre metodi:
- étape0 elabora la richiesta iniziale e invia page0
- étape1 elabora il modulo su page0 e invia page1 o nuovamente page0 se si è verificato un errore
- étape2 elabora il modulo nella pagina1 e invia la pagina2 o nuovamente la pagina1 se si è verificato un errore
- step0
- visualizza la pagina0 con un nome vuoto
- Passo 1
- Recupera il parametro "name" dal modulo della pagina0.
- Verifica che il nome esista (non sia nullo). In caso contrario, la pagina 0 viene visualizzata nuovamente come se fosse la prima chiamata.
- Verifica che il nome non sia vuoto. Se lo è, viene visualizzata nuovamente la pagina0 insieme a un messaggio di errore.
- memorizza il nome nella sessione corrente e visualizza la pagina 1 se il nome è valido.
- passaggio2
- Recupera il parametro name dalla sessione corrente.
- Verifica che il nome esista (non sia nullo). In caso contrario, visualizza nuovamente la pagina 0 come se fosse la prima chiamata.
- Recupera il parametro age dalla richiesta corrente inviata da page1.
- Verifica che l'età sia valida. In caso contrario, visualizza nuovamente la pagina 1 con un messaggio di errore.
- Memorizza il nome e l'età come attributi della richiesta e visualizza la pagina 2 se il nome e l'età sono validi.
La pagina page0.jsp è la seguente:
<%@ page import="java.util.*" %>
<% // page0.jsp
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
// attributs valides ?
if(nom==null){
// retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
<html>
<head>
<title>page 0</title>
</head>
<body>
<h3>Page 0/2</h3>
<form name="frmNom" method="POST" action="/suitedepages/main">
<input type="hidden" name="etape" value="1">
<table>
<tr>
<td>Votre nom</td>
<td><input type="text" name="nom" value="<%= nom %>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<% // erreurs ?
if (erreurs!=null){
%>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<% for(int i=0;i<erreurs.size();i++){ %>
<li><%= erreurs.get(i) %>
<% }//for %>
</ul>
<% }//if %>
</body>
</html>
- La pagina page0.jsp può essere richiamata dal servlet principale in due casi:
- durante la richiesta iniziale
- dopo l'elaborazione del modulo page0 quando si verifica un errore
- Il parametro `nameToDisplay` viene fornito dal servlet principale, insieme a un eventuale elenco di errori. Il servlet page0.jsp inizia quindi recuperando queste due informazioni.
- Il modulo viene "inviato" al servlet principale con il campo nascosto "etape", che indica in quale fase dell'applicazione si trova l'utente.
La pagina page1.jsp è la seguente:
<%@ page import="java.util.*" %>
<% // page1.jsp
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
// attributs valides ?
if(nom==null || age==null){
// retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
<html>
<head>
<title>page 1</title>
</head>
<body>
<h3>Page 1/2</h3>
<form name="frmAge" method="POST" action="/suitedepages/main">
<input type="hidden" name="etape" value="2">
<table>
<tr>
<td>Nom</td>
<td><font color="green"><%= nom %></font></td>
</tr>
<tr>
<td>Votre âge</td>
<td><input type="text" name="age" size="3" value="<%= age %>"></td>
</tr>
</table>
<input type="submit" value="Suite">
</form>
<% // erreurs ?
if (erreurs!=null){
%>
<hr>
<font color="red">
Les erreurs suivantes se sont produites
<ul>
<% for(int i=0;i<erreurs.size();i++){ %>
<li><%= erreurs.get(i) %>
<% }//for %>
</ul>
<% }//if %>
</body>
</html>
La pagina page1.jsp ha una struttura simile a quella della pagina page0.jsp, con l'eccezione che ora riceve due attributi dal servlet principale: name e age. Infine, la pagina page2.jsp è la seguente:
<%
// page2.jsp
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
// attributs valides ?
if(nom==null || age==null){
// retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
<html>
<head>
<title>page 2</title>
</head>
<body>
<h3>Page 2/2</h3>
<table>
<tr>
<td>Nom</td>
<td><font color="green"><%= nom %></font></td>
</tr>
<tr>
<td>Votre âge</td>
<td><font color="green"><%= age %></font></td>
</tr>
</table>
</body>
</html>
Anche la pagina page2.jsp riceve gli attributi name e age dal servlet principale. Si limita a visualizzarli. Infine, la pagina error.jsp, che ha il compito di visualizzare un errore in caso di inizializzazione errata del servlet, è la seguente:
<%
// jspService
// une erreur s'est produite
String msgErreur= request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
<head>
<title>Suite de pages</title>
</head>
<body>
<h3>Suite de pages</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
Visualizza l'attributo msgError che gli è stato passato dal servlet principale.
In conclusione, possiamo vedere che durante le tre fasi dell'applicazione, è sempre il servlet principale ad essere interrogato per primo dal browser. Tuttavia, non è il servlet principale a generare la risposta da visualizzare, ma una delle quattro pagine JSP. L'utente non se ne accorge, poiché il browser continua a visualizzare l'URL inizialmente richiesto — quello del servlet principale — nel campo "Indirizzo".
