Хранение сеансов в MySQL: приложения на Perl

Задача
Вы хотите встроить поддержку сеансов в сценарии на Perl.

Решение
Модуль Apache::Session предлагает простой способ работы с различными типами хранения, включая использование MySQL.

Обсуждение
Простой в использовании модуль Apache::Session предназначен для хранения информации о состоянии на протяжении нескольких веб-запросов. Во-преки своему названию он не связан с сервером Apache и может использоваться и в другом контексте, например, для запоминания состояния при последовательных вызовах сценария из командной строки. С другой стороны, в Apache::Session отсутствуют средства для работы с идентификаторами сеансов (отправка идентификатора клиенту в ответе на его первый запрос и получение его в последующих запросах). В приведенном ниже примере приложение использует cookies для передачи идентификатора сеанса в предположении, что их использование разрешено на клиенте.

Установка модуля Apache::Session
Если у вас нет модуля Apache::Session, то можно получить его из архива CPAN (по адресу http://cpan.perl.org). Установка не вызывает затруднений, хотя, возможно, вам придется предварительно установить несколько модулей, используемых Apache::Session.


(В процессе установки Apache::Session сам сообщит вам, каких модулей не хватает.) После того, как все будет установлено, создайте таблицу, в которой будут храниться записи сеансов. Спе-цификация таблицы имеется в документации к модулю, для просмотра ко-торой используйте команду:

% perldoc Apache::Session::Store::MySQL

Таблица может располагаться в любой базе данных (мы будем использовать cookbook), но должна называться sessions и иметь такую структуру:

CREATE TABLE sessions
(
id CHAR(32) NOT NULL, # идентификатор сеанса
a_session BLOB, # дата сеанса
PRIMARY KEY (id)
);

В столбце id хранятся сгенерированные модулем идентификаторы сеансов в формате 32-символьных строк, кодированных по алгоритму MD5. Столбецa_session содержит дату сеанса в форме сериализованной строки. Модуль Apache::Session пользуется модулем Storable для сериализации и десериализации данных.

Интерфейс модуля Apache::Session
Чтобы использовать таблицу sessions в сценарии, включите в него модуль поддержки MySQL:

use Apache::Session::MySQL;

Информация о сеансах представлена в Apache::Session с помощью хеша.


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

my %session;
tie %session,
"Apache::Session::MySQL",
$sess_id,
{
DataSource => "DBI:mysql:host=localhost;database=cookbook",
UserName => "cbuser",
Password => "cbpass",
LockDataSource => "DBI:mysql:host=localhost;database=cookbook",
LockUserName => "cbuser",
LockPassword => "cbpass"
};

Тогда Apache::Session использует параметры для установки собственного со-единения с MySQL, которое закрывается при закрытии или уничтожении сеанса. Во-вторых, можно передать дескриптор уже открытого соединения с базой данных (в данном случае – $dbh):

my %session;
tie %session,
"Apache::Session::MySQL",
$sess_id,
{
Handle => $dbh,
LockHandle => $dbh
};

Если вы передаете дескриптор открытого соединения, то Apache::Session оставляет его открытым, когда вы завершаете или уничтожаете сеанс, считая, что дескриптор используется с другими целями где-то в сценарии.


Когда работа завершена, вам следует самостоятельно закрыть соединение. Аргумент $sess_id, передаваемый в tie, представляет идентификатор сеанса.

Его значением должно быть или undef для начала нового сеанса, или идентификатор, соответствующий существующей записи о сеансе (тогда значение должно совпадать со столбцом id некоторой существующей записи таблицы sessions).

После того как сеанс открыт, можно получить доступ к его содержимому. Например, после открытия нового сеанса вы захотите определить его иде-тификатор, чтобы отправить его клиенту. Значение идентификатора сеанса можно получить так:

$sess_id = $session{_session_id};

Имена элементов хеша сеанса, которые начинаются с символа подчеркивания (например, _session_id), зарезервированы Apache::Session для внутренне-го использования. Помня об этом ограничении, вы можете использовать любые придуманные вами имена для хранения значений сеанса. Например, вы можете поддерживать скалярное значение счетчика так (счетчик инициализируется для нового сеанса, увеличивается и извлекается для отображения):

$session{count} = 0 if !exists ($session{count}); # инициализация счетчика
++$session{count}; # увеличение счетчика
print "counter value: $session{count}\n"; # вывод значения

Для сохранения в записи о сеансе нескалярного значения, такого как массив или хеш, будем хранить ссылку на него:

$session{my_array} = \@my_array;
$session{my_hash} = \%my_hash;

В этом случае изменения, сделанные в @my_array или %my_hash перед закрытием сеанса, будут отражены в содержимом сеанса.


Для сохранения в сеансе независимой копии массива или хеша, которая не будет меняться при изменении оригинала, создадим ссылку на нее так:

$session{my_array} = [ @my_array ];
$session{my_hash} = { %my_hash };

Для извлечения нескалярного значения разыменуем ссылку, хранящуюся в сеансе:

@my_array = @{$session{my_array}};
%my_hash = %{$session{my_hash}};

При завершении работы для закрытия сеанса передайте его в untie:

untie (%session);

Когда вы закрываете сеанс, Apache::Session сохраняет его в таблице sessions, если были сделаны какие-то изменения. После этого значения сеанса становятся недоступными, так что не закрывайте сеанс до тех пор, пока нет уверенности в том, что больше не нужно будет обращаться к нему. Тестовое приложение Сценарий sess_track.pl представляет полную (хотя и короткую) реализацию приложения, использующего сеанс. Он применяет Apache::Session для отслеживания количества запросов в сеансе и времени каждого запроса, обновляя и выводя информацию при каждом вызове. Сценарий sess_track.pl использу-ет файл cookies с именем PERLSESSID для передачи идентификатора сеанса с помощью CGI.pm-интерфейса управления файлами cookies.

#! /usr/bin/perl -w
# sess_track.pl – вывод количества запросов сеанса и временных меток
use strict;
use lib qw(/usr/local/apache/lib/perl);
use CGI qw(:standard);
use Cookbook;
use Apache::Session::MySQL;
my $title = "Perl Session Tracker";
my $dbh = Cookbook::connect (); # соединение с MySQL
my $sess_id = cookie ("PERLSESSID"); # идентификатор сеанса
# (undef для нового сеанса)
my %session; # хеш сеанса
my $cookies; # cookies для отправки клиенту
# открыть сеанс
tie %session, "Apache::Session::MySQL", $sess_id,
{
Handle => $dbh,
LockHandle => $dbh
};if (!defined ($sess_id)) # это новый сеанс
{
# получить новый идентификатор сеанса, инициализировать данные
# сеанса, создать cookies для клиента
$sess_id = $session{_session_id};
$session{count} = 0; # инициализировать счетчик
$session{timestamp} = [ ]; # инициализировать массив временных меток
$cookie = cookie (-name => "PERLSESSID", -value => $sess_id);
}
# увеличить счетчик и добавить текущую временную метку в массив
++$session{count};
push (@{$session{timestamp}}, scalar (localtime (time ())));
# сформировать содержимое тела страницы
my $page_body =
p ("This session has been active for $session{count} requests.")
.


p ("The requests occurred at these times:")
. ul (li ($session{timestamp}));
if ($session{count} < 10) # закрыть (и сохранить) сеанс
{
untie (%session);
}
else # удалить сеанс после 10 вызовов
{
tied (%session)->delete ();
# вернуть cookies в исходное состояние, чтобы указать броузеру
# на необходимость очистки cookies сеанса
$cookie = cookie (-name => "PERLSESSID",
-value => $sess_id,
-expires => "-1d"); # "истек вчера"
}
$dbh->disconnect ();
# сформировать страницу вывода
print
header (-cookie => $cookie) # отправить cookies в заголовки (если определены)
. start_html (-title => $title, -bgcolor => "white")
. $page_body
. end_html ();
exit (0);

Опробуйте сценарий, установив его в каталоге cgi-bin и запросив из броузера. Для повторного вызова используйте функцию обновления страницы, имеющуюся в броузере.
Сценарий sess_track.pl открывает сеанс и увеличивает счетчик до того, как приступить к формированию страницы. Это необходимо, поскольку клиенту нужно отправить файл cookies, содержащий имя сеанса и его идентификатор,если сеанс новый. Все cookies отправляются как часть заголовка ответа, по-этому тело страницы невозможно вывести до тех пор, пока не отправлены заголовки.

Сценарий также генерирует ту часть тела страницы, которая использует данные сеанса, но сохраняет ее в переменной, а не выводит сразу же. Причина такого поведения в том, что приложение, когда ему надо закрыть сеанс, отправляет броузеру файл cookies, содержащий команду удаления предыдущего cookies, переданного ему ранее. Это тоже нужно сделать до отправки заголовков и счетчиков.

Срок хранения данных сеанса
Модуль Apache::Session требует наличия в таблице sessions только столбцов id и a_session и не отслеживает время истечения срока сеанса. Но модуль и не запрещает вам добавлять другие столбцы, так что вы можете включить в таблицу столбец TIMESTAMP для хранения времени последнего обновления каждого сеанса. Например, можно добавить в таблицу sessions столбец t типа TIMESTAMP, используя ALTER TABLE:

ALTER TABLE sessions ADD t TIMESTAMP NOT NULL;

Тогда вы сможете завершать сеансы, периодически выдавая запрос для очистки таблицы от старых записей. Приведем запрос, в котором срок хранения записей равен четырем часам:

DELETE FROM sessions WHERE t < DATE_SUB(NOW(),INTERVAL 4 HOUR);

Имейте в виду, что удаление записей сеанса может вызвать проблему: tie по-рождает исключение, если вы пытаетесь выполнить поиск записи сеанса, используя не-undef-идентификатор сеанса для несуществующей записи. То есть, например, если клиент передает идентификатор сеанса, срок жизни которого истек, ваш сценарий может завершиться с ошибкой. Можно открывать сеанс в блоке eval, чтобы отлавливать ошибки. Если возникает ошибка, создаем новую запись сеанса:

eval
{
tie %session, "Apache::Session::MySQL", $sess_id,
{
Handle => $dbh,
LockHandle => $dbh
};
};
if ($@) # ошибка - старый сеанс недоступен, создать новый сеанс
{
$sess_id = undef;
tie %session, "Apache::Session::MySQL", $sess_id,
{
Handle => $dbh,
LockHandle => $dbh
};
}

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела MySQL на эту тему:
Хранение сеансов в MySQL: менеджер сеансов PHP
Хранение сеансов в MySQL: Tomcat