ASP на блюдечке. Часть 2

Загрузка файлов с помощью ASP и VBScript

Неужели нельзя обойтись без ActiveX-компонента? Конечно, можно. Давайте рассмотрим, каким образом можно загрузить на сервер, например, файл с иллюстрацией без использования встраиваемого компонента. Попробуем обойтись исключительно средствами ASP. Разумеется, время выполнения операции существенно увеличится, однако в нашем случае (при загрузке всего одного или двух файлов) это будет почти не ощутимо.

Проблема заключается в том, что, как правило, компоненты, предоставляемые «третьими» фирмами, не являются частью самого ASP, а представляют собой так называемые черные ящики, настроить которые под конкретные нужды невозможно. Будучи «третьими» компонентами, они должны устанавливаться на серверы, а это, в свою очередь, означает, что понадобится скопировать файлы компонентов (DLL или OCX) на сервер и зарегистрировать их. Какие проблемы? Никаких, если вы сами осуществляете хостинг вашего сайта. Однако если этим занимается какая-нибудь другая компания, то могут возникнуть проблемы с размещением и (или) регистрацией компонентов на сервере, обслуживающем ваш сайт.

Для того чтобы понять, как работает upload-скрипт, для начала посмотрим, каким образом посылаются данные из окна нашего браузера серверу с помощью протокола HTTP, то есть поймем, как работает «multipart/form-data».

В начало В начало

Форма загрузки

Давайте разберемся в механизмах обработки HTML-форм более подробно. Итак, атрибут формы enctype определяет тип содержимого, используемый для кодирования множества элементов (полей) формы с целью их последующей отправки на сервер. Атрибут enctype, используемый по умолчанию, равен «application/x-www-form-urlencoded». Для передачи больших объемов данных, таких как файлы и (или) двоичные данные, используется значение атрибута «multipart/form-data».

Сообщение типа «multipart/form-data» содержит последовательности, каждая из которых представлена блоками, каждый из которых, в свою очередь, содержит следующие обязательные поля:

  • заголовок content-disposition, значение которого равно «form-data», — определяет тип пришедших на сервер данных;
  • имя атрибута — определяет имя элемента формы.

Для файлов этот набор выглядит несколько иначе:

  • заголовок content-type посылаемых двоичных данных;
  • атрибут с именем файла и полным путем к нему на компьютере клиента.

Рассмотрим простой пример HTML:

<FORM METHOD="POST" ENCTYPE="multipart/form-data"  ACTION="upload.asp">
<INPUT TYPE="Text" NAME="email" VALUE="rouben@iname.com"><BR>
<INPUT TYPE="file" NAME="blob"><BR>
<INPUT TYPE="submit" NAME="Enter">
</FORM>

По нажатии кнопки «SubmitQuery» в этом окошке на сервер придет следующий запрос:

-----------------------------7cf87224d2020a
Content-Disposition: form-data; name="email"
rouben@iname.com
-----------------------------7cf87224d2020a
Content-Disposition: form-data; name="blob"; filename="f:\CD.GIF"
Content-Type: image/pjpeg
яШяаJFIFHHяЫC…
…яЩ
-----------------------------7cf87224d2020a
Content-Disposition: form-data; name="Enter"
Submit Query
-----------------------------7cf87224d2020a--

Эти данные могут быть отображены, если их послать клиенту в качестве ответа на запрос. Двоичные данные должны считываться с помощью ASP-функции Request.binaryRead, а записываться с помощью Response.binaryWrite:

<%
Response.BinaryWrite(Request.BinaryRead(Request.TotalBytes))
%>

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

-----------------------------7cf87224d2020 a, а последний разграничитель завершается    ' -- '

Для каждого блока (поля, элемента формы) имеется одно значение content-disposition. Атрибут name указывает, к какому именно элементу формы относится тот или иной блок (в нашем случае e-mail, blob или Enter). Для элемента формы файла (в нашем случае blob) имя файла также является частью заголовка content-disposition, а заголовок content-type определяет тип данных.

В начало В начало

Скрипт-загрузчик

Очевидно, что все содержимое HTTP-запроса должно быть «разобрано» по частям, рассмотрено и обработано нашим скриптом. В таких языках, как VB или C++, это весьма тривиальная задача, так как для этого предусмотрено множество объектов и методов. Применяя VBScript, сделать это не так просто, но все же возможно.

Поскольку посылаемые данные представлены в двоичном формате, мы будем вынуждены пользоваться для работы с ними соответствующими функциями языка VBScript. Логично предположить, что эти данные представляют собой последовательности байтов и такие функции, как MidB, InstrB и LenB, — именно то, что нам нужно. Мы также должны избегать использования классических строк в VBScript потому, что они представляются в формате Unicode и не подходят для передачи одиночных байтов. Это единственные функции VBScript, предназначенные для операций с байтами. Нам же нужен метод, позволяющий получать unicode-строку из «разбираемых» данных, с тем чтобы в дальнейшем использовать ее в VBScript-коде. И еще нам потребуется функция — преобразователь Unicode-строки в байт-строку, с тем чтобы использовать эту строку в качестве аргумента функции InstrB.

Определим объект «UploadRequest». Он содержит все поля нашей формы и выглядит следующим образом:

UploadRequest :
"email", UploadControl 1 :
"Value", rouben@iname.com
"blob" , UploadControl 2 :
"filename", F:/CD.GIF
"ContentType" : image/gif
"Value" : GIF89ai…

Такой «объектный» подход к организации позволит в дальнейшем упростить доступ к обрабатываемым данным.

Для начала надо найти границу байта, которая позволит определить, где заканчивается цикл по каждому полю (элементу формы).

PosBeg = 1
PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(13)))
boundary = MidB(RequestBin,PosBeg,PosEnd-PosBeg)
boundaryPos = InstrB(1,RequestBin,boundary)

Проблема в том, что, как было уже отмечено, функция InstrB требует строки байтов, в то время как мы располагаем Unicode-строками. Функция getByteString (String) позволит нам преобразовать unicode-строку в строку байтов. Теперь в цикле найдем конец последовательности:

Do until (boundaryPos=InstrB(RequestBin,boundary & getByteString("--")))
Для каждого шага в цикле мы будем обрабатывать одно поле. Все данные, относящиеся к этому полю, будем сохранять в объекте типа Dictionary, и, таким образом, для каждого элемента формы будет порождаться новый объект UploadControl.
Dim UploadControl
Set UploadControl = CreateObject("Scripting.Dictionary")

Сначала мы получим имя поля из заголовка «content-disposition». В конце имени располагается символ chr(34). Этим мы и будем руководствоваться для определения конца.

Pos = InstrB(BoundaryPos,RequestBin,_getByteString("Content-Disposition"))
Pos = InstrB(Pos,RequestBin,getByteString("name="))
PosBeg = Pos+6
PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(34)))
Name = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg))

Далее необходимо проверить, является поле элементом типа «файл» или элементом типа «текст». Если это элемент типа «текст», никаких данных, кроме имени поля, нам не потребуется, а если это элемент типа «файл», то нам понадобятся имя файла и заголовок content-type.

PosFile=InstrB(BoundaryPos,RequestBin,getByteString("filename="))
PosBound = InstrB(PosEnd,RequestBin,boundary)
If PosFile<>0 AND (PosFile<PosBound) Then 'Проверка файл ли это?

Если это файл, то добавим имя и путь к файлу в объект dictionary. Имя файла есть строка символов, которую необходимо преобразовать в формат unicode. Сделать это можно при помощи функции getString().

 ' Чтение имени файла, content-type и содержимого файла
PosBeg = PosFile + 10
PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(34)))
FileName = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg))
' Добавление имени файла к объекту dictionary
UploadControl.Add "FileName", FileName
Pos = InstrB(PosEnd,RequestBin,getByteString("Content-Type:"))
PosBeg = Pos+14
PosEnd = InstrB(PosBeg,RequestBin,getByteString(chr(13)))
'Добавление content-type к объекту dictionary
ContentType = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg))
UploadControl.Add "ContentType",ContentType

А вот теперь можно получить содержимое файла, которое нет необходимости преобразовывать, поскольку это двоичные данные. Попросту необходимо сохранить в файловой системе на сервере или разместить в базе данных как объект типа blob (binary long object).

 ' Получение содержимого объекта
PosBeg = PosEnd+4
PosEnd = InstrB(PosBeg,RequestBin,boundary)-2
Value = MidB(RequestBin,PosBeg,PosEnd-PosBeg)
Else

Если же это не тип «текст», необходимо преобразовать содержимое в строку Unicode для дальнейшего использования в VBScript.

 ' Получение содержимого объекта
Pos = InstrB(Pos,RequestBin,getByteString(chr(13)))
PosBeg = Pos+4
PosEnd = InstrB(PosBeg,RequestBin,boundary)-2
Value = getString(MidB(RequestBin,PosBeg,PosEnd-PosBeg))
End If

Содержимое также добавляется к объекту dictionary.

 'Добавление содержимого к объекту dictionary
 UploadControl.Add "Value" , Value

В конце концов объект dictionary должен быть добавлен к глобальному объекту dictionary всей формы.

 'Добавление объекта dictionary к глобальному объекту dictionary
UploadRequest.Add name, UploadControl
'В цикле следующий объект
BoundaryPos=InstrB(BoundaryPos+LenB(boundary),RequestBin,boundary)
Loop
End Sub

Функция преобразования однобайтовых строк в двухбайтовые (формата Unicode).

Function getString(StringBin)
    getString =""
    For intCount = 1 to LenB(StringBin)
        getString = getString & chr(AscB(MidB(StringBin,intCount,1)))
    Next
End Function

Функция преобразования строки в строку байтов (используется для форматирования аргументов для функции InstrB).

Function getByteString(StringStr)
    For i = 1 to Len(StringStr)
        char = Mid(StringStr,i,1)
        getByteString = getByteString & chrB(AscB(char))
    Next
End Function

Вызов скрипта загрузки

byteCount = Request.TotalBytes
RequestBin = Request.BinaryRead(byteCount)
Dim UploadRequest
Set UploadRequest = CreateObject("Scripting.Dictionary")
BuildUploadRequest RequestBin

Извлечение данных формы

email = UploadRequest.Item("email").Item("Value")
picture = UploadRequest.Item("blob").Item("Value")
contentType = UploadRequest.Item("blob").Item("ContentType")
filepathname = UploadRequest.Item("blob").Item("FileName")

Загруженные таким образом данные можно, к примеру, переслать обратно клиенту:

Your email is : <%=email%>
File name of you picture is <%=filepathname%>
File type of your picture is <%=contentType%>

Двоичные данные можно таким же образом переслать обратно клиенту. Для этого можно использовать метод BinaryWrite:

Response.ContentType = contentType
Response.BinaryWrite picture

Однако, как правило, в случае если объект представляет собой файл, необходимо записать данные на жесткий диск в виде файла или записи в базе данных. С этой целью мы воспользуемся объектом FileSystem.

'Создадим экземпляр объекта типа FileSytemObject
Set ScriptObject = Server.CreateObject("Scripting.FileSystemObject")

Каталог, в котором мы собираемся сохранить наш файл, может быть указан несколькими способами: абсолютным, а также относительно каталога, зарегистрированного на сервере в качестве корневого каталога Web-сайта (для IIS это каталог C:\InetPub\wwwroot\ по умолчанию). Получить этот «виртуальный» каталог можно при помощи серверной переменной «PATH_INFO».

Метод «Write», с помощью которого и будет осуществляться запись в файл, требует ввода unicode-строки в качестве аргумента, и нам потребуется преобразовать массив байтов в unicode-строку. Метод Write преобразовывает эту Unicode-строку и записывает ее в формате ASCII. Таким образом и формируется файл, содержащий двоичный образ нашей входной строки байтов:

 'Создать файл и записать в него данные
Set MyFile = ScriptObject.CreateTextFile(Server.mappath(Request.ServerVariables_("PATH_INFO")) & "uploaded" & filename)
For i = 1 to LenB(value) MyFile.Write chr(AscB(MidB(value, i, 1))) Next
MyFile.Close

Таким образом, окончательная версия обработчика нашей формы в случае применения только ASP и VBScript-функций (файл Upload2DBE.asp) для загрузки файла будет выглядеть следующим образом:

…
<!--#include file="upload.asp"--> <!—Основные функции реализованы именно в этом файле>
<%
…
Response.Expires = 0
Response.Buffer = TRUE
Response.Clear
byteCount = Request.TotalBytes
RequestBin = Request.BinaryRead(byteCount)
Dim UploadRequest
Set UploadRequest = CreateObject("Scripting.Dictionary")
BuildUploadRequest RequestBin ‘ Функция-обработчик
 ‘Чтение полей формы
DI = UploadRequest.Item("DETAILS").Item("Value")
AUT = UploadRequest.Item("Author").Item("Value")
TIT = UploadRequest.Item("Title").Item("Value")
ART = UploadRequest.Item("Article").Item("Value")
SBJ = UploadRequest.Item("Subject").Item("Value")
ANN = UploadRequest.Item("Annotation").Item("Value")
IsTopNew = UploadRequest.Item("IsTopNewSelector").Item("Value")
Password = UploadRequest.Item("Password").Item("Value")
value = UploadRequest.Item("IFile1").Item("Value")
‘ Определение содержимого и пути, а также имени файла
contentType = UploadRequest.Item("IFile1").Item("ContentType")
filepathname = UploadRequest.Item("IFile1").Item("FileName")
filename = Right(filepathname,Len(filepathname)-InstrRev(filepathname,"\"))
‘Создание указателя на объект типа FileSystemObject
Set ScriptObject = Server.CreateObject("Scripting.FileSystemObject")
‘Создание пути, по которому файл будет сохранен
MainPath = Server.mappath(Request.ServerVariables("PATH_INFO"))
pathEnd = Len(MainPath) — 14
‘Создание файла
Set MyFile = ScriptObject.CreateTextFile("c:\InetPub\wwwroot\Articles\"  & filename)
 
‘Запись в файл последовательности значений
For i = 1 to LenB(value)
MyFile.Write chr(AscB(MidB(value,i,1)))
Next
MyFile.Close
…
%>
…
В начало В начало

А теперь давайте попробуем сами разработать ActiveX-компонент…

Согласитесь, что было бы неплохо самим научиться создавать ActiveX-компоненты для ASP. Тем, кто активно программирует, освоить это будет очень несложно, тем более что для этого подходят почти все современные средства разработки. Мы будем рассматривать способы создания ActiveX-компонентов для ASP как на Microsoft Visual Basic 6.0 — для неискушенных в области классического программирования, так и в Microsoft Visual C++ 6.0 — для тех, кто знаком с языком программирования C++ и кому он ближе и понятнее.

В начало В начало

…с помощью Microsoft Visual Basic 6.0

Для начала попробуем создать активный серверный компонент для загрузки файла на сервер с помощью Microsoft Visual Basic 6.0. Для простоты рассматрим простейший пример HTML-формы загрузки файла (файл OCUpload.htm).

Данная форма предназначена для загрузки на сервер единственного файла. Определим также скрипт-реакцию на эту форму: ACTION="UploadReceive.asp"

Соответственно вызов активного серверного компонента из ASP-скрипта (файл UploadReceive.asp) будет выглядеть следующим образом:

…
<%
Set ObjUpload = Server.CreateObject("UploadProject.UploadClass")
ObjUpload.DoUpload
%>
… 

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

  1. Запустим Microsoft Visual Basic 6.0;
  2. В появившемся диалоговом окне выберем «ActiveX DLL»;
  3. Нажмем на «Open»;
  4. В окне инспектора проекта, выделив имя проекта, нажмем правую кнопку мыши и выберем пункт «Project1 Properties»;
  5. Поменяем имя проекта на «UploadProject» и нажмем на «ОК»;
  6. Выбрав класс «Class1», в окошке пониже перепечатаем имя класса с «Class1» на «UploadClass»;
  7. После открытия проекта войдем в пункт меню Project -> References;
  8. Установим флажок напротив пункта «Microsoft Active Server Pages Object Library»;
  9. Нажмем на «OK»;
  10. В окне кода класса впечатать функцию «DoUpload».

Как и у любого компонента, используемого в ASP, необходимо определить контекстные объекты скрипта, которые мы собираемся использовать в нашем проекте. Мы «перехватим» объекты в функции «OnStartPage». А поскольку нам понадобится лишь ASP-метод «Request», то это единственный контекстный объект, который мы сделаем доступным. Так выглядит код определения контекста процедуры загрузки, которую мы будем создавать:

Option Explicit
‘--- Определим ASP-объекты --
Private MyScriptingContext As ScriptingContext
Private MyRequest As Request
Public Sub OnStartPage(PassedScriptingContext As ScriptingContext)
‘--- Создадим ASP объекты --
Set MyScriptingContext = PassedScriptingContext
Set MyRequest = MyScriptingContext.Request
End Sub

Таким образом, мы можем использовать объектную переменную «MyRequest» точно так же, как если бы мы использовали любой Request-объект в ASP-файле. В нашем случае мы применяем ASP-файл (UploadReceive.asp) для управления ActiveX-компонентом. Для того чтобы использовать любой ASP-объект и его свойства в коде, мы должны передавать ASP-объект нашему компоненту вышеприведенным фрагментом кода. Мы будем использовать лишь два Request-метода ASP: «TotalBytes» и «BinaryRead».

Однако следует учитывать одно важное ограничение. Так как мы будем считывать данные (содержимое) файла при помощи функции «BinaryRead», то не следует забывать, что в этом случае использовать метод «Request.Form» нельзя. Иначе говоря, мы можем получить данные о нашем файле исключительно с помощью функции «BinaryRead». Однако, как будет показано ниже, это не создаст видимых трудностей при считывании имени или других атрибутов.

Как уже отмечалось выше, заголовок HTTP, посылаемый «multipart/form-data»-формой, включает в себя блоки, содержащие данные элементов формы. Мы будем извлекать первую часть заголовка HTTP (первый его блок), так как в нашем случае поле типа «файл» расположено в форме первым. Далее мы считаем его в строку, откуда извлечем имя файла, используя для этого VB-функцию «InStr» и цикл. Извлечь содержимое файла (сами данные) гораздо проще.

Теперь подошло время создания главной процедуры загрузки файла — «DoUpload». Объявим публичную функцию:

Public Sub DoUpload()

‘~~~~~~~~~~ Определим переменные ~~~~~~~~~~~ Dim varByteCount
Dim binArray() As Byte
Dim lngFileDataStart As Long
Dim lngFileDataEnd As Long
Dim strHeadData As String
Dim intFileTagStart As Integer
Dim strPathName As String
Dim intPathNameStart As String
Dim strFileName As String
Dim intFileNameStart As Integer
Dim intFileNameEnd As Integer
Dim strDelimeter As String
Dim intCount As Integer
Dim lngCount As Long
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Прежде всего нам предстоит определить количество посланных клиентом байтов. А поскольку переменная «TotalBytes» есть константа, то мы присвоим ее значение объявленной нами переменной «varByteCount».

 '~~~~~ Подсчет числа байтов данных ~~~~~~~~~~~~
varByteCount = MyRequest.TotalBytes
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Теперь можно заняться и «сбором» данных, считывать которые посредством вызова функции «BinaryRead» мы будем в массив типа «Byte», а извлеченные данные помещать в массив «binArray». Размерности массива «binArray» мы переопределим в соответствии с размерностью наших данных следующим образом:

 '~~~~~ Размещение данных в массиве байтов ~~~~~~~~~
ReDim binArray(varByteCount)
binArray = MyRequest.BinaryRead(varByteCount)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Итак, нам удалось вычленить данные из HTTP-заголовка. Теперь попробуем извлечь из полученных данных имя посланного нам клиентом файла. Для этого необходимо «разобрать» полученную строку байтов стандартными функциями операций со строками.

 '~~~~~ Разбор данных заголовка на предмет нахождения первого элемента ~~~~~
intCount = 0 Do Until Right(strHeadData, 4) = vbCrLf & vbCrLf
strHeadData = strHeadData & Chr(binArray(intCount))
intCount = intCount + 1
Loop

Заголовок HTTP располагается в самом начале нашего массива байтов. Цикл «Do Until» просматривает этот массив до тех пор, пока не обнаружит два последовательно идущих символа перевода строки, которые и обозначают конец блока параметров и одновременно начало данных файла. Каждый повтор цикла добавляет к строке «strHeadData» один байт. По окончании цикла мы получим пару {имя_элемента, значение_элемента} в отдельной строке «strHeadData». Эта пара может выглядеть следующим образом:

---------------------7ce3023980c CR LF
Content-Disposition: form-data; name = “UploadFormName”: filename = “E:\WebWors97.mdb”  CR LF
Content-Type: text/plain CR LF CR LF

Сам процесс «извлечения» имени файла реализуем в несколько шагов следующим образом:

intFileTagStart = InStr(strHeadData, "UploadFormName")

Найдя в ней значение «filename=», увеличим значение указателя на 10, таким образом переместившись в необходимую нам позицию. Сейчас мы находимся на позиции начала имени файла в строке.

intPathNameStart = InStr(intFileTagStart, strHeadData, "filename=") + 10

Для того чтобы найти символ конца имени файла в строке, поищем первый символ «CR LF» с помощью функции «intFileTagStart»:

intFileNameEnd = InStr(intFileTagStart, strHeadData, vbCrLf) — 1

Совсем неплохо было бы выяснить, ввел ли пользователь значение. Конечно, это следовало сделать гораздо раньше, и не в ActiveX-модуле, а средствами VBScript, или JavaScript непосредственно в модуле формы (как это было показано ранее), или, на худой конец, с помощью самого ASP, но все-таки настоятельно рекомендую писать код активных серверных компонентов таким образом, чтобы обеспечить независимость от тех, кто в дальнейшем будет их использовать, то есть обеспечить универсальность и защиту «от дурака»:

If intPathNameStart = intFileNameEnd Then Exit Sub strPathName = 
                Mid(strHeadData, intPathNameStart, intFileNameEnd — intPathNameStart)

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

For intCount = intFileNameEnd To intPathNameStart Step -1
If Mid(strHeadData, intCount, 1) = "\" Then
intFileNameStart = intCount + 1
Exit For
End If
Next strFileName = Mid(strHeadData, intFileNameStart, intFileNameEnd — intFileNameStart)

Переместимся на следующий разделитель (то есть на конец текущего блока)

strDelimeter = Left(strHeadData, InStr(strHeadData, vbCrLf) — 1)
lngFileDataStart = InStr(intFileTagStart, strHeadData, vbCrLf & vbCrLf)  + 4
lngFileDataEnd = CLng(varByteCount) — (Len(strDelimeter) + 6)

и сохраним данные в файле в корневом (или любом другом) каталоге на жестком диске «C:».

Open "C:\" & strFileName For Binary Access Write As #1
For lngCount = lngFileDataStart To lngFileDataEnd
Put #1, , (binArray(lngCount))
Next
Close #1

Полный файл-архив проекта с рассмотренным примером можно найти на нашем CD-ROM.

В начало В начало

… с помощью Microsoft Visual C++ 6.0

А зачем, собственно, нужен C++? Ведь, в конце концов, Visual Basic намного проще, и процесс создания COM-компонентов с его помощью занимает гораздо меньше времени. Занимаясь разработкой чего бы-то ни было на C++, мы увеличиваем как стоимость, так и время разработки. К сожалению, это мнение в последнее время высказывается все чаще. Однако не следует забывать, что, несмотря на все эти казалось бы резонные доводы, среди всевозможных языков программирования и описания сценариев (VB, Java, ASP и так далее) C++ остается чемпионом по скорости выполнения и эффективности кода. И хотя выигрыш этот кажется невеликим при сравнении компонента для одного клиента, в реальной Web-обстановке, когда клиентов сотни, а порой даже тысячи и сотни тысяч и вычислительная нагрузка на серверы растет в геометрической прогрессии, этот выигрыш колоссален. Пара строк ASP-кода, обрабатывающая длинные строки символов, выглядит великолепно на сервере с небольшим числом клиентов, но может «поставить на колени» сервер с множеством клиентов.

Отсюда мораль: самые «узкие» места целесообразно разрабатывать на каком-нибудь языке программирования вроде C++, а ASP использовать в качестве «клея» между быстро работающими ActiveX-компонентами, созданными с его помощью. Но хватит слов, давайте перейдем к делу. Для того чтобы посвятить читателя в азы разработки ActiveX-компонента и его применения в нашем газетном сайте, предусмотрим следующую функцию.

Пусть у пользователя будет возможность просматривать содержимое каталога «C:\InetPub\wwwroot\Articles», то есть список всех файлов этой папки на сервере, причем с возможностью просмотра каждого файла в отдельности. Первое, что приходит в голову в этом случае, это страница, формируемая с помощью ActiveX-компонента, возвращающего список файлов заданного каталога. Сформируем этот компонент прямо сейчас, используя Microsoft Visual C++ 6.0.

Итак, надеюсь, вы уже запустили MSVC. Для начала создайте новый проект, в появившемся диалоговом окне укажите его тип («ATL Com AppWizard») и задайте имя. Назовем его «Dir» — в честь популярной команды MS-DOS.

Далее необходимо уточнить формат разрабатываемого компонента, а именно динамически подключаемой библиотеки в следующем диалоговом окне.

Как вы, наверное, заметили, во втором диалоге MSVC предупреждает о том, что мастер создает лишь ATL-проект и что по завершении его работы понадобится вручную создавать COM-объект в его составе. А сделать это можно следующим образом.

После выбора в появившемся меню строки «New ATL Object…» появится диалоговое окно, в котором необходимо будет уточнить тип создаваемого ActiveX-объекта, а именно активный серверный компонент («ActiveX Server Component»).

Теперь необходимо определить имена создаваемого класса и исходных файлов. Пусть наш класс называется «FilesList». Если вы введете это имя в соответствующем поле следующего диалога, то увидите следующее окно.

Следует уделить внимание двум другим вкладкам этого диалога: «Attributes» и «ASP». Перейдя во вкладку «Attributes», можно, в частности, определить поточную модель разрабатываемого COM-компонента. Вкладка «ASP» позволяет определить, какие объекты самого ASP будут доступны из разрабатываемого компонента, то есть указатели, на какие объекты ASP будут создаваться в реализации (implementation) самого компонента.

Следует также заметить, что конструирование указателей на внутренние (Intrinsic) объекты ASP производится в конструкторе компонента OnStartPage (не путать с одноименной функцией файла Global.asa), деструктурирование — в деструкторе OnEndPage, без применения которых доступ к внутренним объектам ASP будет закрыт, поэтому галочка в поле OnStartPage/OnEndPage должна быть выставлена. Реально в разрабатываемом компоненте нам необходим лишь объект ASP «Response», однако в целях обучения выделим все объекты. После всех проделанных операций рекомендую сохранить проект.

Как видно, MSVC создал функции OnStartPage и OnEndPage в IfilesList в нашем объекте CfilesList. Посмотрим полученный код:

STDMETHODIMP CFileslist::OnStartPage (IUnknown* pUnk)
{
if (!pUnk) return E_POINTER;
 
CcomPtr <IScriptingContext> spContext;
HRESULT hr;
 
// Получаем дескриптов контекстного интерфейса IScriptingContext
hr = pUnk->QueryInterface(IID_IScriptingContext, (void **)&spContext);
if (FAILED(hr)) return hr;
 
// Получаем указатель на объект ASP Response
hr = spContext->get_Response(&m_piResponse);
if (FAILED(hr)) {
m_piRequest.Release();
return hr;
}
…
m_bOnStartPageCalled = TRUE;
return S_OK;
}
 
STDMETHODIMP CFileslist::OnEndPage ()
{
m_bOnStartPageCalled = FALSE;
// Высвобождаем интерфейсы
m_piResponse.Release();
…
return S_OK;
}

Далее необходимо добавить функцию, которая и будет выполнять необходимые нам действия. Напишем ее:

STDMETHODIMP CFileslist::WriteDir()
{
char *str, *Lstr; // Два указателя на строки
 
const int MaxMem = 4096; // 4 Кбайт памяти будет выделено
           // под считывание данных о файлах каталога
int FullLen = 0; // Значение длины формируемой строки
 
 str = new char[MaxMem]; // Создаем указатели
 Lstr = new char[MaxMem]; // на объекты заданного размера
 
 strcpy (str, ""); // И обнуляем их
strcpy (Lstr, "");
 
struct _finddata_t fnddat; // Определяем переменную типа_finddata_t
 long Handle; // Длинное целое для возврата результата поиска файлов
 int done; // Целое для возврата результата поиска файлов
 
Handle = _findfirst("c:\\inetpub\\wwwroot\\Articles\\*.*",&fnddat);
// Ищем все файлы заданного каталога, а результат записываем в структуру типа _finddata_
done = Handle;
 
while (done != -1) { // Пока такие файлы есть
 done = _findnext(Handle, &fnddat); // Находим следующий файл
if (done != -1) {
FullLen = FullLen + strlen (fnddat.name) + 45; // Добавляем суммарную длину получаемой строки
 if (FullLen >= MaxMem — 150) { // Если объем выделенной оперативной памяти близится к завершению
 strcpy (Lstr, "Maximum memory allocated for dirinfo overriden!"); // То формируем предупреждение
 break; // И прекращаем работу цикла поиска новых файлов
}
else { // В противном случае
 strcat (Lstr, "<a href= http://localhost/Articles/"); // Формируем ссылку
 strcat (Lstr, fnddat.name); // на файл
strcat (Lstr, ">");
strcat (Lstr, fnddat.name);
strcat (Lstr, "</a>");
strcat (Lstr, "<br>");
}
}
}
 
CComVariant vtHeader(OLESTR("<H1>Articles files: </H1>")); // Конструируем OLE-строку
 m_piResponse->Write(vtHeader); // И выводим ее на страницу, используя указатель
           // на объект ASP Response — m_piResponse
 
CComVariant vtHeader1(OLESTR(str)); // Таким же образом поступаем с
 m_piResponse->Write(vtHeader1); // сформированной строкой
 
delete (str); // Удаляем указатели на созданные объекты (текстовые строки)
delete (Lstr);
 
return S_OK; // И возвращаем значение S_OK
}

Не забудьте прописать заголовок нашей новой функции WriteDir в файле FilesList.h в разделе Active Server Pages Methods public сразу за объявлениями функций OnStartPage и OnEndPage:

STDMETHOD(WriteDir)();

Кроме того, мы использовали функции «_findfirst» и «_findnext», а также структуру, которую они заполняют. Все эти компоненты объявлены вo включаемом файле «io.h», поэтому не забудьте включить и его, что удобно сделать в файле «StdAfx.h».

После всех действий нажмите на клавишу F7, и если вы все сделали правильно, то MSVC построит вашу библиотеку и зарегистрирует ее в системе. Самим же это можно сделать, выполнив команду:

 Regsrv32 Имя_DLL_Файла.

Итак, нам осталась самая малость. Написать ASP-скрипт, который, используя функцию «WriteDir», выведет в окне клиента список всех файлов каталога с погруженными статьями и иллюстрациями к ним нашего газетного сайта со ссылками на них (файл Dir.asp):

…
<%
Dim myObject ‘ Создадим переменную,
 set myObject = Server.CreateObject("Dir.FilesList") ‘ Которой присвоим указатель
‘ На класс FilesList компонента 
Dir
 myObject.WriteDir() ‘ И выполним ее метод WriteDir()
%>
…

Страничка — результат выполнения данного скрипта — будет выглядеть следующим образом.

Полный файл-архив проекта с рассмотренным примером находится на нашем CD-ROM.

В начало В начало

Еще несколько слов в защиту C++

Компилируемые языки программирования, такие как Visual C++, Borland C++ или Delphi, используют исходный текст для генерации машинного кода (набора машинных команд). В результате формируется набор инструкций, к которым вычислительная машина может обращаться напрямую (отсутствует необходимость в использовании каких бы то ни было препроцессоров, «до-компиляторов» и так далее) и которые поэтому выполняются очень быстро.

Интерпретируемые языки программирования, такие как Visual Basic и J++, в техническом смысле не компилируются. Конечно, в результате компиляции получается выполняемый модуль или библиотека, но это не машинный код. Вместо него, попросту говоря, используется объектный код, который интерпретируется в машинный во время выполнения самой команды, что, безусловно, требует большей вычислительной мощности от процессора.

Кроме «скоростного» фактора нельзя забывать об уникальных возможностях, предоставляемых такими языками, как C++ и Pascal (Delphi). Они, как никакие другие, активно используют объектно-ориентированные концепции в программировании (технологии OOP и OOD), которые позволяют не терять контроль над кодом при разработке больших проектов и осуществлять переносимость целых классов функций. Конечно, с точки зрения новичка все это может показаться сложным и недоступным, но, поверьте, это не так.

Ничего не имея против использования Visual Basic для обучения, разрабатывать COM-объекты, встраиваемые в ASP-страницы, с его помощью я все же не советую.

В начало В начало

Заключение

Хотелось бы сориентировать разработчиков ASP-страниц в мире ASP-компонентов. Дело в том, что автор настоящей статьи провел не один день, сидя за Интернетом в поисках всевозможных ASP-компонентов для решения всевозможных задач.

AspUpload 2.0 Copyright — загрузка файлов на сервер в online-режиме — (c) 1998-2000  Persist Software, Inc. (http://www.persits.com). Временная одномесячная версия лежит на нашем CD-ROM.

XUpload 2.0 Copyright — загрузка каталогов на сервер в online-режиме с поддержкой множества интересных функций (drag-and-drop и т.п.) — (c) 1998-2000  Persist Software, Inc. (http://www.persits.com). Временная версия лежит на нашем CD-ROM.

ASP E-mail 4.4 — довольно полезный компонент для работы с электронной почтой лежит на нашем CD-ROM.

Желающим скачать множество полезных активных серверных компонентов для их использования в ASP советую посетить:

http://www.aspfree.com/components/Default.asp

http://www.hotscripts.com/ASP/Scripts_and_Components/Mailing_List_Managers/index.html

http://asp.imeca.co.kr/download/default.htm

http://www.dynu.com/dynucom.htm

http://www.oingo.com/topic/11/11502.html

http://www.advcomm.net/aspcmpnts.htm

http://www.aspdeveloper.net/components.asp

Множество полезных компонентов для работы с графическими компонентами можно скачать с http://websupergoo.com/download.htm

Замечательная книга популярного издательства O’REILLY (http://www.oreilly.com/catalog/devaspcom/index.html)

Сайт Java-компонентов для ASP (http://www.zaks.demon.co.uk/code/articles/javacom1.html)

КомпьютерПресс 10'2000


Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует