Сервер Web своими руками. Язык HTML, приложения CGI и ISAPI, установка серверов Web для Windows© Александр Фролов, Григорий ФроловТом 29, М.: Диалог-МИФИ, 1997, 288 стр. Принципы работы и структура расширения ISAPIКак мы только что сказали, расширение ISAPI создается в виде библиотеки динамической компоновки DLL. Обращение к такой библиотеки выполняется в документах HTML аналогично обращению к программам CGI - из форм или ссылок, созданных, соответственно, при помощи операторов <FORM> и <A>. Когда пользователь обращается к расширению ISAPI, соответствующая библиотека DLL загружается в адресное пространство сервера Microsoft Information Server и становится его составной частью. Так как расширение ISAPI работает в рамках процесса сервера Microsoft Information Server, а не в рамках отдельного процесса (как это происходит при запуске программы CGI), оно может пользоваться всеми ресурсами, доступными серверу. Это благоприятно сказывается на производительности. Другой важный момент, влияющий на производительность, имеет особое значение в тех случаях, когда расширение сервера используется активно сразу многими пользователями. Пусть, скажем, 20 пользователей обратятся одновременно к одной и той же программе CGI. В результате на сервере будет создано 20 процессов, по одному для каждого пользователя. Так как создание процесса отнимает достаточно много системных ресурсов, это приведет к потере производительности. Если же 20 пользователей одновременно обратятся к одному и тому же расширению ISAPI, в память серверного процесса будет загружена одна копия библиотеки DLL расширения, которая будет работать в мультизадачном режиме. Очевидно, при таком подходе полностью исключаются накладные расходы системных ресурсов на запуск процессов. Сравнивая программы CGI и расширения ISAPI, нужно заметить, что несмотря на существенное превосходство в быстродействии расширений ISAPI, программы CGI также имеют свои преимущества. Так как расширения ISAPI работают в рамках серверного процесса, они должны отлаживаться особенно тщательно. Ошибка в расширении ISAPI может привести к аварийному завершению всего сервера Microsoft Information Server. Что же касается программы CGI, работающей как отдельный процесс в своем собственном адресном пространстве, то она едва ли способна вывести из строя сервер. Если в программе CGI будет допущена критическая ошибка, это приведет всего лишь к аварийному завершению самой программы, но не всего сервера. Напомним, что расширение ISAPI работает в мультизадачном режиме, что приводит к дополнительным проблемам при отладке. Особенности программирования для мультизадачного режима мы описали в 26 и 27 томах “Библиотеки системного программиста”, которые называются “Программирование для Windows NT. Часть 1” и “Программирование для Windows NT. Часть 2”. Там же вы найдете информацию о том, как создавать библиотеки DLL, предназначенные для работы в среде Microsoft Windows NT и Microsoft windows 95. Вызов расширения ISAPI сервером WWWСтруктура расширения ISAPI очень проста. Библиотека DLL расширения должна экспортировать всего две функции с именами GetExtensionVersion и HttpExtensionProc. Первая из этих функций предназначена для того, чтобы расширение могло сообщить серверу версию спецификации, которой оно соответствует, и строку описания расширения. Функция HttpExtensionProc выполняет всю работу по передаче данных между расширением и сервером. Дополнительно расширение ISAPI может экспортировать функцию TerminateExtension, которая вызывается сервером перед тем, как ненужное больше приложение ISAPI выгружается из памяти. Функция TerminateExtension должна освободить ресурсы, загруженные динамически при инициализации расширения ISAPI. Функция GetExtensionVersionНасколько проста реализация функции GetExtensionVersion вы можете судить по следующему фрагменту кода, взятому нами из исходных текстов приложения FILEUPL (это приложение будет полностью рассмотрено позже): // ============================================================= // Функция GetExtensionVersion // ============================================================= BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVersion) { pVersion->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR,HSE_VERSION_MAJOR); lstrcpyn(pVersion->lpszExtensionDesc, "Remote File Upload", HSE_MAX_EXT_DLL_NAME_LEN); return TRUE; } При вызове функции GetExtensionVersion передается указатель на структуру типа HSE_VERSION_INFO. Эта структура и указатель на нее LPHSE_VERSION_INFO определены в файле httpext.h следующим образом: #define HSE_MAX_EXT_DLL_NAME_LEN 256 typedef struct _HSE_VERSION_INFO { DWORD dwExtensionVersion; CHAR lpszExtensionDesc[HSE_MAX_EXT_DLL_NAME_LEN]; } HSE_VERSION_INFO, *LPHSE_VERSION_INFO; Константы HSE_VERSION_MINOR и HSE_VERSION_MAJOR указывают текущую версию интерфейса расширения ISAPI и также определены в файле httpext.h: #define HSE_VERSION_MAJOR 2 // верхний номер версии #define HSE_VERSION_MINOR 0 // нижний номер версии Функция HttpExtensionProcТеперь рассмотрим вторую функцию, которую должна экспортировать библиотека DLL расширения ISAPI. Она называется HttpExtensionProc и имеет следующий прототип: DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB); Функция HttpExtensionProc получает единственный параметр - указатель на структуру типа EXTENSION_CONTROL_BLOCK, определенную в файле httpext.h: typedef struct _EXTENSION_CONTROL_BLOCK { DWORD cbSize; // размер структуры в байтах DWORD dwVersion; // версия спецификации ISAPI HCONN ConnID; // идентификатор канала DWORD dwHttpStatusCode; // код состояния HTTP CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; // текстовая строка, // закрытая двоичным нулем, в которой находится информация // протоколирования, специфичная для данного расширения LPSTR lpszMethod; // переменная REQUEST_METHOD LPSTR lpszQueryString; // переменная QUERY_STRING LPSTR lpszPathInfo; // переменная PATH_INFO LPSTR lpszPathTranslated; // переменная PATH_TRANSLATED DWORD cbTotalBytes; // полный размер данных, полученных от // навигатора DWORD cbAvailable; // размер доступного блока данных LPBYTE lpbData; // указатель на доступный блок данных // размером cbAvailable байт LPSTR lpszContentType; // тип принятых данных // Функция GetServerVariable для получения значения переменных BOOL (WINAPI * GetServerVariable)(HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSize); // Функция WriteClient для посылки данных удаленному пользователю BOOL (WINAPI * WriteClient)(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwReserved); // Функция ReadClient для получения данных от удаленного // пользователя BOOL (WINAPI * ReadClient) (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize); // Вспомогательная функция ServerSupportFunction // для выполнения различных операций BOOL (WINAPI * ServerSupportFunction)(HCONN hConn, DWORD dwHSERRequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType); } EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK; Рассмотрим отдельные поля этой структуры. · cbSize В самом начале структуры EXTENSION_CONTROL_BLOCK находится поле cbSize, в которое при вызове расширения сервер записывает размер структуры в байтах. · dwVersion Поле dwVersion содержит номер версии расширения ISAPI. Верхнее и нижнее значения номера версии можно получить, соответственно, при помощи макрокоманд HIWORD и LOWORD. · ConnID В поле ConnID сервер записывает идентификатор канала, созданного для расширения. Это поле вы не должны изменять. · dwHttpStatusCode Поле dwHttpStatusCode должно заполняться расширением ISAPI. Вы должны записать сюда результат завершения операции (код состояния транзации). В случае успеха в это поле записывается значение 200 (как указано в спецификации HTTP). · lpszLogData Поле lpszLogData предназначено для записи сообщения о выполнении транзакции в журнал сервера WWW. Это сообщение должно быть в виде текстовой строки, закрытой нулем. Размер строки в байтах не должен превышать значения HSE_LOG_BUFFER_LEN. · lpszMethod Поле lpszMethod заполняется сервером и содержит название метода передачи данных от удаленного пользователя серверу в виде текстовой строки, закрытой двоичным нулем. Расширения ISAPI используют те же самые методы, что и программы CGI - метод GET и метод POST. Проводя аналогию с программами CGI дальше, скажем, что поле lpszMethod эквивалентно переменной среды с именем REQUEST_METHOD, создаваемой для программы CGI. · lpszQueryString Аналогично, поле lpszQueryString соответствует переменной среды с именем QUERY_STRING. В это поле записываются данные, принятые от удаленного пользователя методом GET. · lpszPathInfo В поле lpszPathInfo записывается виртуальный путь к программному файлу библиотеки DLL расширения ISAPI. Напомним, что аналогичная информация для программ CGI передавалась через переменную среды с именем PATH_INFO. · lpszPathTranslated Это поле содержит физический путь к программному файлу библиотеки DLL расширения ISAPI. Оно соответствует переменной среды с именем PATH_TRANSLATED, создаваемой для программ CGI. · cbTotalBytes В поле cbTotalBytes записывается общее количество байт данных, которое необходимо получить от удаленного пользователя. Часть этих данных (размером не более 48 Кбайт) считывается сервером автоматически и становится доступной сразу после того как функция HttpExtensionProc получит управление. Остальные данные необходимо дочитать в цикле при помощи функции ReadClient, о которой мы еще будем говорить. · cbAvailable В поле cbAvailable записывается размер блока данных, полученных от удаленного пользователя автоматически. Как мы только что сказали, размер этого блока не может превышать 48 Кбайт. Этого, однако, вполне достаточно для обработки данных, полученных от форм обычного размера. · lpbData Указатель на область памяти, в которую записан сервером полученный от удаленного пользователя блок данных размером cbAvailable байт. · lpszContentType Поле lpszContentType содержит тип принятых данных, например, text/html. · GetServerVariable Помимо полей данных, структура EXTENSION_CONTROL_BLOCK содержит указатели на функции. С помощью этих функций расширение ISAPI может выполнять различные операции, такие как прием данных от удаленного пользователя. Поле GetServerVariable содержит указатель на функцию, с помощью которой расширение ISAPI может получить информацию, которая доступна программам CGI через переменные среды, описанные нами в предыдущей главе этой книги. · WriteClient В поле WriteClient находится адрес функции, которую расширение ISAPI должно использовать для посылки данных удаленному пользователю. Таким образом, вместо того чтобы записывать данные в стандартный поток вывода STDOUT, как это делает программа CGI, приложение ISAPI посылает данные с помощью функции WriteClient. · ReadClient С помощью функции, адрес которой передается в поле ReadClient, приложение может дочитать дополнительные данные, не поместившиеся в буфер предварительного чтения, имеющий адрес lpbData и размер, не превышающий 48 Кбайт. Аналогичную операцию приема данных от пользователя выполняет программа CGI в случае применения метода передачи данных POST. Отличие заключается в том, что программа CGI получает данные через стандартный поток ввода STDIN, а расширение ISAPI берет эти данные из буфера предварительного чтения и при необходимости дочитывает данные функцией ReadClient. · ServerSupportFunction С помощью функции, адрес которой передается через поле ServerSupportFunction, расширение ISAPI может выполнять различные действия, такие как посылка стандартного заголовка протокола HTTP и некоторые другие. При успешном завершении функция HttpExtensionProc должна вернуть значение HSE_STATUS_SUCCESS, а при ошибке - значение HSE_STATUS_ERROR. Соответствующие константы определены в файле httpext.h. Получение данных расширением ISAPIПрограмма CGI получает данные из переменных среды и стандартного потока ввода STDIN (в случае применения метода доступа POST). Расширение ISAPI делает это по-другому. Функция HttpExtensionProc получает указатель на структуру типа EXTENSION_CONTROL_BLOCK, некоторые поля которой заполняются сервером и должны использоваться для получения входных данных. Прежде всего это поле lpszMethod, через которое передается метод, использованный для посылки данных (GET или POST), поле lpszQueryString, в котором передаются параметры запуска расширения или данные при использовании метода GET, а также другие поля, описанные выше. Через структуру EXTENSION_CONTROL_BLOCK передаются адреса функций GetServerVariable и ReadClient, специально предназначенных для получения данных от навигатора удаленного пользователя. Функция GetServerVariableПрототип функции GetServerVariable определен в структуре EXTENSION_CONTROL_BLOCK, описанной нами ранее: BOOL (WINAPI * GetServerVariable)(HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSize); Через параметр hConn вы должны передать этой функции идентификатор канала, полученный через поле ConnID структуры EXTENSION_CONTROL_BLOCK. Параметр lpszVariableName должен содержать указатель на строку имени переменной, содержимое которой необходимо получить. Это содержимое будет записано функцией в буфер, адрес которого передается через параметр lpvBuffer, а размер - через параметр lpdwSize. Ниже мы перечислили возможные значения строк, передаваемых через параметр lpszVariableName: · AUTH_TYPE Переменная среды AUTH_TYPE содержит тип идентификации, который применяется сервером. · HTTP_ACCEPT В этой переменной перечислены типы данных MIME, которые могут быть приняты навигатором от сервера WWW. · CONTENT_LENGTH Количество байт данных, которые расширение ISAPI должно получить от навигатора. · CONTENT_TYPE Тип данных, присланных навигатором. · PATH_INFO Путь к виртуальному каталогу, в котором находится библиотека DLL расширения ISAPI. · PATH_TRANSLATED Физический путь к библиотеки DLL расширения ISAPI. · QUERY_STRING Строка параметров, указанная в форме или операторе ссылки <A>. Эта строка указывается после адреса URL библиотеки DLL расширения ISAPI вслед за разделительным символом “?”. · REMOTE_ADDR Адрес IP узла, на котором работает навигатор удаленного пользователя. · REMOTE_HOST Доменное имя узла, на котором работает навигатор удаленного пользователя. Если эта информация недоступна (например, для узла не определен доменный адрес), то вместо доменного имени указывается адрес IP, как в переменной REMOTE_ADDR. · REMOTE_USER Имя пользователя, которое используется навигатором для аутентификации. · UNMAPPED_REMOTE_USER Имя пользователя до обработки фильтром ISAPI, которое используется навигатором для аутентификации. · REQUEST_METHOD Метод доступа, который используется для передачи данных от навигатора серверу WWW. · SCRIPT_NAME В эту переменную записывается путь к виртуальному каталогу и имя библиотеки DLL расширения ISAPI. Анализируя эту переменную, расширение ISAPI может определить путь к своему загрузочному файлу. · SERVER_NAME Доменное имя сервера WWW или адрес IP сервера WWW, если доменное имя недоступно или не определено. · SERVER_PROTOCOL Имя и версия протокола, который применяется для выполнения запроса к расширению ISAPI. · SERVER_PORT Номер порта, на котором навигатор посылает запросы серверу WWW. · SERVER_PORT_SECURE Если обработка запроса выполняется через защищенный порт, в этой строке записано значение 1, а если через незащищенный - значение 0. · SERVER_SOFTWARE Название и версия программного обеспечения сервера WWW. Версия следует после названия и отделяется от последнего символом “/”. · ALL_HTTP Строка, закрытая двоичным нулем, в которую записаны значения всех переменных, имеющих отношение к протоколу HTTP. Это, например, такие переменные как HTTP_ACCEPT, HTTP_CONNECTION, HTTP_USER_AGENT и так далее. Извлекать содержимое отдельных переменных ваша программа должна самостоятельно. При этом следует учесть, что названия переменных отделены от их значений символом двоеточия “:”, а поля переменных разделены символом перевода строки. Обратите внимание, что названия этих строк почти совпадают с названиями переменных среды, создаваемых для программ CGI, однако совпадение все же не полное. В случае успешного завершения функция GetServerVariable возвращает значение TRUE, а при возникновении ошибки - значение FALSE. Код ошибки можно определить с помощью функции GetLastError, вызвав ее сразу после функции GetServerVariable. Эта функция может вернуть в данном случае следующие коды ошибок:
Ниже мы привели пример использования функции GetServerVariable для получения содержимого переменной с именем ALL_HTTP в буфер szTempBuf. CHAR szTempBuf[4096]; DWORD dwSize; dwSize = 4096; lpECB->GetServerVariable(lpECB->ConnID, (LPSTR)"ALL_HTTP", (LPVOID)szTempBuf, &dwSize); strcat(szBuff, szTempBuf); Функция ReadClientПрототип функции ReadClient находится в определении структуры EXTENSION_CONTROL_BLOCK и выглядит следующим образом: BOOL (WINAPI * ReadClient) (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize); Через параметр hConn этой функции надо передать идентификатор канала, полученный через поле ConnID структуры EXTENSION_CONTROL_BLOCK. Функция ReadClient читает данные в буфер, адрес которого передается через параметр lpvBuffer, а размер - через параметр lpdwSize. В случае успеха функция возвращает значение TRUE, а при ошибке - значение FALSE. Код ошибки можно получить при помощи функции GetLastError. Работа с функцией ReadClient имеет некоторые особенности. Когда расширение ISAPI получает управление, через структуру типа EXTENSION_CONTROL_BLOCK передается адрес предварительно прочитанного блока данных, полученного от удаленного пользователя. Как вы знаете, адрес и размер этого блока данных указаны, соответственно, в полях lpbData и cbAvailable структуры EXTENSION_CONTROL_BLOCK. Однако размер предварительно прочитанных данных не может превышать 48 Кбайт. Если все данные не поместились в буфер предварительного чтения, их необходимо дочитать функцией ReadClient. При этом следует использовать следующий алгоритм. Прежде всего следует сравнить размер предварительно считанных данных с полным размером данных, которые нужно считать (этот размер передается в поле cbTotalBytes структуры EXTENSION_CONTROL_BLOCK). Если все данные уже были считаны предварительно, функцию ReadClient вызывать не нужно. В том случае, когда значение, передаваемое через поле cbTotalBytes, превышает значение cbAvailable, вы должны воспользоваться функцией ReadClient для того чтобы прочесть (cbTotalBytes - cbAvailable) байт данных от пользователя. Заметим, что функция ReadClient не будет читать заново данные, предварительно прочитанные в буфер lpbData. Она прочитает только оставшиеся данные, причем не исключено, что для чтения оставшихся данных эту функцию придется вызывать в цикле несколько раз. Причина этого заключается в том, что функция ReadClient не обязательно сможет прочитать все оставшиеся данные за один прием. После успешного завершения чтения функция ReadClient записывает размер прочитанного блока данных в переменную, адрес которой передается через параметр lpdwSize. Если при первом вызове значение этого размера меньше величины (cbTotalBytes - cbAvailable), вы должны вызвать функцию ReadClient еще один или несколько раз для чтения оставшихся данных. Пример использования функции ReadClient вы найдете в разделе “Приложение FILEUPL”. Там мы привели исходные тексты расширения ISAPI, позволяющее использовать сервер WWW довольно необычным способом - для получения файлов от удаленных пользователей и записи их на диск сервера. Так как размеры передаваемых файлов могут быть значительны, приложение FILEUPL вызывает функцию ReadClient в цикле. Посылка данных расширением ISAPIВместо того чтобы записывать выходные данные в стандартный поток вывода STDOUT, как это делает программа CGI, расширение ISAPI пользуется для посылки данных функциями WriteCilent и ServerSupportFunction, указатели на которые передаются расширению ISAPI через структуру типа EXTENSION_CONTROL_BLOCK. Функция WriteCilentПрототип функции WriteClient, взятый из определения структуры EXTENSION_CONTROL_BLOCK, приведен ниже: BOOL (WINAPI * WriteClient)(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwReserved); Через параметр hConn функции WriteClient передается идентификатор канала, полученный через поле ConnID структуры EXTENSION_CONTROL_BLOCK. Функция WriteClient посылает удаленному пользователю данные из буфера Buffer, причем размер передаваемого блока данных должен быть записан в переменную типа DWORD, адрес которой передается через параметр lpdwBytes. Параметр dwReserved зарезервирован для дальнейших расширений возможностей функции. В случае успеха функция возвращает значение TRUE, а при ошибке - значение FALSE. Код ошибки можно получить при помощи функции GetLastError. Заметим, что после посылки данных функция WriteClient записывает в переменную, адрес которой был ей передан через параметр lpdwBytes, количество успешно переданных байт данных. В отличие от функции ReadClient, функция WriteClient посылает данные за один прием, поэтому нет необходимости вызывать ее в цикле. Если же эта функция смогла передать только часть данных, то это означает, что произошла ошибка. Функция ServerSupportFunctionПрототип функции ServerSupportFunction, определенный в структуре типа EXTENSION_CONTROL_BLOCK, приведен ниже: BOOL (WINAPI * ServerSupportFunction)(HCONN hConn, DWORD dwHSERRequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType); Через параметр hConn функции ServerSupportFunction передается идентификатор канала, полученный через поле ConnID структуры EXTENSION_CONTROL_BLOCK. С помощью параметра dwHSERRequest вы можете задать один из нескольких кодов запроса, определяющих операцию, выполняемую этой функцией. Через параметр lpvBuffer передается размер буфера, который используется при выполнении операции. Размер этого буфера должен быть записан в переменной типа DWORD, адрес которой передается через параметр lpdwSize. После выполнения операции передачи данных в эту переменную будет записан размер успешно переданного блока данных. Параметр lpdwDataType используется для указания дополнительной строки заголовка или дополнительных данных, которые будут добавлены к заголовку, передаваемому удаленному пользователю. Если для параметра lpdwDataType указать значение NULL (что допустимо), к заголовку будут добавлены символы конца строки “\r\n”. Какие операции можно выполнять при помощи функции ServerSupportFunction? Ниже мы привели список возможных значений параметра dwHSERRequest, определяющего код выполняемой операции: · HSE_REQ_SEND_RESPONSE_HEADER Эта операция предназначена для посылки удаленному пользователю стандартного заголовка HTTP. При необходимости добавления других заголовков следует воспользоваться параметром lpdwDataType. В качестве дополнительного заголовка вы можете указать любую строку, закрытую символами конца строки “\r\n” и двоичным нулем. Если ваше расширение ISAPI динамически формирует документ HTML и посылает его пользователю, то ей не нужно вызывать функцию WriteClient. Все необходимые для этого действия можно сделать при помощи одной только функции ServerSupportFunction. Ниже мы показали фрагмент кода, в котором эта функция используется для посылки документа HTML, подготовленного заранее в буфере szBuff: CHAR szBuff[4096]; wsprintf(szBuff, "Content-Type: text/html\r\n\r\n" "<HTML><HEAD><TITLE>Simple ISAPI Extension</TITLE></HEAD>\n" "<BODY BGCOLOR=#FFFFFF><H1>Hello from ISAPI Extension!</H1>\n"); strcat(szBuff, "<H1>Заголовок документа</H1>"); strcat(szBuff, "<HR>"); strcat(szBuff, "</BODY></HTML>"); lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, NULL, NULL, (LPDWORD)szBuff); Заметим, однако, что функция ServerSupportFunction не позволяет посылать двоичные данные. Для посылки двоичных данных вы обязательно должны использовать функцию WriteClient. · HSE_REQ_SEND_URL Используя операцию HSE_REQ_SEND_URL, расширение ISAPI может послать удаленному пользователю данные, заданные адресом URL, как будто бы эти данные были запрошены непосредственно пользователем по этому адресу URL. Такая возможность удобна для посылки либо динамически созданных данных, либо для посылки предварительно подготовленных данных. Адрес URL должен быть указан в виде текстовой строки, закрытой двоичным нулем, через параметр lpvBuffer. Размер строки вы должны указать в параметре lpdwSize. Что же касается параметра lpdwDataType, то при выполнении данной операции этот параметр игнорируется. · HSE_REQ_SEND_URL_REDIRECT_RESP Посылка сообщения с номером 302 (URL Redirect). Адрес URL указывается аналогично тому, как это делается при выполнении операции HSE_REQ_SEND_URL. · HSE_REQ_MAP_URL_TO_PATH Преобразование логического адреса URL в физический. Адрес логического пути передается через параметр lpvBuffer. По этому же адресу записывается результат преобразования. Размер буфера при вызове функции задается как обычно с помощью параметра lpdwSize. После преобразования переменная типа DWORD, адрес которой указан параметром lpdwSize, будет содержать длину строки результата преобразования. · HSE_REQ_DONE_WITH_SESSION В том случае, когда расширение ISAPI оставляет канал открытым для выполнения длительной обработки данных, с помощью этой команды можно сообщить серверу о завершении обработки. Все параметры функции ServerSupportFunction при указании этой команды игнорируются. |