ГЛАВА 10
Создание Интернет-ориентированных и клиент-серверных программ
В предыдущей главе был описан CGI "изнутри". Использование этого интерфейса позволяет строить небольшие программы, которые предназначены для обработки пользовательских запросов и формирования динамических Web-страниц.
Когда в рамках одного сайта нужно генерировать одну-две страницы, то создание интерактивной части такого ресурса займет немного времени, и это можно сделать классическими средствами. Однако если создается целая информационная система и в ведении программиста находятся уже не конкретные задачи по обработке получаемых сведений, а методология обработки данных, если при развитии ресурса число интерактивных элементов постоянно возрастает, то один человек уже не в силах самостоятельно охватить и контролировать все потоки информации.
В таких случаях прибегают к средствам моделирования. Созданные модели должны описывать не процессы, происходящие на низком уровне, вроде получения содержимого переменных окружения или отправки заголовков, а абстракции, представляющие собой инструмент управления информационными ресурсами.
В гл.5 был затронут язык UML, который обеспечивает всесторонний анализ данных и построение на базе этого аначиза программных комплексов. Насколько нам известно, основной производитель средств для работы с языком UML — фирма Rational Software, не позаботилась о создании приложения для работы с языком Object Pascal — ядра Delphi, и поэтому прямая генерация кода на основании описанных диаграмм невозможна. Однако, к счастью для разработчиков, подход, примененный в Delphi к созданию Интернет-систем, обеспечивает гибкое, быстрое и высокоэффективное строительство информационных систем любого масштаба.
Основы инструментария для создания Интернет-систем в Delphi
Для создания серверных программ в Delphi предусмотрен специальный мастер, действие которого тривиально: создается объект для разбора пользовательского запроса и запуска процедуры его обработки. Давайте создадим новый проект серверного модуля. Для этого выбираем следующую последовательность меню: File | New. Вам в этом случае предоставляется выбор, приведенный на рис. 10.1.
Рис. 10.1. Выбор типа создаваемого приложения
В появившемся окне New Items нужно выбрать объект Web Server Application.
Если у вас нет этого типа приложения, то это значит, что установлена версия Delphi Professional или Standard Edition. Для работы со средствами, предназначенными для написания сетевых приложений, нужно установить Client/Server Suit или Enterprise, в зависимости от номера версии пакета. В дальнейшем, если это не оговорено специально, речь будет идти об общих для различных версий Delphi возможностях. Под версией 3.0 будет также подразумеваться версия 2.0, а под 5.0 — версия 4.0.
После выбора типа приложения необходимо определить программное окружение, в котором будет работать Web-модуль. Кроме обычного (стандартизированного) варианта CGI существуют несколько вариантов реализации этого шлюза. В частности, некоторые производители серверов изменяют способы организации приема-передачи информации (посредством использования не переменных окружения, а файлов, например) от самого серверного модуля к серверному окружению, однако это почти не затрагивает функциональной составляющей программы. Меню выбора типа Web-приложения осуществляется по нажатии кнопки ОК в диалоговом окне, приведенном на рис. 10.1. В появившемся меню выберите пункт CGI Stand-alone Executable. После нажатия клавиши ОК будет создан проект, который желательно сразу сохранить. Можно размещать файлы проекта в любом другом месте жесткого диска, но при этом следует указать каталог cgi-bin Web-сервера в качестве выходного для создаваемых ЕХЕ-файлов (Output Directory). Все это делается для облегчения последующей отладки серверных модулей.
В сформированной рабочей среде, в отличие от обычных настольных проектов, нет формы в классическом понимании. Вид этой среды по умолчанию для Delphi версии 6.0 представлен на рис. 10.2 .
Вместо обычной формы здесь присутствует контейнер для невизуальных компонентов. Этот контейнер представляет собой объект класса TWebModule, со своими свойствами и событиями, которые можно редактировать посредством окна редактора свойств объекта Object Inspector. Данный Web-модуль является центральным звеном любого серверного приложения, поскольку именно он отвечает за получение HTTP-запроса и запуск его обработчика.
Аналогично формам и визуальным компонентам, в этот Web-модуль можно помещать невизуальные компоненты, например, для работы с базами данных.
Рис. 10.2. Вид рабочей области в Delphi 6.0
Рис. 10.3. Отображение Web-страницы
За генерацию Web-страниц отвечает компонент TWebProducer, значок которого находится на вкладке Internet Component Palette.
Реализуем с использованием этого компонента Web-страницу, которая представлена на рис. 10.3.
Для того чтобы эта страница была предоставлена пользователю, необходимо создать объект, который будет отвечать за.ее передачу. Работа с такими объектами описывается ниже.
Обработка запросов на получение Web-страниц
При создании серверных модулей программист предусматривает, как правило, динамическое создание Web-страницы на основании клиентского запроса. Этот запрос может включать какие-либо сведения, учитываемые или обрабатываемые серверным модулем, либо просто определяющие методику построения страницы из заранее заготовленных объектов. Вообще говоря, удобно создавать страницы на основании HTML-шаблонов, но об этом чуть позже.
Итак, при создании CGI-модуля с использованием визуальных средств Delphi программист должен, прежде всего, определить варианты поведения или, назовем иначе — действия. Действие —это набор операций, которые осуществляет модуль при получении некоторого запроса от клиента. Каждое действие вводится как элемент массива всех действий данного модуля и описывается следующими свойствами (табл. 10.1).
Таблица 10.1. Свойства действия
Свойство |
Описание |
Default |
Определяет, вызывается ли данное действие по умолчанию |
Enabled |
Определяет включение/отключение действия |
MethodType |
Определяет тип HTTP-запроса, который может быть обработан данным действием |
Name |
Содержит название действия |
Pathlnfo |
Содержит строку URL, по получении которой, активизируется это действие |
Producer |
Содержит имя объекта, отвечающего за генерацию и отправку страницы |
OnAction |
Название процедуры, вызываемой при активизации данного . действия |
Эти свойства определяют уникальные характеристики действия.
Важно
При получении запроса Web-модуль перебирает свойство Pathinfo у всех, предварительно определенных действий с целью нахождения совпадения с запрашиваемым адресом.
В случае если она совпала с полем Pathinfo одного из действий, то автоматически активизируется этот вариант поведения модуля. Активизация действия означает выполнение кода, записанного в его свойстве OnAction, а также исполнение своих прямых обязанностей объектом, указанным в свойстве Producer.
Если же запрошенная строка на совпадает ни с одним Pathinfo, то по умолчанию активизируется действие, в котором свойство Default установлено в значение true.
Для добавления действия в проект и описания его свойств необходимо выполнить следующую последовательность операций:
1. Активизировать окно Action Editor. Для этого нужно щелкнуть правой кнопкой мыши по полю Web-модуля, и из всплывающего меню выбрать строчку Action Editor. В Delphi 5.0 появится меню, приведенное на рис. 10.4. (В Delphi 3.0 вместо пиктограмм используются кнопки с надписями.) В появившемся меню нужно нажать кнопку Add,. вслед за чем в меню появится новое действие.
Рис. 10.4. Меню редактора действий
2. Описать свойства действия. Впишите в окне Object Inspector следующие свойства нового действия: Default: true; Enabled: true; MethodType: mtPost; name: hello; pathinfo: /hello.
3. Создать объект, который будет генерировать содержимое Web-страниц, и вписать его название в поле Producer. Таким объектом может являться как один из предназначенных для этих целей компонентов Delphi, так и написанные вручную объекты, являющиеся подмножеством класса TWebProducer.
Генератор страниц (компонент TPageProducer)
Поскольку основная цель использования вкладки "Internet" Component Palette Delphi заключается в облегчении и ускорении процесса создания серверных модулей, то наряду с автоматизацией процесса разбора пользовательских запросов, предусмотрена возможность отправки Web-страницы пользователю, причем программист должен лишь указать либо путь и название отправляемого файла, либо само его содержимое. Остальные детали отправки данных, например указание типа данных, HTTP-заголовков, и даже открытие файлов, чтение и передачу данных в поток вывода, выполняет компонент TPageProducer. Как и остальные невизуальные компоненты, о которых идет речь, в данной главе, он расположен на вкладке "Internet" Component Palette (рис. 10.5).
Свойство |
Описание |
Dispatcher |
Определяет объект, осуществляющий разбор пользовательского запроса |
HTMLDoc |
Содержит массив строк, являющихся телом HTML-документа, который будет отправлен клиенту. Задается как альтернатива предыдущему свойству |
HTMLFile |
Содержит путь и название HTML-файла, который обрабатывается и отсылается клиенту данным компонентом |
Рис. 10.5. Значок компонента TPageProducer
Этот компонент предназначен для передачи ответной страницы клиенту. Если задать имя HTML-файла, то объект данного класса передаст содержимое указанного файла клиенту. Можно также в режиме выполнения программы определять теги, из которых должна состоять Web-страница, и тогда вместо указанного файла они будут посланы браузеру. Основные свойства компонента TPageProducer приведены в табл. 10.2.
Таблица 10.2. Свойства компонента TPageProducer
Свойство |
Описание |
Name |
Содержит название объекта |
StripParamQuotes |
Указывает на необходимость отсечения двойных кавычек вокруг значений атрибутов тегов в используемых шаблонах. Это нужно для правильной интерпретации данных шаблонов при построении на их основании Web-страниц |
Tag |
Используется для хранения целого числа либо указателя |
Методы компонента TPageProducer приведены в табл. 10.3.
Таблица 10.3. Некоторые методы компонента TPageProducer
Метод |
Описание |
Content |
Возвращает результат работы объекта — экземпляра класса TPageProducer в программе |
Content FromStream |
В качестве параметра этой функции выступает поток, содержащий HTML-шаблон. Результатом является обработанный шаблон, т. е. HTML-документ |
Content FromString |
Аналогично предыдущему методу, но параметром является строка, содержащая шаблон |
Давайте вернемся к реализации нашего маленького проекта и добавим в него объект класса TPageProducer, поместив соответствующий компонент в Web-модуль. Установим свойство Name равным hello, а в HTMLFile пропишем путь к файлу hello.htm. Кроме того, вернувшись к списку действий, нужно определить название генератора ответа для действия hello, которым и будет являться объект hello.
Таким образом, в нашем проекте имеются все основания для правильной работы серверного модуля: есть действие, которому присвоен статус "по умолчанию"; для этого действия определен объект, выполняющий отправку результата работы — объект hello, который выдаст клиенту некоторый HTML-файл.
Теперь скомпилируем проект и посмотрим, как он работает. Если вы сохранили проект в подкаталоге под названием, указанным выше, то, запустив Web-сервер и введя адрес:
http://localhost/cgi-bin/hello
вы можете увидеть приведенную на рис. 10.3 страницу.
Разумеется, эта страница могла быть загружена привычным способом — путем ввода в адресную строку браузера непосредственного пути к ней, однако наш пример демонстрирует формирование ответа на основании статических данных. Другой способ определения данных, отправляемых клиенту, — это генерация ответных страниц с использованием шаблонов.
Использование шаблонов при формировании ответа
В абсолютном большинстве задач, для решения которых используется динамическое формирование Web-страниц, необходимо генерировать лишь некоторый фрагмент HTML-кода. Остальная часть страницы, например, заголовок, различного рода сопроводительная информация, текстовые фрагменты и т. д. остаются неизменными. Пример Web-страницы, в которой динамическим является лишь небольшой фрагмент, приведен на рис. 10.6.
Рис. 10.6. Пример двух страниц, в которых динамически формируемым является лишь активное содержимое
Для того чтобы при создании серверных модулей программист контролировал лишь активное содержимое и не заботился о полном наборе данных, составляющих HTML-файл, используются шаблоны.
Основой шаблона являются так называемые "прозрачные" теги. Эти теги представляют собой специальным образом помеченные строки, при нахождении которых в теле данных, передаваемых объектом класса TPageProducer, вызывается событие OnHTMLTag для их замены на содержательную, динамически формируемую информацию, которая и должна составлять активную часть Web-страницы.
От обычных тегов "прозрачные" отличаются тем, что между символом начала тега < и самой командой ставится знак #, без пробелов. Пример таких тегов приведен в листинге 10.1.
Листинг 10.1. Пример "прозрачного" тега
<TABLE>
<#TR> Как только эта строка появится в теле передаваемого файла,
активизируется событие OnHTMLTag.
<#TD> To же самое.
</TABLE>
При передаче этого примера в составе Web-страницы будет дважды вызвано событие OnHTMLTag для замены строк шаблона. Если обработчик этого события не указан, или он не вернул конечные значения, то в результирующем потоке "прозрачные" теги будут опущены и пользователю будет передано содержимое шаблона без них.
Замечание
Если один шаблон содержит несколько таких тегов, то событие ОnHTMLTag активизируется не один раз в пределах обработки одного шаблона.
Давайте создадим серверный модуль, который будет генерировать Web-страницу с указанием текущего времени часов сервера, по часовому поясу, в котором находятся Москва и Афины.
Для этого, прежде всего, нужно сделать шаблон. Один из возможных вариантов шаблона приведен в листинге 10.2.
Листинг 10.2. Шаблон Web-страницы, на основании которого \в дальнейшем будет создан серверный модуль
<HTML>
<Н2>Привет!</Н2>
<Р>Сейчас на сервере: <#time ID=1>
<Р>А в Афинах: <#time ID=2>
<Н2>Счастливо!</Н2>
</HTML>
В шаблоне использовано два "прозрачных" тега, причем они отличаются лишь значением атрибута ID.
Создадим новый проект серверного модуля в Delphi с названием interact и добавим в него новое действие someinfo. Адрес на его активизацию укажем аналогично названию. После этого на вкладке "Internet" Component Palette нужно выбрать компонент TPageProducer и поместить его в наш Web-модуль. После присвоения свойству NAME нового генератора страниц, значения siproducer, его можно присвоить значению атрибута Producer действия someinfo.
После того как завершен процесс согласования свойств различных объектов системы, можно приступить к описанию функциональных особенностей работы объекта siproducer.
Поскольку в своей работе он будет использовать готовый шаблон, то нужно указать путь к этому файлу. Создайте файл шаблона, текст которого приведен в листинге 10.2 в каталоге HTDOCS того каталога, где установлен Apache и укажите в значении свойства HTMLFile объекта путь к нему.
Если скомпилировать проект в том виде как есть и, запустив сервер и браузер, указать в адресной строке последнего путь http://Iocalhost/cgi-bin/interact/someinfo, то загрузится страница без необходимых интерактивных данных. Обеспечим их появление.
Для этого необходимо активизировать свойство оштмьтад объекта siproducer.
В результате в тексте модуля Unitl появится код, приведенный в листинге 10.3.
Листинг 10.3. Шаблон для ввода функции — обработчика "прозрачных" тегов
procedure TWebModulel.SIpro'ducerHTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var ReplaceText: String); begin
end;
Несколько параметров, предусмотренных в этой процедуре, предназначены как для идентификации данных, с которыми должен работать код процедуры, так и для возврата результата работы модуля. Рассмотрим их подробнее:
Таблица 10.4. Предопределенные типы тегов
Название тега |
Описание |
tgLink |
Соответствует появлению в шаблоне тега ссылки <А>. Результатом работы процедуры должна быть ссылка |
tglmage |
Это значение параметр Tag принимает в случае, когда "прозрачным" тегом является HTML-элемент IMG. Процедура в результате своей работы должна предоставить описание изображения |
tgTable |
To же самое для таблицы |
tglmageMap |
То же самое для клиентской карты |
tgObject |
То же самое для Active-объекта |
tgEmbed |
То же самое для внедренного документа |
tgCustom |
В случае когда в шаблоне встречается "прозрачный" тег, отличный от описанных выше, то параметр Tag принимает это значение |
Итак, в добавленной процедуре необходимо обеспечить замещение "прозрачных" тегов новым HTML-кодом. Для этого нужно в тело процедуры вписать код, приведенный в листинге 10.4.
Листинг 10.4. Тело процедуры
procedure TWebModulel.SiproducerHTMLTag(Sender: TObject; Tag: TTag;
const TagString: String; TagParams: TStrings; var RepiaceText: String); begin if (tagparams.Values['ID1]=('!')) then
RepiaceText:=Timetostr(Now)
else RepiaceText:=(Timetostr(Now-Encodetime(l,0,0,0)));
end;
Поскольку в шаблоне отсутствуют "прозрачные" теги, отличные от time, то проверку параметра Tagstring производить не нужно. В противном случае, оператор if изменится следующим образом:
if (TagString='time') and (tagparams.Values['ID1]=('1')) then ...
Во время работы серверного модуля процедура siproducerHTMLTag вызывается дважды для обработки двух заданных тегов.
В результате получится страница, приведенная на рис. 10.7.
Рис. 10.7. Полученная Web-страница
Комбинация различных "прозрачных" тегов и их параметров позволяет организовать достаточно развитые механизмы выбора алгоритма наполнения Web-страницы активными элементами.
Получение параметров клиентских запросов (Класс TCGIRequest)
Разумеется, использование шаблонов и соответствующей процедуры их обработки не исчерпывает всех возможностей CGI. При интенсивном обмене данными между клиентом и сервером необходимо иметь доступ ко всему множеству параметров запроса, исходящего от клиента. В отличие от обычного CGI, реализованного классическими средствами консольного приложения, алгоритмы, которые создаются программистом на базе объектов Delphi, не нуждаются в прямом доступе к переменным окружения и потокам ввода с последующим переводом содержащихся в них данных, к удобному для использования формату. При запуске модуля создается объект Request класса TCGIRequest. В этот объект, при его инициализации, автоматически помешается вся информация, характеризующая запрос. В своей работе программист может использовать объект Request, в полях которого собраны воедино все данные пользователя. Наиболее важные свойства данного объекта приведены в табл. 10.5.
Таблица 10.5. Некоторые свойства объекта Request
Свойство |
Описание |
Accept |
Тип MIME-данных, которые готов принять клиент в ответном потоке |
Authorization |
Содержит информацию об аутентификации |
CacheControl |
Содержит описание способа кэширования информации клиентом |
Connection |
Содержит тип соединения, которое поддерживает браузер клиента |
Content |
В случае если в запросе данные отправлялись методом Post, то это поле содержит тело запроса |
ContentLength |
Содержит число символов в запросе |
ContentType |
Содержит тип данных, отправленных в запросе |
Cookie |
Поля cookie-файлов |
Date |
Содержит дату и время отправки запроса |
Method |
Содержит метод запроса |
Pathlnfo |
Включает часть URL, содержащую адрес к модулю, обрабатывающему запрос |
Query |
В случае, когда запрос осуществлялся методом Get, это поле содержит строку запроса (часть URL после знака ?) |
RemoteAddr |
Содержит IP-адрес клиента |
UserAgent |
Определяет программное обеспечение клиента (модель браузера и операционной системы) |
ContentFields |
Tstrings-массив, содержащий поля Post-запроса |
CookieFields |
Tstrings-массив, содержащий поля cookie, переданные вместе с запросом |
QueryFields |
Tstrings-массив, содержащий пола GET-запроса |
Обратите внимание, что последние три свойства являются очень удобными для получения данных запросов. Если в классическом CGI-модуле приходится вводить функции для раскодировки содержимого полей форм, то здесь все данные уже доступны по имени элемента формы, в котором они были введены.
Рассмотрим пример. Пусть на Web-странице имеется форма для ввода смещения часового пояса клиента относительно Гринвича. Модернизируем предыдущий проект таким образом, чтобы клиент, кроме московского времени, мог узнать время в любом поясе Земли.
Прежде всего, необходимо заготовить начальную страницу, с которой пользователь отправит число, означающее интересующее его смещение времени.
Такая страница может иметь код, приведенный в листинге 10.5.
Листинг 10.5. Код страницы, в которой клиент указывает смещение времени
<HTML>
<Р>Укажите интересующий часовой пояс:
<FORM action="http://localhost/cgi-bin/interact/someinfo" method="POST">
<INPUT type="text" size=l maxlength=l name="dispersion">
<INPUT type="Submit" value="send">
</FORM>
</HTML>
Сохраните этот код в файле с названием someinfo.htm в том каталоге, где были сохранены предыдущие файлы примеров.
Кроме того, нужно изменить имеющийся шаблон, как показано в листинге 10.6.
Листинг 10.6. Измененный шаблон )
<HTML>
<Н2>Привет!</Н2>
<Р>Сейчас на сервере: <#time ID=1>
<Р>В Афинах: <ltime ID=2>
<Р>А в интересущем часовом поясе — <#interestedtime>
<Н2>Счастливо!</Н2>
</HTML>
Откройте предыдущий пример и дополните процедуру siproducerHTMLTag, как представлено в листинге 10.7.
Листинг 10.7. Дополненная процедура siproducerHTMLTag
procedure TWebModulel.SIProducerHTMLTag(Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String); begin if (TagString='time') and (tagparams.Values['ID']=('1')) then ReplaceText:=Timetostr(Now) else if (TagString='time')
then ReplaceText: = (Timetostr(Now-Encodetime(1, 0, 0,0))) else ReplaceText:=Timetostr(Now-Encodetime(2,0,0,0)+ Encodetime(strtoint(Request. ContentFields.Values['dispersion']),0,0,0))
end;
Сохраните HTML-файл,, заполните форму, и на экране получится результат, приведенный на рис. 10.8.
Рис. 10.8. Результат работы модифицированного модуля
Как увидеть, выражение Request. ContentFields. Values ['dispersion'] обеспечивает удобный доступ к содержимому поля dispersion HTML-формы.
Обратите внимание, что в отсутствии введенных данных при обращении к полю формы не возникает исключительной ситуации.
Формирование параметров серверного ответа (класс TCGIResponse)
Поскольку зачастую ответ клиенту необходимо строить не только на основании шаблонов, но и используя различные сервисы, предоставляемые CGI, бывает удобно переложить весь процесс формирования ответа на самостоятельно подготовленный программистом код. Доступ ко всем информационным параметрам сообщения от сервера к клиенту обеспечивают объекты класса TCGIResponse.
Программист может посредством этих объектов не только определять наполнение ответа, но и управлять его отправкой. Это позволяет делать программы поистине гибкими и функциональными. Как и в случае с объектами класса TCGiRequest, где, правда атрибуты определены только для чтения, поля рассматриваемого класса предоставляют доступ ко всем необходимым параметрам.
Кроме того, многие поля предоставляют доступ к служебной информации, которая носит описательный характер и генерируется внутренними методами объектов класса. Обычно, при работе серверного модуля объекты класса TCGiResponse создаются автоматически и называются Response. Основные свойства этого объекта приведены в табл. 10.6.
Таблица 10.6. Основные свойства объекта Response
Свойство |
Описание |
Allow , |
Определяет тип методов запросов, которые может обслуживать объекты данного класса |
Content |
Содержит ответное сообщение. Данное поле имеет строковый формат и, как правило, должно содержать тело HTML-документа, либо двоичные данные, если, например, клиенту отправляется динамически созданный графический файл |
ContentLength |
Возвращает число байт информации, отправляемой пользователю |
ContentType |
Тип MIME содержимого ответа |
Expires |
Содержит дату и время, когда информация, отосланная клиенту, должна считаться устаревшей |
Server |
Содержит марку Web-сервера |
StatusCode |
Содержит код, идентифицирующий статус выполнения клиентского запроса |
ContentStream |
Как и свойство Content, содержит тело передаваемого сообщения, однако это поле имеет формат потока, что оптимизирует многие операции, например чтение из графического файла, с последующей отправкой содержимого |
Cookies |
Содержит набор cookie-заголовков, которые должны быть отосланы клиенту вместе с ответом |
CustomHeaders |
Содержит объект TStrings, который позволяет программисту устанавливать собственные HTTP-заголовки |
HTTPRequest |
Объект Request, который вызвал создание данного объекта Response |
Sent |
Содержит булевское свойство, которое возвращает информацию о том, отослан ли ответ клиенту |
Location |
URL-адрес конечного документа. Используется в случае, когда вместо обработки запроса серверный модуль осуществляет его переадресацию |
Кроме этих полей, данный объект содержит методы, приведенные в табл. 10.7.
Таблица 10.7. Методы объекта Request
Метод |
Описание |
SendRedirect |
Функция, которая вместо отправки содержимого ответа посылает клиенту сообщение о необходимости переадресации запроса. Аргументом является URL, на который осуществляется этот переход |
SendResponse |
Выдает команду объекту отослать ответ |
SendStream |
Отправляет содержимое потока, указанное в аргументе, в качестве ответа клиенту |
Sent |
Аналогично свойству Sent, возвращает информацию о том, отослан ли ответ клиенту |
GetCustomHeader |
Возвращает значение HTTP-заголовков, установленных специально, по имени соответствующего заголовка |
SetCookieField |
Добавляет к ответу cookie-параметр |
SetCustomHeader |
Добавляет заголовок к HTTP-ответу |
Работать с данным объектом удобно в контексте обработчика события OnAction действия.
Рассмотрим пример, возвращающий пользователю изображение, которое предварительно будет загружено серверным модулем и, затем, отправлено в поток вывода.
Как обычно, для этого необходимо вначале создать новый серверный модуль, затем добавить в него новое действие. Не важно, как оно будет называться, главное установить его вызов по умолчанию. После этого активизируйте это действие и перейдите на вкладку Events в окне Object Inspector. Если теперь два раза щелкнуть мышью по событию OnAction, то в окне Code Editor откроется "скелет" для ввода содержимого функции. Добавьте в нее код, приведенный в листинге 10.8.
Листинг 10.8. Пример кода, демонстрирующий отправку клиенту содержимого графического файла
procedure TWebModulel.WebModulelWebActionltemlAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var
stream:tmemorystream; begin
stream:=tmemorystream.Create;
stream.LoadFromFile('d:\кaпля.gif'); Response.ContentType:=('image/gif); Response.ContentStream:=stream; Response.SendResponse;
end;
Разумеется, название функции будет зависеть от названия действия. В примере оно оставлено по умолчанию. Путь также можно вводить к произвольному файлу. Главное, чтобы тип указанных MIME-данных совпал с фактическим содержимым файла.
Если скомпилировать проект (предварительно сохранив его в каталоге cgi-bin сервера) и указать адрес к исполняемому модулю, то в браузере появится следующее изображение (рис. 10.9).
Рис. 10.9. Изображение, которое было получено из потока вывода серверного модуля
В этом примере содержимое файла сначала загружается в поток stream, а затем отправляется клиенту. Перед отправкой необходимо указать тип содержимого.
Подробное описание применения объектов Response и Request смотрите в разделе, посвященном реализации информационной системы фирмы "Два кирпича" данной главы.
Работа с базами данных и генерация таблиц
Поскольку работа с базами данных занимает очень важное, если не центральное место в вопросах клиент-серверного взаимодействия, то мы в третий раз в данной книге коснемся этого вопроса, на этот раз с чисто практической точки зрения. Для начала, рассмотрим общие вопросы настройки и подключения баз данных. Опытные разработчики могут этот раздел пропустить.
Для использования базы данных (БД) ее необходимо зарегистрировать в системе. Это позволяет избавиться от необходимости работать с физическим представлением информации в виде файла, и переложить ряд сервисов по обслуживанию доступа к данным, которые хранятся в БД, на соответствующий драйвер. Более того, регистрация БД скрывает многие аспекты взаимодействия с ней, такие как расположение самого файла с таблицами, его формат, и даже обеспечивает универсальность доступа к данным — с помощью языка SQL (см. гл. 3).
В поставку Delphi входит утилита Borland Database Engine (BDE), обеспечивающая взаимодействие программ, написанных с использованием продуктов этой фирмы, с БД. BDE обеспечивает взаимодействие программ как с локальными, так и удаленными БД, и, поскольку она должна включаться в комплект к любому программному продукту, написанному с использованием Delphi, то разработчики имеют право на неограниченное распространение этой утилиты.
Подключим к системе базу данных, находящуюся на дискете с примерами в каталоге database.
Прежде всего, необходимо перенести БД на жесткий диск. Для этого, скопируйте папку database в любой удобный каталог диска. Далее необходимо запустить утилиту BDE Administrator, значок которой находится на Панели управления. Перед вами откроется следующее диалоговое окно (рис. 10.10).
Здесь слева представлены пункты, соответствующие БД, зарегистрированным в системе. При активизации этих БД справа будут появляться их свойства. Для добавления новой БД необходимо выбрать пункты меню Object | New, после чего на экране появится окно выбора драйвера подключаемой базы данных. Название драйвера нужно оставить по умолчанию — Standard. После этого, в списке псевдонимов подключенных баз данных появится новый — Standard 1. Переименуйте его, выбирая опцию Rename контекстного меню (чтобы оно появилось, нужно щелкнуть правой кнопкой мыши, указав курсором мыши на псевдоним), на название shop. Теперь необходимо установить опцию, появившуюся в правой части окна Default Driver в Paradox и указать путь к каталогу, в котором хранятся файлы базы данных.
Все! Для любой программы, которую вы напишете на Delphi, уже неважно, где находится БД, как с ней нужно работать и т. д. Есть только псевдоним shop, по которому необходимые данные будут доступны.
Рис. 10.10. Вид утилиты BDE Administrator
Невизуальные объекты для работы с БД
Обычно при разработке настольных приложений, взаимодействующих с БД, создается специальный объект — контейнер, напоминающий форму, но отличающийся от последней тем, что служит для размещения только невизуальных компонентов. При написании CGI-модулей все невизуальные компоненты, относящиеся к работе с БД, можно помещать в автоматически создаваемый Web-модуль. В Delphi есть два фундаментальных компонента, обеспечивающие подключение к зарегистрированным БД: ттаЫе и TQuery. В отличие от настольных приложений, в которые нужно добавлять компоненты для отображения таблиц БД на экране, а также объект класса TDataSource, как "промежуточный слой" в обмене данными между последними и компонентами ттаЫе и TQuery, при работе в полностью невизуальной среде этого не требуется. Пиктограммы компонентов ттаЫе, TQuery и TDataSource приведены на рис. 10.11.
Компонент ттаЫе предназначен для доступа к таблицам, чтения и модифицирования данных, хранящихся в них. Использование этого компонента существенно облегчает и ускоряет разработку приложений, работающих с БД. Однако в сетевых программных комплексах лучше применять другой компонент — TQuery, обеспечивающий работу с таблицами путем передачи команд языка SQL, что позволяет создавать более гибкие решения, базирующиеся на всей мощи этого языка.
Рис. 10.11. Значки компонентов TTable, TQuery и TDataSource
Выполнение SQL-запросов и доступ к ячейкам таблицы
Подробно работу с базами данных мы описывать не будем, поскольку существует большое число хороших пособий по данной теме. Однако поскольку для реализации запланированного примера нам потребуется компонент TQuery, то коснемся основных моментов его работы.
Класс TQuery обладает основными свойствами, приведенными в табл. 10.8.
Таблица 10.8. Некоторые свойства объектов класса TQuery
Свойство |
Описание |
Constrained |
Устанавливает запрет (значение true) на добавление и вставку в таблицу записей, не согласующихся с условиями проведенной выборки данных SELECT |
ParamCount |
Возвращает число установленных параметров выборки |
Params |
Содержит параметры SQL-запроса |
RequestLive |
Если значение равно false, то в результате SQL-запроса генерируется таблица read-only. В противном случае, создается таблица, которая может быть изменена |
RowsAffected |
Возвращает число строк, которые были получены, обновлены или удалены в результате выполнения последнего SQL-запроса. Если значение равно -1, то запрос не выполнился из-за возникшей ошибки |
SQL |
Обозначает объект TStrings, который содержит строки SQL-запроса |
Text |
Возвращает массив PChar — указателей на символы отправленного SQL-запроса |
Filter |
Содержит выражение для фильтрации записей |
Filtered |
Обеспечивает включение/отключение фильтрации записей |
FilterOptions |
Определяет чувствительность к регистру фильтрации записей, а также разрешение на частичное совпадение фильтруемого выражения |
RecNo |
Содержит порядковый номер текущей записи |
RecorciCount |
Содержит общее число записей в сформированной таблице. Работает с таблицами Paradox и DBase |
RecordSize |
Содержит размер памяти, отводимый под хранение одной записи таблицы |
Active |
Открывает/закрывает текущий SQL-запрос |
Bof |
Если значение равно true, то курсор находится на первой записи таблицы. В противном случае, принимает значение false |
Eof |
Если значение равно true, то курсор находится на последней записи таблицы. В противном случае, принимает значение false |
FieldCount |
Содержит число полей в записи |
FieldValues |
Это свойство обеспечивает доступ к значению поля, которое указано в параметре свойства, для текущей записи |
Для того чтобы получить доступ к представлению таблицы, сформированному в результате выполнения SQL-запроса, необходимо:
Наиболее часто используемые методы объектов класса TQuery описаны в табл. 10.9.
Таблица 10.9. Некоторые методы объектов класса TQuery
Метод |
Описание |
ExecSQL |
Выполняет SQL-команду, указанную в свойстве SQL. He используется для команды SELECT |
Cancel |
Отменяет изменения в текущей записи таблицы, если они еще не были отправлены в базу данных |
Post. |
Отправляет изменения, внесенные в таблицу, в БД |
Append |
Добавляет новую запись в представление таблицы, сформированное на основании SQL-запроса |
Delete |
Удаляет текущую запись в таблице |
Edit |
Открывает процесс редактирования данных в представлении таблицы |
First |
Помещает курсор на первую позицию представления таблицы |
Insert |
Вставляет новую запись в текущую позицию курсора |
InsertRecord |
Вставляет новую запись и заполняет значения полей параметрами процедуры |
Last |
Переводит курсор на последнюю запись представления таблицы |
MoveBy |
Перемещает курсор на запись, отстоящую от текущей на число, указанное в параметре. Если число положительное, то курсор перемещается к последующей записи, а если отрицательное, то к предыдущей |
Next |
Перемещает курсор к следующей записи |
Refresh |
Обновляет содержимое представления таблицы из БД. Используется, если базу редактирует несколько клиентов одновременно |
SetFields |
Устанавливает значения всех полей текущей записи из параметров процедуры |
Замечание
Описание синтаксиса языка SQL смотрите в гл. 3, а многочисленные примеры использования объектов класса TQuery — в разделе, посвященном реализации информационной системы фирмы "Два кирпича", настоящей главы.
Автоматическое создание HTML-таблиц на основе выполненного запроса
Объекты класса TQuery преобразуют данные, полученные в результате SQL-запроса, во внутреннее табличное представление. Данные можно из этой таблицы извлекать и модифицировать, однако для того, чтобы их можно было отобразить в виде таблицы, необходимо либо напрямую генерировать HTML-код, либо воспользоваться специальным объектом — экземпляром класса TQueryTabieProducer. Этот объект, взаимодействуя с источником данных класса TQuery, генерирует HTML-таблицу на основании данных, которые были извлечены последним из БД.
Рассмотрим пример. На основании SQL-запроса сгенерируем и отправим клиенту таблицу из базы данных shop.
Создайте новый проект и сохраните его под именем Webtable в каталоге cgi-bin Web-сервера, либо в любом другом месте жесткого диска, но после этого установите каталог cgi-bin Web-сервера в качестве выходного для создаваемых ЕХЕ-файлов (Output Directory).
Добавьте новое действие и установите его вызов по умолчанию. Теперь необходимо добавить В Web-МОДуЛЬ два компонента:
TQuery
И
TQueryTableProducer.
Установите следующие значения свойств компонента TQuery:
Значения свойств необходимо устанавливать именно в такой последовательности: сначала определяем БД, с которой будем работать, затем строку SQL-запроса. И именно в результате выполнения этой команды формируется таблица, с которой будет вестись работа. После этого можно открывать запрос к БД.
Выполнение команды SELECT в описанном виде приведет в выборке всех записей и полей, содержащихся в таблице. Подробное описание этой функции приведено в гл. 3.
Следует отметить, что после определения свойства SQL имеется возможность для выполнения запроса уже в режиме проектирования приложения. Для этого нужно лишь установить значение параметра Active в True.
Теперь можно заняться настройкой отображения таблицы. Присоединим компонент TQueryTableProducer к объекту Query. Для этого нужно прописать имя последнего в свойстве Query генератора таблиц. Далее в навигаторе объектов нашего Web-модуля появятся названия полей таблицы Products (в Delphi 5.0). Щелкая мышью по названиям полей, можно в окне Object Inspector их активизировать и, соответственно, получать доступ к их свойствам. Кроме того, существует возможность для визуального наблюдения за отображением столбцов и строк таблицы на Web-страниие. Для этого нужно лишь щелкнуть мышью по свойству columns в Object Inspector, когда текущим Объектом является Используемый Экземпляр класса TQueryTableProducer.
В этом редакторе можно устанавливать варианты отображения таблицы, наблюдая за результатом изменения настроек в нижней части редактора.
Рис. 10.12. Вид меню редактирования столбцов таблицы
Для определения свойств отображения конкретного столбца надо активизировать его в меню редактирования столбцов таблицы и, затем, обратиться к окну Object Inspector, где и станут доступны нужные параметры (рис. 10.12).
Здесь можно назначить цвет заднего плана, как для всего столбца, так и для его заголовка, поменять название столбца, отображаемое в таблице, и т. д.
Код, который генерирует этот компонент по умолчанию, содержит только таблицу, начинающуюся с тега <TABLE> и заканчивающуюся </TABLE>. Этот код можно вставлять в Web-страницу, посылаемую клиенту. Доступ к сгенерированному HTML-коду, являющемуся фрагментом документа, осуществляется путем вызова функции TqueryTabieProducer. Content, где название класса должно быть заменено именем соответствующего ему экземпляра. Однако генератор таблиц можно использовать и для получения всего HTML-документа. Для этого необходимо ввести коды, которые должны предшествовать таблице в документе, в свойство HEADER и последующие за ней — в FOOTER. Так мы и поступим в нашем примере. Добавим в свойство HEADER код, приведенный в листинге 10.9.
Листинг 10.9. Начало Web-страницы
<HTML>
<МЕТА http-equiv="Content-Type" content="text/html; charset=windows-1251">
<TITLE> Страница,; содержащая таблицу, полученную в результате выполнения
.SQL-запроса </TITLE> <Р>Сама таблица:
А в свойство FOOTER соответственно:
</HTML>
Теперь осталось лишь добавить новое действие, определить его вызов по умолчанию, в свойстве Producer записать название используемого объекта класса TQueryTabieProducer и запустить в браузере предварительно скомпилированный проект. Результат проведенной работы можно пронаблюдать на рис. 10.13.
Рис. 10.13. Динамически сформированная Web-страница
Программная реализация информационной системы на базе Web-технологий
В гл. 5была спроектирована ИС фирмы, осуществляющей продажи строительных материалов посредством Интернет-магазина. Наступило время проработать конечные структуру и взаимодействие объектов системы между собой, а также с источниками данных и создать несколько составляющих этого проекта.
Анализ и построение интерфейсов объектов
На диаграмме классов был представлен ряд объектов, из которых можно выделить группу, обеспечивающую непосредственно навигацию по магазину, разбор клиентского запроса, а также генерацию Web-страниц на базе представления, сформированного другими объектами.
Прежде всего, для ясного понимания функциональности работы системы необходимо четко отделить бизнес-логику от средств доступа к внешним данным в широком смысле этого слова. Это значит, что нужно на одни объекты возложить обязанности по приему данных от клиента и отсылке их ему, а на другие — обработку этих данных. Поскольку мы подошли к проекту с функциональной точки зрения (см. гл. 5),то и конечные обработчики запросов необходимо освободить от анализа внешних данных, возложив эту обязанность на некий промежуточный слой.
Вообще, жизненный цикл проектирования информационных систем включает в себя несколько стадий поступательного развития проекта, с возвращением назад для уточнения тех моментов, видение которых открывается разработчику только по мере приближения к финалу. Развитие происходит по спирали, когда создатель возвращается к уже проработанным этапам для их уточнения, а затем формирует проект на новом уровне детализации. Это позволяет, с одной стороны, избавляться от возникающих противоречий, а с другой — "двигаться не только вглубь, но и вширь", как сказали бы философы. Однако данная книга в большей степени посвящена технологиям реализации, а не проектирования, и поэтому мы примем к реализации именно тот проект, который был описан в гл. 5.
Итак, у нас есть диаграмма классов, представленная на рис. 5.4. С учетом знаний, изложенных в этой и предыдущих главах, ее элементам, а точнее классам, можно придать функциональный смысл. Справедливости ради, следует отметить, что авторы книги пользовались промежуточным вариантом проекта, но для четкости предоставляемых знаний мы показываем только конечный вариант проекта.
Класс, осуществляющий разбор сведений, содержащихся в запросе
Назовем этот класс TDispatcher. Очевидно, что нерационально самостоятельно программировать процесс получения данных запроса и затем модернизировать их представление в удобный к использованию вид. Пусть это делает стандартный для CGI-приложения объект Request. Работа TDispatcher начнется после получения запроса и вызова обработчика onAction единственного действия серверного модуля. Всю дальнейшую обработку сведений осуществят бизнес-объекты под управлением объекта класса TDispatcher.
Этот класс является наследником обычного Web-модуля (класса rwebModuie), и обладает некоторыми дополнительными методами
Нужно обеспечить выполнение как минимум двух процедур: извлечение всей интересующей информации и реализацию алгоритма работы системы с использованием других объектов. На основании этих требований опишем интерфейс класса TDispatcher (листинг 10.10).
Листинг 10.10 Интерфейс класса TDispatcher
TDispatcher = class(TWebModule)
procedure Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean)'; private
functionCreateCorrespondingView (position : String) : TBasicView;
end;
В качестве параметра процедуры CreateCorrespondingView выступает строка, которая является частью GET-запроса и передается вместе с информацией о получении Web-страницы. В результате работы данного метода, один из объектов из набора TCategoriesView, TDetailsView, TPositionView, TCartView, TOrderView, TBiiiview будет создан и ссылка на него будет возвращена рассматриваемой функцией.
Класс TBillView, создаваемый для генерации счет- фактуры, обеспечивает работу ActiveX-формы, технология реализации которой будет описана в гл. 12.В данной версии реализации системы мы опустим описание и внедрение этого класса.
Как вы уже, наверное, догадались, данные объекты генерируют представления соответствующих страниц. Так вот, в соответствии со строкой, которая указана в качестве параметра, и создается соответствующий объект.
Процедура Action является единственным действием по умолчанию (при повторении проекта нужно это действие соответствующим образом создать).
Она Использует Приватный метод данного класса CreateCorrespondingView для вызова соответствующего объекта и передачи ему данных запроса. Кроме данных запроса, объекту — генератору ответа будет передаваться ссылка на структуру (объект) Response для помещения в него ответа.
Классы, генерирующие содержимое основных информационных страниц
Поскольку в работе магазина участвует целый набор страниц, предоставляющих различную информацию, содержание которых получается примерно одинаковым способом, то имеет смысл выделить объект, выполняющий работу по генерации соответствующего представления Web-страниц, который назван TBasicview. В дальнейшем, данный класс будет унаследован потомками для корректировки способа генерации представления Web-страниц.
Прежде всего, данный класс должен обеспечивать генерацию ответа клиенту. Для этого будет служить метод ForwardRequest, а параметрами будут являться объекты TWebRequest и TWebResponse. При вызове данного метода объектом класса Dispatcher ему передается информация о запросе и объект для помещения в него содержимого ответа. После этого класс выполняет определенные действия над запросом с целью подготовки всех служебных данных, а затем вызывает свой метод createContent для генерации HTML-содержимого ответа. Удобно использовать метод замещения динамическим содержимым "прозрачные" теги. Для этого нами будет использоваться генератор страниц (класс TPageProducer), при использовании которого нужно подготовить соответствующие шаблоны и описать процедуру опнтмътад. С этой целью объекты класса TBasicview и содержат этот метод. Кроме того, для различных целей, которые могут появиться при реализации классов- наследников, добавим виртуальный абстрактный метод HandieRequest. Это сделано с целью сохранения функциональных обязянностей других методов данного класса в случае, когда для подготовки HTML-содержимого нужно провести дополнительные операции. Итак, интерфейс класса TBasicview приведен в листинге 10.11.
Листинг 10.11. Интерфейс класса TBasicview
TBasicview = class private
fileName : TFileName; protected
request : TWebRequest; response : TWebResponse; public
constructor Create (flName : TFileName); procedure ForwardRequest (req : TWebRequest;
resp : TWebResponse); procedure OnHTMLTag (Sender: TObject; Tag: TTag;
const TagString: String;TagParams: TStrings; var ReplaceText: String); procedure HandieRequest (); virtual; function CreateContent (const TagString: string) : String;
virtual; abstract;
end;
Теперь нам осталось описать интерфейсы классов-наследников TBasicview.
За некоторым исключением их интерфейсы повторяют друг друга. Это видно в листинге 10.12.
Листинг 10.12. Интерфейсы классов, генерирующих представление различных разделов магазина
// Отображение категорий товаровTCategoriesView = class (TBasicView) public
function CreateContent (const TagString: string) : String;
override;
end;
// Отображение позиций (конкретных товаров)TPositionView = class (TBasicView) public function CreateContent (const TagString: string) : String;
override;
end;
// Отображение детальной информации о товареTDetailsView = class (TBasicView) public function CreateContent (const TagString: string) : String;
override;
end;
// Отображение корзинки TCartView = class (TBasicView) private cartltems : TStrings; public procedure HandleRequest ();
override;
function CreateContent (const TagString: string) : String;
override;
end;
// Отображение формы уточнения заказаTOrderView = class (TBasicView) public function CreateContent (const TagString: string) : String;
override;
Различия в интерфейсах этих объектов лучше понимать, анализируя непосредственно реализационную часть, и поэтому перейдем к ее описанию.
Разумеется, все классы можно описывать и реализовывать по-разному. Более того, в этой системе можно было и вовсе обойтись без объектно-ориентированного подхода, поскольку реализуемый фрагмент ИС достаточно прост. В таких способах реализации Web-систем выбор алгоритма работы ложится на действия, а формирование Web-страниц осуществляется посредством использования шаблонов и объектов класса TWebProducer. С другой стороны, при кажущейся простоте и универсальности этих решений остается открытым вопрос о взаимодействии бизнес-объектов системы с объектами, которые взаимодействуют с CGI. Всегда необходимо находить разумное сочетание, распределяя функции между двумя основными компонентами системы. Это делается, поскольку единожды реализованное приложение, как правило, с течением времени модернизируется, улучшается и поэтому всегда уже на этапе проектирования нужно об этом задумываться, делая проект максимально гибким, а код "прозрачным". В принципе, в нашем примере сделан крен в область использования бизнес-объектов, но это лишь один из примеров разделения полномочий между объектами бизнес-логики и взаимодействия с CGI. При дополнении системы новыми функциями соответствующие добавляемые компоненты должны органично вписаться в созданную систему.
Реализация фрагмента информационной системы
Реализуемый фрагмент ИС будет иметь визуальное представление в браузере в виде фреймов. Его вид представлен на рис. 10.14.
Это представление обеспечивается кодом фрейм-содержащей страницы, приведенном в листинге 10.13.
Листинг 10.13. Содержание начальной Web-страницы модуля ;
<HTML>
<МЕТА http-equiv="Content-Type" content="text/html; charset=windows-1251"> <ТIТLЕ>Вход в магазин</ТIТLЕ> <FRAMESET rows="*,150"> <FRAMESET cols="250,*">
<FRAME src="http://localhost/cgi-bin/shop?position=leftframe" name="leftframe">
<FRAME src="http://localhost/cgi-bin/shop?position=rightframe" name="rightframe">
</FRAMESET>
<FRAME src="http://localhost/cgi-bin/shop?position=korz" name="korz"> </FRAMESET> </HTML>
Рис. 10.14. Вид рабочей области ИС
При получении запроса клиента автоматически создается объект класса TDispatcher, являющийся наследником класса TWebModuie, и естестестенно нужно определить действие, которое будет выполняться по происхождению этого события. Для этого, как обычно, необходимо зайти на вкладку Events (События) окна Object Inspector и, дважды щелкнув мышью по полю, в которое помещается название требуемой функции, вызвать шаблон для ввода кода функции. После этого переименуйте функцию в Action и приведите ее к виду, приведенному в листинге 10.14.
Листинг 10.14. Код обработчика действия
//---- Задача: обработать запрос и подготовить контент ---
procedure TDispatcher.Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var
view : TBasicView; begin
view := CreateCorrespondingView
(request.QueryFields.Values['position']);
(Здесь произошло извлечение из запроса значения поля с названием position. Обратите внимание на то, каким образом указаны ссылки во фрейм-содержащей странице. Именно указанием параметра position определяется, какую страницу серверный модуль должен создавать. Функция CreateCorrespondingView определяет тип объекта — обработчика запроса, и возвращает его в переменную view.)
if view <> Nil then begin
view.ForwardRequest (request, response);
// Был вызван метод ForwardRequestс целью получения ответа в объект
// response, который и будет отправлен клиенту.
view.Destroy;
end
end;
Как видим, первым делом нужно определить, какой собственно объект должен создавать HTML-код. Перейдем к реализации этой процедуры, приведенной в листинге 10.15.
Листинг 10.15. Реализация процедуры CreateCorrespondingView
//- — — Задача: выбрать нужное отображение ---------
function TDispatcher.CreateCorrespondingView
(position : String) : TBasicView; begin
if 'position = 'leftframe' then
result := TCategoriesView.Create ('templates\\Categories.htm') else if position = 'rightframe' then
result := TPositionView.Create ('templates\\Position.htm') else if position = 'details' then
result := TDetailsView.Create ('templates\\Details.htm') else if position = 'korz' then
result := TCartView.Create ('templates\\Cart.htm') else if position = 'order' then
result := TOrderView.Create ('templates\\0rder.htm') else if position = 'bill' then
result := TBillView.Create ('templates\\Bill.htm') else
result := nil;
end;
Обратите внимание, что в качестве параметра данного метода выступает название области, которую должен сгенерировать объект класса-наследника от TBasicView, а уже при создании экземпляра последнего ему передается путь к файлу-шаблону, на базе которого и формируется отправляемый HTML-документ.
Теперь можно переходить к реализации самих классов, генерирующих Web-страницы, и сначала следует создать главный класс TBasicview, от которого с некоторой модификацией наследуются другие. Еще раз отметим, что объекты их TBasicview не будут сами генерировать Web-страницу, это сделают наследники. Роль же этого класса заключается в описании общих для всех наследников методов и свойств. Такой подход позволит при необходимости легко модифицировать систему, корректируя лишь существующего наследника или добавляя новый. Реализация класса TBasicview приведена в листинге 10.16.
Листинг 10.16. Реализация класса TBasicview
constructor TBasicView.Create (flName : TFileName);
// Параметром конструктора является имя файла-шаблона Web-страницы,
// которое выбрал создатель данного объекта.begin
inherited Create; fileName := flName;
// Здесь имя файла-шаблона было занесено в поле объекта.
end;
//—--- Общая процедура реакции на запрос -----------------
procedure TBasicView.ForwardRequest ( req : TWebRequest;
resp : TWebResponse);
var
PageProducer : TPageProducer; begin
request := req; response := resp;
// ОбработкаHandleRequest ();
// Подготовка нового отображения на основе шаблонаPageProducer := TPageProducer.Create(Nil); PageProducer.HTMLFile := fileName; PageProducer.OnHtmlTag := OnHtmlTag; resp.Content := PageProducer.Content; PageProducer.Destroy;
end;
//----- Реакция на директиву подстановки ---------
procedure TBasicView.OnHTMLTag (Sender: TObject; Tag: TTag; const TagString: String; TagParams: TStrings; var ReplaceText: String);
begin
ReplaceText := CreateContent (TagString);
end;
//----- Пустой обработчик запроса -------
{В случае необходимости дополнительной обработки, потомки могут переопределять эту процедуру.}
procedure TBasicView.HandleRequest ();
begin
end;
После реализации основного класса можно переходить к его наследникам, программный код которых приведен в листинге 10.17.
Листинг 10.17. Реализация потомков класса TBasicView
//----- Формирование отображения категорий товаров -------
function TCategoriesView.CreateContent (const TagString: string) :. String;
var
query: TQuery;
begin
query:=tquery.create(Nil);
// Создан объект для организации запроса к базе данных.
query.DatabaseName:='shop';
// Указывается, какая база должна использоваться.
query.SQL.Add('SELECT DISTINCT КАТЕГОРИЯ FROM Products'); {Задана SQL-команда для выборки. Обратите внимание на параметр DISTINCT, означающий необходимость выборки только уникальных (неповторяющихся) записей из- поля КАТЕГОРИЯ }
query.open;
query.First;
// Запрос выполнен.
while not query.Eof do
{Здесь происходит перебор всех извлеченных записей с целью сформировать представление Web-страницы с категориями. Условием окончания перебора является достижение последней записи (EOF — End Of File)}
begin
result := result + ('<LI><A href="http:\\localhost\cgi-bin\shop?' + 'position=rightframe&category=' + query.FieldValues['КАТЕГОРИЯ'] + '"target="rightframe">' + query.FieldValues['КАТЕГОРИЯ']+'</А>');
{В результате создается HTML-представление маркированного списка с названием категорий и соответствующими ссылками}
query.Next;
end;
query.Destroy;
end;
//----- Формирование отображения позиций (конкретных товаров) -----
function TPositionView.CreateContent (const TagString: string) : String;
var
Integer; query : TQuery; categories : String;
begin
categories := request.QueryFields.Values['category']; if categories = (' ') then
begin
{Здесь происходит обработка варианта, когда клиент только пришел в магазин и в качестве названия категории' ничего не указано)
result:='<Р> Добро пожаловать в магазин фирмы "Два кирпича".';
result:=result+'<>Выбирайте товары из категорий, представленных слева'
end
else
begin
result:=('<НЗ>Вы находитесь в категории </НЗ><Н4>'+categories+'</Н4>');
{Был помещен заголовок списка товаров, присутствующих в выбранной категории}
result: =Result+ ( <ТАBLE><TR><TD>Название товара<ТО>Единица изме-рения<ТО>Цена за единицу');
query:=Tquery.Create(Nil); query.DatabaseName:='shop';
query.SQL.Add('SELECT IDJTOBAPA, МАРКА, Категория, ЕДИНИЦА_ИЗ, ЦЕНА_ЗА_ЕД FROM Products');
query.SQL.Add('WHERE Категория = "'+categories+ "" ); query.open; query.First;
{Был осуществлен запрос к базе данных с указанием условия на выборку: поле "Категория" отбираемых записей должно содержать такое же слово, • какое содержится в переменной categories)
for i:=l to query.RecordCount do
begin
Result:=Result+
'<TR><TD>'+query.FieldValues['Марка']+
'<TD>'+query.FieldValues['Единица_из']+
'<TD>'+IntToStr(query.FieldValues['Цена_за_ед']) + (' руб.') -t-
'<TDXA href="http://localhost/cgi-bin/shop?'+
'position=details&ID='+
inttostr(query.FieldValues['ID'JTOBAPA'])+ '" target="new">nofipo6Hoe описание...</А>'+ '<TD>'+
'<A href="http://localhost/cgi-bin/shop?position=order&ID='+ inttostr(query.FieldValues['ID_TOBAPA'])+ 1">3аказать...</A>';
// Произойти описание строк таблицы.
query.Next;
end;
query.Destroy;
end;
end;
//----- Формирование отображения детальной информации о товаре----
function TDetailsView.CreateContent (const TagString: string) : String;
var
query : TQuery; id : String;
begin
query:=Tquery.Create(Nil);
id := request.QueryFields.Values['ID'];
query.DatabaseName:='shop';
query. SQL. Add ('SELECT * FROM Products WHERE ID_Товара = '"+id+"");
query.open;
if TagString = 'MARK' then
result := query.FieldValues['МАРКА'] else if TagString = 'REPLACE' then
begin
if (query.FieldValues['ФОТОГРАФИЯ1] <> 'none') then
result := '<IMG src='+query.FieldValues['ФОТОГРАФИЯ']+
' аlt="фотография">' ;
result := result + '<Н4>Технические характеристики:</Н4>'+ '<H5>'+query.FieldValues['ТЕХНИЧЕСКИ']+'</H5>'+ '<H4Производитель</H4><H5>'+ query.FieldValues['ПРОИЗВОДИТ']+'</H5>'+ '<Н4>Единица измерения</Н4ХН5>' + query.FieldValues['ЕДИНИЦА_ИЗ']+'</H5>'+ '<Н4>Цена</Н4><Н5>'+
inttostr(query.FieldValues['ЦЕНА_ЗА_ЕД'])+'</H5>'+ ' <Н4Жатегория</Н4ХН5>' +query. FieldValues [' КАТЕГОРИЯ' ] + '</H5>'+ '<H4 >Примечание</Н4 ><H5>' + query.FieldValues['ПРИМЕЧАНИЕ']+'</H5>'; end;
query.Destroy;
end;
//----- Обработка запроса ---------
procedure TCartView.HandleRequest (}; var
addingID : String;
addingValue : String;
begin
// На основе Cookies, формируем список товаров в корзинке
cartltems := request.CookieFields;
// Если необходимо добавить новый товар
addingValue := request.QueryFields.Values['addingvalue']; addingID := request.QueryFields.Values['addingid']; if addingValue <> '' then
begin
if StrToInt(addingValue) > 0 then
begin
// Добавляем к списку товаров в корзинкеcartltems.Add (addingid+'='+addingValue);
// А так же, сохраняем добавленный товар в Cookieswith response.Cookies.Add do
begin
Name := addingID; Value := addingValue;
end;
end;
end;
end;
//----- Формирование отображения карзинки-----------
function TCartView.CreateContent (const TagString: string) : String;
var
query : TQuery; itemID : String;
i, price, sumCost, amount : Integer;
begin
if cartltems.Count = 0 then
result :=result+ ('<TR><TD> соlspan=Корзина пока пуста</ТАВLЕ>')
else
begin
query := TQuery.create(Nil); query.DatabaseName: ='shop'; for i:=0 to (cartltems.Count-1) do
begin
itemID := cartltems.Names[i];
amount := StrToInt (cartltems.Values[itemID]);
query.sql.Clear;
query.SQL.add('SELECT * FROM Products WHERE ID_TOBAPA = ' +
itemID); query.Open;
query.First;
price := query.FieldValues['ЦЕНА_ЗА_ЕД'];
result :=r.esult + '<TR><T'D>'+query. FieldValues ['МАРКА'] +
'<TD>'+IntToStr(amount) + '<TD>' +IntToStr(price) + '<TD>' +
IntToStr(price * amount); sumCost:=sumCost + price * amount; query.close; end; result :=result+'</TABLE>06iuaH стоимость : '+IntToStr(sumCost) +
1руб.'; query.Destroy;
end;
end;
//---- Формирование отображения формы уточнения заказа ----
function TOrderView.CreateContent (const TagString: string) : String;
var
query : TQuery; id : String/begin
id := request.QueryFields.Values['ID']; if TagString = 'MARK' then
begin
query:=Tquery.Create(Nil);
query.DatabaseName:='shop';
query.SQL.Add('SELECT * FROM Products WHERE 10_Товара = "'+
id+"");
query.open;
result :=query.FieldValues['МАРКА']; query.Destroy; end else if TagString = 'ID' then
result := id;
end;
В заключение описания реализационной части приведем код шаблонов, совместно с которыми работают рассмотренные классы. Класс TCartview, создающий представление корзины, обращается к шаблону cart.htm, исходный текст которого представлен в листинге 10.18.
Листинг 10.18. Шаблон cart.htm
<HTML>
<BODY>
Позиции заказа:<BR> <TABLE border=l> <TR><TH width="50%">Hаименование товара
<TH width="15%">Kоличество
<TH width="15%">Ценa, рублей
<TH width="20%">Стоимость
<#REPLACE>
<ВН>Чтобы подтвердить покупку, нажмите
<А href='.'http: //localhost/cgi-bin/shop?position=bill"
target="_PARENT">Здесь</A>
</BODY>
</HTML>
При формировании содержимого правого фрейма классом icategoriesview используется шаблон categories.htm, код которого приведен в листинге 10.19.
Листинг 10.19. Шаблон categories.htm
<HTML>
<BODY>
<Р>Сегодня Интернет-магазин располагает следующими категориями товаров<ВЕ> <UL>
<#REPLACE>
</UL>
<Р>Для перехода к'интересующему разделу, щелкните по ссылке.
</BODY>
</HTML>
Подробное описание товаров создается классом TDetailsview с использованием шаблона, представленного в листинге 10.20.
Листинг 10.20. Шаблон Details.htm
<HTML>
<ТIТLЕ>Описание <#MARK> - магазин "Два кирпича" </TITLE> .
<BODY> <Hlx#MARKx/Hl>
<#REPLACE>
</BODY>
</HTML>
Добавление нового товара в корзину покупателя осуществляется посредством анкеты, которая создается объектом класса TOrderview, использующего шаблон, представленный в листинге 10.21.
Листинг 10.21. Шаблон Order.htm :
<HTML>
<BODY>
<hЗ>Помещение товара в корзину покупателя.</hЗ>
<р>Вы выбрали ' <#MARK> '
</р>
Укажите заказываемое количество единиц товара:
<form name="adding" method="GET" action="http://localhost/cgi-bin/shop"
target="korz">
<input type=hidden name="position" value="korz">
<input type=hidden name="addingid" value="<#ID>">
<input type="text" name="addingvalue" size="5"> ед. товара <br><br>
<input type="submit" уа!ие="Поместить в корзину" name="Submit"
onClick="window.history.back(2)">
</form>
</BODY>
</HTML>
В момент посещения клиентом начальной страницы магазина, HTML-код правого фрейма будет генерироваться на основании шаблона Position.htm, представленного в листинге 10.22.
Листинг 10.22. Шаблон Position.htm
<HTML>
<BODY>
<#REPLACE>
</BODY>
</HTML>
Демонстрация работы серверного модуля
Для демонстрации работы модуля установите его на ваш компьютер, переписав содержимое каталога source\chapterl0\ дискеты с примерами в каталог cgi-bin Web-сервера. Файл shop.htm необходимо скопировать отдельно в каталог для Web-страниц (в Apache это htdocs). После этого откройте файл shop.dpr в Delphi и откомпилируйте проект в запускаемый файл (меню Project | Build). Если вы не подключили базу данных, как описано в разд. "Подключение базы данных" настоящей главы, то сделайте это.
Данная реализация системы еще не завершена. В последующих главах будет, изложен материал, касающийся подготовки счета на базе технологии ActiveX, а также сервера, осуществляющего сохранения данных о клиентах в базе данных.
Наберите в браузере адрес http://localhost/shop. В окне браузера откроется наш пример магазина. Поработайте с ним. Посмотрите содержимое Web-страниц и особенно — гиперссылок, которые формируются динамически и определяют дальнейшее поведение системы при переходе по ним.