Skip to content

10. Aplikacja internetowa MVC [personne] – wersja 5

10.1. Introduction

W tej wersji wprowadzamy dwie zmiany:

Pierwsza dotyczy sposobu, w jaki klient informuje serwer o akcji, którą chce wykonać. Do tej pory była ona określana za pomocą parametru o nazwie [action] w żądaniu wysyłanym przez klienta o nazwie GET lub POST. W tym przypadku akcja będzie określona przez ostatni element adresu URL żądanego przez klienta, jak pokazuje poniższa sekwencja:

Image

W przypadku [1] adres URL, na który wysłano formularz, to [/personne5/do/validationFormulaire]. To właśnie ostatni element adresu URL, [validationFormulaire], pozwolił kontrolerowi rozpoznać akcję, którą należy wykonać. W przypadku [2], akcja POST wywołana przez link [Retour au formulaire] została wykonana pod adresem URL [/personne5/do/retourFormulaire]. Również w tym przypadku ostatni element adresu URL, [retourFormulaire], wskazuje kontrolerowi, jaką akcję należy wykonać.

Wprowadzamy tę zmianę, ponieważ jest to metoda stosowana przez najpopularniejsze frameworki do tworzenia stron internetowych, takie jak Struts czy Spring MVC.

Wszystkie adresy URL aplikacji będą miały postać [/personne5/do/action]. Plik [web.xml] aplikacji [/personne5] wskaże, że aplikacja ta akceptuje adresy URL w postaci [/do/*]:


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

Kontroler pobierze nazwę akcji do wykonania w następujący sposób:

         // pobieramy akcję do wykonania
String action=request.getPathInfo();

Metoda [getPathInfo] obiektu [request] zwraca ostatni element adresu URL żądania.

Druga wprowadzona zmiana dotyczy sposobu przechowywania danych wprowadzonych przez użytkownika pomiędzy dwoma cyklami żądania/odpowiedzi. Obecnie informacje te są przechowywane w sesji. Metoda ta może mieć swoje wady, jeśli jest wielu użytkowników i trzeba zapamiętać duże ilości danych dla każdego z nich. Każdy użytkownik ma bowiem swoją osobistą sesję. Ponadto sesja ta pozostaje aktywna przez pewien czas po opuszczeniu serwisu przez użytkownika, chyba że zapewniono mu opcję wylogowania. W ten sposób 1000 sesji po 1000 bajtów zajmie 1 MB pamięci. Jest to nadal umiarkowane zapotrzebowanie i niewiele jest aplikacji, w których jednocześnie aktywnych jest 1000 sesji.

Istnieją jednak alternatywy dla sesji, mniej obciążające pamięć, i warto je znać. W tym przypadku wykorzystamy metodę plików cookie. Zilustrujmy to na przykładzie.


Krok 1: użytkownik zatwierdza formularz:


Ten cykl żądania/odpowiedzi powoduje następującą wymianę danych HTTP między klientem a serwerem:

[1] : [demande du 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/osoba5/do/formularz
Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

txtNom=pauline&txtAge=18

Jest to typowy przypadek POST. Nie ma tu nic szczególnego do odnotowania, poza tym, że mimo iż nie będziemy korzystać z sesji, serwer WWW i tak ją tworzy. Widać to po tokenie sesji, który przeglądarka odsyła do serwera w wierszu 11 i który wcześniej otrzymała od serwera.

[2]: [réponse du serveur]

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

Widać, że w wierszach 3 i 4 do przeglądarki klienta wysłano nagłówki HTTP i [Set-Cookie] – jeden dotyczący imienia (wiersz 3), a drugi wieku (wiersz 4). Wartości tych plików cookie to wartości przesłane w wierszu 14 powyższych nagłówków POST i [1].


Krok 2: Powrót do formularza


Image

Ten cykl żądania/odpowiedzi powoduje następującą wymianę danych HTTP między klientem a serwerem:

[1] : [demande du 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/osoba5/do/validationFormulaire
Cookie: nom=pauline; age=18; JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

Obserwujemy tutaj komunikat POST wywołany kliknięciem linku [Retour au formulaire]. W wierszu 11 widzimy, że przeglądarka odsyła do serwera pliki cookie, które otrzymała ([nom, age, JSESSIONID]), za pomocą nagłówka HTTP [Cookie]. Na tej zasadzie działają pliki cookie. Klient odsyła do serwera pliki cookie, które otrzymał od niego. W tym przykładzie kontroler otrzyma wartości [pauline, 18], które musi umieścić w polach [txtNom, txtAge] widoku [formulaire] wyświetlanego w [2].

[2] : [réponse du serveur]

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

Nie ma tu nic szczególnego do odnotowania poza tym, że w tej odpowiedzi serwer nie wysłał żadnych plików cookie. Nie przeszkodzi to jednak przeglądarce w odesłaniu podczas następnej wymiany wszystkich plików cookie, które otrzymała od serwera, nawet jeśli nie ma to żadnego sensu. Zmniejsza się zatem obciążenie dostępnej pamięci serwera kosztem zwiększenia przepływu znaków w wymianie danych między klientem a serwerem.

10.2. Projekt Eclipse

Aby utworzyć projekt Eclipse [mvc-personne-05] dla aplikacji internetowej [/personne5], należy skopiować projekt [mvc-personne-04], postępując zgodnie z procedurą opisaną w punkcie 6.2.

10.3. Konfiguracja aplikacji internetowej [personne5]

Plik web.xml aplikacji /personne5 ma następującą treść:


<?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>
    <!--  Mapowanie ServletPersonne-->
    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
    <!--  pliki startowe -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Plik ten jest identyczny z plikiem z poprzedniej wersji, z wyjątkiem kilku szczegółów:

  • wiersz 6: nazwa wyświetlana aplikacji internetowej zmieniła się na [mvc-personne-05]
  • wiersz 18: adresy URL przetwarzane przez aplikację mają postać [/do/*]. Wcześniej przetwarzany był tylko adres URL [/main]. Teraz mamy tyle adresów URL, ile jest akcji do przetworzenia.

Strona główna [index.jsp] ulega zmianie:


<%@ 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"/>
  • wiersz 5: strona [index.jsp] przekierowuje klienta na adres URL [/personne5/do/formulaire], co sprowadza się do polecenia kontrolerowi wykonania akcji [formulaire].

10.4. Kod widoków

Widoki [formulaire, réponse, erreurs] ulegają niewielkim zmianom. Jedyna zmiana polega na tym, że akcja do wykonania nie jest już określana w taki sam sposób jak wcześniej, kiedy to była definiowana w ukrytym polu o nazwie [action] w wysyłanych formularzach. Obecnie jest ona określona w docelowym adresie URL wysyłanych formularzy, c.a.d, w atrybucie [action] tagu <form>:

[formulaire.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>
  • wiersz [13]: parametr [action] formularza pojawia się ponownie po pewnym czasie nieobecności w poprzednich wersjach. Aby zrozumieć wartość tego atrybutu w tym miejscu, należy pamiętać, że wszystkie adresy URL przetwarzane przez aplikację mają postać [/do/action]. W wierszu [13] atrybut [action] ma wartość adresu URL względnego (niezaczynającego się od /). W związku z tym przeglądarka uzupełni go adresem URL aktualnie wyświetlanej strony, a więc koniecznie adresem URL o postaci [/do/action]. Ostatni element zostanie zastąpiony względnym adresem URL z atrybutu [action] w tagu <form>, co da adres URL [/do/validationFormulaire] jako cel dla POST.
  • Ukryte pole [action] zniknęło

[réponse.jsp]:


...

<html>
...
  <body>
      ...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>

  • wiersz [7]: docelowym elementem dla POST będzie [/do/retourFormulaire]
  • ukryte pole [action] zniknęło z formularza w wierszach 7–8.

[erreurs.jsp]:


...
<html>
...
  <body>
...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>

  • wiersz [6]: docelowym polem dla POST będzie [/do/retourFormulaire]
  • ukryte pole [action] zniknęło z formularza w wierszach 6–7.

Zachęcamy czytelnika do przetestowania tych nowych widoków zgodnie z zasadą omówioną w poprzednich wersjach.

10.5. Kontroler [ServletPersonne]

Kontroler [ServletPersonne] aplikacji internetowej [/personne5] będzie obsługiwał następujące akcje:

nr
żądanie
źródło
przetwarzanie
1
[GET /personne5/do/formulaire]
adres URL wpisany przez użytkownika
- wysłać pusty widok [formulaire]
2
[POST
/osoba5/do/validationFormulaire]
z parametrami [txtNom, txtAge]
opublikowanych
kliknięcie przycisku
[Envoyer] w widoku
[formulaire]
- sprawdzić wartości parametrów [txtNom, txtAge]
- jeśli są nieprawidłowe, wyślij widok [erreurs(erreurs)]
- jeśli są prawidłowe, wyślij widok [reponse(nom,age)]
3
[POST
/osoba5/do/retourFormulaire]
bez wysyłania parametrów
kliknij link [Powrót do
formularza] w widokach
[réponse] i [erreurs].
- wysłanie widoku [formulaire] wstępnie wypełnionego najnowszymi wprowadzonymi wartościami

Szkielet kontrolera [ServletPersonne] jest identyczny jak w poprzedniej wersji. Omówimy zmiany wprowadzone w metodach [doValidationFormulaire, doRetourFormulaire, doGet], ponieważ metody [init, doInit, doPost] pozostały bez zmian.

10.5.1. Metoda [doGet]

Metoda [doGet] nie pobiera akcji do wykonania w taki sam sposób jak w poprzednich wersjach:

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

         // sprawdzamy, jak przebiegła inicjalizacja serwletu
        if (erreursInitialisation.size() != 0) {
...
        }
         // pobieramy metodę wysłania żądania
        String méthode=request.getMethod().toLowerCase();
         // pobieramy akcję do wykonania
        String action=request.getPathInfo();
         // akcja?
        if(action==null){
            action="/formulaire";
        }
         // wykonanie akcji
        if(méthode.equals("get") && action.equals("/formulaire")){
             // uruchomienie aplikacji
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
             // walidacja formularza wprowadzania danych
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/retourFormulaire")){
             // powrót do formularza wprowadzania danych
            doRetourFormulaire(request,response);
            return;
        }
         // inne przypadki
        doInit(request,response);
    }
  • wiersz 12: pobierana jest akcja do wykonania. Ma ona postać [/action].
  • wiersze 18–22: przetwarzanie akcji [/formulaire] wywołanej przez żądanie GET
  • wiersze 23–27: przetwarzanie akcji [/validationFormulaire], o którą poproszono w żądaniu POST
  • wiersze 28–32: przetwarzanie akcji [/retourFormulaire] wywołanej przez żądanie POST

10.5.2. Metoda [doValidationFormulaire]

Metoda ta przetwarza żądanie nr 2 [POST /personne5/do/validationFormulaire] wraz z [txtNom, txtAge] w przesłanych elementach. Jej kod jest następujący:

// walidacja formularza
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
         // pobieranie parametrów
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
         // i zapisujemy je w pliku cookie
        response.addCookie(new Cookie("nom",nom));
        response.addCookie(new Cookie("age",age));
         // weryfikacja parametrów
        ...
    }

Nowości:

  • metoda [doValidationFormulaire] zwraca w odpowiedzi jeden z widoków [réponse, erreurs]. Niezależnie od treści tej odpowiedzi kontroler umieszcza w niej dwa pliki cookie (wiersze 8–9). Plik cookie jest reprezentowany przez obiekt [Cookie], którego konstruktor przyjmuje dwa parametry: klucz pliku cookie oraz wartość z nim powiązaną.
  • wiersz 8: wartość wprowadzona w polu „nazwa” jest umieszczana w pliku cookie o kluczu „nazwa”
  • wiersz 9: wartość wprowadzona dla pola „wiek” jest umieszczana w pliku cookie o kluczu „wiek”
  • Plik cookie jest dodawany do odpowiedzi HTTP wysyłanej do klienta za pomocą metody [response.addCookie]. Odpowiedź ta jest tutaj jedynie przygotowywana. Zostanie ona faktycznie wysłana dopiero po wykonaniu strony JSP z widoku przesłanego do klienta.

10.5.3. Metoda [doRetourFormulaire]

Metoda ta przetwarza żądanie nr 2 [POST /personne5/do/retourFormulaire] bez przesłanych elementów. Jej kod jest następujący:

         // wyświetlenie wstępnie wypełnionego formularza
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
         // pobieramy pliki cookie użytkownika
        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++;
                }
            }
        }
         // przygotowuje się szablon formularza
        request.setAttribute("nom",nom);
        request.setAttribute("age",age);
         // wyświetla się formularz
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

Nowości:

Metoda [doRetourFormulaire] ma wyświetlać formularz wstępnie wypełniony ostatnimi wprowadzonymi danymi. W poprzedniej wersji dane te były przechowywane w sesji. W obecnej wersji nie korzysta się już z sesji, lecz z plików cookie do zapamiętywania elementów między kolejnymi wymianami danych między klientem a serwerem. Gdy klient zażądał zatwierdzenia formularza, otrzymał w odpowiedzi widok [réponse] lub [erreurs], w zależności od sytuacji, wraz z dwoma plikami cookie o nazwach „nom” i „age”. Po kliknięciu linku [Retour au formulaire] w tych dwóch widokach, co powoduje wyświetlenie widoku POST pod adresem URL [/do/retourFormulaire], przeglądarka odeśle do serwera dwa otrzymane pliki cookie.

  • wiersze 4–18: pobieramy wartości plików cookie o nazwach „nazwa” i „wiek”. Co dość dziwne, nie istnieje metoda pozwalająca uzyskać wartość pliku cookie na podstawie jego klucza. W związku z tym musimy przejrzeć każdy z otrzymanych plików cookie.
  • Po wykonaniu tej czynności dwie uzyskane wartości są umieszczane w szablonie widoku [formulaire] (wiersze 20–21), aby mógł je wyświetlić.

10.6. Tests

Uruchom lub ponownie uruchom Tomcat po dodaniu do niego projektu Eclipse [personne-mvc-05], a następnie wywołaj adres URL [http://localhost:8080/personne5].