Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад]

Активный сервер Web: расширения CGI

А.В. Фролов, Г.В. Фролов,  (МИР ПК #08/97)

Исходные тексты программ

Посещая в Internet различные Web-серверы, вы, наверно, заметили, что одни из них просто предоставляют некоторую информацию, а другие пытаются вступить с вами в диалог. Конечно, и статическая информация может быть очень интересна и полезна сама по себе. Однако ваш сервер будет намного привлекательнее для пользователей, если вы сделаете его активным. Активные серверы могут содержать счетчик посещений, книгу записей для гостей, в которой каждый желающий может оставить свои координаты, формы для обращения к базам данных, динамически изменяющиеся изображения, кнопки, списки и т. д.

Для централизованной обработки информации, которая поступает от клиентов, просматривающих содержимое страниц сервера с помощью Netscape Navigator и Microsoft Internet Explorer, можно воспользоваться расширениями серверов Web.

Мы предполагаем, что вы умеете создавать документы HTML. Неплохо, если в вашем распоряжении имеется Web-сервер, на котором вы можете экспериментировать.

Расширения CGI и ISAPI

Чаще всего активные серверы Web создаются с помощью программ CGI, использующих так называемый стандартный шлюзовой интерфейс, - Common Gateway Interface (CGI).

Что же представляет собой программа CGI? Вообще-то это обычное консольное приложение. Такая программа не ведет никакого диалога с пользователем, а работает со стандартными потоками ввода и вывода, а также с переменными среды (environment variables). Если вы когда-либо создавали консольные программы для операционных систем MS-DOS, OS/2 или Windows NT, то составление программ CGI вам вполне по плечу. Как правило, программа CGI анализирует содержимое некоторых переменных среды, получает данные от удаленного пользователя через стандартный поток ввода, а результат обработки этих данных записывает в стандартный поток вывода. Вот и все! Ну хорошо, почти все...

Каждый раз, когда удаленный пользователь обращается к расширению CGI, на сервере Web запускается программа, которая выполняется как отдельный процесс, причем в собственном адресном пространстве. На запуск процесса требуется время, и если ваш сервер пользуется популярностью, то программы CGI могут полностью его загрузить. В результате увеличится время реакции сервера, что может оттолкнуть от него пользователей.

Для повышения производительности в некоторых Web-серверах (в частности, Microsoft Internet Information Server) используется другой способ создания расширений. Расширение создается как библиотека динамической загрузки DLL с использованием программного интерфейса ISAPI (Internet Server API). Когда такое расширение активизируется в первый раз, оно загружается в адресное пространство процесса Web-сервера и начинает свою работу. В памяти всегда находится только одна копия соответствующей библиотеки DLL, поэтому при одновременном обращении к расширению ISAPI нескольких пользователей системные ресурсы расходуются более экономно, а кроме того, не тратится время на дополнительные загрузки расширения.

Программирование приложений ISAPI представляет собой более сложную задачу, нежели создание программ CGI. Поскольку такие приложения всегда работают в мультизадачном режиме, необходимо обеспечить синхронизацию доступа к критичным ресурсам. Ошибки в приложениях ISAPI могут привести к аварийной остановке сервера Web, а вот при использовании программ CGI это маловероятно. Поэтому, несмотря на то что расширения ISAPI в целом более эффективны по сравнению с программами CGI, первые эксперименты по созданию активных серверов следует начинать именно с программ CGI.

Пишем первую программу CGI

Наша первая программа CGI не принимает данные от пользователя. Она запускается, если пользователь щелкает левой кнопкой мыши по соответствующей ссылке в документе HTML, и возвращает ему новый документ HTML, созданный динамически.

Для ссылки на программу CGI мы использовали оператор <A>, как это показано в листинге 1.

Параметр HREF оператора <A> задается почти таким же образом, как и при организации обычной ссылки на другой документ HTML, файл графического изображения или произвольный двоичный файл. Отличие заключается лишь в том, что вслед за именем загрузочного файла программы CGI после разделительного символа "?" следует необязательная строка параметров. В нашем случае программа CGI называется cgi1.exe, а строка параметров равна "param1".

Что произойдет, если щелкнуть на ссылке, расположенной в этом документе?

Сервер с адресом http://frolov начнет поиск программы cgi1.exe и в случае успеха запустит ее. Перед запуском будет сформирован набор переменных среды, с помощью которых программа CGI может получить различную информацию, включающую, в том числе, строку параметров, указанную в операторе <A>.

Сделаем небольшое замечание относительно каталога Scripts. Администратор Web- сервера таким образом настраивает права доступа к каталогам, содержащим документы HTML и другие файлы данных, чтобы удаленные пользователи могли только читать эти файлы. Запись в каталоги и запуск из них программ запрещены из соображений безопасности. Для расширений CGI и ISAPI администратор создает отдельный каталог, разрешая в нем только запуск программ без чтения и записи. Кроме того, каталогу с программными расширениями присваивается так называемое имя виртуального каталога (псевдоним), не имеющее никакой связи с именем физического каталога. Имя виртуального каталога вы можете узнать у администратора сервера. Если же вы сами выполняете функции администратора, то можете создать виртуальный каталог с любым именем, отобразив его на любой физический каталог. О том, как это сделать, вы можете узнать из документации к тому Web-серверу, с которым вы работаете.

Вернемся к нашей программе cgi1.c. Исходный текст этой программы вы найдете в листинге 2.

По своей сложности cgi1.c совсем недалеко ушла от знаменитой программы "Hello, World!". Ее задачей является динамическое формирование документа HTML и отправка его в стандартный поток вывода с помощью функции printf.

При первом вызове функции printf в стандартный поток вывода записывается заголовок протокола HTTP, из которого видно, что далее последует текстовый документ HTML:

printf ("Content-type: text/html\n\n");

Заголовок протокола HTTP всегда отделяется от данных пустой строкой, поэтому в конце приведенной выше строки находятся два символа "\n".

Вслед за заголовком программа cgi1 выводит строки документа HTML.

Необязательный оператор <!DOCTYPE> определяет тип документа и версию языка HTML. Далее в стандартный поток выводятся операторы <HTML>, <HEAD> и <TITLE>, а также заголовок страницы, оформленный в соответствии со стилем <H1>. Для тех, кто когда-либо создавал документы HTML, здесь нет ничего необычного.

На динамически формируемой странице программа cgi1 отображает содержимое четырех переменных среды с именами REQUEST_METHOD, QUERY_STRING, CONTENT_LENGTH и CONTENT_ TYPE. Указатели на соответствующие текстовые строки записываются с помощью функции getenv.

Перед завершением своей работы программа cgi1 записывает в стандартный поток вывода операторы </BODY> и </HTML>, закрывающие документ HTML.

Переменные среды для программ CGI

Запуская программу CGI, сервер Web формирует для нее набор переменных среды, которые передают информацию, полученную от пользователя, строку параметров запуска программы CGI из документа HTML, а также некоторые параметры самого сервера Web.

Перечислим основные переменные среды программы CGI и приведем их краткое описание. Учтите, что набор доступных программе CGI переменных среды зависит от конкретной реализации сервера Web, однако самые важные переменные, такие как REQUEST_ METHOD, QUERY_ STRING, CONTENT_LENGTH и CONTENT_ TYPE, доступны всегда.

REQUEST_METHOD - информация о методе доступа при передаче данных от браузера Web-серверу. Чаще всего используются два метода: GET и POST. В нашем примере применен метод GET, хотя мы нигде это специально не указывали.

Метод GET обеспечивает получение данных от браузера с помощью переменной среды с именем QUERY_STRING. Значение этой переменной равно param1. Это значение соответствует строке параметров, передаваемой программе cgi1.exe в документе HTML, исходный текст которого приведен в листинге 1.

Метод передачи данных GET прост в использовании, однако он подходит только для передачи относительно коротких текстовых строк. Причина этого лежит в ограничении, которое накладывается операционной системой на размер строки переменной среды. Если необходимо передать текстовые данные большого объема (например, содержимое полей диалоговых панелей), более предпочтителен метод POST: тогда программа GCI получает данные от браузера, выполняя чтение из стандартного потока ввода. Для ввода можно вызывать обычные функции стандартной библиотеки Си, например fread.

Если данные передаются методом POST (о чем программа CGI может узнать, анализируя переменную среды REQUEST_METHOD), то объем передаваемых данных записывается в переменную среды с именем CONTENT_LENGTH. Заметим, при использовании метода GET содержимое переменной среды CONTENT_LENGTH анализировать не нужно.

QUERY_STRING - данные, которые передаются программе CGI в случае применения метода GET. Если в документе HTML после символа "?" указана строка параметров, то именно она записывается в эту переменную среды.

CONTENT_LENGTH - эта переменная среды используется в случае применения метода передачи данных POST и содержит количество байт данных, которые программа CGI должна получить от браузера.

CONTENT_TYPE - при использовании метода передачи POST в этой переменной хранится строка с названием типа данных, присланных браузером. Если браузер присылает текстовые данные (как это бывает в большинстве случаев), тип данных может быть application/x-www-form-urlencoded. Однако возможно применение и других типов данных, например multipart/form-data. В этом случае от браузера могут быть приняты как текстовые, так и двоичные файлы*.

AUTH_TYPE - технология WWW допускает защиту страниц HTML, когда доступ к отдельным страницам предоставляется только при предъявлении пароля. Переменная среды AUTH_TYPE содержит тип идентификации, который применяется сервером. Например, для сервера WWW на базе Microsoft Information Server при включении аутентификации в этой переменной будет храниться строка NTLM.

GATEWAY_INTERFACE - в этой переменной находится версия интерфейса CGI, с которой работает Web-сервер.

HTTP_ACCEPT - в этой переменной перечислены типы данных MIME, которые могут быть приняты браузером от Web-сервера.

HTTP_REFER - в переменную HTTP_REFER записывается адрес URL документа HTML, который инициировал работу программы CGI.

HTTP_ACCEPT_LANGUAGE - переменная HTTP_ACCEPT_LANGUAGE содержит идентификатор предпочтительного национального языка для получения ответа от Web-сервера. Сервер Web, однако, может прислать ответ на другом национальном языке.

HTTP_UA_PIXELS - разрешение видеоадаптера, установленное в компьютере пользователя. Анализируя содержимое этой переменной, а также содержимое переменной HTTP_UA_COLOR, описанной ниже, программа CGI может динамически создать документ HTML такого формата, который будет наилучшим образом отображаться на экране компьютера удаленного пользователя.

HTTP_UA_COLOR - допустимое количество цветов в системе пользователя.

HTTP_UA_OS - операционная система, под управлением которой работает браузер.

HTTP_UA_CPU - тип центрального процессора в компьютере удаленного пользователя.

HTTP_USER_AGENT - имя браузера, с помощью которого пользователь работает с сервером Web. Анализируя это имя, программа CGI может принимать решение об использовании тех или иных расширений стандарта языка HTML, допустимых для конкретного браузера.

HTTP_HOST - имя узла, на котором работает Web-сервер.

PATH_INFO - путь к виртуальному каталогу, в котором находится программа CGI.

PATH_TRANSLATED - физический путь к программе CGI.

REMOTE_ADDR - адрес узла IP, на котором работает браузер удаленного пользователя.

REMOTE_HOST - доменное имя узла, на котором работает браузер удаленного пользователя. Если эта информация недоступна (например, для узла не определен доменный адрес), вместо доменного имени указывается адрес IP, как в переменной REMOTE_ADDR.

REMOTE_USER - имя пользователя, которое используется браузером для аутентификации.

SCRIPT_NAME - в эту переменную записывается путь к виртуальному каталогу и имя программы CGI. Анализируя эту переменную, программа CGI может определить путь к своему загрузочному файлу.

SERVER_NAME - доменное имя Web-сервера или адрес IP сервера, если доменное имя недоступно или не определено.

SERVER_PROTOCOL - имя и версия протокола, который применяется для выполнения запроса к программе CGI.

SERVER_PORT - номер порта, на котором браузер посылает запросы Web-серверу.

SERVER_SOFTWARE - название и версия программного обеспечения Web-сервера. Версия следует после названия и отделяется символом "/".

Книга гостей

Следующая наша CGI-программа несколько сложнее и к тому же может быть использована на практике. Она выполняет функции электронной книги гостей, собирая отзывы посетителей и записывая их в файл на сервере Web. Отзывы доступны для всеобщего обозрения.

Для записи отзыва пользователь должен заполнить форму.

Форма содержит четыре поля редактирования текста, три из которых однострочные, а одно - многострочное. Однострочные поля предназначены для записи электронного адреса E-mail, имени и места проживания пользователя, пожелавшего оставить запись в книге гостей. Многострочное поле служит для записи отзыва. В нижней части страницы есть две кнопки с названиями Store и Clear Form. С помощью первой из них вы можете отправить свой отзыв на сервер, а с помощью второй - очистить содержимое полей формы.

Исходный текст документа HTML, содержащего только что описанную форму, приведен в листинге 3.

Форма описана между операторами <FORM> и </FORM>. Рассмотрим самые основные операторы языка HTML, с помощью которых определяются формы. Прежде всего обратите внимание на параметры оператора <FORM>:

<FORM METHOD=POST ACTION="http://frolov/Scripts/guestbk.exe">

Параметр METHOD задает метод передачи данных от браузера расширению сервера Web (в нашем случае программе CGI). Мы указали метод POST, поэтому программа должна получать данные, читая их из стандартного потока ввода. Почему мы использовали здесь метод POST, а не метод GET? Потому что записи, оставляемые пользователями в книге гостей, могут иметь достаточно большую длину, в то время как максимальный размер памяти, выделяемой для переменных среды, ограничен. Методом передачи данных POST мы решаем эту проблему.

Параметр ACTION указывает путь к загрузочному файлу программы CGI. Когда пользователь нажмет кнопку, предназначенную для отправки заполненной формы, будет запущена программа CGI, указанная в параметре ACTION.

Для размещения органов управления внутри формы мы использовали таблицу (оператор <TABLE>). Таблица позволяет сохранить относительное расположение различных элементов формы при изменении размеров окна браузера.

Однострочные поля, предназначенные для ввода текстовых строк адреса, имени и места проживания, вставлены в форму с помощью оператора <INPUT>:

<INPUT TYPE=TEXT NAME="email" SIZE=60>

Параметр TYPE определяет тип органа управления, вставленного в форму оператором <INPUT>. Если значение этого параметра равно TEXT, вставляется однострочное поле, предназначенное для редактирования текстовой строки. Параметр NAME определяет имя поля, которое будет посылаться на сервер вместе с содержимым поля в виде <ИмяПоля>=<Содержимое>. И наконец, параметр SIZE определяет ширину поля редактирования в символах.

Многострочное поле редактирования текста вставляется в форму с помощью оператора <TEXTAREA>, который используется в паре с оператором </TEXTAREA>:

<TEXTAREA NAME="comment" ROWS=4 COLS= 100></TEXTAREA>

Здесь параметр NAME задает имя поля редактирования, а параметры ROWS и COLS определяют размеры окна редактирования. Параметр ROWS задает количество строк в окне, а параметр COLS - ширину поля редактирования в символах.

Кнопки отправления записи на сервер и сброса содержимого формы также вставляются в форму с помощью оператора <INPUT>. Вот как вставляется кнопка, позволяющая сохранить сделанную пользователем запись на сервере:

<INPUT TYPE=SUBMIT VALUE="Store!">

Значение TYPE, определяющее тип кнопки, здесь равно SUBMIT, поэтому кнопка предназначена для передачи данных из формы на сервер. Надпись на кнопке определяется параметром VALUE. Кнопка очистки формы создается аналогично, однако для нее значение параметра TYPE равно RESET:

<INPUT TYPE=RESET VALUE="Clear form">

Теперь, после того как мы изучили форму, предназначенную для записи отзывов пользователей и запуска программы CGI, займемся исходным текстом самой программы CGI, загрузочный файл которой имеет имя guestbk.exe (см. листинг 4). Схема работы программы guestbk.exe такова. Когда самый первый пользователь вашего сервера оставляет запись в книге гостей, программа guestbk.exe создает файл guestbk.dat и сохраняет в нем эту запись в форме, пригодной для отображения в документе HTML. Затем она динамически формирует документ HTML, в котором есть только одна запись, и отсылает его пользователю.

При добавлении второй и последующих записей программа guestbk.exe сначала записывает в создаваемый документ содержимое файла guestbk.dat, затем дописывает к нему новую запись и выводит эту запись в выходной документ HTML. В результате пользователь после добавления своей записи увидит ее в нижней части динамически сформированной страницы.

Программа формирует стандартный заголовок документа HTML, вызывая для этого четыре раза функцию printf. Далее с помощью функции fopen предпринимается попытка открыть для чтения файл книги гостей с именем guestbk.dat. Поиск этого файла производится в том же каталоге, где находится загрузочный модуль нашей программы CGI guestbk.exe. Если файл guestbk.dat существует, то его содержимое копируется в стандартный поток вывода с помощью функций fgets и fputs:

while(!feof(fileGuestBook))
{
  fgets(szBuf, 1024, fileGuestBook);
  if(ferror(fileGuestBook))      
    break;
  fputs(szBuf, stdout);
}
fclose(fileGuestBook);

Обратите внимание, что в качестве второго параметра функции fputs мы указали значение stdout, в результате чего функция будет выполнять запись в стандартный поток вывода. Таким образом отображается старое содержимое книги гостей. После чтения файл guestbk.dat закрывается функцией fclose.

Далее программа проверяет метод передачи данных, выполняя работу только в том случае, если в переменной среды REQUEST_METHOD хранится строка POST. При использовании метода POST программа определяет число байт данных, полученных от пользователя. Эти значения необходимо считать из стандартного потока ввода. Текстовая строка, полученная от функции getenv, преобразуется функцией atoi в двоичное значение и сохраняется в переменной lSize. Входные данные, записанные удаленным пользователем в форму, помещаются функцией fread в буфер szBuf.

На следующем этапе файл книги гостей guestbk.dat открывается функцией fopen для добавления в режиме "a+". Это означает, что если файла нет, он создается и открывается для записи. Если же файл есть, в него будут дописаны новые данные (начиная с символа конца файла). Далее выполняется одновременное добавление новой записи в файл книги гостей и вывод этой записи в стандартный поток вывода. Это необходимо для того, чтобы пользователь сразу увидел добавленную им запись. В начале каждой записи располагается разделитель, создаваемый оператором <HR>. Далее программа CGI выделяет значения отдельных полей формы из данных, принятых в буфер szBuf. Эта процедура не совсем тривиальна, так как данные приходят от браузера в так называемой кодировке URL, о которой мы будем говорить в следующем разделе нашей статьи.

Все необходимые преобразования выполняет функция DecodeStr. Для этой функции мы копируем принятые данные в отдельный буфер szSrcBuf и затем выполняем перекодировку:

szBuf[lSize] = '\0'; strcpy(szSrcBuf, szBuf);
DecodeStr(szSrcBuf);

Как мы уже говорили, значения полей приходят в формате <ИмяПоля>=<Значение>. Если полей несколько, то они разделяются символом "&". Для дальнейшей обработки мы добавляем такой символ в конец перекодированной строки:

szBuf[lSize] = '&'; szBuf[lSize + 1] = '\0';

Далее программа сканирует буфер принятых данных, выделяя в нем отдельные параметры. Содержимое буфера szSrcBuf не используется. Мы выполнили в нем перекодировку всего принятого буфера данных только для демонстрации способа, которым это можно сделать.

В процессе сканирования указатель szPtrValue устанавливается на начало строки значения параметра. Выделенное таким образом значение параметра записывается в стандартный поток вывода и в файл книги гостей. Эта операция повторяется четыре раза для каждого поля формы, при этом мы оформляем выходные данные средствами языка HTML. Например, пользуясь адресом E-mail, мы организуем ссылку, с помощью которой можно легко отправить почтовое сообщение пользователю, оставившему запись в книге гостей:

printf("<A HREF=\"mailto:%s\">%s</A><BR>", szPtrValue, szPtrValue);

Закончив обработку новой записи, программа записывает в стандартный поток вывода операторы </BODY> и </HTML>, завершающие документ HTML, и затем закрывает файл книги гостей guestbk.dat.

Кодировка URL

Рассмотрим подробнее формат данных application/x-www-form-urlencoded. Это поможет понять, как в только что описанной программе guestbk.exe выполняется извлечение содержимого отдельных полей из блока данных, принятых от браузера. Как мы уже говорили, именно этот формат применяется при передаче текстовых данных из формы, расположенной в документе HTML. Данные, поступившие от формы, имеют следующий формат:

<Имя1>=<Значение1>&<Имя2>=<Значение2>&<Имя3>=<Значение3><Имя4>=<Значение4>-

После имени поля, указанного в параметре NAME оператора INPUT, расположенного в форме, следует символ "=" и закодированное значение в кодировке URL. Если форма содержит несколько органов управления, пары <Имя>=<Значение> разделяются символом "&". Значение поля кодируется следующим образом. Все символы пробелов заменяются на символы "+". Управляющие и некоторые другие символы заменяются на последовательность символов вида "%xx", где xx - шестнадцатеричный код перекодированного символа в виде двух символов ASCII. Прежде чем анализировать или каким-либо образом использовать значение полей, необходимо выполнить перекодировку, заменив символы "+" на пробелы, а последовательности вида "%xx" на символы с кодом xx. Вторая задача решается функцией DecodeHex, исходный текст которой вы найдете в листинге 2. Функция DecodeStr, исходный текст которой есть в этом же листинге, выполняет полную перекодировку строки, адрес которой передается ей в качестве параметра.

Динамические ссылки на документы HTML

Чаще всего программы CGI формируют выходной документ HTML, записывая его в стандартный поток вывода, в результате чего этот документ отправляется пользователю и отображается в окне браузера. Именно таким образом работали описанные нами программы cgi1.exe и guestbk.exe.

Есть, однако, и другая возможность. Вместо того чтобы формировать документ HTML что называется "с нуля", программа CGI может динамически выполнить ссылку на любой существующий документ HTML по его адресу URL. Для выполнения такой ссылки программа CGI должна записать в стандартный поток вывода заголовок HTTP специального вида, а именно:

"Location: <Адрес URL>\n\n"

Например, в результате работы приведенной ниже программы CGI в окне браузера появится страница, расположенная на сервере http://www.glasnet.ru в каталоге ~frolov:

#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
  printf("Location: http://www.glasnet.ru/~frolov/index.html\n\n");
}

Вы можете сделать ссылку, указав полный или относительный адрес документа HTML. Во втором случае адрес будет вычисляться от каталога, который содержит ссылку на программу CGI.

Если вам нужно просто организовать в документе HTML ссылку на другой документ HTML, вам вполне хватит возможностей оператора <A> языка HTML. Однако программа CGI может создавать такую ссылку динамически на основе анализа параметров, полученных от пользователя или исходя из других соображений. Скажем, вы можете разместить в документе HTML форму, расположив в ней список ссылок. Просматривая этот список, пользователь выберет ту ссылку, которая ему нужна. Это очень удобно, так как вы можете разместить на одной странице несколько таких списков и пользователю не придется обходить весь ваш сервер в поисках нужной ему информации. Проанализировав данные, полученные от формы, программа CGI динамически сформирует нужную ссылку.

Еще одно применение динамических ссылок - вывод сообщений об ошибках из программы CGI. Вы можете заготовить несколько документов HTML с сообщениями об ошибках и выполнять при необходимости на них ссылку из программы CGI. Например, если пользователь сделал что-нибудь не так при заполнении формы, программа CGI, обрабатывающая эту форму, может сделать ссылку на документ HTML, объясняющий, каким образом нужно заполнять данное поле.

Счетчик посещений

Пожалуй, одно из наиболее распространенных применений программ CGI (а также расширений ISAPI) - это создание на странице сервера счетчика посещений. Знание посещаемости вашего сервера имеет значение не только для удовлетворения собственного самолюбия или тщеславия. Если ваш сервер очень популярен, вы можете размещать на нем рекламу, что принесет вам вполне ощутимый доход. Разумеется, потенциальные рекламодатели обращают внимание на популярность сервера, поэтому вам обязательно следует подумать о том, чтобы разместить на своем сервере счетчик посещений.

Если вы храните свои документы HTML на сервере поставщика услуг Internet, вы можете воспользоваться для создания счетчика посещений готовыми программами CGI. Поставщики услуг Internet обычно предоставляют вам такую возможность. Вот как может выглядеть ссылка на счетчик посещений:

<IMG SRC="/cgi-bin/ Count.cgi?dd=D|ft=2|df=frolov_page.dat">

Обратите внимание, что ссылка на счетчик выполняется оператором <IMG>, который предназначен для вставки графических изображений. Однако параметр SRC ссылается не на адрес URL графического изображения, а на программу CGI с именем Count.cgi. Многочисленные параметры этой программы определяют внешний вид счетчика. Полную документацию, необходимую для использования программы Count.cgi, вы можете найти на сервере GlasNet. Программа CGI, занимающаяся подсчетом посещений, обычно хранит текущее значение счетчика в файле, имя которого передается ей с помощью параметров в документе HTML. Когда пользователь загружает документ HTML со ссылкой на программу счетчика в окно браузера, эта программа запускается. Получив управление, программа считывает текущее значение счетчика из файла и, увеличив его на единицу, опять сохраняет в файле. Затем она формирует в памяти графическое изображение счетчика и возвращает его браузеру через стандартный поток вывода, подготовив соответствующим образом заголовок HTTP. В результате на месте ссылки <IMG> в документе HTML появится графическое изображение счетчика.

Как мы уже говорили, имя файла, содержащего счетчик, обычно передается программе через параметр. Например, для нас на сервере АО "ДиалогНаука" администратор создал несколько файлов с именами frolov1-frolov9. Указывая эти имена в параметре, мы можем разместить на своих страницах несколько различных счетчиков для сбора статистики о посещаемости этих страниц:

<IMG SRC="http://www.dials.ccas.ru/scripts/w3count.exe?frolov1">

Если ваша программа CGI будет работать с сервером Microsoft Internet Information Server в среде операционной системы Microsoft Windows NT, есть еще одно место, удобное для хранения текущего значения счетчика посещений - реестр операционной системы. Ваша программа может создать в ней одну или несколько записей и хранить в этих записях значения различных счетчиков.

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

Подготовка графического изображения счетчика - непростая задача, если речь идет о формировании этого изображения в формате GIF. В сети Internet вы можете найти исходные тексты бесплатных программ, предназначенных для этого. Существует относительно простой способ создания собственного счетчика посещений, изображение которого формируется в формате XBM. Этот формат используется всеми основными браузерами и допускает, кстати, хранение только черно-белых изображений.

Возвращая браузеру в программе CGI графическое изображение XBM, вы должны вначале записать заголовок HTTP следующего вида:

"Content-type: image/x-xbitmap/n/n"

Этот заголовок сообщает, что дальше следует графическое изображение в формате image/x-xbitmap. Графические данные в формате XBM представляют собой определение массива данных на языке программирования Си, например:

#define digit_width  8
#define digit_height 10
static unsigned char digit_bits[] =
{
  0x10, 0x18, 0x14, 0x10,       0x10, 0x10, 
  0x10, 0x10, 0x10, 0x7C 
};

Название файла входит в первую часть имени структуры. Если, например, эти данные хранятся в файле с именем digit, то массив будет называться digit_bits. Массив определяет изображение цифры 1 высотой 8 пикселов и шириной 10 пикселов. Обратите внимание, что изображение зеркальное.

Строки #define, расположенные в начале файла, определяют размеры графического изображения. Имена этих констант образуются из имени файла и строк _width и _height соответственно для значений ширины и высоты изображения. В файле исходного текста нашей программы CGI подготовлены массивы изображений для цифр от 0 до 9. В процессе формирования изображения счетчика программа комбинирует эти цифры в нужной последовательности.

В листинге 5 приведен исходный текст документа HTML, в котором имеется ссылка на программу счетчика. Исходный текст программы CGI счетчика вы найдете в листинге 6.

Программа counter.exe создана для Windows NT, и поэтому для работы с файлом счетчика посещений использованы специфические функции программного интерфейса этой операционной системы. Соответственно в исходный текст программы CGI включен файл windows.h. Вы можете также воспользоваться для работы с файлом счетчика COUNTER.DAT функциями из стандартной библиотеки Си.

Рассмотрим устройство программы. Константы char_width и char_height определяют соответственно ширину и высоту растрового изображения цифр. В массиве char_bits хранятся изображения цифр от 0 до 9. В начале своей работы функция main открывает файл COUNTER.DAT для чтения, читает из него семь байтов, записывая результат в переменную szBuf, а затем закрывает файл. Размещая программу counter.exe на сервере Web, вы должны поместить туда же текстовый файл, содержащий семь цифр начального значения счетчика посещений, например значение 0000000. С помощью функции sscanf программа преобразует прочитанное из файла символьное значение счетчика посещений в двоичное число, записывая его в переменную nCounter. Затем содержимое переменной nCounter увеличивается на единицу, преобразуется в символьную строку и сохраняется в файле COUNTER.DAT. На данном этапе переменная nCounter содержит текущее значение счетчика посещений, которое необходимо отобразить на странице Web-сервера. В буфере szBuf хранится текстовая строка текущего значения счетчика.

Программа записывает в стандартный поток вывода заголовок с указанием типа данных MIME, соответствующего изображению XBM:

printf("Content-type: image/x-xbitmap\n\n");

После этого исходя из числа цифр в строке текущего значения счетчика и размеров изображения отдельных цифр вычисляется общий размер графического изображения счетчика:

nFinalWidth  = char_width * strlen(szBuf);
nFinalHeight = char_height;

Вслед за этим в стандартный поток вывода записываются первые три строки изображения XBM:

printf("#define counter_width %d\n", nFinalWidth);
printf("#define counter_height %d\n", nFinalHeight);
printf("static unsigned char counter_bits[] = {\n");

Далее программа в цикле выводит строки графического изображения счетчика, адресуясь к элементам массива char_bits. После вывода массива программа записывает в стандартный поток вывода закрывающую фигурную скобку:

printf("};\n");

На этом ее работа заканчивается.

Язык Perl

Если вы создаете программу CGI, которая должна будет работать в среде разных операционных систем, следует осторожно подходить к выбору языка программирования. Очевидно, неплохим выбором можно считать язык Си, так как транслятор для этого языка имеется на всех известных нам серверных платформах, а способы работы со стандартными потоками ввода и вывода неизменны в любых реализациях трансляторов. Сказанное, конечно, не означает, что программы CGI не могут пользоваться специфическими функциями конкретных операционных систем (как это делает, например, только что описанная программа counter.exe). Но работа с такими функциями отрицательно скажется на возможности переноса исходного текста программы на другие платформы.

Для создания абсолютно мобильных программ CGI лучше всего воспользоваться языком Perl. Интерпретаторы этого языка созданы практически для всех операционных систем. Этот язык высокого уровня содержит многие функции, упрощающие создание программ CGI. В сети Internet доступны (причем бесплатно) версии интерпретатора Perl для различных платформ (в том числе и для Microsoft Windows NT), разнообразная документация и примеры программ. Все это легко найти, если выполнить поиск по ключевому слову Perl.

Заметим, однако, что возможность использования языка Perl определяется системным администратором сервера Web (это, кстати, относится и к возможности использования любых расширений сервера Web). Поэтому, если вы не являетесь администратором, прежде чем приступать к изучению языка Perl, поинтересуйтесь, станет ли администратор устанавливать интерпретатор Perl на своем сервере.


Алексей Вячеславович Фролов, Григорий Вячеславович Фролов - авторы серии книг "Библиотека системного программиста" и "ПК. Шаг за шагом".

E-mail: alexandre@frolov.pp.ru
Web: http://www.frolov.pp.ru

* Можно передавать локальные двоичные или текстовые файлы от браузера Netscape Navigator к программе расширения сервера, выполненного с применением ISAPI, для их удаленной обработки. На сервере Web АО "ДиалогНаука" таким образом организована бесплатная антивирусная проверка файлов пользователей сети Internet. При этом файлы передаются в формате multipart/form-data.


Листинг 1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
  <HEAD>
    <TITLE>Вызов программы CGI</TITLE>
  </HEAD>
  <BODY BGCOLOR=#FFFFFF>
    <A HREF="http://frolov/Scripts/cgi1.exe?param1">Ссылка для вызова программы CGI</A>
  </BODY>
</HTML>


Листинг 2

#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[])
{
  printf("Content-type: text/html\n\n");
  printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">");
  printf("<HTML><HEAD><TITLE>"
    "Страница, созданная программой CGI"
    "</TITLE></HEAD><BODY BGCOLOR=#FFFFFF>");

  printf("<H1>Содержимое некоторых переменных среды</H1>");
  printf("REQUEST_METHOD = %s<BR>", getenv("REQUEST_METHOD"));
  printf("QUERY_STRING = %s<BR>",   getenv("QUERY_STRING"));
  printf("CONTENT_LENGTH= %s<BR>",  getenv("CONTENT_LENGTH"));
  printf("CONTENT_TYPE= %s<BR>",    getenv("CONTENT_TYPE"));
  printf("</BODY></HTML>");
}


Листинг 3

<HTML>
<HEAD>
   <TITLE>Книга гостей</TITLE>
</HEAD>
<BODY BACKGROUND="bkg.gif">
<H1>Благодарим за посещение</H1>
<P>Пожалуйста, оставьте запись в нашей книге гостей
<P>
<FORM METHOD=POST ACTION="http://frolov/Scripts/guestbk.exe">
  <TABLE>
    <TD><P ALIGN=RIGHT>Укажите ваш адрес E-mail</TD>
    <TD><INPUT TYPE=TEXT NAME="email" SIZE=60></TD>
    <TR>
      <TD><P ALIGN=RIGHT>Ваше настоящее имя</TD>
      <TD><INPUT TYPE=
TEXT NAME= "name" SIZE=60></TD>
    </TR>
    <TR>
      <TD><P ALIGN=RIGHT>Где вы живете?</TD>
      <TD><INPUT TYPE=
TEXT NAME="from" SIZE=60></TD>
    </TR>
  </TABLE>
  <HR>

  Ваши комментарии:
  <P><TEXTAREA NAME="comment" ROWS=4 COLS=100></TEXTAREA>
  <HR>
  <INPUT TYPE=SUBMIT VALUE= "Store!"> 
  <INPUT TYPE=RESET VALUE="Clear form">
</FORM>
</BODY>
</HTML>


Листинг 4

// --------------------------
// Программа CGI - книга гостей для сервера Web
// 
// (c) Александр Фролов, 1997
//
// E-mail: frolov@glas.apc.org
// Web:    http://www.glasnet.ru/~frolov
//         http://www.dials.ccas.ru/frolov
// --------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void DecodeStr(char *szString);
char DecodeHex(char *str);

void main(int argc, char *argv[])
{
  int lSize;
  FILE * fileGuestBook;
  char * szMethod;
  char szBuf[1024];
  char szSrcBuf[1024];
  char szTemp[1024];
  char * szPtr;
  char * szPtrValue;
  char * szParam;

  printf("Content-type: text/html\n\n");
  printf("<!DOCTYPE HTML PUBLIC \"-//"
    "W3C//DTD HTML 3.2//EN\">");
  printf("<HTML><HEAD><TITLE>Книга гостей"
    "</TITLE></HEAD><BODY BACKGROUND=\"bkg.gif\">");
  printf("<H2>Наша книга гостей</H2>");
    
  fileGuestBook = fopen("guestbk.dat", "r");
  if(fileGuestBook != NULL)
  {
    while(!feof(fileGuestBook))
    {
      fgets(szBuf, 1024, fileGuestBook);
      if(ferror(fileGuestBook))      
        break;
      fputs(szBuf, stdout);
    }
    fclose(fileGuestBook);
  }
 
  szMethod = getenv("REQUEST_METHOD");
  if(!strcmp(szMethod, "POST"))
  {
    lSize = atoi(getenv("CONTENT_LENGTH"));
    fread(szBuf, lSize, 1, stdin);

    fileGuestBook = fopen("guestbk.dat", "a+");
    printf("<HR>");
    fwrite("<HR>", 4, 1, fileGuestBook);
    szBuf[lSize] = '\0'; strcpy(szSrcBuf, szBuf);
    DecodeStr(szSrcBuf);
    szBuf[lSize] = '&'; szBuf[lSize + 1] = '\0';
    szParam = szBuf; szPtr = strchr(szParam, '&');
    if(szPtr != NULL)
    {
      *szPtr = '\0'; DecodeStr(szParam);
      szPtrValue = strchr(szParam, '="); szPtrValue++;
      printf("<A HREF=\"mailto:%s\">%s</A><BR>", 
        szPtrValue, szPtrValue);
      sprintf(szTemp, "<A HREF=\"mailto:%s\">%s</A><BR>", 
        szPtrValue, szPtrValue);
      fwrite(szTemp, strlen(szTemp), 1, fileGuestBook);
    }

    szParam = szPtr + 1; szPtr = strchr(szParam, '&');
    if(szPtr != NULL)
    {
      *szPtr = '\0'; DecodeStr(szParam);
      szPtrValue = strchr(szParam, '="); szPtrValue++;
      printf("<ADDRESS><B>%s, ", szPtrValue);
      sprintf(szTemp, "<ADDRESS><B>%s, ", szPtrValue);
      fwrite(szTemp, strlen(szTemp), 1, fileGuestBook);
    }

    szParam = szPtr + 1; szPtr = strchr(szParam, '&');
    if(szPtr != NULL)
    {
      *szPtr = '\0'; DecodeStr(szParam);
      szPtrValue = strchr(szParam, '="); szPtrValue++;
      printf("%s</ADDRESS></B>", szPtrValue);
      sprintf(szTemp, "%s</ADDRESS></B>", szPtrValue);
      fwrite(szTemp, strlen(szTemp), 1, fileGuestBook);
    }

    szParam = szPtr + 1; szPtr = strchr(szParam, '&');
    if(szPtr != NULL)
    {
      *szPtr = '\0'; DecodeStr(szParam);
      szPtrValue = strchr(szParam, '="); szPtrValue++;
      printf("<P>%s", szPtrValue);
      sprintf(szTemp, "<P>%s", szPtrValue);
      fwrite(szTemp, strlen(szTemp), 1, fileGuestBook);
    }
    printf("</BODY></HTML>");
    fclose(fileGuestBook);
    return;
  }
}

void DecodeStr(char *szString)
{
  int src;
  int dst;
  char ch;
  for(src=0, dst=0; szString[src]; src++, dst++)
  {
    ch = szString[src];
    ch = (ch == '+') ? ' ' : ch;
    szString[dst] = ch;
    if(ch == '%')
    {
      szString[dst] = DecodeHex(&szString[src + 1]);
      src += 2;
    }
  }
  szString[dst] = '\0';
}

char DecodeHex(char *str)
{
  char ch;
  if(str[0] >= 'A')
    ch = ((str[0] & 0xdf) - 'A') + 10;
  else
    ch = str[0] - '0';
  ch <<= 4;
  if(str[1] >= 'A')
    ch += ((str[1] & 0xdf) - 'A') + 10;
  else
    ch += str[1] - '0';
  return ch;
}


Листинг 5

<HTML>
  <BODY BGCOLOR="#FFFFFF">
    <P>Количество посещений: 
    <IMG ALT="Счетчик посещений" SRC="http://frolov/scripts/counter.exe?">
  </BODY>
</HTML>


Листинг 6

// ===============================================
// Расширение CGI: счетчик посещений страниц
//
// (C) Фролов А.В., 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//         http://www.dials.ccas.ru/frolov
// ===============================================

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define char_width  8
#define char_height 10

static unsigned char char_bits[10][char_height] = 
{
  { // 0
    0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C 
  },
  { // 1
    0x10, 0x18, 0x14, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C 
  },
  { // 2
    0x3E, 0x22, 0x20, 0x20, 0x10, 0x08, 0x04, 0x02, 0x22, 0x3F 
  },
  { // 3
    0x3E, 0x22, 0x40, 0x40, 0x3C, 0x40, 0x40, 0x40, 0x22, 0x3E 
  },
  { // 4
    0x10, 0x18, 0x14, 0x12, 0x12, 0x3E, 0x10, 0x10, 0x10, 0x38 
  },
  { // 5
    0x3E, 0x02, 0x02, 0x02, 0x3E, 0x20, 0x20, 0x20, 0x22, 0x2C 
  },
  { // 6
    0x1C, 0x06, 0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x22, 0x1C 
  },
  { // 7
    0x3E, 0x22, 0x20, 0x20, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08 
  },
  { // 8
    0x1C, 0x22, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x22, 0x22, 0x1C 
  },
  { // 9
    0x1C, 0x22, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x20, 0x20, 0x1C 
  },
};

void main(int argc, char *argv[])
{
  CHAR   szBuff[4096];
  HANDLE hCounterFile;
  DWORD  dwBytesRead;
  DWORD  dwBytesWritten;
  BOOL   bResult;
  CHAR   szBuf[20];
  INT    nCounter;
  int    nFinalWidth;
  int    nFinalHeight;
  int    nLine;
  unsigned int nChar;
  int    nDigitOffset;

  hCounterFile = CreateFile("COUNTER.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++;
  sprintf(szBuf, "%05.5ld", nCounter);

  hCounterFile = CreateFile("COUNTER.DAT", 
    GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
    FILE_FLAG_SEQUENTIAL_SCAN, NULL);
  WriteFile(hCounterFile, szBuf, strlen(szBuf),
    &dwBytesWritten, NULL);
  CloseHandle(hCounterFile);
  
  printf("Content-type: image/x-xbitmap\n\n");

  nFinalWidth  = char_width * strlen(szBuf);
  nFinalHeight = char_height;

  printf("#define counter_width %d\n", nFinalWidth);
  printf("#define counter_height %d\n", nFinalHeight);
  printf("static unsigned char counter_bits[] = {\n");

  for(nLine=0; nLine < nFinalHeight; nLine++)
  {
    for(nChar=0; nChar < strlen(szBuf); nChar++)
    {
      nDigitOffset = szBuf[nChar] - '0';
      printf("0x%02X, ", char_bits[nDigitOffset][nLine]);
    }
  }
  printf("};\n");
}

Размещено с разрешения редакции журнала МИР ПК

[Назад]