IntraWeb cессии.

 

- For the .NET version you will have to wait some more :)
Патрик Ионеску. Atozed Software[1]

Abstract. Article deals with IntraWeb session management. It reads about business objects supporting of current session, IntraWeb components properties and methods related to session. Session access via TThreadList is discussed. Contains a number of  examples.

 

         Среда WEB не поддерживает состояние (stateless). Это означает, что для сервера все равно, кто из клиентов запрашивает информацию. Как только запрос будет обслужен, вся связанная с ним информация теряется. Если бы разработчики HTTP заложили в протокол поддержку состояния на клиентском конце, ресурсы сети были бы быстро исчерпаны. Однако сохранение состояния клиента необходимо и оно реализовано в концепции сессий.

            Объект Session хранит всю информацию о пользователе. В рамках сессий могут быть порождены любые бизнес-структуры, объем и функциональность которых ограничиваются, разве что ресурсами сервера. Однако, сама IntraWeb-сессия достаточно инертна, поскольку сервер не ведет себя активно в рамках HTML. То есть нельзя, например, с сервера послать команду закрыть WEB-браузеры для определенных сессий и завершить эти сессии. Сессии существуют определенное время, и если в течение этого времени на клиентском компьютере не обнаруживается никакой активности, сессия уничтожается, освобождая ресурсы (см. главу “Контроллер сервера IntraWeb”).

Рассмотрим процесс обработки запросов сервером IntraWeb. Каждый запрос обрабатывается сервером в рамках своей сессии, а со стороны клиента это выглядит так, как если бы каждое Stand alone-приложение, запущенное на его машине, порождало свою сессию на сервере. Таким образом, понятие приложения и сессии очень тесно взаимосвязаны и эта взаимосвязь особенно ясно просматривается при разработке в Delphi. Программист отлаживает и клиентскую и серверную часть одновременно в одном проекте, просто переключая закладки с формами или Units. В режиме отладки (Stand alone) одно приложение, запущенное на машине клиента, порождает одну сессию на сервере. Как сохраняются уникальные данные сессии? При порождении новой сессии, сервер генерирует для нее новый 120-битный ID. Этот идентификатор сессии, в зависимости от настроек контроллера сервера, может храниться как cookie на локальной машине и передаваться для опознания клиента на сервер, либо, если использование cookie нежелательно, передаваться на сервер как часть URL. Используя этот идентификатор, сервер однозначно определяет клиента.

Запуск новой пользовательской сессии осуществляется сервером в обработчике TIWServerController.IWServerControllerBaseNewSession. При этом сначала порождается экземпляр приложения WEBApplication, а затем его свойству Data присваивается текущая пользовательская сессия.

 

procedure TIWServerController.IWServerControllerBaseNewSession(

  ASession: TIWApplication; var VMainForm: TIWAppForm);

begin

  ASession.Data := TUserSession.Create(ASession);

end;

 

Здесь необходимо отметить, что TUserSession по умолчанию является просто заготовкой и не хранит никаких полей или методов клиентского бизнес-объекта. Поэтому если в проекте нет необходимости управлять сессиями, класс TUserSession можно вообще исключить.

Прежде, чем перейти к изучению того, как IntraWeb-сервер управляет сессиями, рассмотрим свойства класса приложения TIWApplication,относящиеся к сессии, более подробно.

 

 

1. Свойства и методы объекта TIWApplication.

 

 

 

 

unit PCPSession;

interface

uses Classes;

type

  TUDPSession = class(TComponent)

public

  Name    : string;

  Address1: string;

  Address2: string;

  City    : string;

  State   : string;

  Zip       string;

//

  PlanLD  : boolean;

  Phone   : string;

//

end;

 

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

 

procedure OnNewSession(ASession: TIWApplication);

begin

  ASession.Data := TUDSession.Create(ASession);

end;

 

Доступ к полям структуры осуществляется путем приведения к типу TUDSession:

 

with TUDSession(WebApplication.Data) do

 begin

  Name := Trim(editName.Text);

  Address1 := Trim(editAddress1.Text);

  Address2 := Trim(editAddress2.Text);

  City := Trim(editCity.Text);

  State := Trim(editState.Text);

  Zip := Trim(editZip.Text);

end;

 

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

 

 

 

Создадим новый IntraWeb-проект и на главную форму поместим IWButton и IWEdit. Запустим приложение на выполнение:

 


 


Теперь разработаем HTML-форму, которую попробуем использовать в качестве темплета. Файл с именем mainForm.html поместим в поддиректорию /templates директории проекта. Код формы представлен ниже:

 

<HEAD><TITLE></TITLE>

</HEAD>

<BODY vLink=#800080 link=#0000ff>

<FORM>

<INPUT TYPE="checkbox" NAME="checkbox1" VALUE="checkbox_value1">

Check for something

</FORM>

{%IWEdit1%}{%IWButton1%}

</BODY>

 

Очевидно, что кроме компонент Delphi-проекта в форме присутствует HTML-объявление компонента checkbox, свойства которого в Delphi-программе недоступны. Разместим на форме компонент IWTemplateProcessorHTML1 с палитры IW Control и укажем форме на его использование: TemplateProcessor:= IWTemplateProcessorHTML1.

 

Вот как теперь выглядит форма приложения:

 

 

Как получить на сервере значение checkbox? Добавим в обработчик нажатия кнопки следующий код:

with WEBApplication do  { TWebRequest }

   begin

     IWEdit1.Text:=Request.Content;

   end;

 

Теперь при нажатии кнопки мы видим всю строку, отправленную серверу со всеми компонентами формы и их свойствами:

 

IW_Action=IWBUTTON1&IW_ActionParam=&checkbox1=checkbox_value1

 

Разделить строку на Параметр=Значение можно, используя методы класса TWebRequest.

 

·       Response. Хранит сформированный ответ сервера (см. Request).

·       Terminate. Метод, позволяющий завершить приложение (сессию).

 

procedure Terminate(const AMsg: string);

 

               Параметр AMsg содержит строку, которая будет отображена перед самым завершением приложения в текущем окне. Замечательно то, что если эта строка содержит HTML-страницу, то она будет показана в окне браузера. Пример:

 

            {в строку s заносится полноценный HTML-код}

 

            s:='<STYLE>BODY {margin-left: 0.5cm}'+

                        ' H1 {margin-left: -0.5cm; font-size: 12pt; background-color: teal; '+

' border-top-width: 0.1pc; border-style: solid; text-align: center; color: white}'+

           '</STYLE>'+

'<BODY background="\files\bg2.gif"><IMG SRC="/files/SignedIn.jpg" BORDER=0 ALIGN="LEFT">'+

'<H1>Вы зарегистрированы на FTP сайта "АРМ Бухучет"</H1><BR>'+

'<div align=center>Завершите работу, а затем загрузите эту страницу (Бухучет FTP)<BR>в Ваш браузер еще раз.</div></BODY>';

 

            {теперь завершить приложение с отображением страницы}

            WEBApplication.Terminate(s);

 

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

 

·       TerminateMessage. Как было сказано, после выполнения процедуры Terminate сессия завершается, но серверу может быть передана строка AMsg (см. выше). Эта строка хранится в переменной TerminateMessage и ее содержимое может быть проанализировано, например, при обработке события завершения сессии:

 

procedure TIWServerController.IWServerControllerBaseCloseSession(

  ASession: TIWApplication);

begin

  ss:=ASession.TerminateMessage;

end;

 

·       TerminateAndRedirect. Метод завершает приложение (сессию) и загружает в окно браузера страницу, указанную в параметре AURL

 

procedure TerminateAndRedirect(const AURL: string);

 

 

 

 

2. Управление сессией.

 

После того, как сессия порождена, начинается управление сессией. Ну, прежде всего, сессию можно удалить, вызвав метод TIWApplication.Terminate. Это действие пользователь должен произвести явно, например, нажав какую-то кнопку, реализующую этот метод. Другой способ удалить сессию и освободить ресурсы – дождаться истечения времени, определенном в свойстве контроллера сервера SessionTimeout. Как уже упоминалось, это свойство определяет время в минутах, в течение которого клиентское приложение может простаивать, пока сервер не завершит его.

Управлять временем жизни сессии очень важно, поскольку она отбирает часть ресурсов компьютера (если в объекте Data не хранится никаких бизнес-объектов, сессия занимает до 1 Кб памяти). Если, например, время жизни сессии равно 10 минутам (как устанавливается в системе по умолчанию) , то все пользователи, пришедшие на сайт будут удерживать ресурсы компьютера в течение именно  этого времени, даже если они покинули сайт в первую же минуту, перейдя по ссылке или закрыв браузер. К концу этого 10-и минутного периода число запущенных сессий может возрасти на порядок, а ведь трафик 10 клиентов в минуту еще не считается напряженным. В результате сервер при такой посещаемости поддерживает приблизительно 90 ненужных сессий из ста.

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

 

3. Переменные и объекты в сессиях и глобальные переменные.

 

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

В модуле ServerController для ваших бизнес-объектов предусмотрена заготовка класса сессии:

 

TUserSession = class

  public

  end;

 

Добавление переменных в раздел public является рекомендуемым разработчиками IntraWeb. Например:

 

TUserSession = class

  Public

            UserID : string;

  end;

 

Доступ к указанной строковой переменной из любого модуля проекта осуществляется следующим образом:

            ASession.UserID:=’Иванов’;

 

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

Глобальная серверная константа, например, текст “О проекте” тоже может быть использована без поддержки потоков. Причиной такого использования может быть экономия ресурсов компьютера: переменная (текст большого объема) инициализируется один раз для всех сессий. Но доступ к этой переменной, при условии, что он производится не в потоке, должен осуществляться “только для чтения”. Например, в интерфейсной секции ServerController объявим глобальную переменную About:

 

var

  About      : string; 

 

implementation

{$R *.dfm}

 

uses

  IWInit….

 

Инициализация ее должна производится один раз при старте сервера в разделе ServerController initialization:

 

 

initialization

 About:='Made in Russia';

end.

 

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

 

procedure TformMain.IWButton1Click(Sender: TObject);

begin

  IWLabel1.Caption:=About;

end;

 

 

4.     События, связанные с объектом Session.

 

С объектом Session связаны следующие события контроллера сервера.

OnNewSession. Это событие вырабатывается при создании новой пользовательской сессии. Процедура обработки содержит 2 параметра

·       ASession хранит текущую сессию и бизнес объекты, сохраненные в  свойстве Data: ASession.Data := TUserSession.Create;

·       Var-параметр VMainForm хранит установленную по умолчанию в IWRun основную форму проекта. Можно установить для пользователя другую основную форму. Для этого необходимо создать ее методом Create и передать в этом параметре ссылку на экземпляр созданной формы.

 

OnCloseSession. Вырабатывется в момент закрытия сессии, которое вызвано истечением времени TimeOut или вызовом WebApplication.Terminate. В передаваемом параметре хранит закрываемую сессию. При закрытии сессии экземпляр бизнес-объекта, сохраненный в параметре Data, автоматически разрушается с освобождением ресурсов.

 

 

5.     Сессии и состояния.

 

 

Приложение stand-alone, как и большинство интерактивных WEB-приложений,  сталкивается с проблемой поддержки предыдущих состояний сессии. Проблема решается просто – по умолчанию предыдущие состояния не поддерживаются. По этой причине кнопка Back (или Forward) браузера в IntraWeb-приложениях оказывается блокированной. Понятно, почему это оправдано: если переход в новое состояние приложения произошел с завершением транзакции в базе данных или с удалением каких-либо экземпляров объектов, то обратного пути в принципе не существует.

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

Принципиальное ограничение на использование кнопки Back (или Forward) может быть снято за счет ухудшения пользовательского интерфейса: пользователь просто предупреждается о том, что вернувшись назад, он нарушает синхронизацию данных. Установите для котроллера сервера HistoryEnabled:=TRUE. В этом случае кнопка Back разблокируется и при ее нажатии выдается сообщение:

 

“You have attempted to post or refresh data from a page that depends on information that is no longer available to the server application”

 

Становится доступной возможность вернуться на предыдущую страницу. Это означает, что пользовательские данные, которые, возможно, не соответствуют состоянию сервера, отображаются на WEB-форме, но игнорируются на сервере. Можно даже отключить и это сообщение, тем более, что для технически неподготовленного клиента (оператора, бухгалтера) от таких сообщений мало проку. Достаточно установить для котроллера сервера ShowResyncWarning:=FALSE. Но в этом случае надо учитывать, что нарушение синхронизации данных формы и состояний баз данных или Delphi-форм может полностью вывести приложение из строя.

 

 

6.     Список пользовательских сессий.

 

Список пользовательских сессий является глобальным объектом, поэтому доступ к нему является предметом подробного и внимательного рассмотрения.

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

Список сессий хранится в глобальной переменной, объявленной в модуле IWApplication.pas и имеющей имя GSessions. Это наследник класса TThreadList, реализующего потоко-безопасные списки объектов.

 

 

6.1. Класс TThreadList.

 

Экземпляр класса TThreadList поддерживает потоко-безопасную работу с  инкапсулированным списком объектов TList. Доступ к этому списку осуществляется после его блокирования для доступа из других потоков, работы с элементами списка и разблокирования. Метод LockList блокирует список объектов и возвращает его как тип TList. Метод UnlockList разблокирует список.

 

6.2. Получение списка сессий.

 

Поместим на форму кнопку, при нажатии которой будет выполняться следующий код:

 

if Assigned(GSessions) then

    with TThreadList(GSessions).LockList do

    try

      for j:=0 to Count-1 do

        SStr:=TIWApplication(Items[j]).AppID

    finally

      TThreadList(GSessions).UnlockList;

    end;

 

Во фрагменте кода, показанном выше, список сессий GSessions приводтся к типу родителя TThreadList, блокируется и в цикле с его элементами выполняется работа: присвоение тестовой строке  SStr идентификатора сессии. Мы заранее знаем, что элементами списка являются сессии, поэтому явно приводим каждый элемент к типу TIWApplication. Блокирование списка обрамлено блоком Tryfinally для обязательного выполнения оператора UnlockList.

Запустив приложение на выполнение, мы обнаружим, что строке SStr ничего не присваивается, так как свойство Count=0. Дело в том, что рассматриваемый список предоставляет только те сессии, которые не подвергаются обработке сервером (render) в данный момент. Наша единственная сессия подвергается такой обработке (нажатие кнопки на форме), поэтому список GSessions ничего не содержит. Нажмите в окне сервера кнопку “Launch selected browser and run application” несколько раз для запуска нескольких сессий. Вот теперь список оказывается непустым и сессии (кроме одной, активной) могут быть обработаны в цикле программы.

 

 

6.3. Пример работы с сессией из списка.

 

Мы описали процесс доступа к сессии из списка сессий. Казалось бы, с сессией, полученной таким образом, можно работать, не испытывая никаких ограничений: прекращать её, переключать активные формы, изменять ReferringURL и так далее. Тем не менее, одно существенное ограничение остается: протокол HTTP. В рамках HTTP сервер остается "неактивным" по отношению к клиенту и не может послать нотификационное сообщение или каким-либо иным образом уведомить клиента о своих действиях.

Для иллюстрации сказанного создадим простой проект с формой, содержащей две кнопки: серверную, при нажатии на которую выполняется код

 

procedure TformMain.IWButton1Click(Sender: TObject);

var

  j           : integer;

begin

  if Assigned(GSessions) then

    with TThreadList(GSessions).LockList do

    try

      for j:=0 to Count-1 do

       begin

IWListBox1.Items.Add(TIWApplication(Items[j]).AppID);

TIWApplication(Items[j]).ShowMessage('Сервер будет остановлен. Завершите работу');

       end;

    finally

      TThreadList(GSessions).UnlockList;

    end;

end;

 

При нажатии этой кнопки всем сессиям, кроме "серверной", которая в  момент обработки нажатия находится в состоянии render, рассылается сообщение о прекращении работы.

При нажатии "клиентской" кнопки выполняется какой-либо код с запросом новых данных с сервера, например, изменение фона активной формы.

 

procedure TformMain.IWButton2Click(Sender: TObject);

begin

  BackGroundColor:=clInfoBk;

end;

 

Запустим сервер и загрузим форму в браузере на двух  компьютерах. Нажмем "серверную" кнопку Адм. на одной машине:

 

 

При этом IntraWeb-приложению, запущенному на другой машине и описываемому ID=749AD… будет послано сообщение о прекращении работы.

Однако синхронно с этим никаких сообщений на экране этого компьютера не появляется. Но стоит только запросить что-либо с сервера (нажать "клиентскую" кнопку), как вместе с запрошенными данными (перед ними) появится сообщение:

 

 

 

            Отсутствие нотификации от сервера к клиенту накладывает ограничения на RAD-разработку для WEB, поскольку Delphi-программист, как правило, ориентируется на синхронное наступление и обработку событий. К этому ограничению нужно просто привыкнуть и слегка изменить стратегию разработки программ. Вероятно, вы остановите свой выбор на той стратегии, которая подходит лично вам, так что мы не будем тратить здесь слишком много времени на обсуждение этой темы.

 

©Ренжиглов Николай (Nick Renzhiglov). http://mabanza.narod.ru  email:mabanza@yahoo.com

Все права защищены. Цитирование только с согласия автора.



[1] - А в .NET-версии будет кое-что получше. Из переписки на форуме news.atozedsoftware.com