Программируем под IIS на Visual C++

Программируем под IIS на Visual C++

Одной из распространенных задач администрирования web-сайтов является анализ log-файлов и сбор данных из них. Поговорим об этой задаче на примере IIS 5.0 (Internet Information Service). В связи с тем, что данный HTTP сервер поддерживает несколько форматов таких файлов (формат W3C, формат NCSA, а также свой формат IIS), анализ log-файлов превращается в довольно трудоемкую задачу. Кроме того для формата W3C список полей может конфигурироваться, что задачу не облегчает. 

Но к счастью вместе с IIS 5.0 в составе прочих компонентов, поставляется компонент MSWC.IISLog, который предназначен для получения данных из log-файлов и предоставляет для этой цели интерфейс ILogScripting. Находится он в файле %windir%system32inetsrvlogscrpt.dll. С помощью этого интерфейса можно читать данные из log-файла, записывать прочтенные данные в другой файл.

Перед тем как начать работу с какими-либо log-файлом, его нужно открыть. Для этого служит метод OpenLogFile:

HRESULT OpenLogFile( [in] BSTR szLogFileName, 

                  [in] IOMode Mode, 

                  [in] BSTR szServiceName, 

                  [in] long iServiceInstance, 

                  [in] BSTR szOutputLogFileFormat), 

где в параметре szLogFileName задается полный путь к файлу; параметр Mode может принимать следующие значения:

ForReading - файл будет открыт для чтения

ForWriting - файл будет открыт для записи;

в параметре szServiceName задается название службы, которой был создан данный файл (например "W3SVC"); парметр iServiceInstance указывает номер экземпляра сервера данной службы (напр. 1); в параметре szOutputLogFileFormat задается название формата, в котором будут читаться или записываться данные (например "W3C"). Если метод выполнился успешно то возвращается 0. 

Для чтения данных из файла служит метод ReadLogRecord:

HRESULT ReadLogRecord(),

который читает строку из текущей файловой позиции и перемещает позиционер файла дальше. Мы можем получить эти данные в виде значений конкретных полей с помощью множества методов, которые возвращают эти значения:

get_DateTime

Отображение даты и времени по Гринвичу (GMT)

get_ServiceName

Отображение имени службы

get_ServerName

Отображение имени сервера

get_ClientIP

Отображение имени узла клиента

 get_UserName

 Отображение имени пользователя

 get_ServerIP

 Отображение IP-адреса сервера

 get_Method

 Отображение типа операции

 get_URIStem

 Отображение адреса URL

 get_URIQuery

 Отображение всех параметров, передаваемых с запросом

get_TimeTaken

Отображение общего времени обработки

 get_BytesSent

 Отображение числа переданных байтов

 get_BytesReceived

 Отображение числа полученных байтов

 get_Win32Status

 Отображение кода состояния Win32

 get_ProtocolStatus

 Отображение состояния протокола

 get_ServerPort

 Отображение номера порта

 get_ProtocolVersion 

Отображение строки версии

 get_UserAgent

 Отображение строки агента пользователя

 get_Cookie

 Отображение имени Cookie клиента

 get_Referer

 Отображение страницы источника ссылки

get_CustomFields

Отображение массива специальных заголовков

  

Все эти методы имеют один формат: HRESULT get_MethodName(VARIANT *pValue). В параметре pValue будет возвращено интересующее нас значение. Если значение запрашиваемого параметра равно "-", то тип pValue будет VT_EMPTY. Если же по каким-то причинам параметр не будет найден в log-файле (напр. параметр отключен, или текущая строка - незначащая), то тип pValue будет VT_NULL.

Для того, чтобы мы сильно не увлеклись существует метод AtEndOfLog, который подобно привычному feof возвращает (или не возвращает :)) признак достижения конца файла и выглядит приблизительно так: 

HRESULT AtEndOfLog([out, retval] VARIANT_BOOL* pfEndOfRead) 

В параметре pfEndOfRead, собственно, и возвращается интересующий нас признак, что позволяет нам все таки когда-нибудь завершить обработку log-файла. 

Помимо всего этого имеется еще метод WriteLogRecord, который позволяет записывать данные, полученные из одного log-файла в другой. Формат его следующий:

HRESULT WriteLogRecord([in] ILogScripting* pILogScripting),

где 

pILogScripting - это указатель на экземпляр ILogScripting, в котором содержатся прочитанные данные. Этот метод похож на ReadLogRecord тем, что записывает в файл одну строку (напомню, что ReadLogRecord читает одну строку).

Для закрытия открытых файлов служит метод CloseLogFiles:

HRESULT CloseLogFiles([in] IOMode Mode),

в котором параметр Mode может принимать следующие значения: 

ForReading - будут закрыты файлы, открытые для чтения

ForWriting - будут закрыты файлы, открытые для записи

AllOpenFiles - будут закрыты все открытые файлы

Чтобы доступиться к компоненту MSWC.IISLog из C++ надо иметь .h файл, описывающий интерфейс ILogScripting. Сделать его нам поможет утилита OLEViewer, которая входит в состав Visual Studio. В меню "File" этой утилиты выберем пункт "View TypeLib" и укажем файл logscrpt.dll (естественно полный путь к нему). В новом окне откроется библиотека типов нашего объекта, которую мы сохраним в качестве .idl файла.

Для этого в меню "File" выберем пункт "Save as", укажем тип файла "idl", и сохраним его в некое место (например туда, где находится проект), к примеру, назвав его "logscrpt.idl". Все бы хорошо, да только в новоиспеченном файле придется сделать косметические изменения. Во-первых в самое начало файла надо вставить следующие строчки:

cpp_quote("DEFINE_GUID(CLSID_LogScripting, 0x26B9ED02, 0xA3D8, 0x11D1, 0x8B, 0x9C, 0x08, 0x00, 0x09, 0xDC, 0xC2, 0xFA);")

cpp_quote("DEFINE_GUID(IID_ILogScripting, 0x26B9ECFF, 0xA3D8, 0x11D1, 0x8B, 0x9C, 0x08, 0x00, 0x09, 0xDC, 0xC2, 0xFA);")

Во-вторых строчки

typedef enum {

  ForReading = 1,

  ForWriting = 2,

  AllOpenFiles = 32

} IOMode;

надо перенести так, чтобы они находились до определения ILogScripting интерфейса.

Теперь осталось только сгенерировать файл logscrpt.h с помощью команды midl.exe logscrpt.idl /h logscrpt.h (проверьте путь к компилятору midl).

В заключение приведу пример программы работы с интерфейсом, которая получает в качестве параметров командной строки путь к log-файлу, название службы, название формата файла, номер экземпляра сервера и выдает на экран список всех URL адресов, к которым были зафиксированы обращения в этом файле. Вот пример вызова этой программы: 

logging.exe d:logsw3svc2ex01060515.log W3SVC W3C 2

#include 

#include 

#include 

#include 

#include "logscrpt.h"



#define SOME_ERROR(lpszErrorText, nErrorNum) 

printf("%s: %X

", lpszErrorText, nErrorNum); 

throw nErrorNum;

int main(int argc, char *argv[])

{

HRESULT hres;

VARIANT vParam;

short nEndOfFile;

_bstr_t bstrLogFile;

_bstr_t bstrServiceName;

_bstr_t bstrLogFormat;

long nServerInstance;

_bstr_t bstrUriStem;

// проверка наличия параметров

if  (argc < 5)

{

printf("Usage: %s LogFileName ServiceNa

         me LogFormatName ServerInstance

", argv[0]);

return 0;

}

// получаем параметры из коммандной строки

try {

bstrLogFile = argv[1];

bstrServiceName = argv[2];

bstrLogFormat = argv[3];

if  (!(nServerInstance = atol(argv[4])))

// экземпляр сервера не может быть 0

nServerInstance = 1;

}

catch (...) {

printf("Something wrong in parameters!

");

return 0;

}

// это будет ссылка на интерфейс

ILogScripting *pLogScripting = NULL;

try {

// инициализируем COM

if  (!SUCCEEDED(hres = CoInitialize(NULL)))

{

SOME_ERROR("CoInitialize error", hres);

}

// теперь создадим экзепляр интерфейса

if  (!SUCCEEDED(hres = CoCreateInstance(CLSID_LogScripting, 

NULL, CLSCTX_ALL, IID_ILogScripting, 

(void **)&pLogScripting)))

{

SOME_ERROR("CoCreateInstance error", hres);

}

// открываем log-файл bstrLogFile для чтения, указывая, что этот 

// файл относится к первому экземляру сервера службы W3SVC, а // 

// формат у него W3C

if  (!SUCCEEDED(hres = 

pLogScripting-OpenLogFile(BSTR(bstrLogFile), ForReading, 

BSTR(bstrServiceName), 1, BSTR(bstrLogFormat))))

{

SOME_ERROR("OpenLogFile error", hres);

}

// теперь можно приступить к анализу содержимого файла

for ( ;; ) {

// проверим на достижение конца файла

if  (!SUCCEEDED(hres = 

              pLogScripting-AtEndOfLog(&nEndOfFile)))

{

 SOME_ERROR("AtEndOfLog error", hres);

}

if  (nEndOfFile)

 // счастливо выходим

 break;

// читаем следующую запись файла

if  (!SUCCEEDED(hres = pLogScripting-ReadLogRecord()))

{

 SOME_ERROR("ReadLogRecord error", hres);

}

// получаем из нее параметр адрес URL

if  (!SUCCEEDED(hres = pLogScripting-get_URIStem(&vParam)))

{

 SOME_ERROR("ReadLogRecord error", hres);

}

if  (vParam.vt == VT_BSTR)

{

 // если параметр не пуст распечатаем его на экране

 bstrUriStem = vParam.bstrVal;

 printf("Uri-stem: %s

", LPSTR(bstrUriStem));

}

}

// закрываем файл

if  (!SUCCEEDED(hres = pLogScripting-CloseLogFiles(ForReading)))

{

SOME_ERROR("CloseLogFiles error", hres);

}

}

catch (...) {}

// последние действия по деинициализации

if  (pLogScripting != NULL)

pLogScripting-Release();

CoUninitialize();

return hres;

}

Dima Mukalov