Моя фотография
Павел Вязовой
Студент факультета информатики Магнитогорского Государственного Университета, линуксоид, емаксер, web-разработчик. Больше информации можно найти на моём сайте. Есть две тематические ленты: runix и emacs.
Просмотреть весь профиль

суббота, 19 декабря 2009 г.

PHP: пишем свою реализацию сессий для обработки мертвой сессии перед зачисткой

Представим ситуацию: есть корзина покупок на сайте, при добавлении в корзину мы ставим на товар т.н. lock, исключающий его из списка доступных для покупки товаров. Когда клиент удаляет товар из корзины - lock снимается. Но что делать, если пользователь просто закрыл браузер? В таком случае сессия будет удалена сборщиком мусора, а локи так и останутся.

Когда я столкнулся с такой ситуацией, первое что мне пришло в голову - хранить локи и дату доступа в БД и периодически дергать её кроном. Но костыльность этого решения очевидна. Я вообще недолюбливаю web-программирование на PHP за часто-встречающуюся костыльность многих решений. А ещё за бред, с которым я столкнулся при решении сабжа: для сериализации и десереализации сессий используются функции и формат, отличные от функций serialize и unserialize. Приходится делать велосипеды для ансериализации сессии.

Ближе к телу: как решил проблему я...
Тут надо сделать замечание, что идею для такого решения мне подал хабраюзер rigid, а помогли решить пару около-сабжевых проблем в конференции php@conference.jabber.ru.

PHP позволяет определять свои функции для обработки сессий. Отвечает за это функция session_set_save_handler. В качестве параметров она принимает список функций, который будут вызываться для работы с сессиями. В мануале есть даже пример, который реализует стандартный механизм работы с сессиями. Его-то мы и возьмем, изменив только функцию gc, которая занимается сборкой мусора, т.е. удалением файлов мертвых сессий.

Пример функции gc:


/* Функция принимает в качестве параметра время жизни сессии */
function gc($maxlifetime)
{
global $sess_save_path; /* путь, где лежат сессии */
foreach (glob("$sess_save_path/sess_*") as $filename)
{
/* Проверяем не пора ли убить сессию */
if (filemtime($filename) + $maxlifetime < time())
{
$tmp_sess=unserializesession(file_get_contents($filename)); /* $tmp_sess у нас теперь аналогична $_SESSION той сессии */
/* Обрабатываем данные, например снимаем локи из этой сессии */
@unlink($filename); /* Удаляем сессию */
}
}
return true;
}


Код функции unserializesession, взятый откуда-то из интернета (скорее всего из комментариев к функции в мануале PHP):


function unserializesession($data) {
$vars=preg_split('/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/',
$data,-1,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
for($i=0; $vars[$i]; $i++) $result[$vars[$i++]]=unserialize($vars[$i]);
return $result;
}


Теперь подключаем это в наш проект:


session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
/* Вероятность чистки мусора на каждый session_start() примерно равна 30%, другими словами - чистка мертвых сессий будет производится при тридцати вызовах session_start() из ста */
ini_set("session.gc_probability", 30); /* Можно настроить на 100%, если у вас там нет никакого медленного кода */
ini_set("session.gc_divisor", 100);
ini_set("session.gc_maxlifetime", 1800); /* Время жизни сессии в секундах (то самое, которое передается в функцию gc) */
session_start();


Есть одно но: в Debian/Ubuntu свой механизм очистки сессий, который выполняется кроном, а у PHP нет возможности удалять файлы сессий. Честно говоря, это "черезжопа". Решить проблему можно задав собственный каталог для файлов сессии и закрыв его в .htaccess (если он находится в document_root).

0 коммент.:

Отправить комментарий

Blog Widget by LinkWithin