Блог веб-разработчика: в помощь программистам

PHP: как не потерять сессию

Достаточно часто встречается ситуация, когда авторизованный пользователь уходит из-за компьютера или просто переключается на какую-либо другую задачу, забывая при этом периодически покликивать по ссылкам сайта. Результат: по возвращении к работе с сайтом после первого же клика его нагло выбрасывает из теплой и уютной админки с нахальным требованием залогиниться заново.
Во время разработки моего последнего проекта подобные ситуации с пользователями также происходили. Согласен, обидно, когда набираемый и корректируемый в течение получаса текст вдруг уходит в небытие и никакими силами его уже не спасти. Решив раз и навсегда покончить с этой проблемой (в конце-то концов сайты делаются, собственно, для их посетителей), я набросал несколько строк кода. И — о чудо! — проблема решилась.

Прочему происходит разрыв сессии

Здесь все просто. Сессия рвется потому, что веб-сервер старается держать только сессии активных пользователей. Активными, по его мнению, считаются те, которое запрашивают у него новые страницы не реже, чем… указано в настройках сервера. Как правило, эта настройка записывается там, куда не имеет доступа даже арендатор сервера.

Можно, конечно, забрасывать письмами админов хостера с требованием увеличить время сессии, а то и сделать ее бесконечной, но мало кто на это пойдет. Подвергать такому риску свои сервера хостер будет только в том случае, если Вы неслабо платите за его услуги. Если же у Вас простой виртуальный хостинг — на положительный ответ можете даже не надеяться. Значит, пойдем другим путем.

Как поддержать сессию автоматически

Теперь, когда мы знаем, что для поддержания сессии нужно всего лишь периодически запрашивать у сервера какие-нибудь данные, решение напрашивается само собой. Нужно просто запрашивать эти самые данные. Это можно делать вручную, кликая по каким-нибудь ссылкам в новых вкладках браузера, в этом случае сессия будет поддерживаться и в той вкладке, где тихо и меланхолично набирается какой-нибудь текст в WYSIWYG-редакторе. Но попробуйте-ка все это растолковать пользователю, который кроме браузера ничего особо и не знает. Он решит, что Вы хотите над ним просто поиздеваться, отправит Вас без снаряжения в пешее эротическое и забьет на Ваш ресурс. И правильно, кстати, сделает! Нехорошо в наши дни заставлять пользователя заниматься таким грязным делом.

Но идея-то с поддержкой сессий таким вот образом — хороша! Настолько хороша, что от нее совершенно не хочется отказываться. А мы отказываться от нее и не будем. Отправлять запросы серверу браузер будет самостоятельно, без какого-либо пользовательского участия. Он просто будет информировать сервер, мол, эй, я живой, не рви мою сессию и сервер его прекрасно поймет. Это чем-то похоже на команду ping, когда клиент просто сообщает о своем присутствии — и ничего больше. Потому этот скрипт я так и решил обозвать — пингер.

Пингер: клиентская сторона

Повторюсь. Для того, чтобы сервер не рвал сессию пользователя, нам нужно периодически запрашивать у сервера какие-либо данные. Желательно, как можно меньших объемов, поскольку эти самые данные нам не нужны. Нам нужно лишь «отметиться» на сервере. Поэтому воспользуемся обычным post-запросом с помощью jQuery:

$(document).ready(function() {
 var result = '';
setInterval(pinger, 300000); });
function pinger() {
$.post("/ajax/pinger.php",
function(result) { });
}

После создания HTML-документа у нас запустится код, заключенный в $(document).ready(). А это значит, что каждые 5 минут (5 x 60 x 1000 = 300000 миллисекунд) будет выполняться функция pinger(), в которой и размещается наш post-запрос. Давайте немного разберем, что и как он делает.

.post( url, [data], [callback], [type] )

Первый (обязательный) параметр — это URL страницы, к которой мы обращаемся. Ее код я дам чуть позже. Второй — данные, передаваемые скрипту на сервер. Третий — функция, которая выполнится после выполнения запроса (нам оно сейчас не надо). И, наконец, четвертый — тип передаваемых данных (XML, JSON, HTML etc). Функцию во втором параметре я оставил на всякий случай — скорее всего мне на боевом проекте понадобится не только «пинговать».

Пингер: серверная сторона

Ну а здесь совсем просто. Запускаем сессию, выводим что-нибудь, чтобы не отдавать пустой документ:

session_start();
echo 'ok';
session_write_close();

Не пугайтесь, уважаемый читатель. Последней инструкцией мы ни в коем случае не убиваем сессию. Мы всего лишь навсего открываем к ней доступ для других скриптов. Вся наша сессия, все данные в ней успешно остаются храниться в ней же. А вот «свежесть» сессии успешно обновляется, что нам, собственно, и нужно.

Результат работы пингера

Результатом работы этого пингера стало отсутствие жалоб от пользователей на «вылетание» админки сайта. А это куча сэкономленных нервов и времени на разъяснения, почему такое происходит. Работает — и работает, пользователь даже не замечает этого. Чего, собственно, я и добивался.

  • ничего нового конечно, давно пользуюсь таким же пингером. но для новичков самое-то :)

  • А смысл какой?
    Где это применимо?
    Разве не можно поставить время сессии?
    Как с вопросом безопасности?

  • Вообще конечно, как вариант использовать это можно, но абсолютно безграмотно увеличивать нагрузку на сайт лишними запросами. А если пользователь реально уехал из города, забыв выйти с сайта?
    Но, если уже вопрос стоит таким образом, что нужно во время работы с редактором, при написанни текста сохранять сессию, то не проще ли сделать с тем же Jquery сохранение данных из формы в виде черновика? И сессия не потеряна и если свет отключат, текст сохранится. Получится аналог написания статьи в WP.

  • Суть в том, что все сессии имеют параметр – начало. Когда запускается скрипт – php читает настройку времени жизни (и вероятности запуска сборщика мусора) и запускает сборщик мусора. Если сборщик мусора наткнулся на сессию, которая прожила больше, чем указано в настройках – она удаляется. Удаляется файл с сессией, а кука у юзера, естественно, остаётся. Соответственно, если запустится любой скипт с настройкой времени сессии в 30 минут и при этом он будет искать сессии в той же папке, где расмещает их другой скрипт с большим временем – он удалит ВСЕ сессии, даже те, которые должны прожить больше. Именно для этого надо сменить папку.

    Вот что написано в официально мануале по сессиям:
    «If different scripts have different values of session.gc_maxlifetime but share the same place for storing the session data then the script with the minimum value will be cleaning the data. In this case, use this directive together with session.save_path.»

  • “А если пользователь реально уехал из города, забыв выйти с сайта?” Значит или пользователю ЭТО надо было реально сделать и сохранить сайт открытым. Или я (да и сервер)не умрет от “нагрузки”.

    Такая систему удобна. когда у Вас реализовано Web приложение. Рассмотрим простой вариант: визуальный редактор документов – сидите, печатаете минут 40 свой документ и сохраняете его, а Вам вдруг бац, и пересылает на форму логина с потерей документа. Но это мелочи.

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

  • Абсолютно согласен. Более того сейчас внедряем на одном инет магазе сессии, пока что на день. Хранится будут в мускуле.

    В иностранных магазах хранятся месяц (в тех магазах, на которые имхо стоит ровняться), без регистрации.

  • А про куки забыли что-ли все? Для запоминания такого рода, они отлично подходят.

  • Да уж автор изгаляется как может причем без логики.

    Сервер: установка печенек при авторизации
    Пользователь: ждем – сессия уничтожается
    Пользователь: отправка статьи
    Сервер: если сессии нет, проверка печенек и проверка на существовании её в бд, если да запуск сессии.

    А ваш пример лишь школьникам в лапы.

    • Есть минус, печеньки стырили, зашли с ними на сайт и рулим от лица пользователей.

      У сессии сам по себе механизм защищен. Если вы хотите экзотику – то можете изменить поведение сессии http://php.net/manual/en/function.session-set-save-handler.php
      Следует понимать, что идентификатор сессии хранится в куках и поэтому может быть стырен. В куках следует хранить только remember_hash – каким-то образом сгенерированная и зашифрованная строка, по которой однозначно можно опознать пользователя. что-нибудь типа
      < ?php sha1($user.$pass.$sault); ?>
      Для MySQL соответственно:
      SHA1(CONCAT(`user`,`pass`,”sault”));

      Намного быстрее будет, если ремембер-хеш будет храниться в таблице. Можете даже его переписывать.
      Вот основные моменты:
      Юзер залогинился с галкой remember. – Записали хеш в куку и в таблицу, данные в сессию.
      Юзер залогинился без remember. – Развернули сессию.
      Юзер разлогинился – стерли куку и хеш(при наличии). Очистили сессию.
      Юзер зашел на сайт сессия открыта. – Продолжаем работу.
      Юзер зашел на сайт, в сессии нет данных. будем искать куку с хешем.
      Юзер зашел на сайт, нет сессии, есть кука с хешем. – Проверяем хеш, все ОК. Разворачиваем сессию.
      Юзер зашел на сайт, нет сессии, нет куки с хешем. – Пожалуйста, авторизируйтесь.

  • Пингер – это здорово. Если его чуть-чуть допилить, кроме ‘Ok’ можно было бы показывать пользователю что-нить действительно полезное, например, новости, последние сообщения с форума или еще чего. Тогда убиваем двух зайцев сразу – и пользователь будет заинтересован в том, чтобы остаться на сайте подолльше, и сессия не умрет по таймауту.

  • Механизм показан, а как пользоваться – уже дело каждого :)

  • Интересная статья. Каждый раз про сессии что-то новое узнаёшь :) Много раз сталкивался на практике с проблемами сессий, но часто хотелось оставить как есть, т.к. решения отнимают много времени.

    • Сессии это очень довольно важная часть для пользователя, особенно для интернет магазина. Да и для форумов и порталов это очень важно.

  • А не лучше ли сделать свой обработчик сессий (session_set_save_handler) и контролировать работу в т.ч сборщика мусора как угодно.
    Кстати через ini_set(…) можно увеличить и время куки, если конечно хостер не запретил.

  • Я всегда при авторизации устанавливаю время жизни сессии, и если пользователь не активен это время и если не была установлена галка помнить, то пользователь автоматом делает логаут. Плюс при авторизации в куку записывается хеш (в совокупе с идентификатором сессии, идентификатором пользователя и еще дополнительным мусором), который сверяется с сессией и еще дополнительная проверка в базу при успешной авторизации записывается хеш текущей сессии, который также поверяется на валидность для текущей авторизации. Именно поэтой причине, авторизация возможно одновременно только на одном компе, если авторизация будет на двух компах, то на том компе, на котором авторизация происходила раньше, произойдет логаут, даже если будет выбрана галка помнить меня!!!

You can follow any responses to this entry through the RSS 2.0 feed.