Статьи

Искусство изящной обработки тайм-аутов сессий с ExtJS

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

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

Если мы определим Session Duration = t1 + t2, мы рассмотрим логическую последовательность следующим образом:

  • Запустите таймер тайм-аута сеанса
  • Когда таймер достигает t1, мы предупреждаем пользователя и спрашиваем его, хочет ли она продлить сеанс
  • Если пользователь не продлевает свой сеанс и достигается t2, мы прекращаем сеанс
  • Если пользователь продлевает свой сеанс, мы сбрасываем таймер сеанса и возвращаемся к шагу 2

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

Давайте теперь посмотрим, что нужно для реализации этого подхода.

Запуск таймера тайм-аута сеанса после выполнения запроса AJAX

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

 

Ext.Ajax.on('requestcomplete', function (conn, response, options) {

if (options.url !== App.SESSION_KILL_URL) {
// Reset the client-side session timeout timers.
// Note that you must not reset if the request was to kill the server-side session.
App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
App.killSessionTask.cancel();
} else {
// Notify user her session timed out.
Ext.Msg.alert(
'Session Expired',
'Your session expired. Please login to start a new session.',
function (btn, text) {

// TODO: Here you need to make sure you kill the server-side session state.

if (btn == App.BTN_OK) {

// TODO: Show logon form here.
}
}
);
}
});

Этот обработчик события requestcomplete состоит из двух ветвей. Первая ветвь будет запущена после того, как мы запросим любой ресурс, кроме страницы, которая завершит сеанс на стороне сервера. Внутри этой ветви мы используем отложенную задачу — App.sessionAboutToTimeoutPromptTask — для отслеживания продолжительности сеанса и отложенную задачу — App.killSessionTask, которая отвечает за запуск запроса на завершение сеанса на стороне сервера. Я объясню, как эти отложенные задачи работают через минуту.

Вторая ветвь обработчика событий будет запущена после того, как мы запросим страницу, которая позаботится о завершении сеанса на стороне сервера. Что нам нужно сделать в этом случае, это уведомить пользователя о том, что сеанс был прерван:

Ext.Msg.alert('Session Expired','Your session expired. Please login to start a new session.',function (btn, text) {
// TODO: Here you need to make sure you kill the server-side session state.
if (btn == App.BTN_OK) {
// TODO: Show logon form here.
}
});

Я использую Ext.Msg.alert для этого примера, но вы можете изменить эту область в соответствии с потребностями вашего приложения.

Давайте теперь посмотрим на отложенные задачи.

Уведомление пользователя о том, что время ее сеанса истекло

Чтобы сообщить пользователю, что время его сеанса истекло, мы будем использовать отложенную задачу, например, так:

App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {

Ext.Msg.confirm(
'Your Session is About to Expire',
String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
function (btn, text) {

if (btn == App.BTN_YES) {
// Simulate resetting the server-side session timeout timer
// by sending an AJAX request.
App.simulateAjaxRequest();
} else {
// Send request to kill server-side session.
App.simulateAjaxRequestToKillServerSession();
}
}
);

App.killSessionTask.delay(App.toMilliseconds(
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
});

В этой задаче мы используем Ext.Msg.confirm, чтобы уведомить пользователя об истечении времени ожидания и попросить его нажать кнопку Да, чтобы продлить сеанс. После нажатия кнопки «Да» наш код выполнит запрос AJAX, который перезапустит сеанс на стороне сервера. Нажатие кнопки Нет выполнит запрос AJAX, который немедленно прекратит сеанс на стороне сервера.

Обратите внимание, что пока мы ожидаем ответа пользователя, мы также запускаем отложенную задачу App.killSessionTask, которая завершит сеанс на стороне сервера после льготного периода, если пользователь не ответил на наше приглашение.

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

Последним элементом нашего механизма тайм-аутов является App.killSessionTask, отложенная задача, которая вызовет запрос на завершение сеанса на стороне сервера после льготного периода:

App.killSessionTask = new Ext.util.DelayedTask(function () {
App.simulateAjaxRequestToKillServerSession();
});

Теперь, когда у нас есть основные части, давайте посмотрим на полное решение:

Ext.onReady(function () {

Ext.ns('App');

App.BTN_OK = 'ok';
App.BTN_YES = 'yes';
// 1 min. before notifying the user her session will expire. Change this to a reasonable interval.
App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN = .25;
// 1 min. to kill the session after the user is notified.
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN = 1;
// The page that kills the server-side session variables.
App.SESSION_KILL_URL = 'kill-session.html';

// Helper that converts minutes to milliseconds.
App.toMilliseconds = function (minutes) {
return minutes * 60 * 1000;
}

// Helper that simulates AJAX request.
App.simulateAjaxRequest = function () {

Ext.Ajax.request({
url: 'foo.html',
success: Ext.emptyFn,
failure: Ext.emptyFn
});
}

// Helper that simulates request to kill server-side session variables.
App.simulateAjaxRequestToKillServerSession = function () {

Ext.Ajax.request({
url: App.SESSION_KILL_URL,
success: Ext.emptyFn,
failure: Ext.emptyFn
});
}

// Notifies user that her session is about to time out.
App.sessionAboutToTimeoutPromptTask = new Ext.util.DelayedTask(function () {

console.log('sessionAboutToTimeoutPromptTask');

Ext.Msg.confirm(
'Your Session is About to Expire',
String.format('Your session will expire in {0} minute(s). Would you like to continue your session?',
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN),
function (btn, text) {

if (btn == App.BTN_YES) {
// Simulate resetting the server-side session timeout timer
// by sending an AJAX request.
App.simulateAjaxRequest();
} else {
// Send request to kill server-side session.
App.simulateAjaxRequestToKillServerSession();
}
}
);

App.killSessionTask.delay(App.toMilliseconds(
App.GRACE_PERIOD_BEFORE_EXPIRING_SESSION_IN_MIN));
});

// Schedules a request to kill server-side session.
App.killSessionTask = new Ext.util.DelayedTask(function () {
console.log('killSessionTask');
App.simulateAjaxRequestToKillServerSession();
});

// Starts the session timeout workflow after an AJAX request completes.
Ext.Ajax.on('requestcomplete', function (conn, response, options) {

if (options.url !== App.SESSION_KILL_URL) {
// Reset the client-side session timeout timers.
// Note that you must not reset if the request was to kill the server-side session.
App.sessionAboutToTimeoutPromptTask.delay(App.toMilliseconds(App.SESSION_ABOUT_TO_TIMEOUT_PROMT_INTERVAL_IN_MIN));
App.killSessionTask.cancel();
} else {
// Notify user her session timed out.
Ext.Msg.alert(
'Session Expired',
'Your session expired. Please login to start a new session.',
function (btn, text) {

if (btn == App.BTN_OK) {

// TODO: Show logon form here.
}
}
);
}
});

// The rest of your app's code would go here. I will just simulate
// an AJAX request so the session timeout workflow gets started.
App.simulateAjaxRequest();
});

Что вы думаете? Хотите попробовать?

С http://miamicoder.com/2011/the-art-of-gracefully-handling-session-timeouts-with-extjs