Создание серверных компонентов для ASP-приложений

Сергей Трепалин

ASP (Active Server Pages) — технология создания Web-страниц, содержащих код, выполняемый Web-сервером (подробнее об истории и современном состоянии этой технологии см. в статье Алексея Федорова «Технология ASP+», № 9’2000, а о практических вопросах применения ASP см. в цикле статей Рубена Садояна <% ASP на блюдечке>, № 9-11’2000. — Прим. ред.). Современная версия данной технологии позволяет использовать в ASP-страницах сторонние серверные компоненты, созданию которых и посвящена данная статья.

В качестве средства разработки ASP-объектов будет использоваться Borland Delphi 5, Enterprise-версия которого позволяет это делать. К сожалению, в документации, поставляемой с Delphi 5, крайне скупо сказано о назначении, последовательности создания и тестирования серверных объектов ASP; кроме того в комплекте поставки Delphi 5 отсутствуют примеры работающих ASP-объектов. Настоящая публикация частично восполняет эти пробелы.

Клиентское приложение, использующее ASP-объекты, представляет собой HTML-документ (этот документ может также включать клиентский и серверный коды на скриптовых языках. — Прим. ред.), который в принципе можно прочесть с помощью любого Web-браузера. Обычно такие HTML-документы размещаются на каком-либо Web-сервере (как правило, это Microsoft Internet Information Server версии 3.0 и выше). Web-сервер, получив требование о предоставлении документа, считывает его из локального хранилища и передает клиенту, при этом часть информации вносится в документ Web-сервером динамически; сам же Web-сервер может обращаться к ASP-объектам, входящим в комплект поставки Internet Information Server или созданным сторонними разработчиками. Обычно Web-документы, содержащие обращения к ASP-объектам, имеют расширение *.asp. Примеры подобных документов можно найти в каталогах, создаваемых при установке Internet Information Server.

Некоторые Web-дизайнеры полагают, что технология ASP заключается, грубо говоря, в замене расширения *.htm в HTML-файле на расширение *.asp. Действительно, при работе с Internet Information Server изменение расширения файла позволит корректно отображать находящийся в нем HTML-документ. Однако на самом деле технология ASP — это обращение к методам специальных объектов, называемых ASP-объектами и представляющих собой COM-серверы. Типичный пример обращения к ASP-серверу из HTML-документа, представляющий собой фрагмент кода на языке VBScript, выглядит следующим образом:

<%
Set FileSystem=Server.CreateObject("Scripting.FileSystemObject")
FileSystem.FindAllFiles
%>

Несмотря на наличие кода на скриптовых языках (VBScript или JavaScript), ASP-страница может быть доступна клиентам, работающим в других операционных системах, например в UNIX. На первый взгляд это может показаться странным, поскольку UNIX-компьютеры не используют ни Basic, ни тем более VBScript. Но дело в том, что скрипты, содержащиеся в ASP-документах, выполняются на сервере, а клиент получает HTML-документ, который является результатом выполнения этого скрипта.

Серверные объекты ASP выполняются в адресном пространстве Internet Information Server (Internet Information Services), работающего под управлением операционной системы Windows NT (Windows 2000) либо Windows 98.

По существу ASP-сервер представляет собой сервер автоматизации, в котором предопределено несколько интерфейсов; среди них — IRequest и IResponse. Интерфейс IRequest содержит методы, вызов которых позволяет передать параметры, введенные пользователем и заполненные на клиенте (об этом будет рассказано ниже). IResponse содержит методы, вызов которых приводит к формированию HTML-документа и передаче данного документа пользователю. Наличие этих признаков делает ASP-сервер похожим на CGI-приложения и ISAPI/NSAPI DLLl (далее — Web-приложения). Идеология выполнения методов в ASP-объекте и Web-приложениях также аналогична: сначала анализируется запрос клиента, затем динамически формируется отклик. Различие заключается в том, что Web-приложения формируют HTML-документ целиком, в то время как отклик ASP-объекта вставляется в исходную HTML-страницу. Например, если документ ASP представлен в виде:

<HTML>
<BODY>
<TITLE> Testing Delphi ASP </TITLE>
<CENTER>
<H3> You should see the results of your Delphi Active Server method below </H3>
</CENTER>
<HR>
<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")
    DelphiASPObj.ScriptContent
%>
<HR>
</BODY>
</HTML>

и результат выполнения метода ScriptContent возвращает строку 'First call to ASP server', то пользователь, получивший данный документ, увидит следующее (рис. 1).

Иными словами, отклик ASP-объекта добавляется к HTML-документу. В одном документе допустимо обращение к нескольким ASP-объектам, и результат их отклика формируется в единый документ — этого невозможно достичь при использовании Web-приложений. Впрочем, имеется одно ограничение: набор ASP-серверов, к которым производится обращение из одного документа, должен быть зарегистрирован на одном и том же Internet Information Server — нельзя обращаться по различным адресам для формирования одного HTML-документа.

Как было сказано выше, Enterprise-версия Delphi 5 содержит эксперт для создания ASP-объектов. Для запуска этого эксперта следует выбрать пункт главного меню File|New среды разработки Delphi, а затем со страницы ActiveX репозитария объектов — пиктограмму Active Server Object.

ASP-сервер, содержащий ASP-объекты, реализуется в виде как исполняемых файлов *.exe, так и библиотек *.dll — это разрешается при создании серверов автоматизации. ASP-сервер, реализованный в виде исполняемого файла, запускается каждый раз в ответ на запрос клиента. При использовании внутренних (in-process) ASP-серверов, выполненных в виде динамически загружаемых библиотек, один экземпляр DLL, загруженный в адресное пространство Internet Information Server, способен обслуживать одновременно нескольких клиентов. При этом возможно либо создание отдельного экземпляра COM-объекта для каждого клиента, либо обслуживание нескольких клиентов единственным экземпляром COM-объекта. Это зависит от модели работы в потоках (Threading Model), выбранной при заполнении диалога, который появляется при запуске эксперта создания ASP-объектов.

Рассмотрим теперь, каким образом работает ASP-сервер на конкретном примере создания внутреннего (in-process) ASP-сервера. Для простоты ограничимся сервером, который выполняет один запрос. Итак, выберем пункт главного меню File|New среды разработки Delphi, со страницы ActiveX репозитария объектов — пиктограмму ActiveX Library и нажмем кнопку OK. В итоге получим новый проект, который сохраним, например, под именем ASP01. Теперь снова выберем пункт главного меню File|New среды разработки Delphi, со страницы ActiveX репозитария объектов — пиктограмму Active Server Object. В появившемся диалоге определим имя будущего COM-класса, например Test.

Поскольку мы создаем in-process-сервер, параметр Instancing не имеет значения — он важен только для исполняемых файлов. Зато в данном случае серьезную роль играет параметр Threading Model. При значении этого параметра равным Single работа сервера неэффективна, поскольку при одновременном обращении к нему нескольких клиентов сервер выполняет запросы последовательно, и если один из клиентов обращается с длительным запросом, то остальные вынуждены ожидать его окончания, даже если их запросы не требуют большого количества времени для выполнения. При этом у пользователей создается впечатление «зависания» браузера, что зачастую приводит к попыткам разными методами прервать задачу. Значение Apartment приводит к разделению запросов клиентов по потокам, причем для каждого клиента будет создан свой экземпляр COM-объекта, в данном примере — класса TTest. При этом при написании методов COM-класса не требуется защиты переменных класса внутри потоков — клиент может свободно модифицировать их, что упрощает разработку кода приложения. Недостаток данной модели состоит в следующем: проект оказывается ресурсоемким и переменные класса инициализируются при каждом обращении, что увеличивает время отклика на запрос. Этих недостатков лишена модель Free, в которой единственный экземпляр COM-объекта обслуживает нескольких клиентов. Однако возможность изменения данных внутри COM-объекта влечет за собой необходимость защиты общих переменных от их изменений из разных потоков, что существенно усложняет процедуру реализации кода приложения и является потенциальным источником трудноуловимых ошибок. Как правило, эту модель используют в ASP-серверах, которые только предоставляют данные, но не позволяют клиенту их модифицировать.

Группа элементов управления Active Server Type дает возможность выбрать назначение ASP-сервера. Если сервер планируется использовать под управлением Internet Information Server версий 3 или 4, то выбирается опция Page Level Events Methods. Объекты, созданные с применением этой опции, можно будет использовать и с Internet Information Services 5.0, но в этом случае опция Object Context позволит создать объект, работающий более эффективно. Эту же опцию следует выбирать, если работой ASP-сервера управляет Microsoft Transaction Server (Windows NT) или Component Services (Windows 2000). Фактически Internet Information Services 5.0 также управляет этим сервером при помощи Component Services, так как оба этих продукта тесно интегрированы.

Опцию Generate a Template Test Script for this object следует оставлять всегда включенной. В этом случае Delphi создаст небольшой HTML-документ, который можно использовать для тестирования ASP-сервера.

Заполнив опции диалога, необходимо нажать кнопку OK, после чего будет создан файл реализации интерфейсов Unit1.pas, который следует сохранить (например, под именем U1_01). Кроме того, будет создана библиотека типов, появятся ее редактор и файл, описывающий библиотеку типов, в данном примере — TEST_TLB.pas.

Если была выбрана опция Page Level Events Methods (как в нашем примере), то библиотека типов будет содержать два предопределенных метода — OnStartPage и OnEndPage. Также будет создан файл Test.asp, который содержит HTML-документ с заготовками на языке VBScript для тестирования сервера. Если заглянуть в файл реализации (U1_01.pas) , то можно увидеть, что класс ТТest является потомком класса TASPObject.

Если была выбрана опция Object Context, библиотека типов не будет содержать предопределенных методов, а сам класс Ttest будет являться потомком класса TASPMTSObject. Оба класса-предка TTest содержат абсолютно одинаковые методы и свойства, но класс TASPObject дополнительно содержит пару методов интерфейса IASPObject — OnStartPage и OnEndPage.

Далее создадим метод, который будет заполнять HTML-документ. Для этого в редакторе библиотеки типов (рис. 2) отметим интерфейс ITest и вызовем команду New Method нажатием кнопки 1.

Назовем вновь созданный метод ScriptContent, отредактировав его название, заданное по умолчанию. Данный метод не должен иметь параметров. Затем вызовем команду Refresh нажатием кнопки 2. После этого в модуле реализации (U1_01.pas) появится заготовка, где следует описать реализацию. Метод реализуем следующим образом:

procedure TTest.ScriptContent;
begin
   if Assigned(Response) then Response.Write('First call to ASP server');
end;

В данном примере происходит обращение к методу Write интерфейса IResponse. Проверка Assigned(Response) гарантирует, что в момент записи сообщений имеется ссылка на интерфейс.

После этого следует модифицировать созданный Delphi HTML-документ для тестирования сервера, хранящегося в файле Test.asp. В этом документе имеется следующий фрагмент кода на языке VBScript:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")
    DelphiASPObj.{Insert Method name here}
%>

В таком виде этот скрипт работать не будет. Необходимо заменить фразу в фигурных скобках {Insert Method name here} на имя метода ASP-сервера, который генерирует отклик. В нашем примере это имя ScriptContent. Исправленный фрагмент выглядит следующим образом:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")
    DelphiASPObj.ScriptContent
%>

Теперь наш проект необходимо скомпилировать, после чего можно приступить к тестированию созданного ASP-объекта. Для этого необходимо создать виртуальную директорию Internet Information Server, которая обязана иметь разрешение как на чтение (из нее будут читаться данные), так и на выполнениe (из нее будет загружена и запущена библиотека ASP01.dll). Альтернатива — разместить эти файлы в разных директориях, одна из которых имеет доступ Read, а вторая — Execute. Однако в любом случае выбранные директории должны быть доступны с помощью протокола HTTP. Поэтому в первую очередь необходимо обратиться к WWW-сервису Internet Information Server, посмотреть список доступных директорий и при необходимости создать новые с соответствующими правами доступа.

В нашем примере на компьютере, который имеет IP-адрес 10.10.10.65, была создана виртуальная директория /Test, имеющая права доступа Read и Execute и соответствующая физическому адресу на компьютере C:\ASPTest. В эту директорию были скопированы оба файла. Далее в Microsoft Internet Explorer в поле Address был введен следующий URL: HTTP://10.10.10.65/Test/Test.asp. Результат выполнения этого запроса показан на рис. 1. Видно, что скрипт (текст между <% и %>) был замещен результатом выполнения метода ScriptContent созданного нами ASP-объекта.

Теперь подробнее рассмотрим, каким образом выполняется скрипт на странице Test.asp. Internet Information Server, получающий запрос о показе этой страницы, считывает ее содержимое, находит скрипт и выполняет его. При этом запускается интерпретатор VBScript и вызывается команда CreateObject. В случае если ASP01.dll ранее не была загружена, происходит ее загрузка. Для данного запроса создается COM-объект — экземпляр класса TTest (он описан в модуле реализации, для данного примера — U1_01.pas). Ссылка на интерфейс IDispatch (он поддерживается в классе TTest) сохраняется в переменной DelphiASPObj.

При последующем написании кода после имени переменной, хранящей ссылку на интерфейс IDispatch, можно набирать практически любой текст. Интерпретатор VBScript использует текст, который содержится в скрипте и следует после имени переменной (в нашем примере: переменная — DelphiASPObj, метод — .ScriptContent), для того чтобы передать его ASP-серверу. Если ASP-сервер найдет метод с данным именем, он его выполнит. В противном случае генерируется исключение. Поэтому при написании скриптов для ASP-сервера следует быть внимательным в названиях методов и при наличии исключений в первую очередь проверить корректность имен методов. При вызове какого-либо метода ASP-серверу становятся доступными интерфейсы IRequest и IResponse.

А теперь рассмотрим пример создания более сложного ASP-сервера, где запрос клиента анализируется при помощи методов интерфейса IRequest. Задачу поставим следующим образом: дадим возможность клиенту найти запись по фрагменту поля ENAME таблицы EMP в базе данных ORCL, сгенерированной Oracle 8.0.4 при его установке. Для этого в каком-либо редакторе форм создадим форму, содержащую однострочный редактор текста и кнопку Submit. HTML-документ с такой формой выглядит следующим образом:

<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=windows-1257">
<meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<title>Untitled Normal Page</title>
</head>
<body bgcolor="#FFFFFF">
<form action="http://10.10.10.65/Test/Test.asp" method="POST"
name="Query">
      <p>Name (fragment)<input type="text" size="20"
      name="T1"></p>
      <p><input type="submit" name="B1" value="Submit"></p>
</form>
</body>
</html>

При реализации этой формы вместо IP-адреса 10.10.10.65 следует указать IP-адрес компьютера, на котором установлен ASP-сервер.

Поместим этот документ в директорию C:\ASPTest, которая имеет доступ на чтение и выполнение (см. выше) под именем Name.htm. Но прежде чем создавать модуль данных и обращаться к серверу баз данных, необходимо выяснить, каким образом анализируется запрос клиента в ASP-объекте.

Запрос клиента можно анализировать при помощи вызова методов интерфейса IRequest, ссылка на который находится в свойстве Request класса TASPObject — предка класса, где реализуется ASP-объект. Интерфейс IRequest предоставляет три свойства: QueryString, Form и Body, в которых находятся ссылки на интерфейс IRequestDictionary. QueryString содержит параметры запроса, Form — список элементов управления, предоставляемых клиенту, а Body — данные, которые клиент ввел в эти элементы управления. Нам потребуются данные, поэтому мы будем анализировать свойство Body, однако все сказанное ниже о методах IRequestDisctionary применимо и к любому другому свойству типа ICustomDictionary — QueryString и Form.

IRequestDictionary определен в модуле ASPTlb.pas следующим образом:

   IRequestDictionary = interface(IDispatch)
      ['{D97A6DA0-A85F-11DF-83AE-00A0C90C2BD8}']
      function Get_Item(Var_: OleVariant): OleVariant; safecall;
      function Get__NewEnum: IUnknown; safecall;
      function Get_Count: SYSINT; safecall;
      function Get_Key(VarKey: OleVariant): OleVariant; safecall;
      property Item[Var_: OleVariant]: OleVariant read Get_Item; default;
      property _NewEnum: IUnknown read Get__NewEnum;
      property Count: SYSINT read Get_Count;
      property Key[VarKey: OleVariant]: OleVariant read Get_Key;
   end;

Документация о свойствах этого интерфейса в Delphi отсутствует, и остается только догадываться, каким образом из него можно извлечь параметры запроса, введенные пользователем. Привлекая документацию по компоненту TWebDispatcher, который используется при создании CGI-приложений и ISAPI DLL, где также можно анализировать параметры запроса пользователя, можно понять, что свойство Count содержит число элементов управления на форме — для формы, содержащейся в документе Name.htm, оно равно 2. Свойство Key содержит имена элементов управления; для формы, содержащейся в документе Name.htm, это имена T1 (текстовое поле) и B1 (кнопка). И наконец, свойство Item содержит введенные пользователем значения.

Свойство Count работает как положено, то есть возвращает двойку для примера, приведенного выше. При попытке же извлечь имена элементов управления обнаруживается неприятная особенность: в коллекции Key[] индексы начинаются с единицы, а не с нуля, как это принято в приложениях подобного типа. Но все же, обращаясь к коллекции Key с соответствующим индексом — 1 или 2 для нашего примера, можно получить названия элементов управления в виде строковых переменных.

Аналогичная попытка извлечь данные, введенные пользователем в элементы управления, ни к чему хорошему не приводит. Так, при присвоении строковой переменной значения из коллекции Item[] (которая объявлена аналогично коллекции Key[]) происходит исключение. При анализе значения, возвращаемого коллекцией Item[I], обнаруживается, что возвращается интерфейс — потомок IDispatch. Методы и свойства этого интерфейса также не описаны.

Отсутствие описания интерфейса, а также возврат ссылки на него в переменной типа OLEVariant (а не IDispatch) обычно характерно для продуктов, которые находятся в стадии разработки. В этом случае заголовки методов интерфейса, список параметров методов и их число постоянно меняются, а во избежание исключений в клиентских приложениях часто используется позднее связывание. Этот факт настораживает: не исключена возможность изменения методов интерфейса в будущем, что может привести к потере работоспособности созданных ранее ASP-серверов. Однако хочется надеяться, что данный интерфейс устоялся, а в Microsoft по какой-либо причине просто забыли внести изменения в интерфейсный модуль.

Интерфейс — потомок IDispatch имеет два свойства: Count, которое всегда возвращает 1, и Item[]-коллекцию, которая возвращает текст, введенный клиентом в элементах управления. Коллекция Item начинается с индекса 1.

Для понимания и тестирования запроса в ASP-объекте сделаем небольшое дополнение к проекту. Воспользовавшись редактором библиотек типов, создадим новый метод RequestProp, как это было описано ранее (см. рис. 2). Напишем следующий код для метода RequestProp:

procedure TTest.RequestProp;
var
   S:string;
   V:OLeVariant;
   I,J,N:integer;
begin
   S:='';
   if Assigned(Request) then if Request.Body.Count>0 then begin
      for I:=1 to Request.Body.Count do begin
         S:=S+'Key'+IntToStr(I)+'='+Request.Body.Key[I]+'<BR>';
         V:=Request.Body.Item[I];
         if not VarIsEmpty(V) then if varType(V)=varDispatch then begin
            N:=V.Count;
            S:=S+'ItemCount'+IntToStr(I)+'='+IntToStr(N)+'<BR>';
            if N>0 then for J:=1 to N do S:=S+V.Item[J]+'<BR>';
         end;
      end;
   end;
   if Assigned(Response) then Response.Write(S);
end;

Скомпилируем проект и в созданном ранее файле Test.asp изменим код на языке VBScript следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. RequestProp. После этого в Internet Explorer необходимо обратиться к странице Name.htm командой http://10.10.10.65/Test/Name.htm, где вместо 10.10.10.65 следует набрать IP-адрес сервера.

В полученной форме (рис. 3) введем какое-либо значение в элемент управления для редактирования данных и нажмем кнопку Submit. В итоге имеем результат выполнения приведенного выше кода метода RequestProp:

Key1=T1
ItemCount1=1
AM
Key2=B1
ItemCount2=1
Submit

Итак, для определения параметров, введенных клиентом в какой-либо элемент управления, необходимо просмотреть всю коллекцию Keys, найти индекс интересующего нас элемента управления (в нашем примере — это 1, что соответствует ключу T1) и извлечь значение, введенное клиентом, посредством вызова команды Request.Body.Item[Index].Item[1].

Теперь можно перейти к модификации имеющегося сервера — созданию нового метода для доступа к базам данных.

Модуль данных, в который можно помещать невизуальные компоненты, экспертом создания ASP-объекта не генерируется — его необходимо создавать отдельно. Поэтому выберем пункт File|New главного меню среды разработки и из репозитария объектов выберем пиктограмму Data Module. В результате к проекту будет добавлен модуль данных, который мы сохраним под именем U1_02.pas. В этот модуль данных будут помещены невизуальные компоненты доступа к данным (визуальные компоненты в ASP-объектах использовать нельзя).

Вообще говоря, в Web-приложениях (ASP, ISAPI/NSAPI, CGI) показ модальных форм с элементами управления (а диалоги — частный вид таких форм) ни к чему хорошему не приводит. При попытке показать диалог элементы управления на диалоге будут созданы, и приложение будет ожидать, когда диалог будет закрыт (нажатием кнопки OK или Cancel), чтобы продолжить свою работу. Особенность заключается в том, что диалог невидим. Поэтому его нельзя закрыть ни с помощью нажатия на кнопки (они не получают сообщения OnClick), ни с помощью клавиш-акселераторов (сигналы с клавиатуры не посылаются невидимым элементам управления). Визуально программист наблюдает следующее: приложение «висит», отклика от ASP-объекта клиент не получает, а для повторной компиляции проекта требуется перезапуск Internet Information Server либо перезагрузка операционной системы. Даже если команды показа диалогов в ASP-сервере отсутствуют, они могут быть показаны в процессе работы приложения — например, какая-либо из библиотек, используемых приложением, пришлет сообщение об ошибке. Данный факт надо принимать во внимание при написании кода, в котором следует тщательно проверять данные перед их использованием, чтобы внешние приложения или библиотеки не присылали сообщений об ошибках в виде диалогов.

Традиционно доступ к данным в Delphi оcуществляется с помощью механизма Borland Database Engine (BDE), при этом необходимо использовать компоненты TSession, TDatabase и TQuery. Однако при создании ASP-объектов для доступа к данным выяснилось, что в них нельзя использовать BDE для доступа к SQL-серверам — при попытке соединиться с базой данных после передачи параметров, содержащих имя пользователя и пароль, происходит исключение (данный факт был проверен для Oracle 8.0.4 и IB Database 5.5). Через BDE удалось получить доступ только к базе данных DBDEMOS, которая не требует аутентификации пользователя при обращении к данным. К сказанному следует добавить, что в Windows 2000 нельзя получить доступ к данным через BDE в традиционных Web-серверных приложениях ISAPI DLL и CGI, в отличие от предыдущих версий Windows (95/98/NT).

К счастью, в Delphi 5 имеется альтернативный способ доступа к данным — с помощью ADO (ActiveX Data Objects). Для работы с ADO прежде всего необходимо использовать компонент TADOConnection. Поместим его в модуль данных. В инспекторе объектов выберем свойство ConnectionString и вызовем диалог для создания строки. В предложенном диалоге выберем Microsoft OLE DB Provider for Oracle и нажмем кнопку Next (рис. 4).

На второй странице диалога необходимо указать имя сервера (в данном примере — beq-local) и параметры аутентификации: имя пользователя (SCOTT) и пароль (TIGER). Обязательно следует отметить опцию Allow Saving Password, иначе ASP-объект попытается показать диалог ввода имени пользователя и пароля. Протестировать соединение можно нажатием кнопки Test Connection — должно появиться сообщение об успешном соединении с сервером.

Далее в инспекторе объектов следует установить свойство LoginPromp равным False. При работе с другими примерами нужно также изменять свойство DefaultDatabase — имя базы данных, но для данного примера это не обязательно. Проверить правильность установок можно при помощи установления свойства Connected равным True, при этом не должны появляться ни диалог ввода имени пользователя и пароля, ни информация об исключении.

Поместим компонент TADOQuery в модуль данных и в его свойстве Connection сошлемся на определенный выше компонент ADOConnection1.

Следует учесть, что только что разработанный модуль данных автоматически при загрузке ASP-сервера или при обращении клиента к ASP-объекту создаваться не будет. Поэтому необходимо переписать конструктор и деструктор класса TTest, реализация которого находится в файле U1_01.pas. Сошлемся на модуль U1_02.pas в модуле U1_01.pas. В объявлении класса TTest в секции private определим переменную FData типа TDataModule1. В секции public объявим процедуры AfterConstruction и BeforeDesctruction c обязательной директивой override:

TTest = class(TASPObject, ITest)
private
      FData:TDataModule1;
protected
…
public
      procedure AfterConstruction; override;
      procedure BeforeDestruction; override;
end;

Реализуем процедуры AfterConstruction и BeforeDestruction в секции реализации:

procedure TTest.AfterConstruction;
begin
   inherited;
   FData:=TDataModule1.Create(nil);
end;
procedure TTest.BeforeDestruction;
begin
   if Assigned(FData) then begin
      FData.Query1.Active:=False;
      FData.ADOConnection1.Connected:=False;
      FData.Free;
   end;
   inherited;
end;

Далее в библиотеке типов (см. рис. 2) следует создать новый метод, который назовем QueryResponse. Реализуем его следующим образом:

procedure TTest.QueryResponse;
var
   S:string;
   I,J:integer;
begin
   S:=Request.Body.Item[1].Item[1];
   if FData.ADOQuery1.Active then FData.ADOQuery1.Close;
   FData.ADOQuery1.SQL.Clear;
   FData.ADOQuery1.SQL.Add('Select * from EMP');
   FData.ADOQuery1.SQL.Add('where ENAME like ''%'+S+'%''');
   FData.ADOQuery1.Active:=True;
   if FData.ADOQuery1.RecordCount>0 then begin
      FData.ADOQuery1.First;
      for J:=0 to FData.ADOQuery1.Fields.Count-1 do Response.Write(FData.ADOQuery1.Fields[J].FieldName+' ');
      Response.Write('<BR>');
      for I:=1 to FData.ADOQuery1.RecordCount do begin
         for J:=0 to FData.ADOQuery1.Fields.Count-1 do Response.Write(FData.ADOQuery1.Fields[J].AsString+' ');
         Response.Write('<BR>');
         if I<FData.ADOQuery1.RecordCount then FData.ADOQuery1.Next;
      end;
   end;
end;

В данном методе динамически создается SQL-запрос, при этом используются параметры, введенные клиентом в форму на рис. 3. С этим запросом происходит обращение к серверу баз данных, и возвращаемые данные помещаются в HTML-документ. В созданном ранее файле Test.asp изменим код на VBScript следующим образом: вместо строки DelphiASPObj.ScriptContent напишем строку DelphiASPObj. QueryResponse. После этого запустим Microsoft Internet Explorer и снова обратимся к странице Name.htm. Результат выполнения запроса приведен на рис. 5.

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

Пример проиллюстрируем следующим образом: определим в заголовке класса TTest (U1_01.pas) две переменные: FCompanyName:string и FCopyrightYear:string. Определим в библиотеке типов (см. рис. 2) два новых свойства: CompanyName:string и CopyrightYear:integer. В методах *Read и *Wirte для этих свойств определим чтение и возврат данных из описанных выше переменных. В библиотеку типов добавим новый метод ShowCopyright, который реализуем следующим образом:

procedure TTest.ShowCopyright;
var
   S:OLEVariant;
begin
   S:=Format('Copyright (C) %d by %s',[FCopyrightYear,FCompanyName]);
   if Assigned(Response) then Response.Write(S);
end;

В созданном ранее файле Test.asp изменим код на языке VBScript:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")
    DelphiASPObj.CompanyName = "My Company"
    DelphiASPObj.CopyrightYear = 1999
    DelphiASPObj.ShowCopyright
%>

Результатом обращения к ASP-объекту при помощи команды http://10.10.10.65/Test/Test.asp (вместо 10.10.10.65 следует ввести IP-адрес вашего сервера) будет генерация следующей страницы (рис. 6).

Если в файле Test.asp изменить имя компании (а это можно сделать при помощи любого текстового редактора), соответствующие изменения отобразятся в HTML-документе.

При помощи ASP-объектов можно передавать не только текстовую информацию, но и двоичную, например графические изображения. Создадим обычную HTML-страницу следующего вида:

<HTML><BODY>
<TITLE> Picture display </TITLE>
<CENTER><H3> Refer to picture from ASP</H3></CENTER>
<HR><IMG SRC="http://192.168.0.2/Test1.asp" border="0"><HR>
</BODY></HTML>

Файл Test1.asp выглядит следующим образом:

<% Set DelphiASPObj = Server.CreateObject("ASP01.Test")
    DelphiASPObj.GetPicture
%>

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

Метод GetPicture реализуем следующим образом:

procedure TTest.GetPicture;
var
   BM:TBitmap;
   OV:OLEVariant;
   S:TMemoryStream;   
P:pointer;
begin
   Response.Buffer:=True;
   Response.Clear;
   Response.ContentType:='image/bmp';
   BM:=nil; S:=nil;
   try
      BM:=TBitmap.Create;
      BM.LoadFromFile('C:\InetPub\scripts\finance.bmp');
      S:=TMemoryStream.Create;
      BM.SaveToStream(S);
      OV:=VarArrayCreate([1,S.Size],varByte);
      P:=VarArrayLock(OV);
      Move(S.Memory^,P^,S.Size);
      VarArrayUnlock(OV);
      Response.BinaryWrite(OV);
      Response.End_;
   finally
      BM.Free;
      S.Free;
   end;
end;

Для передачи двоичных данных необходимо указать их тип в свойстве Response.ContentType и воспользоваться методом BinaryWrite объекта Response. В качестве параметра этого метода используется переменная типа OLEVariant. В этой переменной должны находиться двоичные данные в виде массива байтов. Этот массив передается как отклик. В результате обращения к исходному HTML-документу клиент получит отклик (рис. 7).

До сих пор мы рассматривали in-process-серверы, которые работают в адресном пространстве Internet Information Server и реализуются в динамически загружаемых библиотеках — DLL. В заключение следует сказать и о создании out-of-process ASP-серверов. Такие серверы реализуются в виде исполняемых файлов и работают в отдельном адресном пространстве.

Для создания out-of-process-сервера необходимо открыть готовый проект, компиляция которого приводит к созданию исполняемого файла, или создать новый проект с помощью пункта меню File|New Application. После этого следует выбрать пункт главного меню File|New среды разработки Delphi, а затем со страницы ActiveX репозитария объектов — пиктограмму Active Server Object. В результате будет сгенерирована библиотека типов, содержащая методы OnStartPage и OnEndPage. Все, что было сказано выше по поводу in-process-сервера, справедливо и в отношении out-of-process-сервера: разработка ASP-объекта заключается в создании новых методов, которые будут вызываться из VBScript-кода ASP-страницы.

Сложности возникают при попытке протестировать out-of-process-сервер. По умолчанию параметры настройки Internet Information Server таковы, что запуск им приложений запрещен, а разрешена только загрузка DLL. Более того, в администраторе Internet Information Server отсутствует опция, позволяющая разрешить или запретить использование приложений как ASP-серверов. Для разрешения запуска исполняемого файла как ASP-сервера необходимо выполнить следующий код:

   Set oWebService = GetObject("IIS://LocalHost/W3svc")
   oWebService.Put "AspAllowOutOfProcComponents", True
   oWebService.SetInfo

Для выполнения данного скрипта необходимо, чтобы пользователь, инициирующий его выполнение, имел статус администратора. По этой причине данный скрипт бесполезно определять в HTML-документе и запускать его с использованием Internet Explorer, так как любой пользователь Internet имеет статус гостя (Guest), а не администратора. Скрипт необходимо поместить в обработчик какого-либо события в среде разработки Visual Basic или VBA и запустить его оттуда (либо использовать Windows Scripting Host. — Прим.ред.).

К сожалению, в Delphi отсутствует метод, аналогичный методу VBScript GetObject. Очевидно, что метод GetObject возвращает ссылку на интерфейс IDispatch Internet Information Server. Однако при этом в качестве параметра он использует строку, которая не является классовой (ее GUID отсутствует в системном реестре). В Delphi аналогичных методов нет, по крайней мере, в виде простых вспомогательных функций. Возможно, подобный метод станет доступным в следующих версиях Delphi.

КомпьютерПресс 2'2001