Сервер Web своими руками. Язык HTML, приложения CGI и ISAPI

       

Счетчик посещений 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");


Содержание раздела