MySQL / 19. Управление веб-сеансами с помощью MySQL

Хранение сеансов в MySQL: Tomcat

Задача
Вы хотите хранить данные сеансов для сценариев на Java.

Решение
Tomcat самостоятельно осуществляет управление сеансами. По умолчанию в качестве хранилища используются временные файлы, но вы можете указать в конфигурационном файле Tomcat  server.xml  соответствующие пара-метры JDBC для хранения сеансов в MySQL.

Обсуждение
Описанные ранее механизмы сеансов Perl и PHP требуют, чтобы приложения явно указывали на то, что для хранения сеансов следует применять MySQL. В Perl сценарий должен сообщить, что он хочет использовать соответствующий модуль  Apache::Session. В PHP 4 менеджер сеансов встроен в язык, но каждое приложение, которое хочет использовать модуль хранения в MySQL, должно его зарегистрировать.

В приложениях на Java, работающих под управлением Tomcat, все по-другому. Tomcat сам обрабатывает сеансы, и если вы хотите хранить данные сеанса в MySQL, то следует переконфигурировать Tomcat, а не приложения.

Другими словами, веб-программы Java свободны от деталей, связанных с обработкой сеансов, которые в других языках присутствуют именно на уровне приложений. Например, идентификаторами сеансов заведует Tomcat, а не приложения. Tomcat проверяет, включена ли поддержка cookies, и модифицирует URL, добавляя в него идентификатор сеанса, если cookies недоступны. Разработчикам приложений не приходится думать о том, какой метод использовать, так как доступность идентификатора не зависит от способа
его передачи.

Для иллюстрации независимости приложения от метода управления сеансом, используемого Tomcat, в этом разделе рассматривается простое приложение JSP, применяющее сеанс. Описано изменение конфигурации Tomcat для хранения данных сеанса в MySQL, а не в хранилище сеансов по умолча-нию, при этом никаких изменений в самом приложении выполнять не нуж-но. Однако начать следует с разговора об интерфейсе сеансов.

Интерфейс сеансов для сервлетов и страниц JSP
Tomcat использует стандартный интерфейс сеансов, описанный в спецификации Java Servlet. Этот интерфейс может быть использован и сервлетами, и страницами JSP. В сервлете вы получаете доступ к сеансу, импортируя класс  javax.servlet.http.HttpSession и вызывая метод  getSession() вашего объекта HttpRequest:

import javax.servlet.http.*;
HttpSession session = request.getSession ();

Для страниц JSP поддержка сеансов включена по умолчанию, то есть эти предложения как будто уже выполнены к моменту начала исполнения страницы. Таким образом, сеанс неявно доступен через переменную session, которая уже установлена без вашего участия.

Полный интерфейс сеансов определен в разделе  HttpSession спецификации Java Servlet (см. приложение C). Приведем некоторые наиболее характерные методы объектов сеансов:

isNew ()
Возвращает истину или ложь в зависимости от того, начинается ли сеанс текущим запросом.

getAttribute (String attrName)
Содержимое сеанса состоит из атрибутов, которые являются объектами, связанными с именами. Для доступа к атрибуту сеанса укажите его имя. Метод getAttribute() возвращает объект, связанный с указанным именем, или null, если объект с таким именем не существует.

setAttribute (String attrName, Object obj)
Добавляет объект в сеанс и связывает его с указанным именем.

removeAttribute (String attrName)
Удаляет атрибут с указанным именем из сеанса.

invalidate ()
Объявляет некорректным сеанс и все связанные с ним данные. Следую-щий запрос клиента начнет новый сеанс.

Пример JSP-приложения, использующего сеансы
Рассмотрим страницу  JSP sess_track.jsp, которая ведет счетчик запросов сеанса и журнал времени запросов. Для более наглядной иллюстрации операций, связанных с сеансами, страница состоит в основном из встроенного ко-да Java, который напрямую использует интерфейс сеанса HttpSession:

<%-- sess_track.jsp - вывод количества запросов сеанса и временных меток --%>
<%@ page import="java.util.*" %>
<%
    // получить переменные сеанса, инициализируя их в случае отсутствия
    int count;
    Object obj = session.getAttribute ("count");
    if (obj == null)
        count = 0;
    else
        count = Integer.parseInt (obj.toString ());
    ArrayList timestamp = (ArrayList) session.getAttribute ("timestamp");
    if (timestamp == null)
        timestamp = new ArrayList ();
    // увеличить счетчик, добавить текущую временную метку в массив
    count = count + 1;
    timestamp.add (new Date ());
    if (count < 10)     // сохранить обновленные значения в объекте сеанса
    {
        session.setAttribute ("count", String.valueOf (count));
        session.setAttribute ("timestamp", timestamp);
    }
    else                // начать новый сеанс после 10 запросов
    {
        session.removeAttribute ("count");
session.removeAttribute ("timestamp");
    }
%>
<html>
<head>
<title>JSP Session Tracker</title>
</head>
(body bgcolor="white")
<p>This session has been active for <%= count %> requests.</p>
<p>The requests occurred at these times:</p>
<ul>
<%
    for (int i = 0; i < timestamp.size (); i++)
        out.println ("<li>" + timestamp.get (i) + "</li>");
%>
</ul>
(/body)
</html>

Вызовите  sess_track.jsp  несколько раз в броузере, чтобы посмотреть на то, как меняется вывод.

Метод  session.setAttribute(), применявшийся в странице  sess_track.jsp, помещает информацию в сеанс, с тем чтобы она в дальнейшем могла быть обнаружена при последующих вызовах сценария. Атрибуты сеанса могут совместно использоваться несколькими сценариями. Чтобы убедиться в этом, создайте копию  sess_track.jsp  и вызовите копию из броузера. Вы увидите, что страница обращается к тем же данным сеанса, что и sess_track.jsp.

Некоторые операции с сеансами, приведенные в  sess_track.jsp, можно вы-полнить при помощи тегов JSTL, содержащих переменную sessionScope для обращения к неявному объекту сеанса JSP:

<%-- sess_track2.jsp - вывод количества запросов и временных меток сеанса --%>
<%@ page import="java.util.*" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<c:if test="${empty sessionScope.count}">
    <c:set var="count" scope="session" value="0" />
</c:if>
<c:set var="count" scope="session" value="${sessionScope.count+1}" />
<%
    ArrayList timestamp = (ArrayList) session.getAttribute ("timestamp");
    if (timestamp == null)
        timestamp = new ArrayList ();
    // добавить текущую временную метку в массив, сохранить результат в сеансе
    timestamp.add (new Date ());
    session.setAttribute ("timestamp", timestamp);
%><html>
<head>
<title>JSP Session Tracker 2</title>
</head>
(body bgcolor="white")
<p>This session has been active for
<c:out value="${sessionScope.count}" />
requests.</p>
<p>The requests occurred at these times:</p>
<ul>
<c:forEach var="t" items="${sessionScope.timestamp}">
    <li><c:out value="${t}" /></li>
</c:forEach>
</ul>
<%-- достигнут ли предел в 10 запросов? --%>
<c:if test="${sessionScope.count ge 10}">
    <c:remove var="count" scope="session" />
    <c:remove var="timestamp" scope="session" />
</c:if>
(/body)
</html>

Как указать Tomcat сохранять записи сеанса в MySQL
Документация Tomcat, относящаяся к работе с сеансами, доступна по адресу:

http://jakarta.apache.org/tomcat/tomcat-4.0-doc/config/manager.html

В Tomcat есть собственный механизм хранения сеансов, используемый по умолчанию (во временных файлах). Для сохранения сеансов в MySQL при помощи JDBC выполните следующие операции:

1. Создайте таблицу для хранения записей сеансов.

2. Убедитесь в том, что у Tomcat есть доступ к нужному драйверу JDBC.

3. Измените конфигурационный файл Tomcat server.xml, задав использование постоянного менеджера сеансов для соответствующего контекста (контекстов) приложений.

Ни один из этих шагов не касается изменения самого приложения, это отражает тот факт, что Tomcat реализует поддержку сеансов выше уровня приложений.

Создание таблицы сеансов Tomcat
Tomcat сохраняет в таблице сеанса различные виды информации:

• Идентификатор сеанса. По умолчанию – 32-символьные значения MD5.

• Данные сеанса. Это сериализованная строка.

• Однобайтовый признак корректности сеанса.

• Максимально разрешенное время простоя в секундах в виде 32-битного целого.

• Время последнего обращения в виде 64-битного целого.

Этим условиям удовлетворяет таблица, которую следует создать прежде, чем переходить к следующему разделу:

CREATE TABLE tomcat_session
(
    id              CHAR(32) NOT NULL,
    data            BLOB,
    valid_session   CHAR(1) NOT NULL,
    max_inactive    INT NOT NULL,
    last_access     BIGINT NOT NULL,
    PRIMARY KEY (id)
);

Размещение драйвера JDBC там, где Tomcat сможет его найти
Поскольку Tomcat сам управляет сеансами, он должен иметь возможность доступа к драйверу JDBC, используемому для сохранения данных сеансов в базе данных. Принято хранить драйверы в каталоге lib дерева Tomcat, чтобы к ним могли обращаться все приложения. Но для того чтобы драйвер был до-ступен и серверу Tomcat, он  должен размещаться в каталоге  common/lib. (То есть если у вас драйвер MySQL Connector/J установлен в lib, переместите его в common/lib.) После перезапуска Tomcat сможет его использовать.

Изменение конфигурационного файла Tomcat
Чтобы сообщить Tomcat о том, что следует использовать таблицу tomcat_session, необходимо изменить файл server.xml в каталоге Tomcat conf. Для это-го поместите элемент <Manager> в тело элемента <Context> каждого контекста приложения, который должен использовать MySQL-хранилище сеансов. (Если в контексте нет такого элемента, создайте его.) Для контекста приложения mcb элемент <Context> можно создать так:

<Context path="/mcb" docBase="mcb" debug="0" reloadable="true">
  <Manager
    className="org.apache.catalina.session.PersistentManager"
    debug="0"
    saveOnRestart="true"
    minIdleSwap="900"
    maxIdleSwap="1200"
    maxIdleBackup="600">
    <Store
      className="org.apache.catalina.session.JDBCStore"
      driverName="com.mysql.jdbc.Driver"
      connectionURL=
        "jdbc:mysql://localhost/cookbook?user=cbuser&password=cbpass"
      sessionTable="tomcat_session"sessionIdCol="id"
      sessionDataCol="data"
      sessionValidCol="valid_session"
      sessionMaxInactiveCol="max_inactive"
      sessionLastAccessedCol="last_access"
    />
  </Manager>
</Context>

Атрибуты элемента  <Manager> определяют общие параметры, относящиеся к сеансу. Внутри тела элемента <Manager> атрибуты элемента <Store> определяют особенности драйвера JDBC. Дальше мы будем говорить об атрибутах, приведенных в примере, но существуют и другие, которые вы также можете использовать. Обратитесь за информацией к документации по управлению сеансами Tomcat.

Использованные в примере атрибуты <Manager> имеют следующие значения:

className
Указывает класс Java, реализующий постоянное хранилище данных. Должен иметь значение org.apache.catalina.session.PersistentManager.

debug
Задает уровень подробности протоколирования. Нулевое значение отменяет отладочный вывод; чем больше значение, тем подробнее протокол.

saveOnRestart
Позволяет сеансам приложений пережить перезагрузку сервера. Должен быть установлен в true, если вы хотите, чтобы Tomcat сохранял текущие сеансы при отключении (и заново загружал их при перезапуске).

maxIdleBackup
Указывает количество секунд, в течение которых неактивные сеансы мо-гут сохраняться в MySQL. Установка в –1 означает «никогда».

minIdleSwap
Задает количество секунд, в течение которых сеанс может находиться в состоянии ожидания, прежде чем он сможет быть свопированным (вы-груженным из области памяти сервера и сохраненным в MySQL). Уста-новка в –1 означает «никогда».

maxIdleSwap
Задает количество секунд, в течение которых сеанс может находиться в состоянии ожидания и по истечении которого он должен быть скопирован. Установка в  –1 означает «никогда». Если параметр установлен, его значение должно превышать minIdleSwap и maxIdleBackup.

Внутри тела элемента  <Manager> атрибуты элемента  <Store> указывают, как осуществлять соединение с сервером базы данных, какую базу данных и таблицу использовать для хранения записей сеанса, и каковы имена столбцов таблицы: className

Имя класса, реализующего интерфейс  org.apache.catalina.Store. Для менеджеров хранения на основе JDBC этот атрибут должен иметь значение org.apache.catalina.session.JDBCStore.

driverName
Имя класса драйвера JDBC. Для драйвера MySQL Connector/J этот атри-бут следует установить в значение com.mysql.jdbc.Driver.

connectionURL
Указывает, как подключаться к серверу базы данных. Следующий URL служит для соединения с сервером MySQL, работающим на локальном хосте, с именем пользователя cbuser и паролем cbpass:

jdbc:mysql://localhost/cookbook?user=cbuser&password=cbpass

Однако содержимое  server.xml  записывается на XML, поэтому символ  &, разделяющий параметры соединения  user и  password, следует записать как объект &, а весь адрес станет таким:

jdbc:mysql://localhost/cookbook?user=cbuser&password=cbpass

Когда Tomcat читает файл server.xml, программа синтаксического анали-за преобразует  & обратно в символ  &, который и передается драйверу JDBC.

sessionTable
Называет таблицу для хранения записей сеанса. В нашем примере это описанная ранее таблица tomcat_session.

Оставшиеся атрибуты <Store> примера определяют имена столбцов таблицы сеансов. Это атрибуты sessionIdCol, sessionDataCol, sessionValidCol, sessionMa-xInactiveCol и sessionLastAccessedCol, которые очевидным образом соответст-вуют столбцам таблицы tomcat_session.

После изменения файла  server.xml  перезапустите Tomcat. Затем вызовите несколько раз сценарий sess_track.jsp или sess_track2.jsp для инициации сеанса. Все они должны вести себя так же, как до изменения конфигурации Tomcat. После периода неактивности, равного значению атрибута  maxIdle-Backup элемента <Manager>, вы должны увидеть, как запись о сеансе появится в таблице tomcat_session. Если вы следите за журналом запросов MySQL, то
должны заметить и сохранение сеансов в MySQL при отключении сервера Tomcat.

Изменение server.xml является глобальным, чем напоминает изменение зна-чения session.save_handler в конфигурационном файле PHP php.ini. Однако, в отличие от PHP (где изменение глобального файла инициализации влияет на других разработчиков того же хоста таким образом, что у них может возник-нуть необходимость изменения сценариев, применяющих сеансы), измене-ние конфигурации Tomcat для хранения сеансов при помощи JDBC остается абсолютно невидимым для сервлетов и страниц JSP. То есть вы можете вы-полнять изменения, не беспокоясь о том, что другие разработчики, использующие тот же сервер Tomcat, обвинят вас в предумышленном злодеянии.

Истечение срока сеанса в Tomcat
Продолжительность сеанса по умолчанию равна 60 минутам. Чтобы явно определить продолжительность для менеджера сеансов, добавьте maxInactive-Interval в соответствующий элемент <Manager> файла conf/server.xml сервера.

Для указания длительности в рамках определенного контекста приложения добавьте элемент  <session-config> в файл  WEB-INF/web.xml контекста. Например, установим продолжительность в 30 минут:

<session-config>
  <session-timeout>30</session-timeout>
</session-config>

Если вы изменяете server.xml или web.xml, перезапустите Tomcat.

Отслеживание сеансов в Tomcat
Ваши страницы JSP не должны предпринимать никаких действий для на-стройки сеансов или использования JDBC для хранения  данных сеанса, но им может понадобиться убедиться в том, что сеансы корректно переходят от запроса к запросу. Это необходимо, если вы генерируете страницы, содержа-щие гиперссылки на другие страницы, участвующие в том же сеансе.

Tomcat автоматически генерирует идентификатор сеанса и следит за сеансом при помощи cookies, если получает от клиента cookies с идентификатором сеанса. Если у клиента отключена поддержка cookies, Tomcat следит за сеансом, включая его идентификатор в URL. Для вас не важно, какой именно способ использует Tomcat, необходимо лишь позаботиться о корректной передаче идентификатора сеанса в случае, если он передается в URL. То есть если вы создаете страницу, которая включает в себя ссылку на другую стра-ницу, входящую в сеанс, то нельзя просто формировать путь к странице как:

To go to the next page,
(a href="nextpage.jsp")click here(/a).

Эта ссылка не содержит идентификатор сеанса. Если Tomcat отслеживает сеанс за счет перезаписи URL, то вы потеряете идентификатор, когда пользо-ватель выберет такую ссылку. Поэтому следует передать ссылку в функцию encodeURL() для того, чтобы Tomcat добавил в URL идентификатор сеанса:

To go to the next page,
(a href="<%= response.encodeURL ("nextpage.jsp") %>")click here(/a).

Если Tomcat отслеживает сеанс по cookies, то encodeURL() вернет URL без из-менений. Если же для слежения за сеансом используется URL, то encodeURL() автоматически добавит идентификатор сеанса в путь страницы, и он будет выглядеть как-то так:

mypage.jsp;jsessionid=xxxxxxxxxxxxxxxx

Необходимо формировать URL с использованием  encodeURL() для ссылок в любых тегах, которые приводят пользователя на страницу в текущем сеан-се. Речь идет о тегах (a), <form>, <frame> и, возможно, даже о теге <img>, если по какой-то причине такие теги вызывают сценарий, формирующий изобра-жения в рамках сеанса.

Вероятно, лучше всего завести привычку применять  encodeURL() при создании URL в приложениях, использующих сеансы. Даже если вы думаете, что у всех работающих с приложением включена поддержка cookies, однажды это предположение может оказаться неверным.

Имя метода  java.net.URLEncoder.encode() похоже на  encodeURL(), но на этом сходство заканчивается. Метод преобразует специальные символы в нота-цию %xx для обеспечения их безопасного использования в URL.