Введение В первой статье серии "ASP на блюдечке" ("ASP на блюдечке. Часть 1. Построение интерфейса к базе данных") мы ознакомились с ASP, а также с принципами построения с его помощью простейшего интерфейса к базе данных (газетный сайт со встроенными возможностями его пополнения новыми статьями, снабжаемыми фотографиями непосредственно с самого сайта и без программирования). Теперь я предложу читателям обогатить этот интерфейс возможностями загрузки (upload) HTML-статьи и иллюстрации к ней непосредственно с сайта (то есть с HTML-формы), ведения автоматической статистики посещений, организации показа баннеров, а также методами использования и азами разработки Активных Серверных Компонентов (Active Server Components) для ASP. Статья будет весьма полезна начинающим Web-программистам, профессионалам же достаточно беглого взгляда, чтобы понять принципы и вникнуть в детали работы активных серверных страниц – ASP. Итак... Что же такое Active X? (Небольшое "лирическое" отступление) Как вы уже догадываетесь, в настоящей статье речь пойдет о технологиях ActiveX и COM. Те, кому эти технологии и принципы их работы известны, могут пропустить настоящий параграф. Однако рекомендую все-таки освежить в памяти некоторые детали. Именно благодаря этим технологиям процесс разработки активных серверных страниц может стать гораздо проще и быстрее. Итак, что же такое COM (Component Object Model) и ActiveX? Чтобы ответить на этот вопрос, зададимся сначала другим: каким образом одна часть программного обеспечения должна получать доступ к сервисам, предоставляемым другой частью? Сегодня ответ на этот вопрос зависит от того, что представляют собой эти части. Например, приложения, скомпонованные с библиотекой, могут пользоваться ее сервисами, вызывая функции из этой библиотеки. Приложение может также использовать сервисы другого – являющегося совершенно отдельным процессом. В данном случае два локальных процесса взаимодействуют посредством некоего механизма связи, который обычно требует определения протокола между этими приложениями (набор сообщений, позволяющий одному приложению выдавать запросы, а другому соответствующим образом отвечать на них). Еще пример: приложение, использующее сервисы операционной системы. Здесь приложение обычно выполняет системные вызовы, обрабатываемые операционной системой. Наконец, приложению могут понадобиться сервисы, предоставляемые программным обеспечением, выполняемым на другой машине, доступ к которой осуществляется по сети. Получить доступ к таким сервисам можно множеством способов, таких как обмен сообщениями с удаленным приложением или вызовы удаленных процедур. В принципе, проблема одна: первая часть программного обеспечения должна получить доступ к сервисам, предоставляемым второй частью. Но в каждом отдельном случае механизм доступа разный: вызовы локальных функций, передача сообщения средствами связи между процессами, системные вызовы (которые с точки зрения программиста выглядят практически так же, как и вызовы функций) или одна из разновидностей сетевых коммуникаций. Каждый такой объект поддерживает один или несколько интерфейсов, состоящих из методов. Метод – это функция или процедура, которая выполняет некоторое действие и может быть вызвана программным обеспечением, использующим данный объект (клиентом объекта). Методы, составляющие каждый из интерфейсов, обычно определенным образом взаимосвязаны. Клиенты могут получить доступ к сервисам объекта COM только через вызовы методов интерфейсов объекта – у них нет непосредственного доступа к данным объекта. А зачем, собственно, применять Active X-технологию в ASP? Применение встраиваемых в ASP ActiveX-компонентов значительно облегчает задачу создания сайтов. Посудите сами: ведь создание громоздких функций, выполняющих сложные вычисления, да и простых подпрограмм гораздо удобнее с помощью известных всем языков программирования, таких как Visual Basic, Delphi или C++, а сгенерированный код (.dll или .ocx) будет работать во много раз быстрее, ведь скомпилированный код модуля выполняется почти мгновенно, в то время как препроцессор ASP формирует HTML-страницу для каждого клиента гораздо дольше. Реализация этих функций средствами самого ASP во многих случаях будет нетривиальна, а в некоторых — попросту невозможна. Кроме того, многие полезные компоненты доступны со стороны так называемых третьих фирм (third-party components), которые зачастую распространяются бесплатно (или почти бесплатно). Итак, я надеюсь, что эта статья поможет вам научиться применять "чужие" компоненты (third-party components) и разрабатывать свои. С чего начать? Давайте для начала разберемся, что же от нас требуется. Во-первых, необходимо подготовить шаблон базы данных с соответствующими полями для заголовка, аннотации, рубрики, даты публикации и текста самой статьи, а также HTML-файла и иллюстрации, загружаемой как в виде текста с HTML-формы, так и в виде HTML-файла. В последнем случае полностью сохраняется формат статьи, что достаточно удобно. Кроме того, статья на сайте может быть снабжена иллюстрацией, которую тоже необходимо загрузить с формы на сервер и отображать по усмотрению пользователя на главной странице сайта. Попросту говоря, пользователь может определить статьи, которые будут отображаться на главной страничке сайта (так называемые горячие новости), а также будет ли отображаться иллюстрация (если таковая имеется) к самой последней из них. Для этого в соответствующей HTML-форме необходимо предусмотреть два регистра. Далее необходимо организовать ввод всех полей формы, включая локальные пути (на компьютере клиента) к загружаемым файлам (в нашем случае с HTML-версией статьи и с иллюстрацией), загрузить их на сервер в определенный каталог и присвоить соответствующим полям базы данных значения имен этих файлов (уже на сервере). Что для этого понадобится? Для реализации вышеизложенной задачи необходим персональный компьютер с Microsoft Windows NT или Windows 2000 (можно и Workstation или Server), установленный IIS (Internet Information Server), какой-нибудь HTML-редактор (советую использовать Macromedia Dreamweaver), Microsoft Access (версии 95, 97 или 2000) и самый обычный текстовый редактор. Создание и подготовка базы данных Для начала нашего первого эксперимента создадим базу данных статей, а для этого: - запустим приложение Microsoft Access;
- любым из известных способов создадим новую базу данных (назовем ее "Articles");
- в созданной базе данных создадим таблицу с именем, например, "Articles";
- пользуясь инструментом "Конструктор", определим поля нашей таблицы (рис.1) и типы принимаемых ими значений:
Рис.1. - заполним таблицу (рис.2) несколькими статьями в соответствии с созданными полями;
Рис.2. - сохраним базу данных в файле "ArticlesDB.mdb"
Далее необходимо прописать нашу базу данных в соответствующем разделе источников данных систем. Для этого: - запустим программу-конфигуратор источников данных (Data Sources ODBC) – Start->Settings->Control Panel->Administrative Tools->Data Sources ODBC;
- перейдем во вкладку "System DSN" и создадим новый источник данных, нажав на "Add...";
- в появившемся списке драйверов выберем драйвер баз данных Microsoft Access – "Microsoft Access Driver (*.mdb)" и нажмем на "Finish";
- в строке "Data Source Name" зададим имя нашей базы данных, например "Articles" (это то имя, по которому мы в дальнейшем будем обращаться к ней);
- нажмем на "Select...", выберем подготовленный нами файл "ArticlesDB.mdb" и нажмем "OK".
Появляется строка (рис.3) в списке источников данных в вашей системе: Рис.3. Применение ActiveX в ASP Мировая компьютерная сеть "кишит" и платными, и бесплатными ActiveX-компонентами. Последний параграф настоящей статьи – попытка перечислить наиболее популярные. Для загрузки файла с HTML-формы советую использовать компонент AspUpload 2.0 Copyright (c) 1998-2000 Persist Software, Inc. (). Компонент платный, но временную одномесячную версию вы сможете найти на сайте производителя. Формируем главную страницу (index.asp) ... <!- Определяем таблицу -> <table BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="710"> <tr> <td WIDTH="232"><img SRC="Images\Banner.jpg" height=60 width=232></td> <td WIDTH="10"></td> <td WIDTH="468"></td> </tr> <tr> <td BGCOLOR="#000000"><b><font color="#FFFFFF"> Today is: <!- Отобразим дату -> <% D = Date() D = FormatDateTime(D,2) Response.Write D %> </font></b></td> <td> </td> <td></td> </tr> <!- Линки на основные страницы -> <tr> <td BGCOLOR="#FFCC99" valign = top> <a href="http://localhost/List.asp" class="antiLine"> <b> Show Articles List</b></a><br> <a href="http://localhost/Search/SearchForm.asp" class="antiLine"> <b>Search for an article</b></a><br> <a href="http://localhost/Upload/UploadForm.asp" class="antiLine"> <b>Upload an article</b></a><br> <a href="http://localhost/Signin.asp" class="antiLine"> <b>Signing In</b></a><br> </td> <td></td> <td> <% ' Подключимся к таблице Articles ' базы данных Articles и откроем ее Set db = Server.CreateObject("ADODB.Connection") db.Open "Articles" ' Сформируем SQL запрос и выполним его sSQL = "SELECT * FROM Articles Where " _ & "IsTopNew = '1' Order by ID DESC" Set rs = db.Execute(sSQL) Display = 0 ' В цикле будем считывать значения ' полей и добавлять их в таблицу Do While NOT Rs.EOF ' Имя файла с иллюстрацией к текущей статье MainImage = rs.Fields("IFile1").value ShouldDisplay = rs.Fields("IsDImage").value ' Переменная, определяющая, должна ли ' иллюстрация отображаться If NOT rs.EOF and Display = 0 and MainImage <> "No" and ShouldDisplay = "1" Then ' Если да, то сгенерировать соответствующий ' тэг с именем иллюстрации Display = 1 Response.Write "<img SRC=" & "Articles\" & _ MainImage & " height=300 width=468>" End If Rs.MoveNext Loop %> </td> </tr> <% Set rs = db.Execute(sSQL) Cnt = 0 Do While NOT Rs.EOF %> <tr> <td BGCOLOR="#FFCC99"> </td> <td></td> <td> <% ' Если поле текста статьи не равно "No Text" If rs.Fields("Article").value <> "No Text" Then Link = "<a href= http://localhost/ArtTempl.asp?id=" _ & rs.Fields("ID").value & ">" _ & rs.Fields("Title").value & "</a>" Else ' В противном случае сгенерировать ' линк на HTML-файл со статьей Link = "<a href= http://localhost/Articles/" _ & rs.Fields("TextFile").value & ">" _ & rs.Fields("Title").value & "</a>" End If Response.Write Link & "<br>" ' Добавляем автора статьи Response.Write "<i>By " & rs.Fields("Author").value _ & "</i><br>" ' Добавляем аннотацию к статье ANN = rs.Fields("Annotation").value If ANN <> "NA" Then Response.Write ANN End If %> <hr NOSHADE WIDTH="100%"> </td> </tr> <% Rs.MoveNext Cnt = Cnt + 1 Loop db.Close Set db = Nothing %> </table> ... $BREAK$ Следует обратить особое внимание на формирование переменной "Link". В первом случае, когда значение поля "Article" не равно "No Text", ссылка на статью формируется из файла шаблона ArtTempl.asp, которому идентификатор текущей статьи передается в качестве параметра Link ="<a href= http://localhost/ArtTempl.asp?id=" _ & rs.Fields("ID").value&">"&rs.Fields("Title").value _ &"</a>" (дальнейшая обработка в теле файла шаблона подробно обсуждалась в первой части настоящей статьи). В противном случае ссылка на статью формируется из имени файла (которое содержится в переменной TextFile) с ее HTML-версией, который уже подгружен на сервер и располагается в каталоге "Articles". Загружаем файл с HTML-формы Форма загрузки (UploadForm.asp) Для начала необходимо создать соответствующую форму. Предполагается, что читатель знаком с азами построения форм (для этих целей советую воспользоваться Macromedia Dreamweaverом), тем не менее я обращу внимание на пару тэгов формы загрузки файлов (полная версия формы загрузки прилагается к данной статье). Хочется обратить особое внимание на обработку "радиокнопки", определяющей тип статьи ("горячая" — "не горячая") и флажок показа иллюстрации на главной странице. Если читатель знаком с первой статьей, то он, вероятно, вспомнит, что в целях обучения вся обработка формы (проверка корректности ввода полей и первоначальная обработка введенных значений) велась на ASP. Приведенный ниже пример иллюстрирует то, как это на самом деле следует делать с помощью какого-либо языка описания сценариев (в нашем случае JavaScript). В нашем случае мы будем определять состояние флажка показа иллюстрации с помощью небольшого скрипта на JavaScript и "спрятанного" (hidden) поля. Последнее будет принимать значение, соответствующее состоянию флажка. <script> function preprocess () { message = 2; if (mainform.DI.checked) message = 1; else message = 2; mainform.details.value = message; } </script> Как видно, значение поля "details" будет равно 1, если флажок поля "DI" был установлен, и 2 — в противном случае. Вызов самого скрипта удобнее всего выполнять по событию "onsubmit", которое обрабатывается по нажатию на кнопку "Submit", и вплоть до передачи управления скрипту-реакции. Таким образом, на этапе ввода можно осуществлять инициализацию внутренних полей и переменных формы, причем непосредственно на стороне клиента (как известно, языки описания скриптов JavaScript и VBScript именно для этого и предназначены). В нашем случае фрагмент формы будет выглядеть следующим образом: ... <form name="mainform" method="post" enctype="multipart/form-data" action="http://localhost/Upload/Upload2DBS.asp" onsubmit="preprocess();"> ... <input type="radio" name="IsTopNewSelector" value="0" checked> <b> Ordinary type Article <input type="radio" name="IsTopNewSelector" value="1"> Top New type Article</b> ... <b>Display image on Home page <input type="checkbox" name="DI" value="0"> <input type="hidden" name="details"> <i>(only for Top News type articles) </i></b> ... </form> ... Соответственно радиокнопка "IsTopNewSelector" будет возвращать значение "0" для обычной и значение "1" для "горячей" статьи. Обработка формы загрузки с помощью ActiveX-компонента Upload2DBS.asp Работать с ActiveX-компонентами в ASP достаточно просто. Например, загрузка файлов при помощи компонента "ASPUpload" производится всего двумя строчками кода: <% 'Создаем переменную — указатель на интерфейс Upload Set Upload = Server.CreateObject("Persits.Upload.1") ' Вызываем функцию Save этого интерфейса Count = Upload.Save ("c:\InetPub\wwwroot\Articles") ' И присваиваем переменной Count число подгруженных файлов %> Фактически после отработки этих двух строк все файлы, которые были указаны в полях формы, загружаются на сервер — в данном случае в каталог "c:\InetPub\wwwroot\Articles". Далее необходимо произвести обработку остальных полей формы и добавить запись в базу данных: ... <% I = 0 FileI = "No" ' Определяем указатель на погруженный файл Set FI = Upload.Files("IFile1") ' И если он не пуст If Not FI Is Nothing Then FileI = FI.Path ' То читаем полный путь к подгруженному файлу ' Вырезаем из полного пути собственно имя файла strPosition = Instr(25,FileI,"\") intStringLen=strPosition-1 strLeft = Left(FileI,intStringLen) strRight = Right(FileI,(Len(FileI)-Len(strLeft)-1)) FileI = strRight ' И присваиваем его переменной FileI End If ... Аналогичные действия должны быть выполнены и с файлом HTML (если таковой был указан) ... Set TF = Upload.Files("TextFile") If Not TF Is Nothing Then FileT = TF.Path strPosition = Instr(25,FileT,"\") intStringLen=strPosition-1 strLeft = Left(FileT,intStringLen) strRight = Right(FileT,(Len(FileT)-Len(strLeft)-1)) FileT = strRight ' И присваиваем его переменной FileТ End If ' Заводим четыре флажка для обработки ' ошибок ввода полей ErrA = 0 ErrT = 0 ErrP = 0 ErrC = 0 ' Получаем значение флажка показа иллюстрации DI = Upload.Form("DETAILS") If DI = 1 Then IsDisplayImage = "1" End If If DI = 2 Then IsDisplayImage = "0" End If ' Получаем значение поля автора статьи AUT = Upload.Form("Author") If AUT = "" Then ErrA = 1 End If AUT = FormatStr(AUT) ' Получаем значение поля заголовка статьи TIT = Upload.Form("Title") If TIT = "" Then ErrT = 1 End If TIT = FormatStr(TIT) ' Получаем значение поля текста самой статьи ART = Upload.Form("Article") If ART = "" Then ' Если оно пусто, присваиваем переменной ' константу "No Text" ART = "No Text" If File1 = "No" Then ErrC = 1 End If End If ' Форматируем текст самой статьи для его последующего ' показа в формате HTML ART = FormatStr(ART) ' Получаем значение селектора рубрик SBJ = Upload.Form("Subject") IsTopNew = Upload.Form("IsTopNewSelector") ANN = Upload.Form("Annotation") ANN = FormatStr(ANN) If ANN = "" Then ANN = "NA" End If ' Получаем значение поля пароля доступа к базе ' данных Password = Upload.Form("Password") If Password = DBP Then If ErrA = 0 and ErrT = 0 Then ' Открываем базу данных и записываем в нее значения Set db = Server.CreateObject("ADODB.Connection") db.Open "DSN=Articles;UID=sa;PWD=;" sSQL = "insert into Articles(Author,Title,Article,Subject,TextFile,IFile1, _ Published,IsTopNew,Annotation,IsDImage)values('" & AUT & "', '" & TIT & "','" & ART & "','" _ & SBJ & "','" & FileT & "','" & FileI & "','" & TIM _ &"','" & IsTopNew &"','" & ANN & "','" & IsDisplayImage &"')" Set rs = db.Execute(sSQL) db.Close Set db = Nothing End If Else ErrP = 1 End If %> ... Загрузка файлов с помощью 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> Рис.4. По нажатии кнопки "SubmitQuery" в этом окошке (рис.4) на сервер придет следующий запрос: -----------------------------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)) %> Как вы наверняка заметили, блоки последовательности в ответе разделяются между собой следующими разграничителями: -----------------------------7cf87224d2020a а последний разграничитель завершается '--'. Для каждого блока (поля, элемента формы) имеется одно значение content-disposition. Атрибут name указывает, к какому именно элементу формы относится тот или иной блок (в нашем случае e-mail, blob или Enter). Для элемента формы файла (в нашем случае blob) имя файла также является частью заголовка content-disposition, а заголовок content-type определяет тип данных. $BREAK$ Скрипт-загрузчик Очевидно, что все содержимое 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). Рис.5. Данная форма (рис.5) предназначена для загрузки на сервер единственного файла. Определим также скрипт-реакцию на эту форму: ACTION="UploadReceive.asp" $BREAK$ Соответственно вызов активного серверного компонента из ASP-скрипта (файл UploadReceive.asp) будет выглядеть следующим образом: ... <% Set ObjUpload = Server.CreateObject("UploadProject.UploadClass") ObjUpload.DoUpload %> ... Функцией компонента, который мы собираемся создавать, будут являться разбор HTTP-заголовка получения для имени файла и сохранение файла на сервере в определенном каталоге. Для этого выполним следующую последовательность операций: - Запустим Microsoft Visual Basic 6.0;
- В появившемся диалоговом окне выберем "ActiveX DLL";
- Нажмем на "Open";
- В окне инспектора проекта, выделив имя проекта, нажмем правую кнопку мыши и выберем пункт "Project1 Properties";
- Поменяем имя проекта на "UploadProject" и нажмем на "ОК";
- Выбрав класс "Class1", в окошке пониже перепечатаем имя класса с "Class1" на "UploadClass";
- После открытия проекта (рис.6) войдем в пункт меню Project -> References;
Рис.6. - Установим флажок напротив пункта "Microsoft Active Server Pages Object Library";
- Нажмем на "OK";
- В окне кода класса впечатать функцию "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, , (binA |