Счетчик посещений XBMCNT
Еще один счетчик посещений, который мы рассмотрим в нашей книге, создан на основе приложения ISAPI, которое называется XBMCNT. Это приложение создает графическое изображение счетчика в виде файла формата XBM или X-Bitmap, что одно и тоже.
Внешний вид страницы, на которой расположен наш графический счетчик, показан на рис. 8.8.
Рис. 8.8. Графический счетчик на странице сервера WWW
Исходный текст соответствующего документа HTML приведен в листинге8.10.
Листинг 8.10. Файл chap8\xbmcnt\xbmcnt.htm
<HTML>
<BODY BGCOLOR="#FFFFFF">
<H1>Главная страница фирмы XYZ Inc.</H1>
<P>Добро пожаловать на нашу главную страницу!
<HR>
<P>Вы посетитель номер
<IMG ALT="Счетчик доступа" SRC=/scripts/xbmcnt.dll?">с 1 января 1913 года
</BODY>
</HTML>
Обратите внимение, что ссылка на файл библиотеки приложения ISAPI здесь сделана с помощью оператора <IMG>. Когда навигатор отображает такую страницу, он запускает приложение xbmcnt.dll, а то, в свою очередь, посылает навигатору массив данных изображения.
В каком виде?
До сих пор наши приложения ISAPI возвращали навигатору документы HTML, сформированные динамически. Однако есть и другая возможность - приложение ISAPI может возвратить навигатору данные типа MIME, например, графическое изображение.
Для этого в заголовке HTTP, который отправляется навигатору, необходимо указать правильный тип данных. В нашем случае мы будем работать с графическим изображением в формате XBM, для которого определен тип данных MIME, обозначаемый как image/x-xbitmap. Поэтому заголовок Content-type протокола HTTP должен выглядеть следующим образом:
Content-type: image/x-xbitmap
Вслед за этим заголовком должна идти одна пустая строка и массив данных изображения в формате XBM.
Изображение в формате XBM представляет собой достаточно простую вещь - это просто определение массива данных на языке программирования Си. Приведем пример.
Пусть например, у нас есть файл counter.xbm, в котором хранится черно-белое изображение цифры 0 высотой 8 пикселов и шириной 16 пикселов (формат XBM не позволяет хранить цветные изображения). Этот файл должен иметь следующий вид:
#define counter_width 8
#define counter_height 16
static unsigned char counter_bits[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3C, 0x42, 0x42, 0x42, 0x42, 0x42,
0x42, 0x42, 0x42, 0x3C
};
Файл определения изображения XBM всегда текстовый.
Первые две строки файла определяют константы, содержащие размеров графического изображения. Имена этих констант образуются из имени файла (без расширения) и строк _width и _height, соответственно, для значений ширины и высоты изображения.
Далее в файле располагается определение статического массива данных, имя которого образуется из имени файла и строки _bits. В приведенном выше массиве каждый байт соответствует одной строке растра изображения:
0x00 |
||||||||
0x00 |
||||||||
0x00 |
||||||||
0x00 |
||||||||
0x00 |
||||||||
0x00 |
||||||||
0x3C |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x42 |
||||||||
0x3C |
Для того чтобы сделать графический счетчик посещений, в нашем приложении подготовлен массив для каждой цифры от 0 до 9. Отображая значение счетчика посещений, приложение комбинирует одно изображение из указанных массивов.
Теперь, когда мы познакомились с форматом XBM и знаем, какие данные должно вернуть приложение ISAPI навигатору, рассмотрим исходный текст приложения XBMCNT, приведенный в листинге 8.11. Мы сделали это приложение на основе приложения COUNTER из примеров Microsoft Visual C++ версии 4.2, упростив и изменив его таким образом, чтобы не использовать библиотеку классов MFC.
Листинг 8.11. Файл chap8\xbmcnt\xbmcnt.c
// ===============================================
// Расширение ISAPI xbmcnt.c
// Счетчик посещений страниц
//
// (C) Фролов А.В., 1997
// E-mail: frolov@glas.apc.org
// WWW: http://www.glasnet.ru/~frolov
// или
// http://www.dials.ccas.ru/frolov
// ===============================================
#include <windows.h>
#include <httpext.h>
#include <stdio.h>
#include <string.h>
// Ширина символа
#define char_width 8
// Высота символа
#define char_height 16
// Массив растровых изображений цифровых символов
static unsigned char char_bits[10][char_height] =
{
// Изображение цифры 0
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3C, 0x42, 0x42, 0x42, 0x42, 0x42,
0x42, 0x42, 0x42, 0x3C
},
// Изображение цифры 1
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x18, 0x14, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0x7C
},
// Изображение цифры 2
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x22, 0x20, 0x20, 0x10, 0x08,
0x04, 0x02, 0x22, 0x3F
},
// Изображение цифры 3
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x22, 0x40, 0x40, 0x3C, 0x40,
0x40, 0x40, 0x22, 0x3E
},
// Изображение цифры 4
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x18, 0x14, 0x12, 0x12, 0x3E,
0x10, 0x10, 0x10, 0x38
},
// Изображение цифры 5
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x02, 0x02, 0x02, 0x3E, 0x20,
0x20, 0x20, 0x22, 0x2C
},
// Изображение цифры 6
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1C, 0x06, 0x02, 0x02, 0x1E, 0x22,
0x22, 0x22, 0x22, 0x1C
},
// Изображение цифры 7
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3E, 0x22, 0x20, 0x20, 0x10, 0x08,
0x08, 0x08, 0x08, 0x08
},
// Изображение цифры 8
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1C, 0x22, 0x22, 0x22, 0x1C, 0x22,
0x22, 0x22, 0x22, 0x1C
},
// Изображение цифры 9
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1C, 0x22, 0x22, 0x22, 0x3C, 0x20,
0x20, 0x20, 0x20, 0x1C
},
};
void PrintCounter(LPSTR szCounter, LPSTR szBuffer);
// =============================================================
// Функция GetExtensionVersion
// Запись версии интерфейса ISAPI и
// строки описания расширения
// =============================================================
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
// Записываем версию интерфейса ISAPI
pVer->dwExtensionVersion =
MAKELONG(HSE_VERSION_MINOR,HSE_VERSION_MAJOR );
// Записываем строку описания расширения
lstrcpyn(pVer->lpszExtensionDesc,
"ISAPI Counter", HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
// =============================================================
// Функция HttpExtensionProc
// =============================================================
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *lpECB)
{
// Рабочий буфер
CHAR szBuff[4096];
// Идентификатор файла счетчика
HANDLE hCounterFile;
// Количество прочитанных байт
DWORD dwBytesRead;
// Количество записанных байт
DWORD dwBytesWritten;
// Результат выполнения операции
BOOL bResult;
// Временный буфер для работы со счетчиком
CHAR szBuf[20];
// Текущее значение счетчика
INT nCounter;
// Нулевой код состояния - признак успешного выполнения
lpECB->dwHttpStatusCode = 0;
// -----------------------------------------------
// Увеличиваем значение счетчика в файле
// -----------------------------------------------
// Открываем файл счетчика для чтения
hCounterFile = CreateFile("CNTDAT.DAT",
GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// Читаем из файла строку значения счетчика
bResult = ReadFile(hCounterFile, szBuf, 7,
&dwBytesRead, NULL);
// Закрываем файл счетчика
CloseHandle(hCounterFile);
// Преобразуем значение счетчика из текстовой
// строки в численную величину
sscanf(szBuf, "%d", &nCounter);
// Увеличиваем значение счетчика
nCounter++;
// Записываем в буфер szBuf пять цифр нового
// значения счетчика
sprintf(szBuf, "%05.5ld", nCounter);
// Сохраняем новое значение счетчика в файле
hCounterFile = CreateFile("CNTDAT.DAT",
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
WriteFile(hCounterFile, szBuf, strlen(szBuf),
&dwBytesWritten, NULL);
CloseHandle(hCounterFile);
// Записываем в буфер заголовок HTTP
wsprintf(szBuff, "Content-type: image/x-xbitmap\r\n\r\n");
// Выводим биты графического изображения в формате XBM
PrintCounter(szBuf, szBuff);
// Посылаем содержимое буфера удаленному пользователю
if(!lpECB->ServerSupportFunction(lpECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER, NULL, NULL,
(LPDWORD)szBuff))
{
// Если послать данные не удалось,
// завершаем работу нашего расширения ISAPI
// с кодом ошибки
return HSE_STATUS_ERROR;
}
// Записываем код успешного завершения
lpECB->dwHttpStatusCode = 200;
// Возвращаем принак успешного завершения
return HSE_STATUS_SUCCESS;
}
// =============================================================
// Функция PrintCounter
// =============================================================
void PrintCounter(LPSTR szCounter, LPSTR szBuff)
{
// Временный буфер
CHAR szTempBuf[4096];
// Полная ширина изображения счетчика
int nFinalWidth;
// Полная высота изображения счетчика
int nFinalHeight;
// Номер текущей строки растра изображения
int nLine;
// Текущий символ
unsigned int nChar;
// Смещение в массиве изображений цифр
int nDigitOffset;
// Полная ширина изображения вычисляется исходя из
// количества цифр отображаемой строки
nFinalWidth = char_width * strlen(szCounter);
// Полная высота изображения равна высоте
// изображения цифр
nFinalHeight = char_height;
// Отменяем кэширование
wsprintf(szTempBuf, "Expires: Thu, 03 Dec 1996 10:00:00 GMT\r\n");
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "Pragma: no-cache\r\n");
strcat(szBuff, szTempBuf);
// Выводим заголовок изображения XBM
wsprintf(szTempBuf, "#define counter_width %d\r\n",
nFinalWidth);
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "#define counter_height %d\r\n",
nFinalHeight);
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "static unsigned char counter_bits[] = {\r\n");
strcat(szBuff, szTempBuf);
// Выводим в цикле биты изображения счетчика
for(nLine=0; nLine < nFinalHeight; nLine++)
{
for(nChar=0; nChar < strlen(szCounter); nChar++)
{
nDigitOffset = szCounter[nChar] - '0';
wsprintf(szTempBuf, "0x%02X, ",
char_bits[nDigitOffset][nLine]);
strcat(szBuff, szTempBuf);
}
}
// Выводим строку, закрывающую структуру в файле XBM
strcat(szBuff, "};\r\n");
}
Файл определения модуля нашего расширения ISAPI представлен в листинге 8.12.
Листинг 8.12. Файл chap8\xbmcnt\xbmcnt.def
LIBRARY xbmcnt
DESCRIPTION 'ISAPI Counter'
EXPORTS
GetExtensionVersion
HttpExtensionProc
Константы char_width и char_height задают, соответственно, ширину и высоту изображений цифр, из которых формируется графическое изображение счетчика.
В массиве char_bits определено десять растровых изображений - по одному для каждой цифры от 0 до 9.
Функция GetExtensionVersion записывает версию интерфейса ISAPI и строку описания, как это было сделано и в других наших приложениях.
Функция HttpExtensionProc выполняет всю полезную работу.
Прежде всего, она записывает код успешного завершаения в поле dwHttpStatusCode структуры EXTENSION_CONTROL_BLOCK. Если произойдет ошибка, этот код следует изменить.
Далее функция HttpExtensionProc увеличивает значение счетчика посещений в файле. Заметим, что так как библиотека ISAPI работает в мультизадачном режиме, вы должны использовать критическую секцию для предотвращения конфликтов при попытке одновременной работы с файлом счетчика. Когда вы будете создавать свой счетчик на базе нашего примера, не забудьте об этом. О критических секциях мы рассказывали в 26 томе “Библиотеки системного программиста”, который называется “Программирование для Windows NT. Часть 1”. Там же вы найдете соответствующие примеры программ.
Приложение XBMCNT открывает файл счетчика с именем CNTDAT.DAT для чтения в текущем каталоге, пользуясь для этого функцией CreateFile. Далее из этого файла функцией ReadFile приложение считывает семь байт данных в буфер szBuf и закрывает файл функцией CloseHandle.
При помощи функции sscanf прочитанное текстовое значение счетчика преобразуется в двоичную форму и записывается в переменную nCounter.
Далее значение счетчика в переменной nCounter увеличивается на единицу и сохраняется в файле счетчика в текстовом виде.
Все использованные нами функции, предназначенные для работы с файлами, были подробно описаны в 26 и 27 томах “Библиотеки системного программиста”.
После того как содержимое файла счетчика будет обновлено, функция HttpExtensionProc возвращает навигатору заголовок HTTP и биты графического изображения.
Заголовок записывается в буфер szBuff при помощи функции wsprintf:
wsprintf(szBuff, "Content-type: image/x-xbitmap\r\n\r\n");
Далее к этому буферу функция PrintCounter, определенная в нашем приложении, дописывает биты изображения, после чего функция ServerSupportFunction с параметром HSE_REQ_SEND_RESPONSE_HEADER отправляет данные навигатору.
Теперь о функции PrintCounter.
Эта функция получает два параметра - szCounter и szBuff. Первый из них содержит указатель на текстовую строку значения счетчика, второй - указатель на буфер, в который нужно дописывать биты графического изображения.
Вначале функция PrintCounter вычисляет размеры изображения счетчика, умножая количество цифр в строке szCounter на ширину изображения одной цифры.
Далее к буферу дописываются два дополнительных заголовка HTTP, отменяющих кэширование страницы:
wsprintf(szTempBuf, "Expires: Thu, 03 Dec 1996 10:00:00 GMT\r\n");
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "Pragma: no-cache\r\n");
strcat(szBuff, szTempBuf);
На следующем этапе к буферу szBuff дописываются три начальные строки файла в формате XBM:
wsprintf(szTempBuf, "#define counter_width %d\r\n", nFinalWidth);
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "#define counter_height %d\r\n", nFinalHeight);
strcat(szBuff, szTempBuf);
wsprintf(szTempBuf, "static unsigned char counter_bits[] = {\r\n");
strcat(szBuff, szTempBuf);
Далее функция PrintCounter выводит в цикле шестнадцатиричные значения графического изображения счетчика по строкам растра, пользуясь значениями из массива char_bits.
Определение массива завершается дописыванием закрывающей фигурной скобки и символа точка с запятой:
strcat(szBuff, "};\r\n");