Интерфейс Internet
Изначально ISAPI распространялся среди разработчиков CGI как альтернатива программам CGI или как обновление исполняемого файла CGI. Многие исполняемые файлы CGI написаны на C++ или C, поэтому интеграция существующего веб-приложения CGI не очень сложна. Преобразование веб-приложения CGI для использования ISAPI увеличивает производительность веб-приложения. CGI при каждом HTTP-запросе создает новый процесс, что занимает много ресурсов несущего сервера. Расширения ISAPI загружаются в пространство процесса IIS, поэтому узлу не нужно создавать новый процесс при каждом HTTP-запросе. Поскольку Windows загружает динамически подключаемую библиотеку в пространство памяти один раз при первом вызове функции в DLL и хранит ее там неопределенный промежуток времени, расширение ISAPI остается загруженным и не удаляется, до тех пор пока сервер IIS не будет выключен или не будет выгружен экземпляр или виртуальная память. Таким образом, компания Microsoft дает программистам основание использовать ISAPI вместо CGI и легко обновлять ПО, созданное при помощи CGI.
ISAPI рекомендуется для программистов, создающих (или уже создавших) приложение на языке C++, предназначенное для продажи на рынке ПО. Если важным фактором является производительность, и на разработку выделяется больше времени, чем на создание обычного сценария для интернета, рассмотрите вариант использования ISAPI. Кроме всего прочего, ISAPI выполняет на несущем узле некоторые задачи, которые нельзя выполнить при помощи других технологий. Программное обеспечение ISAPI создано таким образом, что при его выполнении другие веб-приложения, написанные на языках сценариев с использованием других расширений ISAPI (например, .NET Framework или ASP.DLL), не рассматривают задачи, выполняемые расширением ISAPI.
К недостаткам рассматриваемой технологии относится сложность ISAPI в
работе и в отладке. Отладка кода в интегрированной среде разработки
(Integrated
Кроме всего прочего, код ISAPI создается с помощью неконтролируемого кода C++. Новые возможности, предлагаемые Visual Studio .NET для управляемого кода C++ в технологии .NET Framework, нельзя использовать в проекте ISAPI.
Примечание. Если программа создается с помощью контролируемого кода, то в этом случае реализуется технология.NET Framework. Эта технология используется языками C# и Visual Basic. Термин "контролируемый" означает, что технология .NET Framework контролирует очистку памяти, отведение памяти и другие процессы управления ресурсами низкого уровня. Программа на C++ не сможет работать с технологией .NET Framework, если не применяются Managed Extensions (Контролируемые расширения) для C++. Контролируемый C++ означает использование технологии .NET Framework и контролируемых расширений C++. Код C++, созданный без использования контролируемых расширений C++, является неконтролируемым кодом C++.
Приложения ISAPI представляют собой библиотеки DLL, напрямую
взаимодействующие с IIS API. Программное обеспечение ISAPI – это
расширение или фильтр. Расширения ISAPI являются библиотеками DLL,
вызываемыми посредством квалифицированного запроса в IIS. Фильтры ISAPI
вызываются независимо от других запросов IIS. Запросы HTTP передаются
напрямую расширению ISAPI с помощью ссылки URL или данных, отправляемых
из формы HTML. Расширение ISAPI может вызываться косвенно посредством
связывания файла с определенным расширением ISAPI в IIS. При установке
связей файлов выполняются действия, аналогичные ассоциированию файлов
ответа сервера (SRF) с конкретным расширением ISAPI в
Расширения ISAPI – наиболее частый способ применения ISAPI. Фильтры ISAPI довольно сложны в создании, и сфера их использования ограничена. Данная тема выходит за рамки книги и рассматриваться не будет.
URL представляет собой строку, указывающую конкретный ресурс на сервере в интернете. URL формируется согласно следующему синтаксису:
<схема>://<пользователь>:<пароль>@<узел>:<порт>/<url-путь>/<дополнительная информация>
Ниже приведен URL, реализующий запрос файла ISAPI DLL с именем SEUX.dll. В URL указаны параметры parm1 и parm2. В таблице 5.1 данный
URL разбит на части, чтобы показать, как определяется обычный URL,
осуществляющий запрос библиотеки ISAPI DLL.
http://amd1700v2/simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO? parm1=value1parm2=value
Многие компоненты URL используются для описания значений серверных переменных, о которых мы поговорим позже. Обратите внимание, что поля, отсутствующие в примере, редко встречаются в URL. В качестве примера приведен наиболее распространенный общий URL.
Если ISAPI DLL запрашивается напрямую через ссылку URL, в секции url-путь определяется имя файла библиотеки DLL расширения ISAPI. В
секции URL <дополнительная информация> располагаются пары
параметр = значение, передаваемые расширению ISAPI с помощью этой
секции или посредством отправки данных HTTP из формы HTML.
Если IIS получает запрос и считает, что необходимо использовать
расширение ISAPI (запрошен файл, связанный с расширением ISAPI или само
расширение ISAPI), то IIS передает запрос HTTP вместе с расширением
ISAPI. Чтобы расширение ISAPI получило запрос HTTP, используется
определенный программный интерфейс. Как известно, приложение ISAPI
содержит
| Компонент URL | Значение из примера |
|---|---|
| схема | http |
| пользователь | Отсутствует в демонстрационном URL |
| пароль | Отсутствует в демонстрационном URL |
| узел | amd1700v2 |
| порт | Отсутствует в демонстрационном URL (подразумевается значение 80) |
| url-путь | simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO |
| дополнительная информация | ?parm1=value1parm2=value |
Расширение ISAPI анализирует данные HTTP-запроса и направляет вызовы другому программному обеспечению, например, программе бизнес-уровня (см. рис. 5.1). Ответы этой программы формируются в виде HTTP-ответов и возвращаются в IIS. IIS возвращает ответ расширения ISAPI веб-пользователю, направившему изначальный запрос.
(рис 5.1) Обзор архитектуры расширения ISAPIАрхитектура, показанная на рисунке 5.1, позволяет использовать расширения ISAPI двумя возможными способами, но не является единственным вариантом технологии. Единственным ограничением является вызов расширения ISAPI при HTTP-запросе и наличие структур и классов, содержащих HTTP-запрос и поддерживающих программное взаимодействие с ответом HTTP и запросом HTTP. Построение абстракции логики – задача разработчика. Направление вызовов библиотеке бизнес-логики необязательно, если бизнес-логика заключена в самом расширении ISAPI. Так как ISAPI напрямую записывает данные в HTTP-запрос, следует создавать архитектуру, абстрагирующую логику представления для исключения повторной компиляции ISAPI DLL при изменении логики представления.
При сравнении
Расширения ISAPI имеют такую несложную структуру, что могут создаваться
без помощи MFC или httpext.h и файл DLL definition export file (DEF)
с экспортом двух следующих функций:
HttpExtensionProc; GetExtensionVersion.
Каждое расширение ISAPI должно поддерживать эти функции. Оно может
также экспортировать функцию TerminateExtension (это не является
обязательным). После настройки проекта DLL расширение нужно применить к
IIS. В Visual Studio .NET имеется шаблон проекта ISAPI, запускающий
мастер расширения ISAPI. Этот мастер создает класс, названный по имени
проекта, наследуемый из класса CHTTPServer и содержащий необходимые
функции ISAPI. Мастер расширения ISAPI использует httpext.h для получения классов и структур
ISAPI.
Работа с мастером расширения ISAPI будет описана позже. Рассмотрим
метод создания простого расширения ISAPI с минимальной поддержкой MFC и
В диалоговом окне New Project (Новый проект) щелкните на узле Visual C++ Projects (Проекты Visual C++) в левой части окна и выберите шаблон проекта Win32. Присвойте проекту имя HelloWorld и выберите место расположения создаваемого проекта.
При нажатии на кнопку More (Больше) диалоговое окно New Project (Новый
проект) отобразит дополнительную информацию о том, в каком месте будет
создан проект. Название данной кнопки изменится на Less (Меньше). По
умолчанию Visual Studio создает каталог с именем, идентичным имени
проекта, в папке, указанной в текстовом поле Location (Расположение), в
котором располагаются все файлы проекта. На рисунке 5.2 показано, что
в текстовом поле Location (Расположение) введено значение C:\bookMaterial\IISBook\17ISAPIExtension\code, а именем проекта
является HelloWorld.
(рис 5.2) Диалоговое окно New Project (Новый проект) с выбранным проектом Win32C:\bookMaterial\IISBook\17ISAPIExtension\code\HelloWorld и разместит в
нем файлы проекта.stdafx,
созданные Visual Studio .NET для его поддержки. В окне Solution
Explorer выделите заголовок stdafx и файлы реализации, щелкните правой
кнопкой мыши и выберите Remove (Удалить) (см. рис. 5.6).
(рис 5.6) Удаление файлов stdafx из проекта
Hello World в соответствии с
именем проекта (см. рис. 5.7). Имя файла должно соответствовать имени
DLL, иначе компилятор C++ отобразит предупреждение о несоответствии
имен файлов. Рассматриваемый файл DEF (файл экспорта определения)
описывает функции, экспортируемые из DLL, чтобы другое приложение могло
загрузить DLL и найти адреса функций.
(рис 5.7) Добавление в проект файла экспорта определенияШаблоны проекта Visual Studio используют встроенный механизм MFC для экспорта функций и использования библиотеки DLL расширения ISAPI программой-потребителем, каковой является IIS. В Visual Studio .NET нет шаблона проекта, создающего конечный продукт без готового заголовка, поэтому следует выбрать шаблон проекта Win32, преобразовать его в DLL, после чего удалить ненужные файлы и настройки. При создании проекта без поддержки MFC иногда возникают некоторые сложности. Однако MFC сами по себе достаточно сложны, поэтому небольшие проекты (как в нашем примере) можно упростить, отказавшись от использования MFC.
Следующим шагом является настройка файла DEF.
После добавления в проект файла DEF имя библиотеки в файле будет идентично имени проекта (иначе Visual Studio .NET отобразит предупреждение). В нашем проекте имя выглядит следующим образом:
LIBRARY HelloWorld
В файл DEF добавим блок DESCRIPTION для описания назначения библиотеки
DLL. Описание заключается в одинарные кавычки. Блок DESCRIPTION не
является обязательным в отличие от блока EXPORTS. Имена функций следуют
после объявления блока EXPORTS. В листинге 5.1 приведен код файла
экспорта определения, использованный в нашем проекте. Функции
приводятся в произвольном порядке, однако они должны соответствовать
именам в реализации расширения.
LIBRARY HelloWorld
DESCRIPTION 'Demonstration of a simple Hello World ISAPI extension'
EXPORTS
HttpExtensionProc
GetExtensionVersion
Файл HelloWorld.cpp необходимо настроить на поддержку функций,
необходимых для обеспечения работы расширения ISAPI.
HelloWorld.cpp для редактирования
посредством двойного щелчка на имени файла в окне Solution Explorer.DLLMain (она не нужна в данном примере).httpext.h и <string> (см. листинг
5.2).GetExtensionVersion (см. листинг 5.2).HttpExtensionProc (см. листинг 5.2).// HelloWorld.cpp : Defines the entry point for
//the DLL application.
#include <httpext.h> //for ISAPI classes and structures
// tell the compiler to shut up about using STL
#pragma warning(disable:4786)
#include <string> //to build response to send back to user
using namespace std;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetExtensionVersion
In: pVer - Pointer to ISAPI structure HSE_VERSION_INFO.
Out: Returns true if you want IIS to use the extension, otherwise
if another value is returned, IIS will not use the extension
Purpose:
Called when the extension is loaded into IIS. The member
variables of HSE_VERSION_INFO, dwExtensionVersion and
lpszExtensionDesc should be filled with the extension
version and description.
Other initialization functionality could be called from this
function to set up the server to use this extension.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
//ISAPI version
const DWORD VERSION_NUMBER = 0.9;
const char* VERSION_NAME = "Hello World";
pVer->dwExtensionVersion = VERSION_NUMBER;
strncpy( pVer->lpszExtensionDesc,
VERSION_NAME,
HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:HttpExtensionProc
In: pECB - pointer to the Extension control block structure
Out: DWORD - HSE status code
Purpose:
main entry point for HTTP request
the possible return codes are:
HSE_STATUS_SUCCESS - everything worked great
HSE_STATUS_SUCCESS_AND_KEEP_CONN - same as HSE_STATUS_SUCCESS
since IIS 4
HSE_STATUS_PENDING - wait until effort completed
HSE_STATUS_ERROR - sends a 500 error code
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
//HTTP headers
const char* BASIC_HEADER = "Content-type: text/html\r\n\r\n";
//output values
const DWORD BUFFER_LENGTH = 4096;
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
string sResponse;
//start our HTML document
sResponse = "<HTML><HEAD></HEAD><BODY><P>";
sResponse += "Hi! Hello World";
sResponse += "</P></BODY></HTML>";
// set content-type header
strcpy(szTempBuffer, BASIC_HEADER);
DWORD dwHeaderSize = strlen(szTempBuffer);
pECB->ServerSupportFunction(pECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
NULL,
dwHeaderSize,
(LPDWORD) szTempBuffer);
//write value to http response
DWORD dwLength=sResponse.length();
pECB->WriteClient( pECB->ConnID,
(PVOID)sResponse.c_str(),
dwLength,
0);
//return a success code
return HSE_STATUS_SUCCESS;
}
Функция GetExtensionVersion использует указатель на структуру HSE_VERSION_INFO в качестве параметра и возвращает значение BOOL в
зависимости от того, использует ли IIS расширение ISAPI. Если функция GetExtensionVersion возвращает значение "истина", то
расширение ISAPI используется. Функция GetExtensionVersion вызывается
при загрузке расширения ISAPI в пространство процесса IIS. После
загрузки расширения ISAPI эта функция повторно не вызывается. Она
отлично подходит для включения кода, выполняющего проверку ключа
лицензии, или для процедур инициализации, проверяющих использование
расширения. В листинге 5.2 файла HelloWorld.cpp функция GetExtensionVersion только получает информацию о версии для расширения
ISAPI.
Структура HSE_VERSION_INFO содержит две переменные dwExtensionVersion и lpszExtensionDesc. Этим переменным присваиваются значения при вызове
функции GetExtensionVersion. В листинге 5.2 переменной dwExtensionVersion присваивается значение 0,9 типа DWORD, а переменной lpszExtensionDesc – значение HelloWorld. Эта функция всегда возвращает
значение "истина", так как в IIS всегда загружается
расширение ISAPI. Если расширение ISAPI зависит от наличия
действительного файла лицензии, другой программной библиотеки или
специальной конфигурации, тогда в функции можно выполнять
соответствующую проверку и возвращать значение "ложь" в
случае отрицательного результата.
Функция HttpExtensionProc в качестве параметра использует одну из
важнейших структур ISAPI – блок контроля расширения (Extension Control
Block,
Изучая код листинга 5.2, следует отметить, что указатель на блок
Первой функцией, вызываемой в блоке ServerSupportFunction. Она устанавливает заголовок в ответе
HTTP для обозначения вида содержимого (текст или HTML) с помощью
следующей строки:
Content-type: text/html\r\n\r\n
За заголовками ответов HTTP должны следовать две пары символов возврата
каретки и перехода на новую строку ( /n и /r ).
Следующей функцией, вызываемой с помощью WriteClient. Фраза "Hi! Hello World" записывается в браузер
с помощью данной функции посредством передачи указателя char (char*)
строковой переменной, содержащей код HTML и строку "Hi! Hello
World". Функции WriteClient нужен пустой указатель ( void* ),
поэтому указатель char приводится к форме пустого указателя с помощью
макроса PVOID.
После успешной компиляции DLL расширение ISAPI можно отгружать на веб-сервер. Если в качестве расположения конечного файла DLL указан экземпляр веб-сервера или корень виртуального каталога, можно немедленно запросить файл из IIS. При выполнении отладки Visual Studio .NET браузер не откроется, и автоматического запроса ISAPI DLL из IIS (как при отладке веб-службы ASP.NET) не произойдет. Visual Studio считает проект библиотекой DLL, поэтому выдаст запрос на присвоение исполняемого файла, который использует данную библиотеку.
Запомните. Если разработчик осуществляет построение библиотеки DLL расширения ISAPI и затем тестирует DLL посредством ее запроса через IIS, при втором построении ISAPI DLL в Visual Studio .NET, скорее всего, не удастся создать DLL. IIS блокирует библиотеку DLL расширения ISAPI при ее запросе из IIS, поскольку DLL загружается в пространство процесса IIS. Для разблокирования ISAPI DLL необходимо выгрузить экземпляр веб-сайта или виртуального каталога либо заново запустить приложение. После разблокирования ISAPI DLL поверх нее в процессе построения осуществляется запись.
При первом запросе ISAPI DLL из IIS 6 в операционной системе Windows Server 2003, скорее всего, возвратится ошибка 404. В WS03 и IIS6 существует новая возможность, ограничивающая по умолчанию всякую программную поддержку серверной части, если она не включена вручную. В предыдущих версиях Windows Server компонент IIS поставлялся с включенной программной поддержкой серверной части (ASP). Эта функция включается в окне Web Service Extensions (Расширения веб-служб) консоли MMC Computer Management (Управление компьютером) (см. рис. 5.8).
(рис 5.8) Включение программной функциональности серверной части в окне Web Service Extensions (Расширения веб-служб)Настроим IIS на разрешение запросов расширения ISAPI HelloWorld.
Расширение HelloWorld появится в окне администрирования расширения
веб-служб консоли Copmuter Management (Управление компьютером), и в
поле Status (Состояние) отобразится значение Allowed (Разрешено).
В качестве альтернативы установите Allowed (Разрешено) для расширения
веб-службы All Unknown ISAPI Extensions (Все неизвестные расширения
ISAPI). Данный параметр разрешает выполнение любого запроса
относительно любого расширения ISAPI.
Включение данной настройки нарушает безопасность сервера, поэтому используйте этот параметр только в изолированных средах, таких как среда разработки.
Разрешения на выполнение веб-экземпляра IIS или виртуального каталога устанавливаются на разрешение выполнения библиотек ISAPI DLL. По умолчанию параметр Execute Permissions (Разрешения на выполнение) в окне свойства веб-экземпляра IIS или виртуального каталога не позволяет выполнять сценарии и исполняемые файлы.
(рис 5.10) Установка разрешений для виртуального каталогаЕсли в IIS все настроено должным образом, ISAPI Hello World будет
функционировать. Запросите библиотеку ISAPI DLL в адресе URL из
браузера, как если бы это был файл HTML – и DLL выполнится (см. рис.
5.11).
(рис 5.11) Выполнение расширения ISAPI HelloWorld
HttpExtensionProc выполняет следующие
задачи.
GetServerVariable./*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:HttpExtensionProc
In: pECB - pointer to the Extension control block structure
Out: DWORD - HSE status code
Purpose:
main entry point for HTTP request
the possible return codes are:
HSE_STATUS_SUCCESS - everything worked great
HSE_STATUS_SUCCESS_AND_KEEP_CONN - same as HSE_STATUS_SUCCESS
since IIS 4
HSE_STATUS_PENDING - wait until effort completed
HSE_STATUS_ERROR - sends a 500 error code
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
string sDoc;
//start our XML document
sDoc = string(HEAD) + string(NEW_LINE) + string(XML_L) +
string(MAIN_ELEMENT_NAME) + string(XML_R) +
string(NEW_LINE);
//START THE ECBServerVariable VARIABLES
sDoc += string(XML_L) +
string("ECBServerVariable") + string(XML_R) +
string(NEW_LINE);
//GET the ALL_HTTP
sDoc += string(XML_L) + string("ALL_HTTP");
GetALLHTTPHeader(pECB, sDoc);
sDoc += string(XML_R_END) +
string(NEW_LINE);//end the first main element
GetECBElement(pECB, string("AUTH_TYPE"), sDoc);
GetECBElement(pECB, string("APPL_MD_PATH"), sDoc);
GetECBElement(pECB, string("APPL_PHYSICAL_PATH"), sDoc);
GetECBElement(pECB, string("CONTENT_LENGTH"), sDoc);
GetECBElement(pECB, string("CONTENT_TYPE"), sDoc);
GetECBElement(pECB, string("GATEWAY_INTERFACE"), sDoc);
GetECBElement(pECB, string("HTTP_ACCEPT"), sDoc);
GetECBElement(pECB, string("HTTPS"), sDoc);
GetECBElement(pECB, string("HTTP_AUTHORIZATION"), sDoc);
GetECBElement(pECB, string("LOGON_USER"), sDoc);
GetECBElement(pECB, string("AUTH_PASSWORD"), sDoc);
GetECBElement(pECB, string("AUTH_TYPE"), sDoc);
GetECBElement(pECB, string("AUTH_USER"), sDoc);
GetECBElement(pECB, string("APPL_PHYSICAL_PATH"), sDoc);
GetECBElement(pECB, string("INSTANCE_ID"), sDoc);
GetECBElement(pECB, string("INSTANCE_META_PATH"), sDoc);
GetECBElement(pECB, string("PATH_INFO"), sDoc);
GetECBElement(pECB, string("PATH_TRANSLATED"), sDoc);
GetECBElement(pECB, string("QUERY_STRING"), sDoc);
GetECBElement(pECB, string("REMOTE_ADDR"), sDoc);
GetECBElement(pECB, string("REMOTE_HOST"), sDoc);
GetECBElement(pECB, string("REMOTE_USER"), sDoc);
GetECBElement(pECB, string("REQUEST_METHOD"), sDoc);
GetECBElement(pECB, string("SCRIPT_NAME"), sDoc);
GetECBElement(pECB, string("SERVER_NAME"), sDoc);
GetECBElement(pECB, string("SERVER_PORT"), sDoc);
GetECBElement(pECB, string("SERVER_PORT_SECURE"), sDoc);
GetECBElement(pECB, string("SERVER_PROTOCOL"), sDoc);
GetECBElement(pECB, string("SERVER_SOFTWARE"), sDoc);
GetECBElement(pECB, string("URL"), sDoc);
//End THE ECBServerVariable VARIABLES
sDoc += string(XML_L_END) +
string("ECBServerVariable") + string(XML_R) +
string(NEW_LINE);
//START THE ECBProperties
sDoc += string(XML_L) +
string("ECBProperties") + string(XML_R) +
string(NEW_LINE);
GetElement(string("lpszLogData"),
string(pECB->lpszLogData),sDoc);
GetElement(string("lpszMethod"),
string(pECB->lpszMethod),sDoc);
GetElement(string("lpszQueryString"),
string(pECB->lpszQueryString),sDoc);
GetElement(string("lpszPathInfo"),
string(pECB->lpszPathInfo),sDoc);
GetElement(string("lpszContentType"),
string(pECB->lpszContentType),sDoc);
//end THE ECBProperties
sDoc += string(XML_L_END) +
string("ECBProperties") + string(XML_R) +
string(NEW_LINE);
//end our XML document
sDoc += string(XML_L_END) +
string(MAIN_ELEMENT_NAME) + string(XML_R) +
string(NEW_LINE);
//write it!
SendResponse(pECB,sDoc);
return HSE_STATUS_SUCCESS;
}
Примечание. Исходный код SEUX доступен на веб-сайте автора книги (см. введение).
Первой задачей (см. листинг 5.3) является открытие документа XML посредством присоединения некоторых констант, представляющих собой части документа XML. Построение XML происходит вручную, и константы объявляются для общих частей документа XML, так как они используются во многих местах. Документ XML размещается в строковой переменной с именем sDoc. Ниже приведены константы XML:
//xml parts
const char* MAIN_ELEMENT_NAME = "HTTPRequestRaw";
const char* QUOTE ="\"";
const char* XML_L ="<";
const char* XML_R =">";
const char* XML_L_END ="</";
const char* XML_R_END ="/>";
const char* NEW_LINE ="\n";
const char* HEAD = "<?xml version=\"1.0\"?>";
Рассматриваемый документ XML достаточно прост, поэтому его построение вручную не вызывает никаких трудностей. Для более сложного документа рекомендуется использовать аналитическую библиотеку XML, такую как MSXML.
После инициализации документа XML начинается работа по размещению в
элементах XML всех серверных переменных. Создаваемый документ XML имеет
родительский элемент HTTPRequestRaw. Внутри HTTPRequestRaw содержатся
два дочерних элемента с ECBServerVariable и ECBProperties. Для каждого
значения серверной переменной, запрашиваемого из ECBServerVariable, независимо от
получения значения соответствующей серверной переменной. Для каждого
запрошенного свойства ECBProperties, которое содержит значение независимо от получения
значения свойства. При вызове расширения ISAPI SEUX с помощью IE 6.0
отобразится документ XML (см. рис. 5.12). В других версиях браузера
XML может отобразится по-другому или не отобразиться вовсе.
(рис 5.12) IE 6.0, отображающий документ XML серверных переменных из расширения SEUX ISAPI
Первой серверной переменной, для которой получается значение, является
переменная ALL_HTTP, представляющая собой все заголовки HTTP,
переданные в запросе HTTP. Результатом остальных запрошенных серверных
переменных является число или строка, легко согласуемая с XML, однако ALL_HTTP возвращает все заголовки в одну и ту же серверную переменную.
Заголовки в значении, возвращаемом из ALL_HTTP, ограничены символами
новой строки, поэтому требуется дополнительный анализ для размещения
каждого заголовка в элементе XML в качестве атрибута. Функция GetALLHTTPHeader выполняет данную задачу с помощью функции GetServerVariable (см. листинг 5.4).
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetALLHTTPHeader
In: pECB - pointer to the Extension control block structure
psElement - string pointer to XML document being built that
will be updated with the element for the ALL_HTTP
server variable value.
Out: nothing returned but the string psElement points to
will be updated.
Purpose:
Updates the XML document string by adding an element
for the ALL_HTTP server variable. This variable contains
all of the http headers so some additional parsing must
take place on the headers to format them into XML.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void GetALLHTTPHeader(EXTENSION_CONTROL_BLOCK *pECB,
string *psElement)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
const string EQUAL("=");
const string SPACE(" ");
string sAllHeaders; //used to cut up 'all headers' returned
int nNewLinePos = 0;
int nEndLinePos = 0;
string sName;
string sValue;
//pull the whole HTTP header
if (pECB->GetServerVariable( pECB->ConnID,
"ALL_HTTP",
szTempBuffer,
dwBufferSize))
{
//if the whole http header was pulled then parse it
sAllHeaders.assign(szTempBuffer);
//get the name / value pairs
while (GetHeaderValuePair( sAllHeaders,
nNewLinePos,
sName,
sValue,
nEndLinePos))
{
//add the attribute to the element
psElement->append( SPACE + sName + EQUAL +
QUOTE +
ValidateValue(sValue) +
QUOTE);
//reset the newline to the last endline
nNewLinePos = nEndLinePos;
//dump the values
sName.erase();
sValue.erase();
}
}
}
Функция GetServerVariable возвращает значение "истина" при
успешном выполнении и значение "ложь" при возникновении
ошибки, как показано в следующем примере:
BOOL WINAPI GetServerVariable( HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer );
Прототип GetServerVariable в данном случае требует передачи четырех
параметров.
При успешном выполнении функция GetServerVariable возвращает значение
"истина". Указатель lpvBuffer указывает значение
запрашиваемой серверной переменной, а lpdwSizeofBuffer – на новое
значение DWORD, отражающее текущий размер значения, включая байт конца
строки. При неудачном завершении работы функция GetServerValue
возвращает значение "ложь". В этом случае вызывается
функция GetLastError, которая возвращает значение DWORD, представляющее
собой код ошибки. В таблице 5.2 показаны возможные ошибки функции GetServerVariable ; это константы, определяемыми во вспомогательном
Возможные значения запрашиваемых серверных переменных приведены в
листинге 5.3 в коде функции HttpExtensionProc в расширении ISAPI SEUX ;
они являются аргументами в вызовах GetECBElement. Значения серверных
переменных могут изменяться в процессе текущего события запроса HTTP в
IIS, и зачастую переменной не присваивается значение. Серверные
переменные содержат значения только при определенных настройках IIS. В
таблице 5.2 приведены серверные переменные, запрашиваемые с помощью
функции GetServerVariable.
| Константа ошибки | Описание ошибки |
|---|---|
ERROR_INVALID_PARAMETER |
Значение hConn является некорректным или закрытым, либо неверны
параметры серверной переменной. |
ERROR_INVALID_INDEX |
Запрашиваемая серверная переменная не поддерживается. |
ERROR_INSUFFICIENT_BUFFER |
Размер lpdwSizeofBuffer слишком мал для содержания значения
запрашиваемой серверной переменной. |
ERROR_NO_DATA |
Запрашиваемая серверная переменная недоступна. |
ALL_HTTP |
Все HTTP-заголовки (разделены символами новой строки), переданные в
запросе HTTP в строке с символом конца строки. Заголовки имеют вид <имя заголовка> : <значение>. |
ALL_RAW |
Все заголовки HTTP в том виде, в котором они были отправлены запрашивающей HTTP-стороной. |
APPL_MD_PATH |
Путь метабазы веб-приложения. Например, /LMW3SVC/1/Root/SimpleISAPI. |
APPL_PHYSICAL_PATH |
Физический путь корневого веб-каталога для веб-приложения. Например: C:\ISAPI\. |
AUTH_PASSWORD |
Пароль, вводимый веб-пользователем в диалоговом окне аутентификации браузера, если установлена базовая аутентификация. |
AUTH_TYPE |
Используемый тип аутентификации. Пустое значение при отсутствии
аутентификации либо значение, соответствующее Kerberos,
пользовательской аутентификации, SSL/ |
AUTH_USER |
Имя пользователя, вводимое пользователем в диалоговом окне аутентификации браузера в случае, если установлена базовая аутентификация. |
CERT_COOKIE |
Уникальный идентификатор сертификата клиента. |
CERT_FLAGS |
Битовые флаги бюро сертификатов (CA) сертификата клиента. Если bit0
равен 1, то CA отсутствует в списке распознаваемых бюро сертификатов
данного сервера и признается недействительным. |
CERT_ISSUER |
Содержит имя сертификата клиента. Например, O=Schmidlaps, OU=House, CN=
имя пользователя, C=USA. |
CERT_KEYSIZE |
Размер ключа в битах при соединении SSL. |
CERT_SERCRETKEYSIZE |
Размер секретного ключа сертификата сервера в битах. |
CERT_SERIALNUMBER |
Серийный номер сертификата клиента. |
CERT_SERVER_ISSUER |
Подробное имя |
CERT_SERVER_SUBJECT |
Подробное имя |
CERT_SUBJECT |
|
CONTENT_LENGTH |
Количество байт, исключая заголовки HTTP-запроса. |
LOGON_USER |
Если конечный пользователь успешно аутентифицировался в Windows, используется учетная запись входа в систему. |
HTTPS |
Возвращает значение off, если в HTTPS не используется SSL, в противном
случае возвращается значение on. |
HTTPS_KEYSIZE |
Размер ключа SSL-соединения в битах. |
HTTP_SECRETKEYSIZE |
Размер секретного ключа сертификата сервера в битах. |
HTTPS_SERVER_ISSUER |
Подробное имя |
HTTPS_SERVER_SUBJECT |
Подробное имя |
INSTANCE_ID |
Номер экземпляра сервера. Значения идентификатора сервера в метабазе, например, 1. |
INSTANCE_META_PATH |
Путь веб- экземпляра в метабазе, например: LM/W3SVC/1. |
PATH_INFO |
Часть URL, расположенная между ISAPI DLL и началом секции с дополнительной информацией в URL. Как правило, в этом месте нет никаких данных, если запрашивающее ПО не добавляет свое значение. |
PATH_TRANSLATED |
Часть веб-экземпляра, связанная с физическим жестким диском, с
конкатенацией значения PATH_INFO. |
|
Строка символов, следующих за символом "?" в секции дополнительной информации URL. |
|
IP-адрес хоста или шлюза запрашивающего ПО. |
|
Имя узла или шлюза запрашивающего ПО, если включен обратный поиск DNS; иначе возвращается значение IP-адреса узла или шлюза запрашивающего ПО. |
REMOTE_USER |
Имя пользователя, осуществляющего HTTP-запрос и аутентифицируемого несущим сервером. Представляет собой пустую строку для анонимного пользователя. |
|
Команда метода HTTP-запроса. |
|
Имя исполняемого двоичного файла, например, имя ISAPI DLL или исполняемого файла CGI. |
SERVER_NAME |
Имя узла сервера или IP-адрес. |
SERVER_PORT |
Порт TCP/IP, по которому получен запрос. |
SERVER_PORT_SECURE |
Значение 0 или 1. Запросы по безопасному порту возвращают 1; иначе возвращается 0. |
SERVER_PROTOCOL |
Имя и версия протокола запроса, например, HTTP 1.1. |
SERVER_SOFTWARE |
Имя и версия IIS, под которой выполняется программа DLL расширения ISAPI, например, Microsoft-IIS/6.0. |
URL |
Значение части url-путь адреса URL, исключая значение PATH_INFO.
Например: /simpleisapi/folder1/folder2/SEUX.dll. |
Для демонстрации значений таблицы 5.2 в листинге 5.5 приведен
документ XML, созданный из расширения ISAPI SEUX.DLL. В данном примере
несущий сервер и IIS настроены таким образом, что многие значения
серверных переменных получаются в процессе HTTP-запроса. Имя несущего
узла – amd1700v2. IIS 6 на amd1700v2 настроен с использованием
следующих значений параметров и файловых расположений.
C:\ISAPI\папка1\папка2\SEUX.dll.C:\inetpub\wwwroot.C:\ISAPI.Расширение ISAPI SEUX.dll запрошено с amd1700v2 с помощью следующих
данных браузера, расположенного на отдельном компьютере.
normaluser.normaluser.http://amd1700v2/simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO?parm1=value1parm2=value<?xml version="1.0" ?> <HTTPRequestRaw> <ECBServerVariable> <ALL_HTTP HTTP_CONNECTION="Keep-Alive" HTTP_ACCEPT= "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*" HTTP_ACCEPT_ENCODING="gzip, deflate" HTTP_ACCEPT_LANGUAGE="en-us" HTTP_AUTHORIZATION="Basic bm9ybWFsdXNlcjpub3JtYWx1c2Vy" HTTP_COOKIE="ASPCLIENTDEBUG=1" HTTP_HOST="amd1700v2" HTTP_USER_AGENT= "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)" /> <AUTH_TYPE>Basic</AUTH_TYPE> <APPL_MD_PATH>/LM/W3SVC/1/Root/SimpleISAPI</APPL_MD_PATH> <APPL_PHYSICAL_PATH>C:\ISAPI\</APPL_PHYSICAL_PATH> <CONTENT_LENGTH>0</CONTENT_LENGTH> <CONTENT_TYPE /> <GATEWAY_INTERFACE>CGI/1.1</GATEWAY_INTERFACE> <HTTP_ACCEPT>image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*</HTTP_ACCEPT> <HTTPS>off</HTTPS> <HTTP_AUTHORIZATION> Basic bm9ybWFsdXNlcjpub3JtYWx1c2Vy </HTTP_AUTHORIZATION> <LOGON_USER>normaluser</LOGON_USER> <AUTH_PASSWORD>normaluser</AUTH_PASSWORD> <AUTH_TYPE>Basic</AUTH_TYPE> <AUTH_USER>normaluser</AUTH_USER> <APPL_PHYSICAL_PATH>C:\ISAPI\</APPL_PHYSICAL_PATH> <INSTANCE_ID>1</INSTANCE_ID> <INSTANCE_META_PATH>/LM/W3SVC/1</INSTANCE_META_PATH> <PATH_INFO>/PATH_INFO</PATH_INFO> <PATH_TRANSLATED>c:\inetpub\wwwroot\PATH_INFO</PATH_TRANSLATED> <QUERY_STRING>parm1=value1parm2=value</QUERY_STRING> <REMOTE_ADDR>169.254.176.147</REMOTE_ADDR> <REMOTE_HOST>169.254.176.147</REMOTE_HOST> <REMOTE_USER>normaluser</REMOTE_USER> <REQUEST_METHOD>GET</REQUEST_METHOD> <SCRIPT_NAME>/simpleisapi/folder1/folder2/SEUX.dll</SCRIPT_NAME> <SERVER_NAME>amd1700v2</SERVER_NAME> <SERVER_PORT>80</SERVER_PORT> <SERVER_PORT_SECURE>0</SERVER_PORT_SECURE> <SERVER_PROTOCOL>HTTP/1.1</SERVER_PROTOCOL> <SERVER_SOFTWARE>Microsoft-IIS/6.0</SERVER_SOFTWARE> <URL>/simpleisapi/folder1/folder2/SEUX.dll</URL> </ECBServerVariable> <ECBProperties> <lpszLogData /> <lpszMethod>GET</lpszMethod> <lpszQueryString>parm1=value1parm2=value</lpszQueryString> <lpszPathInfo>/PATH_INFO</lpszPathInfo> <lpszContentType /> </ECBProperties> </HTTPRequestRaw>
После получения заголовка ALL_HTTP из функции GetServerVariable (см.
листинг 5.4) для отображения содержимого в XML необходимо реализовать
обработку строки. Заголовки разделяются символами новой строки и
возврата каретки. Двоеточие (":") разделяет имя заголовка и
его значение. GetHeaderValuePair инициализирует поиск в указанной
позиции и возвращает имя и соответствующее ему значение для данного
заголовка, а также позицию, в которой прерван поиск заголовков. GetHeaderValuePair единовременно осуществляет поиск одного значения
заголовка.
Как видно из листинга 5.6 функция GetHeaderValuePair осуществляет
поиск символа ":" в HTTP-заголовке, начиная с позиции nStart в строке sHeader. Значение nStart – это счетчик (начинается с
нуля). Если символ " :" не найден, функция завершает работу
с возвращением значения "ложь". При обнаружении символа
" :" (т.е. заголовок существует) в sHeader продолжается
поиск новой строки, начиная с позиции, в которой обнаружен данный
символ. При поиске используется функция find строки Standard Template
Library (\n в качестве аргумента и с
указанием в качестве начальной позиции символа " :". Позиция
новой строки становится конечной позицией, возвращаемой указателем pnEnd, посредством чего вызывающей функции становится известно, в каком
месте остановлен поиск. С помощью всех параметров позиции, выявленных в
процессе вызовов функции поиска строки sHeader, имя заголовка и
значение извлекаются в место расположения памяти, связанное с
указателями psName и psValue.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: GetHeaderValuePair
In: sHeader - string HTTP Header
nStart - integer search start location
psName - pointer to name of header that is being sought
psValue - pointer to string that will be filled if
value found
pnEnd - pointer to integer of the final search position
Out: bool true returned if the header was found,
false returned otherwise
Purpose:
Searches through the HTTP header passed in for a
header value. Returns data about the search
parameters if found or not.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
bool GetHeaderValuePair(const string sHeader,
const int nStart,
string *psName,
string *psValue,
int *pnEnd)
{
const string sColon(":");
//determine if header is a post header
string::size_type idxColonPosition = nStart;
//start looking at beginning
idxColonPosition = sHeader.find(sColon, idxColonPosition);
if (idxColonPosition == string::npos)//no more headers found
return false;//this is failure
//get the name
psName->assign(sHeader.substr
(nStart, idxColonPosition - nStart));
//find next newline
string::size_type idxNewLine;
idxNewLine = sHeader.find('\n', idxColonPosition);
//get the end even if it means not found
*pnEnd = idxNewLine;
if (idxNewLine == string::npos) //a newline was not found
return true;//not a failure - might be the last header
//get the value
//adjust colon position so we do not assign colon in value
idxColonPosition = idxColonPosition +1;
psValue->assign(sHeader.substr(idxColonPosition,
idxNewLine - idxColonPosition));
return true;
После обработки функции HttpExtensionProc значения заголовка ALL_HTTP
остальные серверные переменные обрабатываются с помощью функции GetECBElement (см. листинг 5.7). Каждая серверная переменная,
передаваемая в GetECBElement, извлекается с помощью функции GetServerVariable и записывается в элемент XML, присоединяемый к
строке, на которую указывает psElement. Указатель psElement указывает
на документ XML, конструируемый в HttpExtensionProc.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetECBElement
In: pECB - Pointer to the extension control block for the
purposes of calling the GetServerVariable function.
sName - string name of the server variable that is
being sought.
psElement - string pointer to XML document being built that
will be updated with the name and value for the
server variable extracted from the extension
control block.
Out: nothing returned but the string psElement points to
will be updated.
Purpose:
appends a string of an XML element to the string psElement
points to. The XML element that is created is in the form of
<Server Variable Name>Server Variable Value</Server Variable Name>
for example:
<GATEWAY_INTERFACE>CGI/1.1</GATEWAY_INTERFACE> + newline
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void GetECBElement( EXTENSION_CONTROL_BLOCK *pECB,
const string sName,
string *psElement)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
//get the server variable value
if (pECB->GetServerVariable( pECB->ConnID,
(LPSTR)sName.c_str(),
szTempBuffer,
dwBufferSize))
{
//build the XML element and
//add it to the XML document passed in
psElement->append(string(XML_L) + sName + string(XML_R));
psElement->append(ValidateValue((string)szTempBuffer));
psElement->append(string(XML_L_END) + sName + string(XML_R) +
string(NEW_LINE));
}
}
Функция ValidateValue проверяет, что специальные символы указаны с
помощью альтернативных комбинаций символов. Функция применяется к
строке, перед тем как строке присваивается статус значения элемента.
Для подтверждения значения атрибута можно применять ValidateValue. XML
не разрешает использование определенных специальных символов в позиции
значения, если они не представлены в альтернативном виде. Как видно из
листинга 5.8, символы, используемые для реализации XML-структуры:
" ?" и " ?" – заменяются
альтернативными эквивалентами.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: ValidateValue
In: Constant reference to a string variable sValue. sValue is the
value being checked to see if it has a character requiring
escaping
Out: returns a string with the escaped characters in place
Purpose:
blindly replaces all special characters
with the escape sequence character so that XML will
be valid.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
string ValidateValue(const string sValue)
{
string sReturn;
sReturn = sValue;
FindAndReplace(sReturn, string(""),string("amp;"));
FindAndReplace(sReturn, string("="),string("#61;"));
FindAndReplace(sReturn, string("<"),string("lt;"));
FindAndReplace(sReturn, string(">"),string("gt;"));
FindAndReplace(sReturn, string("'"),string("apos;"));
FindAndReplace(sReturn, string("\""),string("quot;"));
return sReturn;
}
Функция FindAndReplace представляет собой утилиту для замещения всех
вхождений строки. В расширении ISAPI SEUX она является идеальным
механизмом для замещения одной фразы внутри строки другой фразой.
Аргументы представляют собой указатели на строки:
Строка find и replace, используемые функцией FindAndReplace для поиска контейнера всех вхождений цели (см. листинг
5.9). Каждый раз при обнаружении цели в контейнере происходит ее
замена, и начинается новый поиск. По завершении работы функции FindAndReplace контейнер обновляется замещениями, если таковые имеются.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: FindAndReplace
In: psContainer - pointer to a string that will be searched
and edited if a value is discovered.
psTarget - pointer to a string that is being sought for
replacement.
psReplacement - pointer to a string that will replace the
the string pointed to in psTarget.
Out: nothing - but psContainer will be changed
Purpose:
searches string psContainer pointer for the string that
psTarget points to and replaces it with the string that
psReplacement points to.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void FindAndReplace(string *psContainer,
string *psTarget,
string *psReplacement)
{
string::size_type idx;
idx = psContainer->find(*psTarget);
while (idx != string::npos)//an instance was found
{
//are we at the end of the string
if (psContainer->size() == idx)
{
*psContainer += *psReplacement;
break;
}
else
{
psContainer->replace
(idx, psTarget->size() , *psReplacement);
//advance beyond the current character
idx += psReplacement->size();
}
//look for next occurance
idx = psContainer->find(*psTarget, idx);
}
}
Функция GetElement работает аналогично функции GetECBElement ; она
вызывается из функции HttpExtensionProc для конкатенации элементов из
свойств GetElement. GetElement
размещает свойство GetElement. Осуществляется запрос следующих свойств:
HSE_LOG_BUFFER_LEN, используемый для
размещения информации, добавляемой в файл журнала для данной транзакции
HTTP-запроса.GET, PUT или HEAD.QUERY_STRING .CONTENT_TYPE.Когда функция HttpExtensionProc завершает получение содержимого всех
возможных серверных переменных и свойств SendResponse. SendResponse направляет запрашивающей программе строковое
значение, переданное в функцию. Заголовок типа содержимого передается
запрашивающему клиенту с помощью функции ServerSupportFunction (см.
листинг 5.10). Передаваемый заголовок представляется константой BASIC_HEADER, эквивалентной следующей строке: Content-type:
text/html\r\n\r\n. За заголовками HTTP следуют два символа возврата
каретки и новой строки, поскольку возвращаемые данные представляют
собой текст. Можно указать и XML, однако если в запрашивающем браузере
в XML зарегистрированы типы
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: SendResponse
In: pECB - pointer to the extension control block
sValue - string reference to the value to be
written to the HTTP response
Out: nothing
Purpose:
writes the intended value to the HTTP response
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void SendResponse(EXTENSION_CONTROL_BLOCK *pECB, string sValue)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
// set content-type header
strcpy(szTempBuffer, BASIC_HEADER);
DWORD dwHeaderSize = strlen(szTempBuffer);
pECB->ServerSupportFunction(pECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
NULL,
dwHeaderSize,
(LPDWORD) szTempBuffer);
//write value to http response
DWORD dwLength=sValue.length();
pECB->WriteClient( pECB->ConnID,
(PVOID)sValue.c_str(),
dwLength,
HSE_IO_SYNC);
}
После отправки заголовка возврата отправляется содержимое с помощью
функции WriteClient. Как показано в следующем примере, при передаче
данных клиенту отправляется идентификатор соединения ConnID. Имеющееся
значение, полученное из текущего экземпляра указателя, использовано в
листинге 5.10. Содержимое передается функцией WriteClient с помощью
пустого указателя в параметре Buffer. Содержимое, на которое ссылается
указатель Buffer, должно равняться количеству байт, передаваемому
клиенту, и указываться в параметре lpdwBytes. По завершении вызова lpdwBytes содержит количество переданных байт, если запись не
осуществлялась асинхронно. Значение параметра dwSync определяет способ
передачи данных клиенту. В листинге 5.10 с помощью макроса HSE_IO_SYNC
указывается значение 0х00000001, т.е. запись выполняется синхронно, и
пространство памяти, на которое ссылается указатель lpdwBytes,
обновится по завершении WriteClient количеством байт, переданным
клиенту. Если в макросе HSE_IO_ASYNC представлено значение 0х00000002,
то данные, отправляемые клиенту, и функция обратной связи зафиксируют
события передачи информации клиенту. Асинхронное использование функции WriteClient требует объявления функции обратной связи, а также отправки
функцией ServerSupportFunction значения HSE_REQ_IO_COMPLETION для
установки с клиентом транзакции асинхронной записи.
Функция WriteClient является членом WriteClient:
BOOL WriteClient( HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync );
При создании расширения ISAPI с использованием поддержки MFC
воспользуйтесь мастером шаблона проекта ISAPI (ISAPI Project Template
Wizard). Мастер настроит проект на необходимую поддержку для библиотек
ISAPI и создаст класс, унаследованный из CHttpServer. Для ISAPI
предоставляются следующие классы MFC.
CHttpFilter, для обработки содержимого данного события HTTP. Служит для
обработки связанных с рассматриваемым событием HTTP данных, проходящих
через фильтр.CHttpServer,
содержащий методы управления данными, связанными с рассматриваемым
событием HTTP.Классы, предоставляемые в MFC, обеспечивают хороший уровень абстракции
для первоначального взаимодействия со структурами ISAPI и функциями,
приведенными выше. С помощью MFC разработчик осуществляет доступ к
"сырому" ISAPI. Некоторые классы облегчают анализ
заголовков и данных HTTP, выполняемый вручную в расширении ISAPI SEUX.
Мастер создаст класс, которому будет присвоено имя согласно следующей
схеме: C<Имя_проекта>Расширение. Функция с именем Default
является главной точкой входа для ISAPI DLL:
void Default(CHttpServerContext* pCtxt);
Функция Default заменяет функцию HttpExtensionProc, используемую в
расширении ISAPI SEUX в качестве главной точки входа. Указатель на
класс CHttpServerContext, передаваемый в функцию Default, обеспечивает
функциональность и требования разработчика к управлению данными и
HTTP-транзакцией (осуществлялось посредством SEUX ).
Для создания расширения ISAPI с помощью шаблона проекта Vusal Studio .NET откройте Visual Studio .NET и выберите File\New\Project (Файл\Создать\Проект). Откроется диалоговое окно New Project (Новый проект) (см. рис. 5.13). Выберите шаблон проекта MFC ISAPI Extension DLL, введите имя проекта и нажмите на кнопку OK.
(рис 5.13) Выбор шаблона проекта MFC ISAPI Extension DLL в Visual Studio .NETПримечание. Диалоговое окно (см. рис. 5.13) отображает значки, отличающиеся от тех, которые показаны на рис. 5.2. В обоих случаях использованы одинаковые версии Visual Studio .NET. Рисунок 5.2 получен при нажатии на кнопку Large Icons (Большие значки) в правом верхнем углу, рисунок 5.13 – при нажатии на кнопку Small Icons (Мелкие значки). Кнопки Large Icons (Большие значки) и Small Icons (Мелкие значки) являются взаимоисключающими, и они влияют на отображение значков проекта в правой части диалогового окна New Project (Новый проект).
По аналогии с мастером проекта сервера
(рис 5.14) Область Overview (Обзор) мастера расширения ISAPI с проектом ISAPI по умолчаниюВ области Object Settings (Параметры объекта) настраиваются имена классов, сгенерированных мастером, содержатся опции, позволяющие расширению ISAPI подключаться к MFC статически или динамически. Выбор опции Use MFC In A Shared DLL (Использовать MFC в общей DLL) (см. рис. 5.15) определяет динамическое подключение MFC в ISAPI DLL, генерируемой во время компиляции. Подразумевается, что MFC существует в среде разработки, что достигается исключением содержимого из ISAPI DLL.
(рис 5.15) Область Object Settings (Параметры объекта) мастера расширения ISAPI с проектом ISAPI по умолчанию.При выборе опции Use MFC In A
Можно сгенерировать объект фильтра, включив опцию Generate A Filter Object (Генерировать объект фильтра). Фильтры загружаются в IIS для обработки каждого запроса, имеющего место в IIS. Они работают по аналогии с расширением ISAPI, как если бы оно было связано с каждым файлом корневого веб-каталога. Фильтр и расширение не являются взаимоисключающими функциями. Одна библиотека DLL может содержать как расширение, так и фильтр, что позволяет каждому из этих объектов использовать состояние посредством глобальных переменных.
Фильтры ISAPI сложны для разработки и тестирования. Они вызываются при каждом запросе в IIS, поэтому необходимо обеспечить их эффективность и правильность построения. Фильтры могут снизить эффективность сервера или вызвать сбой в его работе при возникновении проблем с памятью.
После нажатия на кнопку Finish (Готово) и завершения мастером генерации файлов проект можно скомпилировать и запустить, как только библиотека DLL будет отгружена на сервер. Если все выполнится правильно, вы увидите в браузере сообщение, имя класса в котором соответствует выбранному имени класса:
This default message was produced by the Internet Server DLL Wizard. Edit your CMFCISAPIExtension::Default() implementation to change it.
Интерфейс Internet
Изначально ISAPI распространялся среди разработчиков CGI как альтернатива программам CGI или как обновление исполняемого файла CGI. Многие исполняемые файлы CGI написаны на C++ или C, поэтому интеграция существующего веб-приложения CGI не очень сложна. Преобразование веб-приложения CGI для использования ISAPI увеличивает производительность веб-приложения. CGI при каждом HTTP-запросе создает новый процесс, что занимает много ресурсов несущего сервера. Расширения ISAPI загружаются в пространство процесса IIS, поэтому узлу не нужно создавать новый процесс при каждом HTTP-запросе. Поскольку Windows загружает динамически подключаемую библиотеку в пространство памяти один раз при первом вызове функции в DLL и хранит ее там неопределенный промежуток времени, расширение ISAPI остается загруженным и не удаляется, до тех пор пока сервер IIS не будет выключен или не будет выгружен экземпляр или виртуальная память. Таким образом, компания Microsoft дает программистам основание использовать ISAPI вместо CGI и легко обновлять ПО, созданное при помощи CGI.
ISAPI рекомендуется для программистов, создающих (или уже создавших) приложение на языке C++, предназначенное для продажи на рынке ПО. Если важным фактором является производительность, и на разработку выделяется больше времени, чем на создание обычного сценария для интернета, рассмотрите вариант использования ISAPI. Кроме всего прочего, ISAPI выполняет на несущем узле некоторые задачи, которые нельзя выполнить при помощи других технологий. Программное обеспечение ISAPI создано таким образом, что при его выполнении другие веб-приложения, написанные на языках сценариев с использованием других расширений ISAPI (например, .NET Framework или ASP.DLL), не рассматривают задачи, выполняемые расширением ISAPI.
К недостаткам рассматриваемой технологии относится сложность ISAPI в
работе и в отладке. Отладка кода в интегрированной среде разработки
(Integrated
Кроме всего прочего, код ISAPI создается с помощью неконтролируемого кода C++. Новые возможности, предлагаемые Visual Studio .NET для управляемого кода C++ в технологии .NET Framework, нельзя использовать в проекте ISAPI.
Примечание. Если программа создается с помощью контролируемого кода, то в этом случае реализуется технология.NET Framework. Эта технология используется языками C# и Visual Basic. Термин "контролируемый" означает, что технология .NET Framework контролирует очистку памяти, отведение памяти и другие процессы управления ресурсами низкого уровня. Программа на C++ не сможет работать с технологией .NET Framework, если не применяются Managed Extensions (Контролируемые расширения) для C++. Контролируемый C++ означает использование технологии .NET Framework и контролируемых расширений C++. Код C++, созданный без использования контролируемых расширений C++, является неконтролируемым кодом C++.
Приложения ISAPI представляют собой библиотеки DLL, напрямую
взаимодействующие с IIS API. Программное обеспечение ISAPI – это
расширение или фильтр. Расширения ISAPI являются библиотеками DLL,
вызываемыми посредством квалифицированного запроса в IIS. Фильтры ISAPI
вызываются независимо от других запросов IIS. Запросы HTTP передаются
напрямую расширению ISAPI с помощью ссылки URL или данных, отправляемых
из формы HTML. Расширение ISAPI может вызываться косвенно посредством
связывания файла с определенным расширением ISAPI в IIS. При установке
связей файлов выполняются действия, аналогичные ассоциированию файлов
ответа сервера (SRF) с конкретным расширением ISAPI в
Расширения ISAPI – наиболее частый способ применения ISAPI. Фильтры ISAPI довольно сложны в создании, и сфера их использования ограничена. Данная тема выходит за рамки книги и рассматриваться не будет.
URL представляет собой строку, указывающую конкретный ресурс на сервере в интернете. URL формируется согласно следующему синтаксису:
<схема>://<пользователь>:<пароль>@<узел>:<порт>/<url-путь>/<дополнительная информация>
Ниже приведен URL, реализующий запрос файла ISAPI DLL с именем SEUX.dll. В URL указаны параметры parm1 и parm2. В таблице 5.1 данный
URL разбит на части, чтобы показать, как определяется обычный URL,
осуществляющий запрос библиотеки ISAPI DLL.
http://amd1700v2/simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO? parm1=value1parm2=value
Многие компоненты URL используются для описания значений серверных переменных, о которых мы поговорим позже. Обратите внимание, что поля, отсутствующие в примере, редко встречаются в URL. В качестве примера приведен наиболее распространенный общий URL.
Если ISAPI DLL запрашивается напрямую через ссылку URL, в секции url-путь определяется имя файла библиотеки DLL расширения ISAPI. В
секции URL <дополнительная информация> располагаются пары
параметр = значение, передаваемые расширению ISAPI с помощью этой
секции или посредством отправки данных HTTP из формы HTML.
Если IIS получает запрос и считает, что необходимо использовать
расширение ISAPI (запрошен файл, связанный с расширением ISAPI или само
расширение ISAPI), то IIS передает запрос HTTP вместе с расширением
ISAPI. Чтобы расширение ISAPI получило запрос HTTP, используется
определенный программный интерфейс. Как известно, приложение ISAPI
содержит
| Компонент URL | Значение из примера |
|---|---|
| схема | http |
| пользователь | Отсутствует в демонстрационном URL |
| пароль | Отсутствует в демонстрационном URL |
| узел | amd1700v2 |
| порт | Отсутствует в демонстрационном URL (подразумевается значение 80) |
| url-путь | simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO |
| дополнительная информация | ?parm1=value1parm2=value |
Расширение ISAPI анализирует данные HTTP-запроса и направляет вызовы другому программному обеспечению, например, программе бизнес-уровня (см. рис. 5.1). Ответы этой программы формируются в виде HTTP-ответов и возвращаются в IIS. IIS возвращает ответ расширения ISAPI веб-пользователю, направившему изначальный запрос.
(рис 5.1) Обзор архитектуры расширения ISAPIАрхитектура, показанная на рисунке 5.1, позволяет использовать расширения ISAPI двумя возможными способами, но не является единственным вариантом технологии. Единственным ограничением является вызов расширения ISAPI при HTTP-запросе и наличие структур и классов, содержащих HTTP-запрос и поддерживающих программное взаимодействие с ответом HTTP и запросом HTTP. Построение абстракции логики – задача разработчика. Направление вызовов библиотеке бизнес-логики необязательно, если бизнес-логика заключена в самом расширении ISAPI. Так как ISAPI напрямую записывает данные в HTTP-запрос, следует создавать архитектуру, абстрагирующую логику представления для исключения повторной компиляции ISAPI DLL при изменении логики представления.
При сравнении
Расширения ISAPI имеют такую несложную структуру, что могут создаваться
без помощи MFC или httpext.h и файл DLL definition export file (DEF)
с экспортом двух следующих функций:
HttpExtensionProc; GetExtensionVersion.
Каждое расширение ISAPI должно поддерживать эти функции. Оно может
также экспортировать функцию TerminateExtension (это не является
обязательным). После настройки проекта DLL расширение нужно применить к
IIS. В Visual Studio .NET имеется шаблон проекта ISAPI, запускающий
мастер расширения ISAPI. Этот мастер создает класс, названный по имени
проекта, наследуемый из класса CHTTPServer и содержащий необходимые
функции ISAPI. Мастер расширения ISAPI использует httpext.h для получения классов и структур
ISAPI.
Работа с мастером расширения ISAPI будет описана позже. Рассмотрим
метод создания простого расширения ISAPI с минимальной поддержкой MFC и
В диалоговом окне New Project (Новый проект) щелкните на узле Visual C++ Projects (Проекты Visual C++) в левой части окна и выберите шаблон проекта Win32. Присвойте проекту имя HelloWorld и выберите место расположения создаваемого проекта.
При нажатии на кнопку More (Больше) диалоговое окно New Project (Новый
проект) отобразит дополнительную информацию о том, в каком месте будет
создан проект. Название данной кнопки изменится на Less (Меньше). По
умолчанию Visual Studio создает каталог с именем, идентичным имени
проекта, в папке, указанной в текстовом поле Location (Расположение), в
котором располагаются все файлы проекта. На рисунке 5.2 показано, что
в текстовом поле Location (Расположение) введено значение C:\bookMaterial\IISBook\17ISAPIExtension\code, а именем проекта
является HelloWorld.
(рис 5.2) Диалоговое окно New Project (Новый проект) с выбранным проектом Win32C:\bookMaterial\IISBook\17ISAPIExtension\code\HelloWorld и разместит в
нем файлы проекта.stdafx,
созданные Visual Studio .NET для его поддержки. В окне Solution
Explorer выделите заголовок stdafx и файлы реализации, щелкните правой
кнопкой мыши и выберите Remove (Удалить) (см. рис. 5.6).
(рис 5.6) Удаление файлов stdafx из проекта
Hello World в соответствии с
именем проекта (см. рис. 5.7). Имя файла должно соответствовать имени
DLL, иначе компилятор C++ отобразит предупреждение о несоответствии
имен файлов. Рассматриваемый файл DEF (файл экспорта определения)
описывает функции, экспортируемые из DLL, чтобы другое приложение могло
загрузить DLL и найти адреса функций.
(рис 5.7) Добавление в проект файла экспорта определенияШаблоны проекта Visual Studio используют встроенный механизм MFC для экспорта функций и использования библиотеки DLL расширения ISAPI программой-потребителем, каковой является IIS. В Visual Studio .NET нет шаблона проекта, создающего конечный продукт без готового заголовка, поэтому следует выбрать шаблон проекта Win32, преобразовать его в DLL, после чего удалить ненужные файлы и настройки. При создании проекта без поддержки MFC иногда возникают некоторые сложности. Однако MFC сами по себе достаточно сложны, поэтому небольшие проекты (как в нашем примере) можно упростить, отказавшись от использования MFC.
Следующим шагом является настройка файла DEF.
После добавления в проект файла DEF имя библиотеки в файле будет идентично имени проекта (иначе Visual Studio .NET отобразит предупреждение). В нашем проекте имя выглядит следующим образом:
LIBRARY HelloWorld
В файл DEF добавим блок DESCRIPTION для описания назначения библиотеки
DLL. Описание заключается в одинарные кавычки. Блок DESCRIPTION не
является обязательным в отличие от блока EXPORTS. Имена функций следуют
после объявления блока EXPORTS. В листинге 5.1 приведен код файла
экспорта определения, использованный в нашем проекте. Функции
приводятся в произвольном порядке, однако они должны соответствовать
именам в реализации расширения.
LIBRARY HelloWorld
DESCRIPTION 'Demonstration of a simple Hello World ISAPI extension'
EXPORTS
HttpExtensionProc
GetExtensionVersion
Файл HelloWorld.cpp необходимо настроить на поддержку функций,
необходимых для обеспечения работы расширения ISAPI.
HelloWorld.cpp для редактирования
посредством двойного щелчка на имени файла в окне Solution Explorer.DLLMain (она не нужна в данном примере).httpext.h и <string> (см. листинг
5.2).GetExtensionVersion (см. листинг 5.2).HttpExtensionProc (см. листинг 5.2).// HelloWorld.cpp : Defines the entry point for
//the DLL application.
#include <httpext.h> //for ISAPI classes and structures
// tell the compiler to shut up about using STL
#pragma warning(disable:4786)
#include <string> //to build response to send back to user
using namespace std;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetExtensionVersion
In: pVer - Pointer to ISAPI structure HSE_VERSION_INFO.
Out: Returns true if you want IIS to use the extension, otherwise
if another value is returned, IIS will not use the extension
Purpose:
Called when the extension is loaded into IIS. The member
variables of HSE_VERSION_INFO, dwExtensionVersion and
lpszExtensionDesc should be filled with the extension
version and description.
Other initialization functionality could be called from this
function to set up the server to use this extension.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
//ISAPI version
const DWORD VERSION_NUMBER = 0.9;
const char* VERSION_NAME = "Hello World";
pVer->dwExtensionVersion = VERSION_NUMBER;
strncpy( pVer->lpszExtensionDesc,
VERSION_NAME,
HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:HttpExtensionProc
In: pECB - pointer to the Extension control block structure
Out: DWORD - HSE status code
Purpose:
main entry point for HTTP request
the possible return codes are:
HSE_STATUS_SUCCESS - everything worked great
HSE_STATUS_SUCCESS_AND_KEEP_CONN - same as HSE_STATUS_SUCCESS
since IIS 4
HSE_STATUS_PENDING - wait until effort completed
HSE_STATUS_ERROR - sends a 500 error code
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
//HTTP headers
const char* BASIC_HEADER = "Content-type: text/html\r\n\r\n";
//output values
const DWORD BUFFER_LENGTH = 4096;
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
string sResponse;
//start our HTML document
sResponse = "<HTML><HEAD></HEAD><BODY><P>";
sResponse += "Hi! Hello World";
sResponse += "</P></BODY></HTML>";
// set content-type header
strcpy(szTempBuffer, BASIC_HEADER);
DWORD dwHeaderSize = strlen(szTempBuffer);
pECB->ServerSupportFunction(pECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
NULL,
dwHeaderSize,
(LPDWORD) szTempBuffer);
//write value to http response
DWORD dwLength=sResponse.length();
pECB->WriteClient( pECB->ConnID,
(PVOID)sResponse.c_str(),
dwLength,
0);
//return a success code
return HSE_STATUS_SUCCESS;
}
Функция GetExtensionVersion использует указатель на структуру HSE_VERSION_INFO в качестве параметра и возвращает значение BOOL в
зависимости от того, использует ли IIS расширение ISAPI. Если функция GetExtensionVersion возвращает значение "истина", то
расширение ISAPI используется. Функция GetExtensionVersion вызывается
при загрузке расширения ISAPI в пространство процесса IIS. После
загрузки расширения ISAPI эта функция повторно не вызывается. Она
отлично подходит для включения кода, выполняющего проверку ключа
лицензии, или для процедур инициализации, проверяющих использование
расширения. В листинге 5.2 файла HelloWorld.cpp функция GetExtensionVersion только получает информацию о версии для расширения
ISAPI.
Структура HSE_VERSION_INFO содержит две переменные dwExtensionVersion и lpszExtensionDesc. Этим переменным присваиваются значения при вызове
функции GetExtensionVersion. В листинге 5.2 переменной dwExtensionVersion присваивается значение 0,9 типа DWORD, а переменной lpszExtensionDesc – значение HelloWorld. Эта функция всегда возвращает
значение "истина", так как в IIS всегда загружается
расширение ISAPI. Если расширение ISAPI зависит от наличия
действительного файла лицензии, другой программной библиотеки или
специальной конфигурации, тогда в функции можно выполнять
соответствующую проверку и возвращать значение "ложь" в
случае отрицательного результата.
Функция HttpExtensionProc в качестве параметра использует одну из
важнейших структур ISAPI – блок контроля расширения (Extension Control
Block,
Изучая код листинга 5.2, следует отметить, что указатель на блок
Первой функцией, вызываемой в блоке ServerSupportFunction. Она устанавливает заголовок в ответе
HTTP для обозначения вида содержимого (текст или HTML) с помощью
следующей строки:
Content-type: text/html\r\n\r\n
За заголовками ответов HTTP должны следовать две пары символов возврата
каретки и перехода на новую строку ( /n и /r ).
Следующей функцией, вызываемой с помощью WriteClient. Фраза "Hi! Hello World" записывается в браузер
с помощью данной функции посредством передачи указателя char (char*)
строковой переменной, содержащей код HTML и строку "Hi! Hello
World". Функции WriteClient нужен пустой указатель ( void* ),
поэтому указатель char приводится к форме пустого указателя с помощью
макроса PVOID.
После успешной компиляции DLL расширение ISAPI можно отгружать на веб-сервер. Если в качестве расположения конечного файла DLL указан экземпляр веб-сервера или корень виртуального каталога, можно немедленно запросить файл из IIS. При выполнении отладки Visual Studio .NET браузер не откроется, и автоматического запроса ISAPI DLL из IIS (как при отладке веб-службы ASP.NET) не произойдет. Visual Studio считает проект библиотекой DLL, поэтому выдаст запрос на присвоение исполняемого файла, который использует данную библиотеку.
Запомните. Если разработчик осуществляет построение библиотеки DLL расширения ISAPI и затем тестирует DLL посредством ее запроса через IIS, при втором построении ISAPI DLL в Visual Studio .NET, скорее всего, не удастся создать DLL. IIS блокирует библиотеку DLL расширения ISAPI при ее запросе из IIS, поскольку DLL загружается в пространство процесса IIS. Для разблокирования ISAPI DLL необходимо выгрузить экземпляр веб-сайта или виртуального каталога либо заново запустить приложение. После разблокирования ISAPI DLL поверх нее в процессе построения осуществляется запись.
При первом запросе ISAPI DLL из IIS 6 в операционной системе Windows Server 2003, скорее всего, возвратится ошибка 404. В WS03 и IIS6 существует новая возможность, ограничивающая по умолчанию всякую программную поддержку серверной части, если она не включена вручную. В предыдущих версиях Windows Server компонент IIS поставлялся с включенной программной поддержкой серверной части (ASP). Эта функция включается в окне Web Service Extensions (Расширения веб-служб) консоли MMC Computer Management (Управление компьютером) (см. рис. 5.8).
(рис 5.8) Включение программной функциональности серверной части в окне Web Service Extensions (Расширения веб-служб)Настроим IIS на разрешение запросов расширения ISAPI HelloWorld.
Расширение HelloWorld появится в окне администрирования расширения
веб-служб консоли Copmuter Management (Управление компьютером), и в
поле Status (Состояние) отобразится значение Allowed (Разрешено).
В качестве альтернативы установите Allowed (Разрешено) для расширения
веб-службы All Unknown ISAPI Extensions (Все неизвестные расширения
ISAPI). Данный параметр разрешает выполнение любого запроса
относительно любого расширения ISAPI.
Включение данной настройки нарушает безопасность сервера, поэтому используйте этот параметр только в изолированных средах, таких как среда разработки.
Разрешения на выполнение веб-экземпляра IIS или виртуального каталога устанавливаются на разрешение выполнения библиотек ISAPI DLL. По умолчанию параметр Execute Permissions (Разрешения на выполнение) в окне свойства веб-экземпляра IIS или виртуального каталога не позволяет выполнять сценарии и исполняемые файлы.
(рис 5.10) Установка разрешений для виртуального каталогаЕсли в IIS все настроено должным образом, ISAPI Hello World будет
функционировать. Запросите библиотеку ISAPI DLL в адресе URL из
браузера, как если бы это был файл HTML – и DLL выполнится (см. рис.
5.11).
(рис 5.11) Выполнение расширения ISAPI HelloWorld
HttpExtensionProc выполняет следующие
задачи.
GetServerVariable./*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:HttpExtensionProc
In: pECB - pointer to the Extension control block structure
Out: DWORD - HSE status code
Purpose:
main entry point for HTTP request
the possible return codes are:
HSE_STATUS_SUCCESS - everything worked great
HSE_STATUS_SUCCESS_AND_KEEP_CONN - same as HSE_STATUS_SUCCESS
since IIS 4
HSE_STATUS_PENDING - wait until effort completed
HSE_STATUS_ERROR - sends a 500 error code
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
string sDoc;
//start our XML document
sDoc = string(HEAD) + string(NEW_LINE) + string(XML_L) +
string(MAIN_ELEMENT_NAME) + string(XML_R) +
string(NEW_LINE);
//START THE ECBServerVariable VARIABLES
sDoc += string(XML_L) +
string("ECBServerVariable") + string(XML_R) +
string(NEW_LINE);
//GET the ALL_HTTP
sDoc += string(XML_L) + string("ALL_HTTP");
GetALLHTTPHeader(pECB, sDoc);
sDoc += string(XML_R_END) +
string(NEW_LINE);//end the first main element
GetECBElement(pECB, string("AUTH_TYPE"), sDoc);
GetECBElement(pECB, string("APPL_MD_PATH"), sDoc);
GetECBElement(pECB, string("APPL_PHYSICAL_PATH"), sDoc);
GetECBElement(pECB, string("CONTENT_LENGTH"), sDoc);
GetECBElement(pECB, string("CONTENT_TYPE"), sDoc);
GetECBElement(pECB, string("GATEWAY_INTERFACE"), sDoc);
GetECBElement(pECB, string("HTTP_ACCEPT"), sDoc);
GetECBElement(pECB, string("HTTPS"), sDoc);
GetECBElement(pECB, string("HTTP_AUTHORIZATION"), sDoc);
GetECBElement(pECB, string("LOGON_USER"), sDoc);
GetECBElement(pECB, string("AUTH_PASSWORD"), sDoc);
GetECBElement(pECB, string("AUTH_TYPE"), sDoc);
GetECBElement(pECB, string("AUTH_USER"), sDoc);
GetECBElement(pECB, string("APPL_PHYSICAL_PATH"), sDoc);
GetECBElement(pECB, string("INSTANCE_ID"), sDoc);
GetECBElement(pECB, string("INSTANCE_META_PATH"), sDoc);
GetECBElement(pECB, string("PATH_INFO"), sDoc);
GetECBElement(pECB, string("PATH_TRANSLATED"), sDoc);
GetECBElement(pECB, string("QUERY_STRING"), sDoc);
GetECBElement(pECB, string("REMOTE_ADDR"), sDoc);
GetECBElement(pECB, string("REMOTE_HOST"), sDoc);
GetECBElement(pECB, string("REMOTE_USER"), sDoc);
GetECBElement(pECB, string("REQUEST_METHOD"), sDoc);
GetECBElement(pECB, string("SCRIPT_NAME"), sDoc);
GetECBElement(pECB, string("SERVER_NAME"), sDoc);
GetECBElement(pECB, string("SERVER_PORT"), sDoc);
GetECBElement(pECB, string("SERVER_PORT_SECURE"), sDoc);
GetECBElement(pECB, string("SERVER_PROTOCOL"), sDoc);
GetECBElement(pECB, string("SERVER_SOFTWARE"), sDoc);
GetECBElement(pECB, string("URL"), sDoc);
//End THE ECBServerVariable VARIABLES
sDoc += string(XML_L_END) +
string("ECBServerVariable") + string(XML_R) +
string(NEW_LINE);
//START THE ECBProperties
sDoc += string(XML_L) +
string("ECBProperties") + string(XML_R) +
string(NEW_LINE);
GetElement(string("lpszLogData"),
string(pECB->lpszLogData),sDoc);
GetElement(string("lpszMethod"),
string(pECB->lpszMethod),sDoc);
GetElement(string("lpszQueryString"),
string(pECB->lpszQueryString),sDoc);
GetElement(string("lpszPathInfo"),
string(pECB->lpszPathInfo),sDoc);
GetElement(string("lpszContentType"),
string(pECB->lpszContentType),sDoc);
//end THE ECBProperties
sDoc += string(XML_L_END) +
string("ECBProperties") + string(XML_R) +
string(NEW_LINE);
//end our XML document
sDoc += string(XML_L_END) +
string(MAIN_ELEMENT_NAME) + string(XML_R) +
string(NEW_LINE);
//write it!
SendResponse(pECB,sDoc);
return HSE_STATUS_SUCCESS;
}
Примечание. Исходный код SEUX доступен на веб-сайте автора книги (см. введение).
Первой задачей (см. листинг 5.3) является открытие документа XML посредством присоединения некоторых констант, представляющих собой части документа XML. Построение XML происходит вручную, и константы объявляются для общих частей документа XML, так как они используются во многих местах. Документ XML размещается в строковой переменной с именем sDoc. Ниже приведены константы XML:
//xml parts
const char* MAIN_ELEMENT_NAME = "HTTPRequestRaw";
const char* QUOTE ="\"";
const char* XML_L ="<";
const char* XML_R =">";
const char* XML_L_END ="</";
const char* XML_R_END ="/>";
const char* NEW_LINE ="\n";
const char* HEAD = "<?xml version=\"1.0\"?>";
Рассматриваемый документ XML достаточно прост, поэтому его построение вручную не вызывает никаких трудностей. Для более сложного документа рекомендуется использовать аналитическую библиотеку XML, такую как MSXML.
После инициализации документа XML начинается работа по размещению в
элементах XML всех серверных переменных. Создаваемый документ XML имеет
родительский элемент HTTPRequestRaw. Внутри HTTPRequestRaw содержатся
два дочерних элемента с ECBServerVariable и ECBProperties. Для каждого
значения серверной переменной, запрашиваемого из ECBServerVariable, независимо от
получения значения соответствующей серверной переменной. Для каждого
запрошенного свойства ECBProperties, которое содержит значение независимо от получения
значения свойства. При вызове расширения ISAPI SEUX с помощью IE 6.0
отобразится документ XML (см. рис. 5.12). В других версиях браузера
XML может отобразится по-другому или не отобразиться вовсе.
(рис 5.12) IE 6.0, отображающий документ XML серверных переменных из расширения SEUX ISAPI
Первой серверной переменной, для которой получается значение, является
переменная ALL_HTTP, представляющая собой все заголовки HTTP,
переданные в запросе HTTP. Результатом остальных запрошенных серверных
переменных является число или строка, легко согласуемая с XML, однако ALL_HTTP возвращает все заголовки в одну и ту же серверную переменную.
Заголовки в значении, возвращаемом из ALL_HTTP, ограничены символами
новой строки, поэтому требуется дополнительный анализ для размещения
каждого заголовка в элементе XML в качестве атрибута. Функция GetALLHTTPHeader выполняет данную задачу с помощью функции GetServerVariable (см. листинг 5.4).
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetALLHTTPHeader
In: pECB - pointer to the Extension control block structure
psElement - string pointer to XML document being built that
will be updated with the element for the ALL_HTTP
server variable value.
Out: nothing returned but the string psElement points to
will be updated.
Purpose:
Updates the XML document string by adding an element
for the ALL_HTTP server variable. This variable contains
all of the http headers so some additional parsing must
take place on the headers to format them into XML.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void GetALLHTTPHeader(EXTENSION_CONTROL_BLOCK *pECB,
string *psElement)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
const string EQUAL("=");
const string SPACE(" ");
string sAllHeaders; //used to cut up 'all headers' returned
int nNewLinePos = 0;
int nEndLinePos = 0;
string sName;
string sValue;
//pull the whole HTTP header
if (pECB->GetServerVariable( pECB->ConnID,
"ALL_HTTP",
szTempBuffer,
dwBufferSize))
{
//if the whole http header was pulled then parse it
sAllHeaders.assign(szTempBuffer);
//get the name / value pairs
while (GetHeaderValuePair( sAllHeaders,
nNewLinePos,
sName,
sValue,
nEndLinePos))
{
//add the attribute to the element
psElement->append( SPACE + sName + EQUAL +
QUOTE +
ValidateValue(sValue) +
QUOTE);
//reset the newline to the last endline
nNewLinePos = nEndLinePos;
//dump the values
sName.erase();
sValue.erase();
}
}
}
Функция GetServerVariable возвращает значение "истина" при
успешном выполнении и значение "ложь" при возникновении
ошибки, как показано в следующем примере:
BOOL WINAPI GetServerVariable( HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer );
Прототип GetServerVariable в данном случае требует передачи четырех
параметров.
При успешном выполнении функция GetServerVariable возвращает значение
"истина". Указатель lpvBuffer указывает значение
запрашиваемой серверной переменной, а lpdwSizeofBuffer – на новое
значение DWORD, отражающее текущий размер значения, включая байт конца
строки. При неудачном завершении работы функция GetServerValue
возвращает значение "ложь". В этом случае вызывается
функция GetLastError, которая возвращает значение DWORD, представляющее
собой код ошибки. В таблице 5.2 показаны возможные ошибки функции GetServerVariable ; это константы, определяемыми во вспомогательном
Возможные значения запрашиваемых серверных переменных приведены в
листинге 5.3 в коде функции HttpExtensionProc в расширении ISAPI SEUX ;
они являются аргументами в вызовах GetECBElement. Значения серверных
переменных могут изменяться в процессе текущего события запроса HTTP в
IIS, и зачастую переменной не присваивается значение. Серверные
переменные содержат значения только при определенных настройках IIS. В
таблице 5.2 приведены серверные переменные, запрашиваемые с помощью
функции GetServerVariable.
| Константа ошибки | Описание ошибки |
|---|---|
ERROR_INVALID_PARAMETER |
Значение hConn является некорректным или закрытым, либо неверны
параметры серверной переменной. |
ERROR_INVALID_INDEX |
Запрашиваемая серверная переменная не поддерживается. |
ERROR_INSUFFICIENT_BUFFER |
Размер lpdwSizeofBuffer слишком мал для содержания значения
запрашиваемой серверной переменной. |
ERROR_NO_DATA |
Запрашиваемая серверная переменная недоступна. |
ALL_HTTP |
Все HTTP-заголовки (разделены символами новой строки), переданные в
запросе HTTP в строке с символом конца строки. Заголовки имеют вид <имя заголовка> : <значение>. |
ALL_RAW |
Все заголовки HTTP в том виде, в котором они были отправлены запрашивающей HTTP-стороной. |
APPL_MD_PATH |
Путь метабазы веб-приложения. Например, /LMW3SVC/1/Root/SimpleISAPI. |
APPL_PHYSICAL_PATH |
Физический путь корневого веб-каталога для веб-приложения. Например: C:\ISAPI\. |
AUTH_PASSWORD |
Пароль, вводимый веб-пользователем в диалоговом окне аутентификации браузера, если установлена базовая аутентификация. |
AUTH_TYPE |
Используемый тип аутентификации. Пустое значение при отсутствии
аутентификации либо значение, соответствующее Kerberos,
пользовательской аутентификации, SSL/ |
AUTH_USER |
Имя пользователя, вводимое пользователем в диалоговом окне аутентификации браузера в случае, если установлена базовая аутентификация. |
CERT_COOKIE |
Уникальный идентификатор сертификата клиента. |
CERT_FLAGS |
Битовые флаги бюро сертификатов (CA) сертификата клиента. Если bit0
равен 1, то CA отсутствует в списке распознаваемых бюро сертификатов
данного сервера и признается недействительным. |
CERT_ISSUER |
Содержит имя сертификата клиента. Например, O=Schmidlaps, OU=House, CN=
имя пользователя, C=USA. |
CERT_KEYSIZE |
Размер ключа в битах при соединении SSL. |
CERT_SERCRETKEYSIZE |
Размер секретного ключа сертификата сервера в битах. |
CERT_SERIALNUMBER |
Серийный номер сертификата клиента. |
CERT_SERVER_ISSUER |
Подробное имя |
CERT_SERVER_SUBJECT |
Подробное имя |
CERT_SUBJECT |
|
CONTENT_LENGTH |
Количество байт, исключая заголовки HTTP-запроса. |
LOGON_USER |
Если конечный пользователь успешно аутентифицировался в Windows, используется учетная запись входа в систему. |
HTTPS |
Возвращает значение off, если в HTTPS не используется SSL, в противном
случае возвращается значение on. |
HTTPS_KEYSIZE |
Размер ключа SSL-соединения в битах. |
HTTP_SECRETKEYSIZE |
Размер секретного ключа сертификата сервера в битах. |
HTTPS_SERVER_ISSUER |
Подробное имя |
HTTPS_SERVER_SUBJECT |
Подробное имя |
INSTANCE_ID |
Номер экземпляра сервера. Значения идентификатора сервера в метабазе, например, 1. |
INSTANCE_META_PATH |
Путь веб- экземпляра в метабазе, например: LM/W3SVC/1. |
PATH_INFO |
Часть URL, расположенная между ISAPI DLL и началом секции с дополнительной информацией в URL. Как правило, в этом месте нет никаких данных, если запрашивающее ПО не добавляет свое значение. |
PATH_TRANSLATED |
Часть веб-экземпляра, связанная с физическим жестким диском, с
конкатенацией значения PATH_INFO. |
|
Строка символов, следующих за символом "?" в секции дополнительной информации URL. |
|
IP-адрес хоста или шлюза запрашивающего ПО. |
|
Имя узла или шлюза запрашивающего ПО, если включен обратный поиск DNS; иначе возвращается значение IP-адреса узла или шлюза запрашивающего ПО. |
REMOTE_USER |
Имя пользователя, осуществляющего HTTP-запрос и аутентифицируемого несущим сервером. Представляет собой пустую строку для анонимного пользователя. |
|
Команда метода HTTP-запроса. |
|
Имя исполняемого двоичного файла, например, имя ISAPI DLL или исполняемого файла CGI. |
SERVER_NAME |
Имя узла сервера или IP-адрес. |
SERVER_PORT |
Порт TCP/IP, по которому получен запрос. |
SERVER_PORT_SECURE |
Значение 0 или 1. Запросы по безопасному порту возвращают 1; иначе возвращается 0. |
SERVER_PROTOCOL |
Имя и версия протокола запроса, например, HTTP 1.1. |
SERVER_SOFTWARE |
Имя и версия IIS, под которой выполняется программа DLL расширения ISAPI, например, Microsoft-IIS/6.0. |
URL |
Значение части url-путь адреса URL, исключая значение PATH_INFO.
Например: /simpleisapi/folder1/folder2/SEUX.dll. |
Для демонстрации значений таблицы 5.2 в листинге 5.5 приведен
документ XML, созданный из расширения ISAPI SEUX.DLL. В данном примере
несущий сервер и IIS настроены таким образом, что многие значения
серверных переменных получаются в процессе HTTP-запроса. Имя несущего
узла – amd1700v2. IIS 6 на amd1700v2 настроен с использованием
следующих значений параметров и файловых расположений.
C:\ISAPI\папка1\папка2\SEUX.dll.C:\inetpub\wwwroot.C:\ISAPI.Расширение ISAPI SEUX.dll запрошено с amd1700v2 с помощью следующих
данных браузера, расположенного на отдельном компьютере.
normaluser.normaluser.http://amd1700v2/simpleisapi/folder1/folder2/SEUX.dll/PATH_INFO?parm1=value1parm2=value<?xml version="1.0" ?> <HTTPRequestRaw> <ECBServerVariable> <ALL_HTTP HTTP_CONNECTION="Keep-Alive" HTTP_ACCEPT= "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*" HTTP_ACCEPT_ENCODING="gzip, deflate" HTTP_ACCEPT_LANGUAGE="en-us" HTTP_AUTHORIZATION="Basic bm9ybWFsdXNlcjpub3JtYWx1c2Vy" HTTP_COOKIE="ASPCLIENTDEBUG=1" HTTP_HOST="amd1700v2" HTTP_USER_AGENT= "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705)" /> <AUTH_TYPE>Basic</AUTH_TYPE> <APPL_MD_PATH>/LM/W3SVC/1/Root/SimpleISAPI</APPL_MD_PATH> <APPL_PHYSICAL_PATH>C:\ISAPI\</APPL_PHYSICAL_PATH> <CONTENT_LENGTH>0</CONTENT_LENGTH> <CONTENT_TYPE /> <GATEWAY_INTERFACE>CGI/1.1</GATEWAY_INTERFACE> <HTTP_ACCEPT>image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*</HTTP_ACCEPT> <HTTPS>off</HTTPS> <HTTP_AUTHORIZATION> Basic bm9ybWFsdXNlcjpub3JtYWx1c2Vy </HTTP_AUTHORIZATION> <LOGON_USER>normaluser</LOGON_USER> <AUTH_PASSWORD>normaluser</AUTH_PASSWORD> <AUTH_TYPE>Basic</AUTH_TYPE> <AUTH_USER>normaluser</AUTH_USER> <APPL_PHYSICAL_PATH>C:\ISAPI\</APPL_PHYSICAL_PATH> <INSTANCE_ID>1</INSTANCE_ID> <INSTANCE_META_PATH>/LM/W3SVC/1</INSTANCE_META_PATH> <PATH_INFO>/PATH_INFO</PATH_INFO> <PATH_TRANSLATED>c:\inetpub\wwwroot\PATH_INFO</PATH_TRANSLATED> <QUERY_STRING>parm1=value1parm2=value</QUERY_STRING> <REMOTE_ADDR>169.254.176.147</REMOTE_ADDR> <REMOTE_HOST>169.254.176.147</REMOTE_HOST> <REMOTE_USER>normaluser</REMOTE_USER> <REQUEST_METHOD>GET</REQUEST_METHOD> <SCRIPT_NAME>/simpleisapi/folder1/folder2/SEUX.dll</SCRIPT_NAME> <SERVER_NAME>amd1700v2</SERVER_NAME> <SERVER_PORT>80</SERVER_PORT> <SERVER_PORT_SECURE>0</SERVER_PORT_SECURE> <SERVER_PROTOCOL>HTTP/1.1</SERVER_PROTOCOL> <SERVER_SOFTWARE>Microsoft-IIS/6.0</SERVER_SOFTWARE> <URL>/simpleisapi/folder1/folder2/SEUX.dll</URL> </ECBServerVariable> <ECBProperties> <lpszLogData /> <lpszMethod>GET</lpszMethod> <lpszQueryString>parm1=value1parm2=value</lpszQueryString> <lpszPathInfo>/PATH_INFO</lpszPathInfo> <lpszContentType /> </ECBProperties> </HTTPRequestRaw>
После получения заголовка ALL_HTTP из функции GetServerVariable (см.
листинг 5.4) для отображения содержимого в XML необходимо реализовать
обработку строки. Заголовки разделяются символами новой строки и
возврата каретки. Двоеточие (":") разделяет имя заголовка и
его значение. GetHeaderValuePair инициализирует поиск в указанной
позиции и возвращает имя и соответствующее ему значение для данного
заголовка, а также позицию, в которой прерван поиск заголовков. GetHeaderValuePair единовременно осуществляет поиск одного значения
заголовка.
Как видно из листинга 5.6 функция GetHeaderValuePair осуществляет
поиск символа ":" в HTTP-заголовке, начиная с позиции nStart в строке sHeader. Значение nStart – это счетчик (начинается с
нуля). Если символ " :" не найден, функция завершает работу
с возвращением значения "ложь". При обнаружении символа
" :" (т.е. заголовок существует) в sHeader продолжается
поиск новой строки, начиная с позиции, в которой обнаружен данный
символ. При поиске используется функция find строки Standard Template
Library (\n в качестве аргумента и с
указанием в качестве начальной позиции символа " :". Позиция
новой строки становится конечной позицией, возвращаемой указателем pnEnd, посредством чего вызывающей функции становится известно, в каком
месте остановлен поиск. С помощью всех параметров позиции, выявленных в
процессе вызовов функции поиска строки sHeader, имя заголовка и
значение извлекаются в место расположения памяти, связанное с
указателями psName и psValue.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: GetHeaderValuePair
In: sHeader - string HTTP Header
nStart - integer search start location
psName - pointer to name of header that is being sought
psValue - pointer to string that will be filled if
value found
pnEnd - pointer to integer of the final search position
Out: bool true returned if the header was found,
false returned otherwise
Purpose:
Searches through the HTTP header passed in for a
header value. Returns data about the search
parameters if found or not.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
bool GetHeaderValuePair(const string sHeader,
const int nStart,
string *psName,
string *psValue,
int *pnEnd)
{
const string sColon(":");
//determine if header is a post header
string::size_type idxColonPosition = nStart;
//start looking at beginning
idxColonPosition = sHeader.find(sColon, idxColonPosition);
if (idxColonPosition == string::npos)//no more headers found
return false;//this is failure
//get the name
psName->assign(sHeader.substr
(nStart, idxColonPosition - nStart));
//find next newline
string::size_type idxNewLine;
idxNewLine = sHeader.find('\n', idxColonPosition);
//get the end even if it means not found
*pnEnd = idxNewLine;
if (idxNewLine == string::npos) //a newline was not found
return true;//not a failure - might be the last header
//get the value
//adjust colon position so we do not assign colon in value
idxColonPosition = idxColonPosition +1;
psValue->assign(sHeader.substr(idxColonPosition,
idxNewLine - idxColonPosition));
return true;
После обработки функции HttpExtensionProc значения заголовка ALL_HTTP
остальные серверные переменные обрабатываются с помощью функции GetECBElement (см. листинг 5.7). Каждая серверная переменная,
передаваемая в GetECBElement, извлекается с помощью функции GetServerVariable и записывается в элемент XML, присоединяемый к
строке, на которую указывает psElement. Указатель psElement указывает
на документ XML, конструируемый в HttpExtensionProc.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name:GetECBElement
In: pECB - Pointer to the extension control block for the
purposes of calling the GetServerVariable function.
sName - string name of the server variable that is
being sought.
psElement - string pointer to XML document being built that
will be updated with the name and value for the
server variable extracted from the extension
control block.
Out: nothing returned but the string psElement points to
will be updated.
Purpose:
appends a string of an XML element to the string psElement
points to. The XML element that is created is in the form of
<Server Variable Name>Server Variable Value</Server Variable Name>
for example:
<GATEWAY_INTERFACE>CGI/1.1</GATEWAY_INTERFACE> + newline
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void GetECBElement( EXTENSION_CONTROL_BLOCK *pECB,
const string sName,
string *psElement)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
//get the server variable value
if (pECB->GetServerVariable( pECB->ConnID,
(LPSTR)sName.c_str(),
szTempBuffer,
dwBufferSize))
{
//build the XML element and
//add it to the XML document passed in
psElement->append(string(XML_L) + sName + string(XML_R));
psElement->append(ValidateValue((string)szTempBuffer));
psElement->append(string(XML_L_END) + sName + string(XML_R) +
string(NEW_LINE));
}
}
Функция ValidateValue проверяет, что специальные символы указаны с
помощью альтернативных комбинаций символов. Функция применяется к
строке, перед тем как строке присваивается статус значения элемента.
Для подтверждения значения атрибута можно применять ValidateValue. XML
не разрешает использование определенных специальных символов в позиции
значения, если они не представлены в альтернативном виде. Как видно из
листинга 5.8, символы, используемые для реализации XML-структуры:
" ?" и " ?" – заменяются
альтернативными эквивалентами.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: ValidateValue
In: Constant reference to a string variable sValue. sValue is the
value being checked to see if it has a character requiring
escaping
Out: returns a string with the escaped characters in place
Purpose:
blindly replaces all special characters
with the escape sequence character so that XML will
be valid.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
string ValidateValue(const string sValue)
{
string sReturn;
sReturn = sValue;
FindAndReplace(sReturn, string(""),string("amp;"));
FindAndReplace(sReturn, string("="),string("#61;"));
FindAndReplace(sReturn, string("<"),string("lt;"));
FindAndReplace(sReturn, string(">"),string("gt;"));
FindAndReplace(sReturn, string("'"),string("apos;"));
FindAndReplace(sReturn, string("\""),string("quot;"));
return sReturn;
}
Функция FindAndReplace представляет собой утилиту для замещения всех
вхождений строки. В расширении ISAPI SEUX она является идеальным
механизмом для замещения одной фразы внутри строки другой фразой.
Аргументы представляют собой указатели на строки:
Строка find и replace, используемые функцией FindAndReplace для поиска контейнера всех вхождений цели (см. листинг
5.9). Каждый раз при обнаружении цели в контейнере происходит ее
замена, и начинается новый поиск. По завершении работы функции FindAndReplace контейнер обновляется замещениями, если таковые имеются.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: FindAndReplace
In: psContainer - pointer to a string that will be searched
and edited if a value is discovered.
psTarget - pointer to a string that is being sought for
replacement.
psReplacement - pointer to a string that will replace the
the string pointed to in psTarget.
Out: nothing - but psContainer will be changed
Purpose:
searches string psContainer pointer for the string that
psTarget points to and replaces it with the string that
psReplacement points to.
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void FindAndReplace(string *psContainer,
string *psTarget,
string *psReplacement)
{
string::size_type idx;
idx = psContainer->find(*psTarget);
while (idx != string::npos)//an instance was found
{
//are we at the end of the string
if (psContainer->size() == idx)
{
*psContainer += *psReplacement;
break;
}
else
{
psContainer->replace
(idx, psTarget->size() , *psReplacement);
//advance beyond the current character
idx += psReplacement->size();
}
//look for next occurance
idx = psContainer->find(*psTarget, idx);
}
}
Функция GetElement работает аналогично функции GetECBElement ; она
вызывается из функции HttpExtensionProc для конкатенации элементов из
свойств GetElement. GetElement
размещает свойство GetElement. Осуществляется запрос следующих свойств:
HSE_LOG_BUFFER_LEN, используемый для
размещения информации, добавляемой в файл журнала для данной транзакции
HTTP-запроса.GET, PUT или HEAD.QUERY_STRING .CONTENT_TYPE.Когда функция HttpExtensionProc завершает получение содержимого всех
возможных серверных переменных и свойств SendResponse. SendResponse направляет запрашивающей программе строковое
значение, переданное в функцию. Заголовок типа содержимого передается
запрашивающему клиенту с помощью функции ServerSupportFunction (см.
листинг 5.10). Передаваемый заголовок представляется константой BASIC_HEADER, эквивалентной следующей строке: Content-type:
text/html\r\n\r\n. За заголовками HTTP следуют два символа возврата
каретки и новой строки, поскольку возвращаемые данные представляют
собой текст. Можно указать и XML, однако если в запрашивающем браузере
в XML зарегистрированы типы
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Name: SendResponse
In: pECB - pointer to the extension control block
sValue - string reference to the value to be
written to the HTTP response
Out: nothing
Purpose:
writes the intended value to the HTTP response
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
void SendResponse(EXTENSION_CONTROL_BLOCK *pECB, string sValue)
{
TCHAR szTempBuffer[BUFFER_LENGTH];
DWORD dwBufferSize = BUFFER_LENGTH;
// set content-type header
strcpy(szTempBuffer, BASIC_HEADER);
DWORD dwHeaderSize = strlen(szTempBuffer);
pECB->ServerSupportFunction(pECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER,
NULL,
dwHeaderSize,
(LPDWORD) szTempBuffer);
//write value to http response
DWORD dwLength=sValue.length();
pECB->WriteClient( pECB->ConnID,
(PVOID)sValue.c_str(),
dwLength,
HSE_IO_SYNC);
}
После отправки заголовка возврата отправляется содержимое с помощью
функции WriteClient. Как показано в следующем примере, при передаче
данных клиенту отправляется идентификатор соединения ConnID. Имеющееся
значение, полученное из текущего экземпляра указателя, использовано в
листинге 5.10. Содержимое передается функцией WriteClient с помощью
пустого указателя в параметре Buffer. Содержимое, на которое ссылается
указатель Buffer, должно равняться количеству байт, передаваемому
клиенту, и указываться в параметре lpdwBytes. По завершении вызова lpdwBytes содержит количество переданных байт, если запись не
осуществлялась асинхронно. Значение параметра dwSync определяет способ
передачи данных клиенту. В листинге 5.10 с помощью макроса HSE_IO_SYNC
указывается значение 0х00000001, т.е. запись выполняется синхронно, и
пространство памяти, на которое ссылается указатель lpdwBytes,
обновится по завершении WriteClient количеством байт, переданным
клиенту. Если в макросе HSE_IO_ASYNC представлено значение 0х00000002,
то данные, отправляемые клиенту, и функция обратной связи зафиксируют
события передачи информации клиенту. Асинхронное использование функции WriteClient требует объявления функции обратной связи, а также отправки
функцией ServerSupportFunction значения HSE_REQ_IO_COMPLETION для
установки с клиентом транзакции асинхронной записи.
Функция WriteClient является членом WriteClient:
BOOL WriteClient( HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync );
При создании расширения ISAPI с использованием поддержки MFC
воспользуйтесь мастером шаблона проекта ISAPI (ISAPI Project Template
Wizard). Мастер настроит проект на необходимую поддержку для библиотек
ISAPI и создаст класс, унаследованный из CHttpServer. Для ISAPI
предоставляются следующие классы MFC.
CHttpFilter, для обработки содержимого данного события HTTP. Служит для
обработки связанных с рассматриваемым событием HTTP данных, проходящих
через фильтр.CHttpServer,
содержащий методы управления данными, связанными с рассматриваемым
событием HTTP.Классы, предоставляемые в MFC, обеспечивают хороший уровень абстракции
для первоначального взаимодействия со структурами ISAPI и функциями,
приведенными выше. С помощью MFC разработчик осуществляет доступ к
"сырому" ISAPI. Некоторые классы облегчают анализ
заголовков и данных HTTP, выполняемый вручную в расширении ISAPI SEUX.
Мастер создаст класс, которому будет присвоено имя согласно следующей
схеме: C<Имя_проекта>Расширение. Функция с именем Default
является главной точкой входа для ISAPI DLL:
void Default(CHttpServerContext* pCtxt);
Функция Default заменяет функцию HttpExtensionProc, используемую в
расширении ISAPI SEUX в качестве главной точки входа. Указатель на
класс CHttpServerContext, передаваемый в функцию Default, обеспечивает
функциональность и требования разработчика к управлению данными и
HTTP-транзакцией (осуществлялось посредством SEUX ).
Для создания расширения ISAPI с помощью шаблона проекта Vusal Studio .NET откройте Visual Studio .NET и выберите File\New\Project (Файл\Создать\Проект). Откроется диалоговое окно New Project (Новый проект) (см. рис. 5.13). Выберите шаблон проекта MFC ISAPI Extension DLL, введите имя проекта и нажмите на кнопку OK.
(рис 5.13) Выбор шаблона проекта MFC ISAPI Extension DLL в Visual Studio .NETПримечание. Диалоговое окно (см. рис. 5.13) отображает значки, отличающиеся от тех, которые показаны на рис. 5.2. В обоих случаях использованы одинаковые версии Visual Studio .NET. Рисунок 5.2 получен при нажатии на кнопку Large Icons (Большие значки) в правом верхнем углу, рисунок 5.13 – при нажатии на кнопку Small Icons (Мелкие значки). Кнопки Large Icons (Большие значки) и Small Icons (Мелкие значки) являются взаимоисключающими, и они влияют на отображение значков проекта в правой части диалогового окна New Project (Новый проект).
По аналогии с мастером проекта сервера
(рис 5.14) Область Overview (Обзор) мастера расширения ISAPI с проектом ISAPI по умолчаниюВ области Object Settings (Параметры объекта) настраиваются имена классов, сгенерированных мастером, содержатся опции, позволяющие расширению ISAPI подключаться к MFC статически или динамически. Выбор опции Use MFC In A Shared DLL (Использовать MFC в общей DLL) (см. рис. 5.15) определяет динамическое подключение MFC в ISAPI DLL, генерируемой во время компиляции. Подразумевается, что MFC существует в среде разработки, что достигается исключением содержимого из ISAPI DLL.
(рис 5.15) Область Object Settings (Параметры объекта) мастера расширения ISAPI с проектом ISAPI по умолчанию.При выборе опции Use MFC In A
Можно сгенерировать объект фильтра, включив опцию Generate A Filter Object (Генерировать объект фильтра). Фильтры загружаются в IIS для обработки каждого запроса, имеющего место в IIS. Они работают по аналогии с расширением ISAPI, как если бы оно было связано с каждым файлом корневого веб-каталога. Фильтр и расширение не являются взаимоисключающими функциями. Одна библиотека DLL может содержать как расширение, так и фильтр, что позволяет каждому из этих объектов использовать состояние посредством глобальных переменных.
Фильтры ISAPI сложны для разработки и тестирования. Они вызываются при каждом запросе в IIS, поэтому необходимо обеспечить их эффективность и правильность построения. Фильтры могут снизить эффективность сервера или вызвать сбой в его работе при возникновении проблем с памятью.
После нажатия на кнопку Finish (Готово) и завершения мастером генерации файлов проект можно скомпилировать и запустить, как только библиотека DLL будет отгружена на сервер. Если все выполнится правильно, вы увидите в браузере сообщение, имя класса в котором соответствует выбранному имени класса:
This default message was produced by the Internet Server DLL Wizard. Edit your CMFCISAPIExtension::Default() implementation to change it.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.