Skip to content

10. Applicazione Web MVC [person] – Versione 5

10.1. Introduzione

In questa versione, stiamo apportando due modifiche:

La prima riguarda il modo in cui il client indica al server l'azione che desidera eseguire. Finora, questa veniva specificata utilizzando un parametro chiamato [action] nella richiesta GET o POST del client. Qui, l'azione sarà specificata dall'ultimo elemento dell'URL richiesto dal client, come mostrato nella seguente sequenza:

Image

In [1], l'URL a cui è stato inviato il modulo è [/person5/do/validateForm]. È l'ultimo elemento [validateForm] dell'URL che ha permesso al controller di riconoscere l'azione da eseguire. In [2], il POST attivato dal link [Torna al modulo] è stato inviato all'URL [/person5/do/returnForm]. Anche in questo caso, l'ultimo elemento [returnForm] dell'URL indica al controller quale azione eseguire.

Stiamo introducendo questa modifica perché è il metodo utilizzato dai framework di sviluppo web più diffusi, come Struts o Spring MVC.

Tutti gli URL dell'applicazione avranno la forma [/person5/do/action]. Il file [web.xml] per l'applicazione [/person5] specificherà che accetta URL della forma [/do/*]:


    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
</servlet-mapping>

Il controller recupererà il nome dell'azione da eseguire come segue:

        // on récupère l'action à exécuter
String action=request.getPathInfo();

Il metodo [getPathInfo] dell'oggetto [request] restituisce l'ultimo elemento dell'URL della richiesta.

La seconda modifica riguarda il modo in cui gli input dell'utente vengono memorizzati tra i cicli di richiesta/risposta. Attualmente, queste informazioni vengono memorizzate in una sessione. Questo approccio può presentare degli svantaggi se gli utenti sono molti e la quantità di dati da memorizzare per ciascuno di essi è elevata. Infatti, ogni utente ha la propria sessione personale. Inoltre, la sessione rimane attiva per un certo tempo dopo che un utente ha effettuato il logout, a meno che non sia stata fornita un'opzione di logout. Pertanto, 1.000 sessioni da 100 byte ciascuna occuperanno 1 MB di memoria. Questo rimane un requisito moderato e poche applicazioni hanno 1.000 sessioni attive contemporaneamente.

Tuttavia, esistono alternative alle sessioni che richiedono meno memoria ed è bene conoscerle. In questo caso, useremo il metodo dei cookie. Illustriamolo con un esempio.


Passaggio 1: l'utente invia un modulo:


Questo ciclo di richiesta/risposta genera i seguenti scambi HTTP tra il client e il server:

[1]: [richiesta del client]

POST /personne5/do/validationFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/formulaire
Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

txtNom=pauline&txtAge=18

Questa è una richiesta POST standard. Non c'è nulla di insolito da notare qui, tranne che, anche se non useremo una sessione, il server web ne crea comunque una. Ciò è evidente dal token di sessione che il browser invia al server alla riga 11, che aveva precedentemente ricevuto dal server.

[2]: [risposta del server]

1
2
3
4
5
6
7
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: nom=pauline
Set-Cookie: age=18
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 547
Date: Mon, 22 May 2006 08:03:51 GMT

Possiamo notare che nelle righe 3 e 4 sono state inviate al browser del client delle intestazioni HTTP [Set-Cookie], una per il nome (riga 3) e una per l'età (riga 4). I valori di questi cookie sono quelli inviati nella riga 14 del POST [1] sopra riportato.


Fase 2: Ritorno al modulo


Image

Questo ciclo di richiesta/risposta genera i seguenti scambi HTTP tra il client e il server:

[1]: [richiesta del client]

POST /personne5/do/retourFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/validationFormulaire
Cookie: nom=pauline; age=18; JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

Qui vediamo la richiesta POST generata cliccando sul link [Torna al modulo]. Alla riga 11, vediamo che il browser rimanda al server i cookie ricevuti [name, age, JSESSIONID] utilizzando l’intestazione HTTP [Cookie]. È così che funzionano i cookie. Il client rimanda al server i cookie che il server gli ha inviato. In questo esempio, il controller riceverà i valori [pauline, 18], che dovrà inserire nei campi [txtName, txtAge] della vista [form] visualizzata in [2].

[2]: [risposta del server]

1
2
3
4
5
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2341
Date: Mon, 22 May 2006 08:16:47 GMT

Non c'è nulla di particolare da notare qui, se non il fatto che in questa risposta il server non ha inviato alcun cookie. Ciò non impedirà al browser di rispedire tutti i cookie ricevuti dal server nello scambio successivo, anche se ciò non serve a nulla. Riduciamo quindi il carico sulla memoria disponibile del server a costo di un aumento del flusso di caratteri negli scambi client/server.

10.2. Il progetto Eclipse

Per creare il progetto Eclipse [mvc-personne-05] per l'applicazione web [/personne5], duplicare il progetto [mvc-personne-04] seguendo la procedura descritta nella sezione 6.2.

10.3. Configurazione dell'applicazione web [personne5]

Il file web.xml per l'applicazione /personne5 è il seguente:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>mvc-personne-05</display-name>
    <!--  ServletPersonne -->
    <servlet>
        <servlet-name>personne</servlet-name>
        <servlet-class>
            istia.st.servlets.personne.ServletPersonne
        </servlet-class>
...
    </servlet>
    <!--  Mapping ServletPersonne-->
    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
    <!--  welcome files -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
 

Questo file è identico a quello della versione precedente, tranne che per alcuni dettagli:

  • riga 6: il nome visualizzato dell'applicazione web è cambiato in [mvc-personne-05]
  • riga 18: gli URL gestiti dall'applicazione hanno il formato [/do/*]. In precedenza, veniva gestito solo l'URL [/main]. Ora, gli URL sono tanti quanti sono le azioni da gestire.

La pagina iniziale [index.jsp] cambia:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<c:redirect url="/do/formulaire"/>
  • riga 5: la pagina [index.jsp] reindirizza il client all'URL [/person5/do/form], il che equivale a chiedere al controller di eseguire l'azione [form].

10.4. Il codice della vista

Le viste [form, response, errors] cambiano molto poco. L'unica modifica è che l'azione da eseguire non è più specificata come prima, quando era definita in un campo nascosto denominato [action] nei moduli inviati. Ora è definita nell'URL di destinazione dei moduli inviati, ovvero nell'attributo [action] del tag <form>:

[form.jsp]:


...
<html>
  <head>
    <title>Personne - formulaire</title>
    <script language="javascript">
...
    </script>
  </head>
  <body>
    <center>
      <h2>Personne - formulaire</h2>
      <hr>
      <form name="frmPersonne" action="validationFormulaire" method="post">
...
      </form>
    </center>
  </body>
</html>
  • Riga [13]: Il parametro [action] del modulo ricompare dopo essere stato assente per un certo periodo nelle versioni precedenti. Per comprendere il valore di questo attributo in questo contesto, ricordiamo che tutti gli URL elaborati dall'applicazione hanno la forma [/do/action]. Nella riga [13], l'attributo [action] ha come valore un URL relativo (che non inizia con /). Pertanto, il browser lo completerà con l'URL della pagina attualmente visualizzata, che è necessariamente un URL del tipo [/do/action]. L'ultimo elemento sarà sostituito dall'URL relativo dell'attributo [action] del tag <form> per ottenere l'URL [/do/validationFormulaire] come destinazione POST.
  • Il campo nascosto [action] è scomparso

[response.jsp]:


...
 
<html>
...
  <body>
      ...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>
 
  • riga [7]: la destinazione POST sarà [/do/returnForm]
  • Il campo nascosto [action] è stato rimosso dal modulo nelle righe 7–8.

[errors.jsp]:


...
<html>
...
  <body>
...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>
 
  • riga [6]: la destinazione POST sarà [/do/returnForm]
  • Il campo nascosto [action] è stato rimosso dal modulo nelle righe 6–7.

I lettori sono invitati a testare queste nuove viste utilizzando l'approccio illustrato nelle versioni precedenti.

10.5. Il controller [ServletPersonne]

Il controller [ServletPersonne] dell'applicazione web [/personne5] gestirà le seguenti azioni:

No.
richiesta
origine
elaborazione
1
[GET /person5/do/form]
URL inserito dall'utente
- invia la vista [form] vuota
2
[POST
/person5/do/formValidation]
con i parametri [txtName, txtAge]
pubblicato
fare clic sul
pulsante [Invia] nel
[modulo]
- controlla i valori dei parametri [txtName, txtAge]
- se non sono corretti, inviare la vista [errors(errors)]
- se sono corretti, invia la vista [response(name,age)]
3
[POST
/person5/do/returnForm]
senza parametri inviati
Clicca sul [Torna al
modulo] dalla
[risposta] e [errori].
- inviare la vista [modulo] precompilata con gli ultimi valori inseriti

Lo scheletro del controller [ServletPersonne] è identico a quello della versione precedente. Esamineremo le modifiche apportate ai metodi [doValidationFormulaire, doRetourFormulaire, doGet]; i metodi [init, doInit, doPost] rimangono invariati.

10.5.1. Il metodo [doGet]

Il metodo [doGet] non recupera l'azione da eseguire allo stesso modo delle versioni precedenti:

        @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        // check how the servlet was initialized
        if (erreursInitialisation.size() != 0) {
...
        }
        // retrieve the request sending method
        String méthode=request.getMethod().toLowerCase();
        // retrieve the action to be executed
        String action=request.getPathInfo();
        // action?
        if(action==null){
            action="/formulaire";
        }
        // execution action
        if(méthode.equals("get") && action.equals("/formulaire")){
            // start application
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
            // validation of input form
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/retourFormulaire")){
            // back to input form
            doRetourFormulaire(request,response);
            return;
        }
        // other cases
        doInit(request,response);
    }
  • riga 12: recupera l'azione da eseguire. Ha il formato [/action].
  • righe 18–22: elaborazione dell'azione [/form] richiesta da una richiesta GET
  • righe 23–27: elaborazione dell'azione [/validateForm] richiesta da una richiesta POST
  • righe 28–32: elaborazione dell'azione [/returnForm] richiesta da una richiesta POST

10.5.2. Il metodo [doValidationFormulaire]

Questo metodo elabora la richiesta n. 2 [POST /person5/do/validationFormulaire] con [txtName, txtAge] negli elementi inviati. Il suo codice è il seguente:

// form validation
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // parameters are retrieved
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
        // stored in a cookie
        response.addCookie(new Cookie("nom",nom));
        response.addCookie(new Cookie("age",age));
        // parameter verification
        ...
    }

Novità:

  • Il metodo [doValidationFormulaire] restituisce una delle viste [response, errors]. Indipendentemente dalla risposta, il controller imposta due cookie al suo interno, righe 8–9. Un cookie è rappresentato da un oggetto [Cookie] il cui costruttore accetta due parametri: la chiave del cookie e il valore ad essa associato.
  • riga 8: il valore inserito per il nome viene inserito in un cookie con la chiave "name"
  • riga 9: il valore inserito per l'età viene inserito in un cookie con la chiave "age"
  • Un cookie viene aggiunto alla risposta HTTP inviata al client utilizzando il metodo [response.addCookie]. Questa risposta viene solo preparata qui. Verrà effettivamente inviata solo quando verrà eseguita la pagina JSP della vista inviata al client.

10.5.3. Il metodo [doRetourFormulaire]

Questo metodo elabora la richiesta n. 2 [POST /person5/do/retourFormulaire] senza alcun dato inviato. Il suo codice è il seguente:

        // display pre-filled form
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // retrieve the user's cookies
        Cookie[] cookies=request.getCookies();
        String nom=null;
        String age=null;
        int nbCookies=0;
        for(int i=0;i<cookies.length && nbCookies<2;i++){
            if(cookies[i].getName().equals("nom")){
                nom=cookies[i].getValue();
                nbCookies++;
            }else{
                if(cookies[i].getName().equals("age")){
                    age=cookies[i].getValue();
                    nbCookies++;
                }
            }
        }
        // prepare the form template
        request.setAttribute("nom",nom);
        request.setAttribute("age",age);
        // the form is displayed
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

Novità:

Il metodo [doRetourFormulaire] dovrebbe visualizzare un modulo precompilato con i dati più recenti. Nella versione precedente, questi venivano memorizzati nella sessione. In questa versione, non utilizziamo più la sessione, ma ricorriamo ai cookie per memorizzare i dati tra uno scambio client-server e l'altro. Quando il client richiedeva la convalida del modulo, riceveva in risposta la vista [response] o [errors], a seconda dei casi, insieme a due cookie denominati "name" e "age". Quando si fa clic sul link [Torna al modulo] su queste due viste — il che innesca una richiesta POST all'URL [/do/retourFormulaire] — il browser invia i due cookie ricevuti al server.

  • Righe 4–18: Recuperiamo i valori dei cookie denominati "name" e "age". Stranamente, non esiste un metodo per recuperare il valore di un cookie in base alla sua chiave. Pertanto, dobbiamo iterare attraverso ciascuno dei cookie ricevuti.
  • Una volta fatto ciò, i due valori ottenuti vengono inseriti nel modello della vista [form] (righe 20–21) in modo che possa visualizzarli.

10.6. Test

Avviare o riavviare Tomcat dopo aver integrato il progetto Eclipse [person-mvc-05] al suo interno, quindi richiedere l'URL [http://localhost:8080/personne5].