Дальнейшее изложение подразумевает, что интегрированная среда разработки Delphi уже запущена у вас на компьютере, и WebModule, изображенный на рисунке 1.1 уже создан. Кроме того, подразумевается, что web-сервер IIS запущен у вас на адресе 127.0.0.1.

Рисунок 1.3

Рисунок 1.4
procedure TwbmdlTest.wbmdlTestWebActionItem1Action (Sender: TObject; Request:TWebRequest; Response:TWebResponse; var Handled: Boolean); begin Response.Content:='test text'; end;Скомпилируйте приложение и запустите его, набрав в строке адреса броузера: http://127.0.0.1/Scripts/test.exe/first. Ваше web-приложение заработало, выдав на экран броузера строчку "test text". Это, конечно, не достижение. Такого же результата вы могли добиться, поместив в корневой каталог сервера простой текстовый файл, содержащий этот текст, и вызвав его по ссылке на имя файла. Смысл всей проделанной работы — в понимании того, как взаимодействуют свойства объекта TWebModule.
procedure TwbmdlTest.wbmdlTestWebActionItem2Action (Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin Response.Content:='Местное время: '+ TimeToStr(Time); end;Скомпилированный проект теперь имеет два действия и два значения PathInfo, вызывая с которыми web-приложение, мы будет наблюдать в броузере различную реакцию сервера. Адрес http://127.0.0.1/Scripts/test.exe/test по прежнему выводит на экран текст "test text", а вот ссылка на адрес http//127.0.0.1/Scripts/test.exe/dinamic выводит на экран броузера значение текущего системного времени, которое динамически изменяется. Это можно проверить, щелкая на кнопке «Обновить» броузера. Проверьте так же, что набор адреса скрипта без PathInfo производит то действие, свойство Default которого было установлено в true.
Почему для демонстрации возможностей динамической генерации в качестве web-приложения я выбрал именно программу web-чата? Первая причина в том, что трудно подобрать какой-то другой тип интернет-сервиса, где страницы генерировались бы более динамическим образом. Именно многопользовальский web-чат практически невозможно создать, не обрабатывая информацию при помощи скрипта на стороне сервера. Вторая причина состоит в том, что именно чат внутри одного приложения позволяет продемонстрировать практически все возможности, которые предоставляет программисту Delphi при создании web-приложений. Третьей же причиной явилось то, что именно с web-чатов начиналось множество современных интернет-порталов. Поэтому, если будет желание и возможность, никто не мешает нам пройти «естественным» путем: построить web-чат, а затем вырастить его в интернет-портал с новостной страницей, рассылкой, форумами, страницами пользователей и возможностями поиска в базах данных.
Создать web-чат без фреймов можно. Но делать это не имеет смысла, поскольку пользователю придется открывать два окна броузера, в одном осуществлять ввод, а во втором читать общий чат. Поэтому фреймовая структура страницы — идеальная техника для создания web-чата. Для работы чата в общем контейнере должно быть как минимум два фрейма, один фрейм чата, другой фрейм — ввода текста. Если мы имеем дело с html-документом, то задача построения страницы с двумя фреймами реализуется простым кодом:
<HTML> <HEAD> <!-- frames --> <FRAMESET rows="*,55"> <FRAME name="Top" src="top.html"> <FRAME name="Bottom" src="bottom.html"> </FRAMESET> </HEAD> </HTML>
Таким образом, для реализации простейшей двухфреймовой структуры нам нужно иметь три html-документа: контейнер, содержащий код, приведенный выше, и два фрейма, верхний и нижний, top.html и bottom.html.
Как реализовать этот код, используя технологию Delphi?
Когда мы создавали наше первое простейшее приложение, для генерации содержимого ответа web-сервера мы использовали прямое присваивание значения свойству Content объекта TWebResponce. В случае создания более сложных приложений, содержимое ответного сообщения генерируется специальными компонентами, производителями контента. На странице Internet палитры компонентов Delphi таких компонент, производителей контента, пять:
Наш простой чат, по крайней мере, в первом его варианте, не будет работать с базами данных. Данные, подлежащие сохранению между обработками клиентских запросов, мы будем сохранять в файле. Поэтому в качестве производителей контента мы выберем TPageProducer.

Рисунок 1.5
<HTML> <HEAD> <!-- frames --> <FRAMESET rows="*,55"> <FRAME name="Chat" src="../chat.exe/Chat"> <FRAME name="Input" src="../chat.exe/Input"> </FRAMESET> </HEAD> </HTML>

Рисунок 1.6
Это конечно же еще не чат. Это только фреймовая структура, которая сгенерирована нашим web-приложением. Для того, чтобы эта фреймовая структура стала похожа на чат, ее нужно научить делать две основные вещи: принимать текстовый ввод от клиента из Input frame, встраивать этот текст в общую страницу чата, и периодически обновлять информацию в фрейме Chat frame, для того, чтобы увидет свой собственный введенный текст и сообщения, отправленные в чат другими пользователями.
Для CGI-приложения передача информации от одного действия к другому составляет определенную сложность. Дело в том, что когда клиент отправляет текстовую строку на сервер, сервер вызывает web-приложение (в нашем случае это chat.exe) передает ему обработку запроса. Web-приложение принимает запрос, обрабатывает его, вызывая для обработки действие, указанное в PathInfo (в нашем случае это Input), после чего завершает свою работу, уничтожая все созданные им во время работы динамические объекты. Отсылка информации с сервера в Chat frame на компьютере клиента производится ДРУГИМ экземпляром web-приложения, которое ничего не знает о присланной текстовой строке. Наиболее простым, постоянно существующим объектом, доступным для всех экземпляров web-приложения является файл (мы не рассматриваем в данный момент работу web-приложения в режиме, когда информация хранится в базе данных, это будет сделано позже). Таким образом, словесное описание работы чата должно выглядеть следующим образом:
Давайте модифицируем код нашей приложения так, чтобы оно начало воспринимать пользовательский ввод в нижнем фрейме, корректно дописывать его в файл, и отображать, периодически обновляя, этот файл в верхнем фрейме.
<html> <head> <METAHTTP-EQUIV="Refresh" CONTENT="5; URL=../chat.exe/Chat" > <body>
<html> <form name="form1" method="post" action="../chat.exe/Input"> <input type="text/text" name="textfield" size=80> <input type="submit" name="Submit" value="Submit"> </form> </html>
procedure TwbmdlChat.WebModuleCreate(Sender: TObject); begin //создаем объект ChatStrings сразу после созданием модуля ChatStrings := TStringList.Create; end; procedure TwbmdlChat.WebModuleDestroy(Sender: TObject); begin //уничтожаем объект ChatStrings непосредственно перед //уничтожением модуля ChatStrings.Free; end;Не забудьте о том, что переменная ChatStrings должна быть объявлена в соответствующем разделе кода:
var wbmdlChat: TwbmdlChat; ChatStrins: TStrings;
procedure TwbmdlChat.wbmdlChatInputAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
st: String;
begin
//загружаем содержимое файла chat_log.html в коллекцию
//строк ChatStrings
ChatStrings.LoadFromFile('chat_log.html');
//формируем строку пользовательского ввода, полученную от
//нижнего фрейма. Функция HTTPDecode преобразует полученную
//строку в читаемый вид. Если вы хотите посмотреть, каков
//нечитаемый вид, то попробуйте удалить эту функцию,
//оставив вместо нее передаваемое ей значение, то, что
//находится в круглых скобках. Тэги <b> и </b> делают
//выводимый на страницу текст полужирным, это облегчает его
//восприятие.
st:='<b>'+ HTTPDecode(Request.ContentFields.Values['textfield'])
+'</b>';
//Вставляем сформированную строку в коллекцию строк на
//четвертую от начала позицию. Индекс имеет значение 3
//потому что отсчет начинается с нуля.
ChatStrings.Insert(3, st+'<BR>');
//записываем результат обратно в файл
ChatStrings.SaveToFile('chat_log.html');
end;
procedure TwbmdlChat.WebModuleBeforeDispatch
(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
begin
//загружаем содержимое файла chat_log.html в свойство
//HTMLDoc производителя контента верхнего фрейма
pgprdcrChat.HTMLDoc.LoadFromFile('chat_log.html');
//и закрываем основные тэги html
pgprdcrChat.HTMLDoc.Append('</body>');
pgprdcrChat.HTMLDoc.Append('</html>');
end;Скомпилировав это приложение, вы обнаружите, что чат издал свой первый младенческий крик. Он автоматически обновляет верхний фрейм с периодом 5 секунд. Вводимый в нижем фрейме текст появляется в верхнем фрейме. И, что самое интересное, в этом чате уже могут разговаривать пару человек. Разговаривать могут и больше, но тогда уже понять, кому принадлежит какое сообщение будет невозможно. Идентификация пользователя и устранение некоторых подводных камней, связанных с бесконтрольным разрастанием файла chat_log.html будет темой следующего раздела.
Ну а кто же он еще, наш чат младенческого возраста? Неуклюжий и шатающийся страусенок. Сейчас мы будем учить его ходить. Для начала, как я и обещал, ограничим разрастание файла chat_log.html количеством строк 20. Для этого в код обработчика события OnAction элемента Input добавим еще одну строку:
procedure TwbmdlChat.wbmdlChatInputAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var st: String; begin … ChatStrings.Insert(4, st1+'<BR>'); //пока общее количество строк в наборе больше 20, удаляем //строку в позиции 20 while ChatStrings.Count > 20 do ChatStrings.Delete(19); … end;
Теперь в файл chat_log.html всегда будет записываться 20 строк, соответственно, на экране будет отображаться 17 строк, потому что первый 3 строки, записываемые в файл не отображаются.
Следующая задача — идентифицировать пользовательский ввод. То есть добиться того, чтобы в начале каждой фразы в чате выводилось имя пользователя, который ее «сказал». Для решения этой задачи во-первых, создадим еще одну страницу, вход в чат с заданием имени пользователя. На следующем этапе на эту страницу можно будет добавить задание и других параметров настройки (количества выводимых на экран строк, времени перезагрузки верхнего фрейма, защиту имени паролем, выбор цвета, и т. д.) Во-вторых, нам нужно будет решить задачу передачи и сохранения имени пользователя, а так же идентификации сообщения.
<html> <head> <title>Flying hedgehoggy </head> <body bgcolor="#FFFFFF" text="#000000"> <p><b><font size="7" color="#FF6666">Flying hedgehoggy </font></b></p> <p><b><font size="7"> Cool chat </font></b></p> <form name="form1" method="post" action="../chat.exe/Base"> <p><font size="5"> Input your name and welcome: </font> <input type="text/html" name=name> <input type=submit name=Submit value="Click it!"> </form> </body> </html>
procedure TwbmdlChat.pgprdcrBaseHTMLTag (Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin //Транспарентный тэг в тексте контейнера Base перед //отправкой клиенту заменяется значением имени //пользователя, полученным от формы Welcome. Это значение //передается в качестве параметра запроса далее фрейму //Input ReplaceText:=Request.ContentFields.Values['name']; end;
<html> <form name=inp method=post action="../chat.exe/Input"> Hi, <#custom>! <input type="text" name="textfield" size=80> <input type="submit" name="Submit" value="Send"> <input type=hidden name=name value=<#custom>> </form> </body> </html>
procedure TwbmdlChat.pgprdcrInputHTMLTag (Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin if Request.Method <> 'POST' //при первой загрузке фрейма значение имени берется из поля //запроса контейнера Base then ReplaceText:=Request.QueryFields.Values['name'] //во всех последующих случаях фрейм Input возвращает себе //то значение имени пользователя, которое сам же послал else ReplaceText:=Request.ContentFields.Values['name']; end;
procedure TwbmdlChat.wbmdlChatInputAction
(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
st: String;
begin
ChatStrings.LoadFromFile('chat_log.html');
//защищаемся от "пустого ввода", строка будет добавлена
//только если она не пустая
if Request.ContentFields.Values['textfield']<>'' then
begin
//Добавляем в строку имя пользователя, полученное от фрейма
//Input
st:='<b>'+HTTPDecode(Request.ContentFields.Values['name'])+'<font size=+1
color="#FF6666">></font>' +
HTTPDecode(Request.ContentFields.Values['textfield'])+
'</b>';
ChatStrings.Insert(3, st+'<BR>');
end;
ChatStrings.SaveToFile('chat_log.html');
end;Все! Мы получили работающий чат. Наш страусенок пошел! Не будем обольщаться, на просторы Интернета это чудо рукоприкладства выпускать еще рано, но поболтать в нем с друзьями уже можно. Не забудьте предварительно перенастроить IIS так, чтобы ваш web-сервер стал виден во внешней сети, и изменить соответствующим образом URL чата. Пока еще за рамками обсуждения остались вопросы защиты имени паролем, задания цвета, частоты обновления, количества строк, возможность организации приватов, возможность добавлять обращения к собеседнику, щелкнув мышью на его имени, и множество других возможностей, которыми должен обладать современный web-чат.
Кроме того, этот чат пока не располагает инструментами администрирования и совершенно беззащитен от посетителей — хулиганов. Я рассчитываю, что многие из этих вопросов мы сможем обсудить в следующей статье.
Отсюда вы можете загрузить исходный код для изучения.
Архитектуры и типы web-приложений, базовые элементы.
