Расширения ISAPI
Анализ пары "Заголовок-Значение"
После получения заголовка ALL_HTTP из функции GetServerVariable (см. листинг 5.4) для отображения содержимого в XML необходимо реализовать обработку строки. Заголовки разделяются символами новой строки и возврата каретки. Двоеточие (":") разделяет имя заголовка и его значение. GetHeaderValuePair инициализирует поиск в указанной позиции и возвращает имя и соответствующее ему значение для данного заголовка, а также позицию, в которой прерван поиск заголовков. GetHeaderValuePair единовременно осуществляет поиск одного значения заголовка.
Как видно из листинга 5.6 функция GetHeaderValuePair осуществляет поиск символа ":" в HTTP-заголовке, начиная с позиции nStart в строке sHeader. Значение nStart – это счетчик (начинается с нуля). Если символ " :" не найден, функция завершает работу с возвращением значения "ложь". При обнаружении символа " :" (т.е. заголовок существует) в sHeader продолжается поиск новой строки, начиная с позиции, в которой обнаружен данный символ. При поиске используется функция find строки Standard Template Library (STL) с символом новой строки \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;Листинг 5.6. Source Code for Function GetHeaderValuePair
Построение остальных элементов XML
После обработки функции 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)); } }Листинг 5.7. Function GetECBElement
Функция 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("&")); FindAndReplace(&sReturn, &string("="),&string("=")); FindAndReplace(&sReturn, &string("<"),&string("<")); FindAndReplace(&sReturn, &string(">"),&string(">")); FindAndReplace(&sReturn, &string("'"),&string("'")); FindAndReplace(&sReturn, &string("\""),&string(""")); return sReturn; }Листинг 5.8. Function ValidateValue
Функция FindAndReplace представляет собой утилиту для замещения всех вхождений строки. В расширении ISAPI SEUX она является идеальным механизмом для замещения одной фразы внутри строки другой фразой. Аргументы представляют собой указатели на строки:
- изменяемая строка (контейнер);
- строка, которую нужно заменить внутри контейнера (цель);
- строка, заменяющая цель в контейнере (замещение).
Строка STL содержит функции 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); } }Листинг 5.9.
Функция GetElement работает аналогично функции GetECBElement ; она вызывается из функции HttpExtensionProc для конкатенации элементов из свойств ECB. Свойства извлекаются из ECB, после чего передаются вместе со своими именами и документом XML в функцию GetElement. GetElement размещает свойство ECB и соответствующее значение в XML документе и конкатенирует его с указателем документа XML, переданным функции GetElement. Осуществляется запрос следующих свойств:
- lpszLogData. Буфер размера HSE_LOG_BUFFER_LEN, используемый для размещения информации, добавляемой в файл журнала для данной транзакции HTTP-запроса.
- lpszMethod. Строковое значение используемого метода HTTP, например, GET, PUT или HEAD.
- lpszQueryString. Строковое значение символов в секции дополнительной информации адреса URL, исключая символ " ?". Аналогично значению переменной сервера QUERY_STRING.
- lpszPathInfo. Строковое значение секции URL, находящейся между библиотекой DLL расширения ISAPI и началом секции дополнительной информации URL. Обычно не содержит данных, если запрашивающее ПО не разместило в этом месте определенное значение.
- lpszContentType. Строковое значение типа содержимого отправленных по HTTP данных. Аналогично значению серверной переменной CONTENT_TYPE.
Когда функция HttpExtensionProc завершает получение содержимого всех возможных серверных переменных и свойств ECB, в документе XML указываются закрывающие тегов XML, и он передается функции SendResponse. SendResponse направляет запрашивающей программе строковое значение, переданное в функцию. Заголовок типа содержимого передается запрашивающему клиенту с помощью функции ECB ServerSupportFunction (см. листинг 5.10). Передаваемый заголовок представляется константой BASIC_HEADER, эквивалентной следующей строке: Content-type: text/html\r\n\r\n. За заголовками HTTP следуют два символа возврата каретки и новой строки, поскольку возвращаемые данные представляют собой текст. Можно указать и XML, однако если в запрашивающем браузере в XML зарегистрированы типы Multipurpose Internet Mail Extensions (MIME), то для отображения 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); }Листинг 5.10. Function SendResponse
После отправки заголовка возврата отправляется содержимое с помощью функции 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 является членом ECB. Может показаться странным, что используемое описание ECB передается функции. Поскольку приложение IIS включает несколько нитей, в любой момент времени может потребоваться несколько экземпляров ECB, и вероятно выполнение записи в экземпляре ECB в другой экземпляр ECB. Ниже приведен пример WriteClient:
BOOL WriteClient( HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwSync );