Отслеживание сессии это механизм, который сервлеты используют, чтобы поддерживать статус между сериями запросов от одного и того же пользователя (запросов произведенных из одного окна броузера) в течение определенного промежутка времени.

Сессии используются разными сервлетами для доступа к одному клиенту. Это удобно для приложений построенных на нескольких сервлетах. Например, Duke's Bookstore использует отслеживание сессии для того, чтобы сохранять книги, выбранные клиентом. Все сервлеты в примере имеют доступ к пользовательской сессии.

Чтобы использовать отслеживание сессии:

  • Создайте для пользователя сессию (объект HttpSession).
  • Сохраняйте или читайте данные из объекта HttpSession.
  • Уничтожьте сессию (необязательное).

Получение сессии

Метод getSession объекта HttpServletRequest возвращает сессию пользователя. Когда Вы вызываете этот метод с аргументом create равным true, среда выполнения создает при необходимости сессию.

Чтобы правильно организовать сессию, Вам надо вызвать метод getSession прежде чем будет запущен выходной поток ответа. (Если Ваш ответ использует Writer, Вам надо вызвать метод getSession, прежде чем Вы получите доступ к Writer, прежде отправки данных ответа.)

Пример Duke's Bookstore использует прослеживание сессии для того, чтобы хранить информацию о книгах в корзине для покупок пользователя. Вот пример сервлета CatalogServlet устанавливающего сессию пользователя:

public class CatalogServlet extends HttpServlet {
    public void doGet (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Получаем сесию пользователя и корзину для покупок
        HttpSession session = request.getSession(true);
        ...
        out = response.getWriter();
        ...
    }
}

Сохранение и получение данных сессии

Интерфейс HttpSession предоставляет методы, которые сохраняют и возвращают данные:

  • Стандартные свойства сессии, такие как идентификатор сессии.
  • Данные приложения, которые сохраняются в виде пары с именным ключом, когда имя это строка (String) и величина - объект Java. (Также как java.util.Dictionary.) Поскольку используется доступ нескольких сервлетов к пользовательской сессии, Вам надо выбрать условное именование для организации имен соответствующих пользовательским данным. Это позволит сервлетам избежать случайной перезаписи одних величин другими. Одина из таких условностей servletname.name, где servletname это полное имя сервлета, включая его пакет. Например, com.acme.WidgetServlet.state это закладка с именем сервлета com.acme.WidgetServlet и именем state.

Пример Duke's Bookstore использует прослеживание сессии для того, чтобы хранить информацию о книгах в корзине для покупок пользователя. Вот пример сервлета CatalogServlet получающего идентификатор пользовательской сессии, который получает и устанавливает данные, соответствующие сессии этого пользователя:

public class CatalogServlet extends HttpServlet {
    public void doGet (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Получаем сесию пользователя и корзину для покупок
        HttpSession session = request.getSession(true);
        ShoppingCart cart = (ShoppingCart)session.getValue(session.getId());
        // Если у пользователя нет корзины, создаем ее
        if (cart == null) {
            cart = new ShoppingCart();
            session.putValue(session.getId(), cart);
        }
        ...
    }
}

Потому как объект может быть ассоциирован с сессией, пример Duke's Bookstore хранит книги отобранные пользователем в объекте. Этот объект типа ShoppingCart и каждая книга, отобранная пользователем, хранится в корзине для покупок, то есть в объекте ShoppingCartItem. Например, вот, что получается в таком случае в методе doGet сервлета CatalogServlet:

public void doGet (HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    HttpSession session = request.getSession(true);
    ShoppingCart cart = (ShoppingCart)session.getValue(session.getId());
    ...
    // Смотрим есть ли покупки в корзине
    String bookId = request.getParameter("Buy");
    //Если пользователь хочет добавить книгу, добавляем и пишем ответ
    String bookToAdd = request.getParameter("Buy");
    if (bookToAdd != null) {
        BookDetails book = database.getBookDetails(bookToAdd);
        cart.add(bookToAdd, book);
        out.println("<p><h3>" + ...);
    }
}

В заключении, отметим, что сессия может быть разработана как новая. Новая сессия - если метод isNew класса HttpSession возвращает true, показывает, например, что, клиент не устанавливал сессию еще. С новой сессий данные еще не ассоциированы.

Вам надо разобраться с ситуациями вызывающими новые сессии. В примере Duke's Bookstore приведенном выше, если у пользователя нет корзины для покупок (единственные данные ассоциированные с сессией), сервлет создает ему новую. С другой стороны, если Вам нужна информация пользователя, чтобы открыть сессию (такая как имя пользователя), Вы, возможно, перенаправите пользователя на "стартовую страницу", где возьмете всю необходимую информацию.

Завершение сессии

Сессия пользователя может быть завершена вручную или, в зависимости от того, где запущен сервлет, автоматически. (Например, Java Web Server автоматически завершает сессию, когда в течение определенного времени не происходит запросов, по умолчанию 30 минут.) Завершить сессию означает удаление объекта HttpSession и его величин из системы.

Чтобы вручную завершить сессию, используйте метод сессии invalidate. У некоторых программ уже есть такие точки, в которых происходит завершение сессии. Пример Duke's Bookstore завершает сессию пользователя после того, как он осуществил покупку книг. Это происходит в сервлете ReceiptServlet:

public class ReceiptServlet extends HttpServlet {
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ...
        scart = (ShoppingCart)session.getValue(session.getId());
        ...
        // Очищаем корзину завершая сессию
        session.invalidate();
        // прежде чем начать вывод, устанавливаем тип содержимого
        response.setContentType("text/html");
        out = response.getWriter();
        ...
    }
}

Управление всеми броузерами

По умолчанию, прослеживание сессии использует закладки, чтобы ассоциировать идентификатор сессии с пользователем. Чтобы также поддерживать пользователей, у которых броузер не работает с закладками, или включен в режим игнорирования их, Вы должны использовать перезапись URL. (По скольку некоторые сервера поддерживают перезапись URL, утилита servletrunner являющаяся частью JSDK2.0 не поддерживает это. Для того чтобы прослеживания сессии работало, когда сервлет запущен с помощью servletrunner, клиент должен поддерживать закладки.)

Когда Вы используете перезапись URL, Вы вызываете методы, когда необходимо, так, чтобы идентификатор был включен в ссылку. Вы должны использовать эти методы для каждой ссылке в ответе сервлета.

Методом, который ассоциирует идентификатор сессии с URL, является метод HttpServletResponse.encodeUrl в пакете JSDK2.0 и HttpServletResponse.encodeURL в пакете JSDK2.1. Если Вы переадресовываете пользователя на другую страницу, методом, который ассоциирует идентификатор сессии с URL, является метод HttpServletResponse.encodeRedirectUrl в пакете JSDK2.0 и HttpServletResponse.encodeRedirectURL в пакете JSDK2.1.

Методы URL кодирования и кодирования переадресации определяют должен ли URL быть перезаписан и возвращают измененный или не измененный URL. (Правила для URL-ов и переадресуемых URL-ов разные, но в основном, если сервер определяет, что данный броузер поддерживает закладки, URL переписан не будет.)

Если бы пример Duke's Bookstore использовал перезапись URL, его код в сервлете CatalogServlet мог бы выглядеть следующим образом:

public class CatalogServlet extends HttpServlet {
    public void doGet (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // Получаем сесию пользователя, корзину для покупок, Writer и  т.п.
        ...
        // пишем ответ
        out.println("" + ...);
        ...
        // берем каталог и отправляем его, красиво отформатировав
        BookDetails[] books = database.getBooksSortedByTitle();
        ...
        for (int i=0; i < numBooks; i++) {
            ...
            //Выводим информацию для книги в две ячейки
            out.println("<tr><td><a href=\"" 
                    + response.encodeURL("/servlet/bookdetails?bookId=" + bookId) 
                    + "\">" + books[i].getTitle() + "</a></td>" 
                    + "<td><a href=\"" 
                    + response.encodeURL("/servlet/catalog?Buy=" + bookId) 
                    + "\"> Add to Cart </a></td></tr>");
                
        }
    }
}

Отметим, что сервлет CatalogServlet возвращает пользователю две ссылки для каждой книги. Одна ссылка предлагает просмотр подробностей касающихся книги, и вторая позволяет добавить книгу в корзину для покупок. Обе ссылки будут переписаны так как, опять, каждая ссылка, которую сервлет возвращает пользователю должна быть переписана, когда Вы используете метод перезаписи URL.

Когда пользователь жмет на ссылку переписанного URL, сервлет определяет и извлекает идентификатор сессии. Далее используется метод getSession, чтобы получить соответствующий идентификатору сессии объект HttpSession

И обратно, если броузер пользователя не поддерживает закладки, и пользователь нажимает на не переписанный URL, сессия пользователя пропадает. Сервлет, взаимодействуя через эту ссылку, создает новую сессию, а новая сессия не содержит данных соответствующих прежней сессии. Если сервлет теряет данные сессии, эти данные теряются для всех сервлетов их использующих. Вы должны также использовать методы перезаписи URL, если хотите, чтобы Ваш сервлет поддерживал клиентов, не работающих или не поддерживающих закладки.