blob: ae3e060807862cd3e717c293533b42472b6cc9cd [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= Учебный курс по электронной коммерции в NetBeans - управление сеансами
:jbake-type: tutorial
:jbake-tags: tutorials
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: Учебный курс по электронной коммерции в NetBeans - управление сеансами - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, Учебный курс по электронной коммерции в NetBeans - управление сеансами
1. link:intro.html[+Введение+]
2. link:design.html[+Проектирование приложения+]
3. link:setup-dev-environ.html[+Настройка среды разработки+]
4. link:data-model.html[+Проектирование модели данных+]
5. link:page-views-controller.html[+Подготовка представлений страниц и сервлета контроллера+]
6. link:connect-db.html[+Подключение приложения к базе данных+]
7. link:entity-session.html[+Добавление классов сущностей и сеансных компонентов+]
8. *Управление сеансами*
9. link:transaction.html[+Интеграция транзакционной бизнес-логики+]
10. link:language.html[+Добавление поддержки языков+]
11. link:security.html[+Обеспечение безопасности приложений+]
12. link:test-profile.html[+Тестирование и профилирование+]
13. link:conclusion.html[+Заключение+]
image::../../../../images_www/articles/68/netbeans-stamp-68-69.png[title="Содержимое на этой странице применимо к IDE NetBeans, версии 6.8 и 6.9"]
Каждое приложение электронной коммерции, предлагающее какие-либо возможности корзины покупок, должно обладать функцией запоминания пользовательских данных в процессе перехода по веб-страницам/страницам веб-сайта. К сожалению, протокол HTTP, посредством которого осуществляется взаимодействие в Интернете, является протоколом _без поддержки состояния_. Каждый запрос, получаемый сервером, является независимым элементом данных, не связанным с ранее поступившими запросами. Поэтому при нажатии кнопки для добавления товара в корзину покупок приложение должно не только обеспечивать обновление корзины пользователя, но и предупреждать влияние данной корзины на корзины других пользователей, просматривающих сайт в это же время.
Чтобы правильно обработать описанный выше сценарий, необходимо реализовать функцию для создания и ведения _сеанса_ на протяжении пребывания пользователя на сайте. Технология сервлета, являющаяся основой для всех веб-приложений на базе Java, предоставляет для этих целей интерфейс link:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpSession.html[+`HttpSession`+]. Также необходимо определить некоторые классы, а именно `ShoppingCart` и `ShoppingCartItem`, что позволит приложению хранить данные пользователя, пока время сеанса не истекло.
Подход данного раздела учебного курса отличается от остальных подходов, рассматриваемых в учебном курсе по электронной коммерции NetBeans. Вместо создания файлов проекта и предоставления пошаговых инструкций с фрагментами кода для копирования и вставки в собственный проект предлагается открыть пример готового проекта для данного раздела и изучить код при помощи отладчика среды IDE и других средств. В процессе работы будут изучены способы применения объекта `HttpSession` к коду, чтобы в результате каждого посещения веб-сайта создавался отдельный сеанс. Также будут рассмотрены _контекстные переменные_ и их использование как в классах Java, так и на страницах JSP. Кроме того, в этом разделе описывается стандартный механизм `HttpSession` для ведения сеансов (например файлов cookie) и пошаговые инструкции, которые необходимо выполнить в случае деактивации файлов cookie в браузере пользователя. В заключение затрагивается время ожидания сеанса и демонстрируется принцип его обработки с созданием элементарного фильтра, перехватывающего запросы для проверки существования сеанса.
Можно просмотреть интерактивную демонстрацию приложения, которое создается в этом учебном курсе: link:http://dot.netbeans.org:8080/AffableBean/[+Демонстрация приложения электронной коммерции NetBeans+]
|===
|Программное обеспечение или материал |Требуемая версия
|link:https://netbeans.org/downloads/index.html[+IDE NetBeans+] |Набор Java, версия 6.8 или 6.9
|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Комплект для разработчика на языке Java (JDK)+] |версия 6
|<<glassFish,Сервер GlassFish>> |v3 или Open Source Edition 3.0.1
|link:http://dev.mysql.com/downloads/mysql/[+Сервер базы данных MySQL+] |Версия 5.1
|link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot5.zip[+Проект AffableBean+] |снимок 5
|===
*Примечания:*
* The IDE NetBeans requires the Java Development Kit (JDK) to run properly. Если указанные материалы не установлены, JDK следует загрузить и установить в первую очередь.
* The IDE NetBeans Java Bundle includes Java Web and EE technologies, which are required for the application you build in this tutorial.
* The IDE NetBeans Java Bundle also includes the GlassFish server, which you require for this tutorial. Можно link:https://glassfish.dev.java.net/public/downloadsindex.html[+загрузить сервер GlassFish отдельно+], но версия, предоставляемая с NetBeans, имеет преимущество, так как автоматически зарегистрирована в среде IDE.
* Этот учебный курс можно выполнять без выполнения предыдущих. Для этого обратитесь к link:setup.html[+инструкциям по настройке+], в которых описана подготовка базы данных и настройка подключений между IDE, GlassFish, и MySQL.
[[session-data]]
== Обработка данных сеанса
Приложения могут управлять пользовательскими сеансами при помощи объекта `HttpSession`. Можно привязать пользовательские данные к объекту `HttpSession` и получить возможность доступа к этим данным на дальнейших этапах. Операции привязки и доступа могут быть выполнены при помощи классов Java, а также переменных в контексте сеанса в выражениях на языке выражений.
* <<httpSession,Работа с объектом HttpSession>>
* <<scopedVariables,Работа с контекстными переменными в веб-приложениях>>
[[httpSession]]
=== Работа с объектом HttpSession
В приложении `AffableBean` используется объект `HttpSession` для идентификации пользователей при неоднократных запросах. Объект `HttpSession` можно получить при помощи метода `getSession()` для заданного запроса:
[source,java]
----
HttpSession session = request.getSession();
----
Если объект сеанса для запроса еще не существует, метод создает и возвращает новый объект сеанса.
Объект сеанса можно использовать в качестве средства для передачи данных между запросами. Метод `setAttribute` используется для привязки объектов к сеансу. Таким же образом метод `getAttribute` используется для извлечения объектов из сеанса. Например, в приложении `AffableBean` корзина покупок пользователя создается и привязывается к пользовательскому сеансу следующим образом:
[source,java]
----
ShoppingCart cart = new ShoppingCart();
session.setAttribute("cart", cart);
----
Для извлечения корзины из сеанса применяется метод `getAttribute`:
[source,java]
----
cart = (ShoppingCart) session.getAttribute("cart");
----
На страницах JSP можно получить доступ к объектам, привязанным к сеансу с использованием выражений на языке выражений. Также, если объект `ShoppingCart` с именем `cart` привязан к сеансу, можно получить доступ к объекту при помощи следующего выражения на языке выражений:
[source,java]
----
${cart}
----
Доступ к объекту `ShoppingCart` сам по себе не представляет большого значения. Целью является доступ к значениям, хранящимся в объекте. При изучении нового класса `ShoppingCart` на снимке проекта обратите внимание, что он содержит следующие свойства:
* `удвоенный итог`
* `int numberOfItems`
* `List<String, ShoppingCartItem> items`
При условии, что свойства соответствуют методам получения, можно получить доступ к значениям отдельных свойств при помощи простого точечного представления в выражении на языке выражений. На странице `cart.jsp` приведен точный способ доступа к свойству `numberOfItems`:
[source,html]
----
<p>Your shopping cart contains ${cart.numberOfItems} items.</p>
----
Для извлечения данных из свойств с несколькими значениями, например списка `items`, приведенного выше, на странице `cart.jsp` используется цикл `<c:forEach>`:
[source,xml]
----
<c:forEach var="cartItem" items="${cart.items}" varStatus="iter">
<c:set var="product" value="${cartItem.product}"/>
<tr class="${((iter.index % 2) == 0) ? 'lightBlue' : 'white'}">
<td>
<img src="${initParam.productImagePath}${product.name}.png"
alt="${product.name}">
</td>
<td>${product.name}</td>
<td>
&amp;euro; ${cartItem.total}
<br>
<span class="smallText">( &amp;euro; ${product.price} / unit )</span>
</td>
...
</tr>
</c:forEach>
----
Принадлежащая `ShoppingCartItem` собственность `product` определяет тип продукта для элемента корзины. Цикл, рассматриваемый выше, использует результаты определения при первой установке переменной `product` в выражении `${cartItem.product}`. Затем переменная используется для получения сведений об этом продукте (например имя, цена).
[[scopedVariables]]
=== Работа с контекстными переменными в веб-приложениях
При работе с технологией JSP/сервлетов доступны четыре контекстных объекта в области приложения. Технология JSP реализует _скрытые объекты_, позволяющие получить доступ к классам, определяемым интерфейсом API сервлетов.
|===
|Контекст |Определение |Класс сервлетов |Скрытые объекты JSP
|*Приложение* |Глобальная память веб-приложения |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/ServletContext.html[+javax.servlet.ServletContext+]` |`applicationScope`
|*Сеанс* |Данные пользовательского сеанса |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpSession.html[+javax.servlet.http.HttpSession+]` |`sessionScope`
|*Запрос* |Данные отдельного запроса сервера |`link:http://java.sun.com/javaee/6/docs/api/javax/servlet/http/HttpServletRequest.html[+javax.servlet.HttpServletRequest+]` |`requestScope`
|*Страница* |Данные, действительные только в контексте отдельной страницы (только JSP) |`неприменимо` |`pageScope`
|===
При открытии файла `category.jsp` проекта в редакторе обратите внимание, что выражения на языке выражений включают в себя различные контекстные переменные, в частности, `${categories}`, `${selectedCategory}` и `${categoryProducts}`. Переменная `${categories}` находится в контексте приложения, устанавливаемого в методе `init` файла `ControllerServlet`:
[source,java]
----
// store category list in servlet context
getServletContext().setAttribute("categories", categoryFacade.findAll());
----
Остальные переменные, `${selectedCategory}` и `${categoryProducts}`, размещаются в контексте сеанса приложения в файле `ControllerServlet`. Например:
[source,java]
----
// place selected category in session scope
session.setAttribute("selectedCategory", selectedCategory);
----
*Примечание.* Если изучить предыдущие разделы руководства, можно заметить, что `${selectedCategory}` и `${categoryProducts}` были изначально помещены в область запроса. В предыдущих разделах такое размещение было целесообразным, но рассмотрим, что случится, если пользователь нажмет кнопку "добавить в корзину" на странице категорий. В ответ на запрос `addToCart` сервлет возвратит текущую страницу категорий. Таким образом нужно будет получить информацию о `selectedCategory` и `categoryProducts`, относящихся к выбранной категории. Вместо того чтобы узнавать данную информацию о каждом запросе, достаточно поместить ее в область сеанса запроса `category`, так чтобы она поддерживалась многими запросами и могла быть получена, когда в ней возникнет необходимость. Рассмотрим также функциональность страницы корзины. (Функции <<cartPage,описываются ниже>>). Кнопка "Продолжить покупки" возвращает пользователя в предыдущую просмотренную категорию. Опять же запрашиваются переменные `selectedCategory` и `categoryProducts`.
При ссылке на контекстные переменные в выражении на языке выражений нет необходимости в указании контекста переменной (при условии отсутствия двух переменных с одинаковым именем в различных контекстах). Механизм JSP выполняет проверку всех четырех контекстов и возвращает первое найденное соответствие переменных. Например, в файле `category.jsp` выражение
[source,java]
----
${categoryProducts}
----
является сокращением для
[source,java]
----
${sessionScope.categoryProducts}
----
[tips]#Для получения дополнительных сведений ознакомьтесь со следующими материалами:#
* link:http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/web-tier/web-tier5.html#1079198[+Проектирование приложений уровня предприятия при помощи платформы J2EE: контексты состояния+]
* link:http://download.oracle.com/docs/cd/E17477_01/javaee/5/tutorial/doc/bnafo.html[+"Совместные данные" > "Использование контекстных объектов"+]
* link:http://download.oracle.com/docs/cd/E17477_01/javaee/5/tutorial/doc/bnahq.html#bnaij[+"Унифицированный язык выражений" > "Скрытые объекты"+]
[[debug]]
== Изучение данных сеанса при помощи отладчика Java
Начало изучения принципов поведения приложения во время выполнения. Используйте отладчик среды IDE для перехода по коду и изучения способов создания объекта `HttpSession` и размещения прочих объектов в контексте сеанса для их дальнейшего извлечения.
1. Откройте link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot5.zip[+пример проекта+] для данного раздела учебного курса в среде IDE. В диалоговом окне "Открытие проекта" ( image::images/open-project-btn.png[] ) перейдите к папке на компьютере, в которой хранится разархивированный файл учебного проекта. Если изучить link:entity-session.html[+предыдущий раздел руководства+], можно заметить, что снимок проекта включает новый пакет `cart`, содержащий классы `ShoppingCart` и `ShoppingCartItem`. Также изменены следующие файлы:
* `WEB-INF/web.xml`
* `css/affablebean.css`
* `WEB-INF/jspf/header.jspf`
* `WEB-INF/jspf/footer.jspf`
* `WEB-INF/view/cart.jsp`
* `WEB-INF/view/category.jsp`
* `WEB-INF/view/checkout.jsp`
* `controller/ControllerServlet`
[start=2]
. Запустите проект ( image::images/run-project-btn.png[] ), чтобы убедиться, что этот проект правильно настроен с помощью используемой базы данных и сервера приложений.
Если при выполнении проекта выводится сообщение об ошибке, еще раз обратитесь к link:setup.html[+указаниям по настройке+], в которых описаны принципы подготовки базы данных и установки соединения между средой IDE, сервером GlassFish и MySQL.
[start=3]
. Протестируйте функциональные возможности приложения в браузере. Если link:entity-session.html[+предыдущий раздел учебного курса+] уже изучен, обратите внимание на следующие расширения.
=== страница категории
* Первое нажатие кнопки "add to cart" активирует корзину покупок и виджеты "proceed to checkout", отображаемые в заголовке.
* Нажатие кнопки "add to cart" приводит к обновлению числа товаров в виджете корзины покупок в заголовке.
* В результате щелчка ссылки "view cart" отображается страница корзины.
* При щелчке ссылки "proceed to checkout" открывается страница кассы.
image::images/category-page.png[title="IDE NetBeans включает функции отладки."]
[[cartPage]]
=== страница корзины покупок
* Щелчок ссылки "clear cart" обеспечивает удаление всех товаров из корзины покупок.
* В результате щелчка ссылки "continue shopping" выполняется возврат к ранее просматриваемой категории.
* При щелчке ссылки "proceed to checkout" открывается страница кассы.
* Ввод числа (от 1 до 99) в поле количества товара и нажатие кнопки "update" запускает повторный расчет общей стоимости товара, а также промежуточной суммы.
* При вводе нуля в поле количества товара и нажатии кнопки "update" товар удаляется из отображаемой таблицы.
image::images/cart-page.png[title="IDE NetBeans включает функции отладки."]
=== страница проверки
* В результате щелчка ссылки "view cart" отображается страница корзины.
* В результате нажатия "подтвердить покупку" на странице подтверждения отображаются результаты покупки (без указания информации о пользователе).
image::images/checkout-page.png[title="Страница кассы включает функцию покупательской корзины"]
[start=4]
. Используйте диалоговое окно "Перейти к файлу", чтобы открыть файл `ControllerServlet` в редакторе. Нажмите ALT+SHIFT+O (CTRL+SHIFT+O в Mac OS), затем введите "`Controller`" в поле диалога и нажмите кнопку "ОК".
image::images/go-to-file-dialog.png[title="С помощью диалогового окна "Переход к файлу" быстро откройте ресурсы проекта в редакторе"]
[start=5]
. Установите точку останова в методе `doPost` на строке, которая создает объект `HttpSession` (строка 150). Для установки точки останова щелкните в левом поле редактора.
image::images/breakpoint.png[title="Щелкните левую границу редактора для задания точек останова"]
Для переключения номеров строк в редакторе щелкните правой кнопкой мыши в левом поле и выберите команду "Показать номера строк".
[start=6]
. Запустите отладчик. Нажмите кнопку 'Отладка проекта' (image::images/debug-project-btn.png[]) на главной панели инструментов IDE. Сервер GlassFish запускается (или перезапускается, если уже работал ) и открывает подключение через сокет по его отладочному номеру порта. В браузере откроется страница приветствия приложения.
Можно просматривать и изменять номер порта отладки в диалоговом окне "Серверы" ("Сервис" > "Серверы"). Выберите вкладку "Java" для используемого сервера. Укажите номер порта в поле "Используемый адрес" под заголовком "Параметры отладки".
[start=7]
. При выводе страницы приветствия в браузере выберите категорию и добавьте несколько элементов в корзину покупок. Следует помнить, что нажатие кнопки "add to cart" отправляет запрос `addToCart` на сервер:
[source,java]
----
<form action="addToCart" method="post">
----
Как уже было описано в разделе link:page-views-controller.html#controller[+Подготовка представлений страницы и сервлета контроллера+], принадлежащий `ControllerServlet` метод `doPost` поддерживает запросы URL-адреса шаблона `/addToCart`. Следовательно, при нажатии кнопки "add to cart" ожидается вызов метода `doPost`.
[start=8]
. Нажмите кнопку "add to cart" для любого элемента на странице категорий. Перейдите к среде IDE и обратите внимание, что отладчик приостановил работу в точке останова.
image::images/breakpoint-suspended.png[title="Отладчик приостанавливает работу по достижении точек останова, заданных в редакторе"]
[start=9]
. Поместите курсор в месте вызова метода `getSession()` и нажмите сочетание клавиш CTRL+ПРОБЕЛ для вывода документации Javadoc.
image::images/javadoc-getsession.png[title="Нажмите Ctrl-Space для вызова документации Javadoc"]
В соответствии с документацией метод `getSession()` возвращает объект `HttpSession`, связанный в настоящее время с запросом, и (при отсутствии сеанса) создает новый объект сеанса.
=== Использование поддержки документации Javadoc в среде IDE
Среда IDE предоставляет встроенную поддержку документации Javadoc для разработки на базе Java EE. Среда IDE связывается со спецификацией интерфейса API Java EE 6, доступной во внешнем браузере по пути "Справка" > "Справочные сведения Javadoc" > "Java EE 6".
Также среда IDE включает в себя различные функции, позволяющие получить быстрый доступ к документации интерфейса API:
* *Диалоговое окно документации Javadoc:* выберите "Окно" > "Прочее" > "Javadoc". В нижней области среды IDE откроется диалоговое окно "Javadoc", в котором отобразится документация интерфейса API, релевантная для позиции курсора в редакторе.
* *Поиск документации по индексу:* выберите в меню "Справка" пункт "Поиск документации по индексу" (SHIFT+F1; fn+SHIFT+F1 в системе Mac). Введите имя искомого класса и выберите класс из списка результатов поиска. Полное описание класса в спецификации интерфейса API отображается на нижней панели диалогового окна.
* *Всплывающее окно документации в редакторе:* документация Javadoc отображается во всплывающем окне при нажатии сочетания клавиш CTRL+ПРОБЕЛ для определенного элемента в редакторе. Можно нажать кнопку 'Внешний браузер' ( image::images/external-browser-btn.png[] ) для открытия документации в используемом браузере. Для использования сочетания клавиш CTRL+ПРОБЕЛ только для автозавершения кода можно деактивировать всплывающее окно документации, открыв диалоговое окно "Параметры" ("Сервис" > "Параметры"; "NetBeans" > "Параметры" для системы Mac), и затем выбрав в меню "Редактор" пункт "Автозавершение кода". Отмените выбор параметра "Автоматический вызов окна документации".
При документировании собственной работы рекомендуется добавить комментарии документации Javadoc к классам и методам. Откройте класс `ShoppingCart` и изучите комментарии документации Javadoc, добавленные к методам класса. Комментарии документации Javadoc отмечены парными символами `/** ... */`. Например, для метода `addItem` добавлен следующий комментарий:
[source,xml]
----
/**
* Adds a <code>ShoppingCartItem</code> to the <code>ShoppingCart</code>'s
* <code>items</code> list. If item of the specified <code>product</code>
* already exists in shopping cart list, the quantity of that item is
* incremented.
*
* @param product the <code>Product</code> that defines the type of shopping cart item
* @see ShoppingCartItem
*/
public synchronized void addItem(Product product) {
----
Это позволяет разработчикам просматривать документацию Javadoc по методу. Для демонстрации откройте 'Навигатор' (Ctrl-7; ⌘-7 в Mac) и подведите курсор мыши к методу `addItem`.
image::images/javadoc-additem.png[title="Подведите курсор мыши к 'Навигатору' для просмотра документации Javadoc"]
Также можно использовать среду IDE для создания набора страниц HTML документации Javadoc. В окне "Проекты" щелкните правой кнопкой мыши узел проекта и выберите пункт "Создание документации Javadoc". Среда IDE создает документацию Javadoc в папке `dist/javadoc` каталога проекта и открывает страницу-указатель в браузере.
Для получения дополнительных сведений о документации Javadoc обратитесь к следующим ресурсам:
* link:http://java.sun.com/j2se/javadoc/[+Официальная домашняя страницы средства Javadoc+]
* link:http://java.sun.com/j2se/javadoc/writingdoccomments/index.html[+Принцип записи комментариев документации для средства Javadoc+]
[start=10]
. Наведите курсор на переменную `session`. Обратите внимание, что отладчик приостанавливает работу на строке, _которая должна быть запущена в нем._ Значение, возвращаемое `getSession()` еще не сохранено в переменной `session` и отображается всплываюшщее окно, в котором указывается, что "`session` не является известной переменной в текущем контексте".
image::images/session-variable.png[title="Наведите курсор на переменные и выражения для определения их текущих значений"]
[start=11]
. Нажмите кнопку 'Обход процедур' (image::images/step-over-btn.png[]) в отладчике на панели инструментов, расположенной выше в редакторе. Строка обрабатывается, и отладчик переходит к следующей строке файла.
[start=12]
. Снова наведите курсор на переменную `session`. Можно увидеть, что текущее значение сохранено в переменной `session`.
[.feature]
--
image::images/session-variable-set.png[role="left", link="images/session-variable-set.png"]
--
В NetBeans 6.9 можно щелкнуть серый указатель ( image::images/grey-pointer.png[] ) во всплывающем окне, чтобы расширить список значений переменных, содержащихся в выделенном элементе.
[start=13]
. Нажмите кнопку 'Обход процедур' ( image::images/step-over-btn.png[] ) (F8; fn-F8 в Mac) для перехода к оператору `if` (строка 154). Поскольку в браузере только что была нажата кнопка "add to cart", выражение `userPath.equals("/addToCart")` должно иметь значение `true`.
[start=14]
. Выделите выражение `userPath.equals("/addToCart")` (нажав клавишу CTRL и щелкнув клавишей мыши). При этом будет выведено всплывающее окно со значением выделенного выражения.
image::images/expression.png[title="Выделите выражения для определения их текущих значений"]
[start=15]
. Нажмите клавишу F8 (fn-F8 в Mac OS) для перехода к следующей строке (строка 158). Приложение спроектировано таким образом, что объект `ShoppingCart` создается для пользовательского сеанса только при первом добавлении элемента в корзину пользователя. Поскольку запрос `addToCart` в этом сеансе отладки получен впервые, предполагается, что объект `cart` имеет значение `null`.
image::images/cart-null.png[title="Объект покупательской корзины появляется только после добавления пользователем элемента к покупательской корзине"]
[start=16]
. Нажмите клавишу F8 (fn-F8 в Mac OS) для перехода к следующей линии (линия 160). Затем в строке 160, где создан объект `ShoppingCart`, нажмите кнопку 'Вход в' ( image::images/step-into-btn.png[] ). Отладчик переходит к вызываемому методу. В этом случае выполняется переход непосредственно к конструктору файла `ShoppingCart`.
image::images/cart-constructor.png[title="Перейдите к методам для отслеживания этапа выполнения для других классов"]
[start=17]
. Нажмите сочетание клавиш CTRL+TAB для перехода к файлу `ControllerServlet`. Обратите внимание, что IDE предоставляет значок 'Стек вызовов' ( image::images/call-stack-badge.png[] ) в строке 160, указывая, что отладчик в настоящее время приостановлен где-то на методе выше в стеке вызовов.
Нажмите ALT+SHIFT+3 (CTRL+SHIFT+3 в Mac OS), чтобы открыть окно стека вызовов.
[start=18]
. Нажмите клавишу F8 (fn+F8 в системе Mac) для продолжения перехода по коду. При завершении обработки отладчиком конструктора `ShoppingCart` выполняется возврат к файлу `ControllerServlet`.
Строка 161 `ControllerServlet` привязывает вновь созданный объект `cart` к сеансу.
[source,java]
----
session.setAttribute("cart", cart);
----
Чтобы в этом удостовериться, откройте окно переменных отладчика. Выберите "Окно" > "Отладка" > "Переменные" или нажмите ALT+SHIFT+1 (CTRL+SHIFT+1 в Mac OS).
[.feature]
--
image::images/variables-win-session.png[role="left", link="images/variables-win-session.png"]
--
При последовательной развертке узлов "session" > "session" > "attributes" можно просмотреть объекты, привязанные к сеансу. На изображении выше отображены два элемента, привязанные в настоящее время к сеансу (выделены на снимке). Это `selectedCategory` и `categoryProducts`, показанные в `ControllerServlet` на строках 83 и 89, соответственно. Оба элемента были привязаны ранее при щелчке изображения категории и обработке запроса страниц категории в файле `ControllerServlet`.
[start=19]
. Нажмите клавишу F8 (fn-F8 в Mac OS) для выполнения кода со строки 161. Объект `cart` привязывается к сеансу, и диалоговое окно "Переменные" обновляется для отображения изменений. Обратите внимание, что в окне "Переменные" сеанс содержит в настоящий момент три атрибута, третьим атрибутом является только что инициализированный объект `ShoppingCart` (выделен на снимке ниже).
[.feature]
--
image::images/variables-win-session-cart.png[role="left", link="images/variables-win-session-cart.png"]
--
Пока что не было "доказано", что сеанс, как указано в диалоговом окне "Переменные", представляет объект `HttpSession`. Как упоминалось выше, `HttpSession` фактически является интерфейсом, так что при рассмотрении объекта `HttpSession` или объекта сеанса фактически имеется в виду любой объект, реализующий интерфейс `HttpSession`. При наведении курсора мыши в диалоговом окне "Переменные" на элемент `session` будет выведено всплывающее окно, указывающее на то, что переменная представляет объект `HttpSession`. Тип `StandardSessionFacade`, отображенный на снимке, является внутренним классом, используемым сервером GlassFish для реализации интерфейса `HttpSession`. При наличии опыта работы с Tomcat обратите внимание, что пути `org.apache.catalina` в столбце "Значение" обусловлены тем, что веб-контейнер/контейнер сервлета GlassFish фактически является производным от контейнера Apache Tomcat.
В сеанс добавляется новый объект `ShoppingCart`, и запрос продолжает обрабатываться. Для завершения реализации функции "add to cart" предпринимаются следующие действия:
* Идентификатор выбранного продукта получается из запроса (строка 165).
* Объект `Product` создается с помощью идентификатора (строка 169).
* Новый экземпляр `ShoppingCartItem` создается с помощью `product` (строка 170).
* Экземпляр `ShoppingCartItem` добавляется в состоящий из `ShoppingCartItem` список `экземпляров` (строка 170).
[start=20]
. Нажмите клавишу F8 (fn+F8 в системе Mac OS), чтобы продолжить выполнение перехода по коду с учетом перечисленных выше четырех действий. Сделайте паузу, когда отладчик временно остановится на строке 170.
[start=21]
. Создайте наблюдение за сеансом. Эта функция позволит просматривать значения, содержащиеся в сеансе, при переходе к методу `addItem` в следующем шаге. Щелкните правой кнопкой мыши сеанс в диалоговом окне "Переменные" и выберите команду "Установить постоянное наблюдение".
image::images/create-watch.png[title="Создайте наблюдения за переменными при переходе по коду в сеансе отладки"]
Кроме того, вы можете поместить курсор на переменной `session` в редакторе, а затем щелкнуть правой кнопкой мыши и выбрать 'Новое наблюдение'. Диалоговое окно создания наблюдения позволяет определить переменные или выражения для постоянного наблюдения за отладкой приложения. (При использовании выражений, сначала выделите выражение, а затем щелкните правой кнопкой мыши и выберите 'Новое наблюдение').
image::images/new-watch-dialog.png[title="Щелкните правой кнопкой мыши переменные и выражения в редакторе и выберите 'Создать наблюдение'"]
Для переменной `session` и всех содержащихся в ней переменных создается наблюдение. Наблюдение отображается в окне 'Наблюдения' (Window > 'Отладка' > 'Наблюдения') или при переключении кнопки наблюдений ( image::images/watch-btn.png[] ) на левой границе окна 'Переменные' оно отображается в верхней строке окна 'Переменные'.
Отладчик позволяет наблюдать за переменными по мере перехода по коду. Это важно, например, при отслеживании изменения для отдельных значений переменных (без просмотра полного списка в окне "Переменные" для каждого шага) или при временном переходе к классу, не содержащему рассматриваемые переменные.
[start=22]
. Нажмите кнопок 'Вход в' ( image::images/step-into-btn.png[] ) для перехода к `ShoppingCart` к методу `addItem`.
[start=23]
. Последовательно шагайте по методу `addItem`, пока не достигните строки 53. Согласно документации Javadoc `addItem` _"добавляет элемент`ShoppingCartItem` в список `items` в файле `ShoppingCart`. Если указанный товар из списка `product` уже существует в списке корзины покупок, количество этого товара увеличивается."_
[start=24]
. Изучите переменную `session`, для которой был создан параметр наблюдения (<<step21,шаг 21>> выше). Выражение `items.add(scItem)` на странице 51 добавляет новый экземпляр `ShoppingCartItem` в список `items` в `ShoppingCart`. Этот процесс можно проследить при переходе к третьему атрибуту, содержащемуся в сеансе (например, переменной `cart`).
[.feature]
--
image::images/variables-window-add-item.png[role="left", link="images/variables-window-add-item.png"]
--
На данном этапе можно изучить принцип создания `HttpSession` для запроса, создания объекта `ShoppingCart` и его прикрепления к сеансу, а также создания элемента `ShoppingCartItem` на основе пользовательского выбора продукта с последующим добавлением в список `items` файла `ShoppingCart`. Последним действием является переадресация запроса в представление `category.jsp`.
[start=25]
. Откройте в редакторе фрагмент JSP заголовка (`header.jspf`) и разместите точку останова в строке 86. Эта строка содержит оператор на языке выражений в пределах виджета корзины покупок, отображающего число элементов корзины.
image::images/breakpoint-jsp.png[title="Отладчик можно приостановить на страницах JSP"]
[start=26]
. Нажмите кнопку 'Продолжить' ( image::images/continue-btn.png[] ) на панели инструментов отладчика. Отладчик продолжает работу до завершения обработки или до следующей точки останова. В последнем случае отладчик приостанавливается на строке 86 фрагмента JSP заголовка.
*Примечание.* Чтобы отложить работу отладчика на странице JSP требуется контрольная точка. Например, если в файле `ControllerServlet` выполняется переадресация запроса в соответствующее представление, отладчик не будет автоматически приостановлен на странице JSP.
[start=27]
. Откройте окно переменных (ALT+SHIFT+1; CTRL+SHIFT+1 в системе Mac OS), если оно еще не открыто. В отличие от классов Java отладчик _не_ предоставляет подсказки при наведении курсора мыши на переменные или выражения на странице JSP. Однако диалоговое окно "Переменные" не позволяет определять значения переменных при переходе по коду. Где можно найти значение для переменной `${cart.numberOfItems}`?
[start=28]
. Последовательно разверните в диалоговом окне "Переменные" узлы "Скрытые объекты" > "pageContext" > "session" > "session" > "attributes". В результате будет предоставлен доступ к объекту сеанса, как и при работе с файлом `ControllerServlet`. Обратите внимание, что сеанс, для которого на шаге 21 было создано наблюдение, указывает на сам объект. Здесь можно убедиться, что значение переменной `${cart.numberOfItems}` составляет "`1`".
[.feature]
--
image::images/variables-window-number-of-items.png[role="left", link="images/variables-window-number-of-items.png"]
--
Разверните окно 'Переменные' или любое окно в IDE, щелкнув правой кнопкой мыши заголовок окна, а затем выбрав 'Развернуть окно' (Shift-Esc).
Отладчик предоставляет доступ к скрытому объекту `pageContext`. `pageContext` предоставляет контекст страницы JSP и открывает прямой доступ к различным объектам, включая объекты `HttpServletRequest`, `HttpSession` и `ServletContext`. Для получения дополнительных сведений обратитесь к link:http://java.sun.com/javaee/5/docs/tutorial/doc/bnahq.html#bnaij[+Учебному курсу по Java EE 5: скрытые объекты+].
[start=29]
. Нажмите кнопку 'Завершить сеанс' ( image::images/finish-session-btn.png[] ). Работа среды выполнения и сеанса отладки завершается. Браузер отображает страницу категорий с полной визуализацией, и виджет корзины покупок в заголовке страницы содержит один элемент.
Надеемся, что теперь использование отладчика среды IDE не только для анализа проекта при неожиданном поведении, но и в качестве средства изучения кода, не вызывает у вас затруднений. Ниже перечислены другие функциональные кнопки на панели инструментов:
* ( image::images/step-out.png[] ) *Выход.* Выполняется выход из вызова текущего метода. Выполняется обработка и удаление самого верхнего вызова метода в стеке вызовов.
* ( image::images/run-to-cursor.png[] ) *Переход к курсору* Выполнение до строки, на которой размещен курсор.
* ( image::images/apply-code-changes.png[] ) *Применить изменения кода.* После редактирования файла можно нажать эту кнопку, чтобы файл был повторно скомпилирован и изменения учитывались в сеансе отладки.
* ( image::images/step-over-expression.png[] ) *Выражение обхода процедур.* Позволяет просматривать входные параметры и получаемые выходные значения всех вызовов методов в выражении. Можно изучить выходные значения для предыдущего метода и входные параметры для следующего метода в диалоговом окне "Локальные переменные". При отсутствии дальнейших вызовов методов, режим работы выражения обхода процедур аналогичено команде Step Over ( image::images/step-over-btn.png[] ).
[[session-track]]
== Изучение параметров отслеживания сеанса
Существует три традиционных способа отслеживания сеансов между клиентом и сервером. Самым распространенным является способ с использованием файлов cookie. Перезапись URL-адресов можно применять в случае, если файлы cookie не поддерживаются или отключены. Скрытые поля формы также могут использоваться в качестве способа "ведения состояния" нескольких запросов, однако они ограничены использованием в пределах формы.
Проект `AffableBean` включает в себя пример метода скрытого поля на странице категорий и корзины. Кнопки "add to cart" и "update", отображаемые для элементов продукта, содержат скрытое поле, передающее идентификатор продукта на сервер при нажатии кнопки. При открытии страницы `cart.jsp` в редакторе можно заметить, что теги `<form>` содержат скрытое поле.
[source,xml]
----
<form action="updateCart" method="post">
*<input type="hidden"
name="productId"
value="${product.id}">*
...
</form>
----
Таким образом, идентификатор продукта отправляется в виде параметра запроса, используемого сервером для идентификации элемента в пользовательской корзине, количество которой необходимо изменить.
Интерфейс API сервлета предоставляет высокоуровневый механизм для управления сеансами. Фактически он создает и передает файлы cookie между клиентом и сервером в каждом цикле "запрос-ответ". Если браузер клиента не принимает файлы cookie, механизм сервлета автоматически возвращается к перезаписи URL-адреса. Следующие два упражнения демонстрируют работу этой функции.
* <<http-monitor,Изучение обмена данными между сервером и клиентом с использованием монитора HTTP>>
* <<url-rewrite,Ведение сеансов с перезаписью URL-адреса>>
[[http-monitor]]
=== Изучение обмена данными между сервером и клиентом с использованием монитора HTTP
По умолчанию механизм сервлета использует файлы cookie для ведения и идентификации сеансов между запросами. Для каждого объекта сеанса создается случайный буквенно-цифровой номер, служащий уникальным идентификатором. Этот идентификатор передается в клиент как файл cookie `JSESSIONID`. При создании запроса клиентом механизм сервлета считывает значение файла cookie `JSESSIONID` для определения сеанса, к которому относится запрос.
Для наглядности отладчик используется вместе с монитором HTTP среды IDE.
1. Начните работу с активации монитора HTTP для используемого сервера. Выберите элементы "Сервис" > "Серверы". В левом столбце окна "Серверы" выберите используемый сервер (GlassFish). Затем выберите на главной панели режим "Включить монитор HTTP".
image::images/servers-win-http-monitor.png[title="Выберите режим 'Включить монитор HTTP', чтобы активировать монитор HTTP"]
[start=2]
. Если сервер уже запущен, необходимо перезапустить его. Однако поскольку планируется использование отладчика, а при запуске отладчика выполняется перезапуск сервера для взаимодействия с другим портом, просто нажмите кнопку 'Отладка проекта' ( image::images/debug-project-btn.png[] ) на главной панели инструментов IDE. Будет выполнена перезагрузка сервера, запустится сеанс отладки, и в браузере откроется страница приветствия приложения. В нижней области среды IDE будет отображен монитор HTTP.
image::images/http-monitor.png[title="Монитор HTTP отображается по умолчанию в нижней области среды IDE"]
[start=3]
. Щелкните запись AffableBean в левом столбце (как показано на снимке выше). При выборе записей в левом столбце правый (т.е. главный) столбец обновляется для отображения соответствующих данных. На изображении выше вкладка "Запрос" отображает идентификатор URI запроса (`/AffableBean/`), метод HTTP (`GET`) и указывает на отсутствие отправки строки запроса вместе с запросом.
[start=4]
. Выберите вкладку "Сеанс". Обратите внимание на утверждение: "Сеанс создан в результате этого запроса." Это вызвано отправкой сервером заголовка `Set-Cookie` для файла cookie `JSESSIONID` в качестве ответа. Также обратите внимание, что новый идентификатор сеанса указан в области "Свойства сеанса". Как будет продемонстрировано ниже, идентификатор сеанса представляет собой значение файла cookie `JSESSIONID`.
image::images/session-tab.png[title="Сведения о сеансе отображаются на вкладке 'Сеанс' в мониторе HTTP"]
Может возникнуть вопрос о способе создания объекта сеанса из запроса для страницы приветствия сайта. Ведь в файле `ControllerServlet` не выполняется обработка начального запроса для `/AffableBean/`, и этот запрос нигде не сталкивается с методом `getSession()`. Или сталкивается? Напомним, что страницы JSP скомпилированы в сервлеты при развертывании. При первом развертывании проекта на сервере можно фактически использовать среду IDE для просмотра скомпилированного сервлета JSP на собственном сервере.
[start=5]
. В окне 'Проекты' щелкните правой кнопкой мыши файл `index.jsp` и выберите 'Просмотреть сервлет'. В редакторе откроется файл `index_jsp.java`. Файл является сервлетом, автоматически скомпилированным на основе страницы `index.jsp`.
[start=6]
. Выполните в файле поиск метода `getSession`. Нажмите Ctrl-F (⌘-F в Mac), введите '`getSession`' на панели поиска, затем нажмите кклавишу Enter.
Ctrl-F (⌘-F в Mac) - это сочетание клавиш для 'Правка' > 'Найти'.
image::images/get-session.png[title="Метод getSession существует в скомпилированном сервлете страницы JSP"]
Фактически выполняется вызов метода `getSession`. Это вызвано тем, что страницы JSP по умолчанию включают в себя скрытый объект `pageContext.session`. Для деактивации этого поведения можно добавить следующую директиву в верхнюю область файла JSP:
[source,java]
----
<%@page session="false" %>
----
, и метод`getSession` в скомпилированном сервлете будет деактивирован.
Для выяснения местоположения скомпилированного сервлета на сервере можно навести курсор мыши на вкладку с именем сервлета над редактором. Будет выведено всплывающее окно с путем к файлу на компьютере.
[start=7]
. Выберите категорию в браузере и добавьте товар в корзину. Перейдите в среду IDE. Заметим, что отладчик приостанавливает работу на точке останова в `ControllerServlet`, поставленную ранее (на строке 150). Все точки останова между сеансами запоминаются. Для удаления точки останова можно щелкнуть метку точки останова ( image::images/breakpoint-badge.png[] ) на левой границе редактора. Тем не менее, поскольку в проект уже добавлено несколько точек останова, откройте окно отладчика "Точки останова" ("Окно" > "Отладка" > "Точки останова").
image::images/breakpoints-window.png[title="Просмотрите все точки останова в проекте в окне 'Точка останова'"]
В окне "Точки останова" можно просматривать и вызывать действия для всех точек останова, установленных в проектах, которые открыты в среде IDE.
[start=8]
. Щелкните правой кнопкой мыши точку останова, установленную в файле `header.jspf`, и выберите команду "Удалить". Then щелкните правой кнопкой мыши the breakpoint set in the `ControllerServlet` and choose Disable. (Позднее в этом упражнении будет выполнено ее повторное включение).
[start=9]
. Нажмите кнопку 'Продолжить' ( image::images/continue-btn.png[] ). Обработка запроса завершается, и на странице категорий браузера выводится добавленный в корзину товар.
[start=10]
. В мониторе HTTP выполните поиск запроса `addToCart` в левом столбце, затем выберите его для отображения подробных данных в главном столбце.
Нажмите кнопку 'Сортировка по возрастанию' ( image::images/ascending-sort-btn.png[] ), чтобы последние записи были указаны в верхней части.
Обратите внимание на идентификатор URI запроса (`/AffableBean/addToCart`), метод HTTP (`POST`) и параметры запроса (`productId` и `submit`) на вкладке "Запрос".
[.feature]
--
image::images/http-monitor-add-to-cart.png[role="left", link="images/http-monitor-add-to-cart.png"]
--
[start=11]
. Выберите вкладку "Файлы cookie". Здесь отображаются данные о существовании файла cookie с именем `JSESSIONID` и его отправке от клиента на сервер. Обратите внимание, что значение файла cookie совпадает с идентификатором сеанса, отображаемом на вкладке "Сеанс".
image::images/cookies-tab.png[title="Файлы cookies отображаются на вкладке 'Файлы Cookies' в мониторе HTTP"]
Схожие данные представлены на вкладке "Заголовок", на которой отображается файл cookie, поскольку `Cookie` является заголовком запроса, отправленного клиентом.
image::images/headers-tab.png[title="Файлы cookies отображаются на вкладке 'Файлы Cookies' в мониторе HTTP"]
Для получения дополнительных сведений о заголовках запроса и ответа обратитесь к странице веб-энциклопедии Wikipedia link:http://en.wikipedia.org/wiki/List_of_HTTP_headers[+Список заголовков HTTP+].
[start=12]
. Выберите вкладку "Сеанс". На вкладке отображается сообщение "Сеанс предшествует запросу". Также обратите внимание, что атрибут `cart` отображается в списке "Атрибуты сеанса после запроса". И это объяснимо, ведь объект `cart` привязывается к сеансу при первой обработке запроса `addToCart`.
image::images/session-tab-add-to-cart.png[title="Атрибуты сеанса отображаются на вкладке 'Сеанс' в мониторе HTTP"]
В следующих нескольких шагах рассматривается поиск идентификатора сеанса и файла cookie `JSESSIONID` в диалоговом окне "Переменные".
[start=13]
. Снова активируйте точку останова, ранее установленную в файле `ControllerServlet`. Нажмите ALT+SHIFT+5 (CTRL+SHIFT+5 в Mac OS), чтобы открыть окно точек останова, затем установите флажок напротив точки останова, чтобы включить ее заново.
[start=14]
. Нажмите в браузере кнопку "add to cart" для одного из перечисленных продуктов.
[start=15]
. Перейдите в среду IDE и обратите внимание, что отладчик приостановился на точке останова, установленной в файле `ControllerServlet`. Нажмите кнопку 'Обход процедур' ( image::images/step-over-btn.png[] ) для назначения переменной `session` объекту сеанса.
[start=16]
. Откройте окно "Переменные" (ALT+SHIFT+1; CTRL+SHIFT+1 в Mac OS), чтобы развернуть session > session. Идентификатор сессии будет указан в качестве значения переменной `id`.
[start=17]
. При поиске файла cookie `JSESSIONID` следует помнить, что обычно файл cookie доступен из сервлета при вызове метода link:http://java.sun.com/webservices/docs/1.6/api/javax/servlet/http/HttpServletRequest.html#getCookies%28%29[+`getCookies`+] в объекте `HttpServletRequest`. Поэтому перейдите к объекту запроса по пути: "request" > "Inherited" > "request" > "request" > "Inherited" > "cookies". Здесь можно просмотреть список `файлов cookie` ArrayList. При развертке списка можно найти файл cookie `JSESSIONID`, значением которого является идентификатор сеанса.
[start=18]
. Нажмите кнопку 'Завершить сеанс' ( image::images/finish-session-btn.png[] ), чтобы завершить сеанс отладки.
[[url-rewrite]]
=== Ведение сеансов с перезаписью URL-адреса
Как упоминалось выше, механизм сервлета обнаруживает возможность поддержки файлов cookie в браузере клиента, в случае невозможности поддержки механизм переходит к перезаписи URL-адреса как способа ведения сеансов. Эти процессы являются прозрачными для клиента. Для разработчиков процесс не является полностью прозрачным.
Необходимо убедиться, что приложение может перезаписывать URL-адреса при каждом отключении файлов cookie. Для этого вызовите метод отклика `encodeURL` для всех URL-адресов, возвращаемых сервлетами в приложении. В результате идентификатор сеанса будет добавлен к URL-адресу в случае, если использование файлов cookie невозможно; в противном случае URL-адрес будет возвращен без изменений.
Например, браузер отправляет запрос для третьей категории приложения `AffableBean` (bakery): `category?3`. Сервер отправляет в ответ идентификатор сеанса, включенный в URL-адрес:
[source,java]
----
/AffableBean/category*;jsessionid=364b636d75d90a6e4d0085119990*?3
----
Как описано выше, _все URL-адреса, возвращенные сервлетами приложения, необходимо зашифровать_. Помните, что страницы JSP компилируются в сервлеты. Как зашифровать URL-адреса на страницах JSP? Для этих целей необходимо использовать тег link:http://java.sun.com/products/jsp/jstl/1.1/docs/tlddocs/c/url.html[+`<c:url>`+] JSTL. Следующие упражнения демонстрируют проблему и ее решение.
1. Временно отключите файлы cookie в браузере. При использовании Firefox можно выбрать пункт "Настройки" в меню "Инструменты" ("Firefox" > "Параметры" в системе Mac). Выберите в открывшемся диалоговом окне вкладку "Приватность", затем выберите в области "История" пункт "будет использовать ваши настройки хранения истории" в предоставленном раскрывающемся списке. Снимите флажок параметра "Принимать cookie с сайтов".
image::images/firefox.png[title="Временно отключите файлы cookie в браузере"]
[start=2]
. Запустите проект `AffableBean`. При отображении страницы приветствия щелкните категорию и попытайтесь добавить элемент в корзину. Обратите внимание, что функциональные возможности приложения строго ограничены.
image::images/compromised.png[title="Для функций приложения возникает угроза в случае, если клиента не принимает файлы cookies"]
Как и ранее, сервер создает сеанс и привязывает к нему объекты. Это метод для отображения выбранной категории и продуктов на странице категорий. Тем не менее, при попытке установить файл cookie `JSESSIONID` на сервере происходит сбой. Следовательно, при повторном запросе клиента (когда пользователь нажмите кнопку "add to cart") сервер не может определить сеанс, к которому относится запрос. Поэтому невозможно найти атрибуты, ранее установленные в сеансе, например `selectedCategory` и `categoryProducts`. По этой причине в отображаемом ответе отсутствуют данные, определяемые этими атрибутами.
[start=3]
. Откройте в редакторе страницу `category.jsp` проекта. Найдите строку, в которой реализуется кнопка "add to cart" (строка 58). Атрибут `action` элемента `<form>` определяет запрос, отправленный на сервер.
[source,java]
----
<form action="addToCart" method="post">
----
[start=4]
. Измените запрос для его передачи посредством тега `<c:url>`.
[source,java]
----
<form action="*<c:url value='addToCart'/>*" method="post">
----
[start=5]
. Для сохранения изменений в файле нажмите сочетание клавиш CTRL+S (⌘-S в Mac). Помните, что среда IDE предоставляет функцию "Развертывание при сохранении", которая активна по умолчанию. Это означает, что все сохраненные изменения автоматически разворачиваются на сервере.
[start=6]
. Выберите в браузере другую категорию для отображения в приложении измененной страницы категорий.
[start=7]
. Изучите исходный код страницы. В Firefox можно нажать Ctrl-U (⌘-U в Mac). Отобразится кнопка "add to cart" для каждого продукта с идентификатором сеанса, добавленным к URL-адресу.
[source,java]
----
<form action="addToCart*;jsessionid=4188657e21d72f364e0782136dde*" method="post">
----
[start=8]
. Нажмите кнопку "add to cart" для любого товара. Теперь сервер способен определить сеанс, которому принадлежит запрос, и создает соответствующий ответ.
[start=9]
. Перед продолжением убедитесь, что файлы cookie в браузере снова включены.
Снова возникает необходимость в шифровании каждой активной ссылки приложения, ответ которой требует определенной формы данных сеанса. Иногда реализация выполняется не так очевидно, как описывается в примере выше. Например, в настоящий момент виджет "clear cart", используемый на странице `cart.jsp`, устанавливает для параметра `clear` значение `true` при щелчке ссылки.
[source,xml]
----
<%-- clear cart widget --%>
<c:if test="${!empty cart &amp;&amp; cart.numberOfItems != 0}">
<a href="viewCart*?clear=true*" class="bubble hMargin">clear cart</a>
</c:if>
----
Тег `<c:url>` можно применить к URL-адресу следующим образом:
[source,xml]
----
<%-- clear cart widget --%>
<c:if test="${!empty cart &amp;&amp; cart.numberOfItems != 0}">
*<c:url var="url" value="viewCart">
<c:param name="clear" value="true"/>
</c:url>*
<a href="*${url}*" class="bubble hMargin">clear cart</a>
</c:if>
----
Параметр `clear=true` устанавливается путем добавления тега `<c:param` между тегами `<c:url>`. Переменная `url` устанавливается при помощи атрибута `var` тега <c:url>, затем доступ к атрибуту `var` осуществляется в теге привязки HTML с использованием выражения `${url}`.
Можно загрузить и изучить link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot6.zip[+снимок 6+] для просмотра способа шифрования всех ссылок проекта.
Перезапись URL-адреса следует использовать только в случае, если файлы cookie не доступны как метод отслеживания. Перезапись URL, по общему мнению, не является оптимальным решением, поскольку он предоставляет идентификатор сеанса в журналах, закладках, ссылочных заголовках и коде HTML в кэше, а также адресной строке браузера. Также для этого требуются ресурсы на стороне сервера, поскольку серверу необходимо выполнить дополнительные шаги для каждого входящего запроса, чтобы извлечь идентификатор сеанса из URL-адреса и согласовать с существующим сеансом.
[[time-out]]
== Обработка истечения времени ожидания сеанса
* <<time-interval,Установка временных интервалов сеанса>>
* <<programmatically,Автоматическая обработка истечения времени ожидания сеанса>>
[[time-interval]]
=== Установка временных интервалов сеанса
Необходимо учитывать максимальный интервал времени, в который выполняется ведение сеансов. Если веб-сайт обрабатывает большой поток данных, большое число сеансов может занять весь объем памяти. Следовательно, необходимо сократить интервал для удаления неиспользуемых сеансов. С другой стороны, следует избегать излишнего сокращения сеансов, поскольку это может привести к проблемам использования веб-сайта, сказывающимся на эффективности работы. В примере проекта `AffableBean` пользователь переходит к кассе после заполнения корзины покупок товарами. Затем пользователь выходит из сети, например, чтобы найти кредитную карту для ввода данных карты. После входа в сеть пользователь заполняет форму на кассе и нажимает кнопку подтверждения. Однако время ожидания сеанса на сервере уже истекло. Корзина покупок становится пустой, и пользователь перенаправляется на домашнюю страницу. Найдется ли у пользователя время на повторение процесса?
Следующие шаги демонстрируют способы установки в проекте `AffableBean` в качестве интервала для истечения времени ожидания сеанса значения в 10 минут. Конечно, фактическая длительность в конечном счете зависит от ресурсов сервера, бизнес-целей приложения и популярности веб-сайта.
1. Откройте в редакторе дескриптор развертывания приложения. Нажмите ALT+SHIFT+O (CTRL+SHIFT+O в Mac OS) для использования диалога "Перейти к файлу". Введите "`web`", затем нажмите кнопку "ОК".
image::images/go-to-file.png[title="Диалоговое окно 'Переход к файлу' позволяет быстро перейти к файлам проекта"]
В редакторе будет выведен файл `web.xml` в представлении XML. Шаблон, предоставляемый NetBeans для файла `web.xml`, включает в себя по умолчанию параметр с интервалом в 30 минут.
[source,xml]
----
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
----
[start=2]
. Выберите вкладку "Общее" и введите в поле "Время ожидания сеанса" значение "`10`".
image::images/session-timeout.png[title="Укажите время ожидания сеанса для приложения на вкладке 'Общие' файла web.xml"]
[start=3]
. Сохраните файл (сочетание клавиш Ctrl-S; ⌘-S в Mac).
При обратном переходе в представление XML можно заметить, что элемент `<session-timeout>` был обновлен.
[source,xml]
----
<session-config>
<session-timeout>10</session-timeout>
</session-config>
----
*Примечание.* В качестве альтернативы вы можете полностью удалить элемент `<session-timeout>` и изменить элемент `session-properties` в дескрипторе развертывания, связанном с GlassFish (`sun-web.xml`). В результате будет установлено глобальное время ожидания для всех приложений в веб-модуле сервера. Получить более подробную информацию можно в руководстве link:http://docs.sun.com/app/docs/doc/821-1752/beaha[+Oracle GlassFish Server 3.0.1 Application Development Guide: Creating and Managing Sessions+] (Oracle GlassFish Server 3.0.1 Руководство по разработке приложений. Создание сеансов и управление ими).
[[programmatically]]
=== Автоматическая обработка истечения времени ожидания сеанса
Если приложение основано на сеансах, необходимо принять меры для обеспечения простоты и удобства обработки ситуаций, когда поступает запрос для сеанса, время ожидания которого истекло или который невозможно определить. Это можно выполнить в проекте `AffableBean`, создав простой фильтр, перехватывающий заголовки запросов в файле `ControllerServlet`. Фильтр проверяет факт существования сеанса; если сеанс не существует, он перенаправляет запрос на страницу приветствия сайта.
1. Начните работу с изучения проблемы, возникающей при истечении времени ожидания сеанса до его завершения из-за посещения сайта пользователем. Временно установите для времени ожидания сеанса значение в 1 минуту. Откройте дескриптор развертывания веб-приложения (`web.xml`) и введите значение "`1`" между тегами `<session-timeout>`.
[source,xml]
----
<session-config>
<session-timeout>*1*</session-timeout>
</session-config>
----
[start=2]
. Запустите проект `AffableBean`. Щелкните на странице категорий в браузере, добавьте несколько элементов в корзину и щелкните ссылку "view cart".
image::images/cart-page-session-intact.png[title="Страница корзины зависит от объекта сеанса при отображении элементов в покупательской корзине"]
[start=3]
. Подождите минимум 1 минуту.
[start=4]
. Обновите количество для одного из товаров на странице корзины. (Допустимо любое число от 1 до 99.) Нажмите кнопку "update". Сервер отправит сообщение со статусом HTTP "500".
image::images/glassfish-error-report.png[title="NullPointerException возникает при получении запроса для сеанса, срок действия которого истек"]
[start=5]
. Изучите журнал сервера GlassFish в среде IDE. Откройте окно вывода (Ctrl-4; ⌘-4 в Mac) и перейдите на вкладку 'Сервер GlassFish'. Прокрутите до конца журнала для изучения трассировки стека ошибок.
[.feature]
--
image::images/gf-server-output.png[role="left", link="images/gf-server-output.png"]
--
Журнал сервера показывает, что исключение `NullPointerException` возникло в строке 184 `ControllerServlet`. В диалоговом окне "Вывод" появляется ссылка на строку, в которой возникло исключение.
[start=6]
. Щелкните ссылку. Вы попадете прямо на строку 184 в `ControllerServlet`. При наведении курсора мыши на значок ошибки в левом поле редактора выводится всплывающая подсказка с описанием исключения.
image::images/nullpointer-exception.png[title="Метка ошибки и подсказка указывают местоположение и причину проблемы"]
Поскольку время ожидания сеанса истекло до получения запроса, механизму сервлета не удалось связать запрос с соответствующим сеансом. Таким образом, оказалось невозможным обнаружить объект `cart` (строка 151). В конце концов, исключение произойдет на строке 184, когда попытка вызова метода по переменной, имеющей значение `null`.
Проблема определена, для ее исправления необходимо реализовать фильтр.
[start=7]
. Нажмите кнопку 'Создать файл' (image::images/new-file-btn.png[]) на панели инструментов IDE. качестве альтернативы нажмите Ctrl-N; ⌘-N в Mac.)
[start=8]
. Выберите категорию "*Веб*", затем выберите *"Фильтр"* и нажмите кнопку "Далее".
[start=9]
. Присвойте фильтру имя `SessionTimeoutFilter`. Введите текст `filter` в поле "Пакеты" для размещения класса фильтра в новом пакете при его создании.
[start=10]
. Нажмите кнопку "Далее". Примите настройки по умолчанию и нажмите кнопку "Готово". Для фильтра `SessionTimeoutFilter` создается и открывается в редакторе шаблон.
*Примечание.* В настоящее время в NetBeans 6.9 не поддерживается использование мастера для задания сопоставления с сервлетом, которые не зарегистрирован в дескрипторе веб-развертывания. (`ControllerServlet` был зарегистрирован с помощью аннотации `@WebServlet`). Следовательно, необходимо изменить созданный код в следующем шаге.
[start=11]
. Измените подпись аннотации `@WebFilter` следующим образом:
[source,java]
----
@WebFilter(*servletNames = {"Controller"}*)
public class SessionTimeoutFilter implements Filter {
----
Фильтр с такими настройками будет перехватывать любой запрос, который управляется `ControllerServlet`. (Также можно сохранить атрибут `urlPatterns` и перечислить все шаблоны, обрабатываемые в файле `ControllerServlet`.)
Обратите внимание, что `Controller` является именем файла `ControllerServlet`, как указано в подписи аннотации `@WebServlet` сервлета. Заметим также, что атрибут `filterName` был удален, поскольку имя класса фильтра используется по умолчанию.
Шаблон фильтра среды IDE предоставляет множество примеров кода, которые рекомендуется изучить. Однако большая часть кода не потребуется для целей этого упражнения. Любой класс фильтра должен реализовывать интерфейс `Filter`, определяющий три метода.
* *`init`:* выполняет действия после инициализации фильтра, но до его размещения в службе.
* *`destroy`:* удаляет фильтр из службы. Этот метод может быть также использован для выполнения операций очистки.
* *`doFilter`:* используется для выполнения операций для каждого запроса, перехваченного фильтром.
Используйте функцию поиска документации по индексу, чтобы вытянуть документацию по интерфейсу `Filter`. Нажмите сочетание клавиш SHIFT+F1 (fn+SHIFT+F1 в системе Mac), введите текст `Filter` в поле поиска и нажмите ENTER. Выберите запись "Interface in javax.servlet". Документация Javadoc выводится на нижней панели средства поиска по индексу.
[start=12]
. Замените тело фильтра `SessionTimeoutFilter` на следующее содержимое.
[source,java]
----
@WebFilter(servletNames = {"Controller"})
public class SessionTimeoutFilter implements Filter {
*public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession(false);
// if session doesn't exist, forward user to welcome page
if (session == null) {
try {
req.getRequestDispatcher("/index.jsp").forward(request, response);
} catch (Exception ex) {
ex.printStackTrace();
}
return;
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {}
public void destroy() {}*
}
----
[start=13]
. Нажмите Ctrl-Shift-I (⌘-Shift-I в Mac) для исправления операторов импорта. (Необходимо добавить импорт для `HttpServletRequest` и `HttpSession`.) Используйте подсказки редактора чтобы добавить аннотацию `@Override` к методам `init`, `destroy` и `doFilter`.
В следующих шагах выполняется запуск отладчика для проекта и переход по методу `doFilter` для просмотра способа определения привязки запроса существующему сеансу.
[start=14]
. Откройте окно точек останова (ALT+SHIFT+5; CTRL+SHIFT+5 в Mac OS) и удостоверьтесь, что не установлена ни одна точка останова. Для удаления точки останова щелкните правой кнопкой мыши точку останова и выбериет 'Удалить'. (Если было выполнено упражнение выше, <<http-monitor,Examining Client-Server Communication with the HTTP Monitor>> (Проверка соединения клиент-сервер с помощью HTTP-монитора), в `ControllerServlet` может быть установлена лишняя точка останова).
[start=15]
. Запустите отладчик. Нажмите кнопку 'Отладка проекта' (image::images/debug-project-btn.png[]) на главной панели инструментов IDE.
[start=16]
. При выводе страницы приветствия в браузере выберите категорию и добавьте несколько элементов в корзину покупок.
[start=17]
. Установите точку останова на строку принадлежащего фильтру `SessionTimeoutFilter` метода `doFilter`, который пытается получить доступ к сеансу (строка 32).
image::images/filter-breakpoint.png[title="Установите точку останова в методе getSession"]
[start=18]
. Щелкните в браузере ссылку "view cart". Перейдите к среде IDE и обратите внимание, что отладчик приостановил работу в точке останова.
Учтите, что метод `getSession()` создает новый объект сеанса, если текущий объект не существует. В данном случае используется метод `getSession(false)`, который не создает новый объект, если объект не найден. Другими словами, метод возвращает значение `null`, если сеанс не существует.
[start=19]
. Нажмите кнопку 'Обход процедур' ( image::images/step-over-btn.png[] ), затем подведите курсор мыши к переменной `session`. При условии, что с момента отправки предыдущего запроса не прошла минута, переменная присваивается типу `StandardSessionFacade`. Он представляет объект сеанса для запроса.
image::images/session-exists.png[title="Наведите курсор на переменные для определения их текущих значений"]
[start=20]
. Продолжайте двигаться по строкам метода, пока запрос не будет обработан. Поскольку переменная `session` не равна `null`, можно пропустить выражение `if`, и фильтр `chain.doFilter` направит запрос прямо к `ControllerServlet` (строка 44).
[start=21]
. Перейдите в браузер, убедитесь, что прошла минута, и обновите количество для одного из элементов продукта в корзине. Это та же самая процедура, которая выполнялась ранее в упражнении с возвратом сообщения 500. Выясним, что происходит при истечении времени ожидания сеанса теперь, когда фильтр перехватывает заголовки запросов для файла `ControllerServlet`.
[start=22]
. После щелчка элемента "Обновить" перейдите в среду IDE и обратите внимание, что отладчик снова приостановился на точке останова, установленной в фильтре.
[start=23]
. Выделите выражение `req.getSession(false)` и наведите на него курсор мыши. Обратите внимание, что выражение имеет значение `null`, поскольку время ожидания сеанса истекло.
image::images/session-null.png[title="Выделите выражения и наведите курсор мыши на них для определения их текущих значений"]
[start=24]
. Продолжите переход по коду. Теперь переменная `session` равна `null`, выражение `if` на странице 35 обрабатывается, и запрос направляется прямо к `/index.jsp`. Когда отладчик завершает работу, в браузере выводится страница приветствия сайта.
[start=25]
. Нажмите кнопку 'Завершить сеанс' ( image::images/finish-session-btn.png[] ), чтобы завершить сеанс отладки.
[start=26]
. Откройте файл `web.xml` проекта и снова измените время ожидания сеанса на 10 минут.
[source,xml]
----
<session-config>
<session-timeout>*10*</session-timeout>
</session-config>
----
[start=27]
. Сохраните файл (Ctrl-S; ⌘-S в Mac).
link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot6.zip[+Снимок 6+] демонстрирует полную версию проекта для данного раздела руководства. Рассмотрим еще один вопрос, касающийся управления сеансом. Можно завершить сеанс явным образом, вызвав метод `invalidate` для объекта сеанса. Если сеанс больше не требуется, необходимо удалить его для сохранения доступной памяти на сервере. После завершения следующего раздела, link:transaction.html[+Интеграция транзакционной бизнес-логики+], можно увидеть, как `ControllerServlet` после успешной обработки заказ клиента, уничтожает пользовательский объект `cart` и прерывает сессию, используя метод `invalidate`.
[source,java]
----
// if order processed successfully send user to confirmation page
if (orderId != 0) {
// dissociate shopping cart from session
cart = null;
// end session
session.invalidate();
...
}
----
Это демонстрируется в link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252Fecommerce%252FAffableBean_snapshot8.zip[+проектный снимок 8+] (и следующих снимках).
link:/about/contact_form.html?to=3&subject=Feedback: NetBeans E-commerce Tutorial - Managing Sessions[+Мы ждем ваших отзывов+]
[[seeAlso]]
== Дополнительные сведения
=== Материалы по NetBeans
* link:../../../../features/java/debugger.html[+IDE NetBeans Features: Debugger+]
* link:../../java/debug-multithreaded.html[+Отладка многопоточных приложений+]
* link:../../java/debug-multithreaded-screencast.html[+Видеообзор многопотоковой отладки с помощью IDE NetBeans+]
* link:../../java/debug-evaluator-screencast.html[+Видео использования блока оценки фрагмента кода в отладчике NetBeans+]
* link:../../screencasts.html[+Video Tutorials and Demos for IDE NetBeans 6.x+]
* link:https://netbeans.org/projects/www/downloads/download/shortcuts.pdf[+Таблица комбинаций клавиш и шаблонов кода+]
* link:../javaee-gettingstarted.html[+Начало работы с приложениями для Java EE+]
* link:../javaee-intro.html[+Введение в технологию Java EE +]
* link:../../../trails/java-ee.html[+Учебная карта по Java EE и Java Web+]
=== Ресурсы по GlassFish
* link:http://wiki.glassfish.java.net/Wiki.jsp?page=Screencasts[+Экранные демонстрации GlassFish+]
* link:https://glassfish.dev.java.net/docs/index.html[+Документация GlassFish v3+]
* link:http://www.sun.com/offers/details/GlassFish_Tomcat.html[+Изучение GlassFish в Tomcat+]
* link:http://docs.sun.com/app/docs/doc/821-1751[+Oracle GlassFish Server 3.0.1 Руководства администратора+]
* link:http://docs.sun.com/app/docs/doc/821-1750[+Oracle GlassFish Server 3.0.1 Руководство по развертыванию приложений+]
* link:http://docs.sun.com/app/docs/doc/821-1752[+Oracle GlassFish Server 3.0.1 Руководство по разработке приложений+]
=== Технические статьи и различные ресурсы
* link:http://java.sun.com/javaee/reference/code/[+Примеры кода Java EE и приложения+]
* link:http://java.sun.com/j2se/javadoc/[+Средство Javadoc+][домашняя страница продукта]
* link:http://java.sun.com/j2se/javadoc/writingdoccomments/index.html[+Принцип записи комментариев документации для средства Javadoc+]
* link:http://java.sun.com/products/servlet/Filters.html[+Сущность фильтров+]
* link:http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html[+Шаблоны Core J2EE - перехватывающий фильтр+]
* link:http://courses.coreservlets.com/Course-Materials/csajsp2.html[+Учебные курсы по сервлетам начального и среднего уровней, JSP и JDBC+]
* link:http://courses.coreservlets.com/Course-Materials/msajsp.html[+Учебный курс по расширенным сервлетам и JSP+]
* link:http://courses.coreservlets.com/Course-Materials/java5.html[+Учебные курсы по Java 5 и Java 6+]
* link:http://www.ibm.com/developerworks/java/library/j-jstl0211.html[+JSTL для начинающих, часть 1: язык выражений+]
* link:http://www.ibm.com/developerworks/java/library/j-jstl0318/index.html[+JSTL для начинающих, часть 2: подробное изучение+]