Опубликован: 24.11.2006 | Уровень: специалист | Доступ: платный
Лекция 5:

Расширения 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("&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;
}
Листинг 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   
);
Дмитрий Васюков
Дмитрий Васюков
Россия, Брянск
Maxim Kuzmin
Maxim Kuzmin
Россия