Skip to content

10. Веб-додаток MVC [personne] — версія 5

10.1. Introduction

У цій версії ми внесли дві зміни:

Перша стосується способу, яким клієнт повідомляє серверу про дію, яку він бажає виконати. Дотепер це вказувалося за допомогою параметра під назвою [action] у запиті GET або POST від клієнта. У цьому випадку дія буде вказана останнім елементом URL-адреси, яку запитує клієнт, як показано в наступній послідовності:

Image

У [1] URL-адреса, на яку було відправлено форму, — це [/personne5/do/validationFormulaire]. Саме останній елемент URL-адреси — [validationFormulaire] — дозволив контролеру розпізнати дію, яку потрібно виконати. У [2] POST, викликаний посиланням [Retour au formulaire], було виконано за URL-адресою [/personne5/do/retourFormulaire]. І знову останній елемент URL-адреси [retourFormulaire] вказує контролеру, яку дію потрібно виконати.

Ми вносимо цю зміну, оскільки саме такий підхід використовується в найпоширеніших фреймворках для веб-розробки, таких як Struts або Spring MVC.

Усі URL-адреси додатка матимуть вигляд [/personne5/do/action]. Файл [web.xml] додатка [/personne5] вказуватиме, що цей додаток підтримує URL-адреси у форматі [/do/*]:


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

Контролер отримає назву дії, яку потрібно виконати, таким чином:

         // отримано дію, яку потрібно виконати
String action=request.getPathInfo();

Метод [getPathInfo] об’єкта [request] повертає останній елемент URL-адреси запиту.

Друга зміна стосується способу збереження даних, введених користувачем між двома циклами запит/відповідь. Наразі ця інформація зберігається в сесії. Такий підхід може мати недоліки, якщо користувачів багато, а даних, які потрібно зберегти для кожного з них, також багато. Адже кожен користувач має свою особисту сесію. Крім того, сесія залишається активною певний час після виходу користувача, якщо тільки не передбачено опцію виходу з системи. Таким чином, 1000 сесій по 1000 байтів займуть 1 МБ пам’яті. Це все ще помірна вимога, і мало які додатки мають 1000 активних сесій одночасно.

Проте існують альтернативи сеансам, які вимагають менше пам’яті, і про них варто знати. Тут ми використаємо метод файлів cookie. Проілюструємо це на прикладі.


Крок 1: користувач підтверджує форму:


Цей цикл запит/відповідь призводить до таких обмінів HTTP між клієнтом і сервером:

[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/personne5/do/formulaire
Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

txtNom=pauline&txtAge=18

Це класичний POST. Тут немає нічого особливого, крім того, що, хоча сесія не використовується, веб-сервер все одно її створює. Про це свідчить токен сесії, який браузер відправляє серверу в рядку 11 і який він раніше отримав від сервера.

[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

Бачимо, що в рядках 3 і 4 заголовки HTTP та [Set-Cookie] були надіслані до клієнтського браузера: один — для імені (рядок 3), а інший — для віку (рядок 4). Значення цих файлів cookie відповідають значенням, відправленим у рядку 14 вищезазначених POST та [1].


Крок 2: Повернення до форми


Image

Цей цикл запит/відповідь призводить до наступного обміну даними HTTP між клієнтом і сервером:

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

Тут ми бачимо POST, викликаний кліком на посилання [Retour au formulaire]. У рядку 11 ми бачимо, що браузер повертає серверу отримані ним файли cookie [nom, age, JSESSIONID] за допомогою заголовка Http [Cookie]. У цьому полягає принцип роботи файлів cookie. Клієнт повертає серверу файли cookie, які той йому надіслав. У цьому прикладі контролер отримає значення [pauline, 18], які він повинен розмістити в полях [txtNom, txtAge] подання [formulaire], що відображається в [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

Тут немає нічого особливого, крім того, що в цій відповіді сервер не надіслав файлів cookie. Це не завадить браузеру під час наступного обміну надіслати всі файли cookie, які він отримав від сервера, навіть якщо це не має сенсу. Таким чином, ми зменшуємо навантаження на доступну пам'ять сервера ціною збільшення потоку символів під час обміну даними між клієнтом і сервером.

10.2. Проєкт Eclipse

Щоб створити проект Eclipse [mvc-personne-05] для веб-додатку [/personne5], потрібно продублювати проект [mvc-personne-04], дотримуючись процедури, описаної в розділі 6.2.

10.3. Налаштування веб-додатку [personne5]

Файл web.xml додатка /personne5 має такий вигляд:


<?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>
    <!--  Маппінг ServletPersonne-->
    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
    <!--  файли-шаблони -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

Цей файл ідентичний файлу попередньої версії, за винятком кількох деталей:

  • рядок 6: ім’я для відображення веб-додатка змінилося на [mvc-personne-05]
  • рядок 18: URL-адреси, що обробляються додатком, мають вигляд [/do/*]. Раніше оброблялася лише URL-адреса [/main]. Тепер кількість URL-адрес дорівнює кількості дій, що підлягають обробці.

Головна сторінка [index.jsp] змінюється:


<%@ 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"/>
  • рядок 5: сторінка [index.jsp] перенаправляє клієнта на URL-адресу [/personne5/do/formulaire], що еквівалентно запиту до контролера виконати дію [formulaire].

10.4. Код переглядів

Види [formulaire, réponse, erreurs] майже не змінюються. Єдина зміна полягає в тому, що дія, яку потрібно виконати, більше не вказується так, як раніше, коли вона визначалася у прихованому полі з назвою [action] у відправлених формах. Тепер вона визначається в цільовому URL-адресі відправлених форм, c.a.d, в атрибуті [action] тегу <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>
  • рядок [13]: параметр [action] форми знову з’являється після того, як на деякий час зник у попередніх версіях. Щоб зрозуміти значення цього атрибута в даному випадку, слід пам’ятати, що всі URL-адреси, які обробляє додаток, мають вигляд [/do/action]. У рядку [13] атрибут [action] має значення відносного URL-адреси (що не починається з /). Тому браузер доповнить її URL-адресою поточної сторінки, тобто обов’язково URL-адресою у форматі [/do/action]. Останній елемент буде замінено на відносний URL-адресу атрибута [action] тегу <form>, щоб отримати URL-адресу [/do/validationFormulaire] як ціль для POST.
  • Приховане поле [action] зникло

[réponse.jsp]:


...

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

  • рядок [7]: ціллю POST стане [/do/retourFormulaire]
  • приховане поле [action] зникло у формі рядків 7–8.

[erreurs.jsp]:


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

  • рядок [6]: цільовим елементом для POST стане [/do/retourFormulaire]
  • приховане поле [action] зникло у формі рядків 6–7.

Читачеві пропонується протестувати ці нові подання за принципом, розглянутим у попередніх версіях.

10.5. Контролер [ServletPersonne]

Контролер [ServletPersonne] веб-додатку [/personne5] оброблятиме такі дії:

запит
джерело
обробка
1
[GET /personne5/do/formulaire]
URL-адреса, введена користувачем
- надіслати порожній вигляд [formulaire]
2
[POST
/personne5/do/validationFormulaire]
з параметрами [txtNom, txtAge]
, опублікованих
натискання на кнопку
[Envoyer] у вікні
[formulaire]
- перевірити значення параметрів [txtNom, txtAge]
- якщо вони неправильні, надіслати вигляд [erreurs(erreurs)]
- якщо вони правильні, надіслати вигляд [reponse(nom,age)]
3
[POST
/personne5/do/retourFormulaire]
без переданих параметрів
натисніть на посилання [Повернутися до
формуляр] переглядів
[réponse] та [erreurs].
- надіслати попередньо заповнений вигляд [formulaire] з останніми введеними значеннями

Структура контролера [ServletPersonne] ідентична структурі попередньої версії. Ми розглянемо зміни, внесені до методів [doValidationFormulaire, doRetourFormulaire, doGet], оскільки методи [init, doInit, doPost] не зазнали змін.

10.5.1. Метод [doGet]

Метод [doGet] не отримує дію, яку потрібно виконати, так само, як у попередніх версіях:

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

         // перевіряється, як пройшла ініціалізація сервлета
        if (erreursInitialisation.size() != 0) {
...
        }
         // отримуємо метод відправлення запиту
        String méthode=request.getMethod().toLowerCase();
         // отримуємо дію, яку потрібно виконати
        String action=request.getPathInfo();
         // дія?
        if(action==null){
            action="/formulaire";
        }
         // виконання дії
        if(méthode.equals("get") && action.equals("/formulaire")){
             // запуск додатка
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
             // перевірка форми введення даних
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/retourFormulaire")){
             // повернення до форми введення даних
            doRetourFormulaire(request,response);
            return;
        }
         // інші випадки
        doInit(request,response);
    }
  • рядок 12: отримується дія, яку потрібно виконати. Вона має вигляд [/action].
  • рядки 18–22: обробка дії [/formulaire], запитаної у запиті GET
  • рядки 23–27: обробка дії [/validationFormulaire], запитаної запитом POST
  • рядки 28–32: обробка дії [/retourFormulaire], ініційованої запитом POST

10.5.2. Метод [doValidationFormulaire]

Цей метод обробляє запит № 2 [POST /personne5/do/validationFormulaire] разом із [txtNom, txtAge] у відправлених елементах. Його код такий:

// перевірка форми
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
         // отримання параметрів
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
         // які зберігаються у файлі cookie
        response.addCookie(new Cookie("nom",nom));
        response.addCookie(new Cookie("age",age));
         // перевірка параметрів
        ...
    }

Нові можливості:

  • метод [doValidationFormulaire] у відповідь надсилає один із видів [réponse, erreurs]. Незалежно від змісту цієї відповіді, контролер додає до неї два файли cookie (рядки 8–9). Файл cookie представлений об’єктом [Cookie], конструктор якого приймає два параметри: ключ файлу cookie та значення, пов’язане з ним.
  • рядок 8: значення, введене для імені, записується у файл cookie з ключем «nom»
  • рядок 9: значення, введене для поля «вік», записується у файл cookie з ключем «age»
  • файл cookie додається до відповіді HTTP, що надсилається клієнту за допомогою методу [response.addCookie]. Ця відповідь тут лише готується. Фактично вона буде надіслана лише під час виконання сторінки JSP з подання, надісланого клієнту.

10.5.3. Метод [doRetourFormulaire]

Цей метод обробляє запит № 2 [POST /personne5/do/retourFormulaire] без переданих елементів. Його код такий:

         // відображення попередньо заповненої форми
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
         // отримуємо файли cookie користувача
        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++;
                }
            }
        }
         // підготовка шаблону форми
        request.setAttribute("nom",nom);
        request.setAttribute("age",age);
         // відображається форма
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

Нові можливості:

Метод [doRetourFormulaire] повинен відображати попередньо заповнену форму з останніми введеними даними. У попередній версії ці дані зберігалися в сесії. У цій версії для збереження даних між двома обмінами між клієнтом і сервером більше не використовується сесія, а використовуються файли cookie. Коли клієнт надіслав запит на підтвердження форми, він отримав у відповідь сторінку [réponse] або [erreurs] (залежно від випадку) разом із двома файлами cookie з іменами «nom» та «age». При натисканні на посилання [Retour au formulaire] у цих двох сторінках, що викликає перехід на сторінку POST за URL-адресою [/do/retourFormulaire], браузер відправляє серверу обидва отримані файли cookie.

  • рядки 4–18: отримуємо значення файлів cookie з іменами «nom» та «age». Досить дивно, але не існує методу, який би дозволяв отримати значення файлу cookie на основі його ключа. Тому доводиться переглядати кожен із отриманих файлів cookie.
  • Після цього обидва отримані значення поміщаються в шаблон подання [formulaire] (рядки 20–21), щоб він їх відобразив.

10.6. Tests

Запустіть або перезапустіть Tomcat після інтеграції в нього проекту Eclipse [personne-mvc-05], а потім введіть URL-адресу [http://localhost:8080/personne5].