7. Приложения CGI

До настоящего момента мы рассказывали вам о том, как разместить в документах HTML различную статическую информацию. Статическую в том смысле, что она не изменяется во времени без вмешательства администратора сервера WWW. Если сервер WWW содержит только статические документы HTML, то такой сервер мы будем называть статическим, или пассивным.

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

Серверы, которые ведут диалог с удаленным пользователем или выполняют обработку данных пользователя, мы будем называть активными.

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

Что кроется за аббревиатурой CGI?

CGI - это стандартный шлюзовой интерфейс (Common Gateway Interface) для запуска внешних программ под управлением сервера WWW. Соответственно, приложениями CGI называются программы, которые, пользуясь этим интерфейсом, получают через протокол HTTP информацию от удаленного пользователя, обрабатывают ее, и возвращают результат обработки обратно в виде ссылки на уже существующий документ HTML или другой объект (например, графическое изображение) или в виде документа HTML, созданного динамически.

Передача информации от удаленного пользователя приложению CGI обычно выполняется следующим образом.

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

Когда пользователь заполняет форму и нажимает указанную кнопку, данные передаются приложению CGI, путь к которому задается в заголовке формы. Это приложение получает через протокол HTTP данные из полей формы в виде пар значений “имя поля/значение”.

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

Все описанные процедуры мы рассмотрим в этой главе более подробно. Кроме того, мы приведем исходные тексты различных приложений CGI. Заметим, что возможны и другие сценарии работы с программами CGI.

Так как приложение CGI является ни чем иным, как программой, вы должны оттранслировать ее для той операционной системы, под управлением которой работает ваш сервер WWW. В некоторых случаях вы можете найти более удобным создавать программы CGI с использованием специально предназначенных для этого интерпретаторов, таких как Perl, или языка пакетных заданий. Например, вы можете создать программу CGI для сервера Microsoft Information Server как обычный пакетный файл *.bat. В нашей книге мы сконцентрируемся на использовании для создания программ CGI мобильного языка программирования C. Транслятор этого языка вы можете найти в любой операционной системе.

Создание форм

Чаще всего программы CGI применяются для обработки данных, введенных удаленными пользователями при помощи форм. Поэтому изучение программ CGI мы начнем с создания форм.

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

Описание формы

Для того чтобы сделать форму в документе HTML, вы должны воспользоваться оператором <FORM>. Этот оператор используется в паре с оператором </FORM>, завершающим описание формы. Между операторами <FORM> и </FORM> находятся описания органов управления в виде операторов <INPUT>, <TEXTAREA> и <SELECT> с соответствующими параметрами.

Вот пример определения простейшей формы:


<FORM METHOD=GET ACTION="http://www.myserver.ru/frolov-cgi/form.exe">
  <TABLE>
  <TR>
    <TD><INPUT TYPE=text NAME="text1" VALUE="Sample of text1"></TD>
  </TR>
  <TR>
    <TD><INPUT TYPE=text NAME="text2" VALUE="Sample of text2"></TD>
  </TR>
  <TR>
    <INPUT TYPE=submit VALUE="Send">
  </TR>
  </TABLE>
</FORM>

Здесь органы управления размещаются в таблице, состоящей из одного столбца и трех строк. В верхних двух строках мы разместили поля для ввода и редактирования текста, в последней строке - кнопку с названием Send. Внешний вид формы при ее просмотре в навигаторе показан на рис. 7.1.

Рис. 7.1. Простейшая форма для ввода двух текстовых строк

Перечислим допустимые параметры оператора <FORM>:

Параметр Описание
ACTION Адрес URL для выполнения действий над формой
METHOD Способ передачи данных из формы в сервер WWW
ENCTYPE Тип MIME передаваемых данных

Параметр ACTION определяет, какое действие будет выполнено над формой, после того как пользователь ее заполнит и передаст серверу WWW. В примере, приведенном выше, в качестве значения для параметра ACTION мы указали путь к программе CGI, которая будет выполнять обработку данных.

С помощью параметра METHOD вы можете выбрать один из двух методов передачи данных из формы серверу WWW. Если значение этого параметра равно GET (как в нашем примере), программа CGI, указанная в параметре ACTION, получит данные из формы через переменную среды с именем QUERY_STRING. В том случае, когда значение параметра METHOD равно POST, программа CGI получит данные из формы через стандартный поток ввода. Позже мы рассмотрим различия между этими методами более подробно.

И, наконец, третий параметр ENCTYPE, используется очень редко и только для метода POST. Он позволяет указать тип передаваемых данных и по умолчанию имеет значение application/x-www-form-urlencoded.

Создание органов управления для формы

Для создания в форме различных органов управления (полей ввода, переключателей, кнопок и так далее) используются операторы <INPUT>, <TEXTAREA> и <SELECT>.

Оператор <INPUT>

Оператор <INPUT> предназначен для вставки в форму таких органов управления, как поля ввода текстовой информации, переключатели, кнопки (обычные и в виде графических изображений), а также органы управления для передачи локального файла через навигатор в удаленный сервер WWW.

Перечислим параметры оператора <INPUT>:

Параметр Описание
TYPE Тип органа управления. В зависимости от значения этого параметра будут создаваться различные органы управления (кнопки, переключатели и так далее)
NAME Имя органа управления. Это имя посылается программе обработки формы и используется для определения состояния органа управления (для переключателей) или получения других данных (например, для получения строки, введенной в текстовом поле)
VALUE Начальное состояние или начальное значение для органа управления. Используется для инициализации органа управления при начальном отображении формы
CHECKED Этот параметр используется для установки начального значения переключателей
SIZE Ширина поля для ввода текстовой информации в символах. По умолчанию поле имеет ширину 20 символов
MAXLENGTH Максимальное количество символов, которое можно ввести в поле редактирования текстовой информации. По умолчанию такое ограничение отсутствует
ALIGN Выравнивание текста, расположенного около формы
SRC Адрес URL графического изображения, если оно используется в органе управления

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

Значение параметра TYPE Тип органа управления
TEXT Однострочное поле для ввода текстовой информации. Размер этого поля определяется параметрами SIZE и MAXLENGTH
TEXTAREA Многострочное поле для ввода текстовой информации. Размер поля также определяется параметрами SIZE и MAXLENGTH
PASSWORD Этот орган управления предназначен для ввода такой информации, как пароли. Он аналогичен органу управления типа TEXT, но отличается тем, что текст, введенный пользователем, не отображается на экране
CHECKBOX Переключатель типа Check Box. Предназначен для использования в наборе независимых друг от друга переключателей или отдельно
RADIO Переключатель для группы зависимых переключателей. Используется для выбора одного значения из нескольких
FILE Орган управления для выбора и передачи файла. Это значение используется по-разному навигаторами Microsoft Internet Explorer и Netscape Navigator
BUTTON Кнопка с заданной надписью
SUBMIT Кнопка, которая предназначена для посылки данных из заполненной формы серверу WWW. Надпись на этой кнопке также можно задавать
RESET С помощью этой кнопки пользователь может сбросить содержимое полей ввода и состояние переключателей в их начальные значения, заданные операторами VALUE
IMAGE Для посылки данных из формы в сервер вы можете использовать не только кнопку типа SUBMIT, но и произвольное графическое изображение, заданное параметром SRC. Соответствующий графический орган управления имеет тип IMAGE
HIDDEN Скрытое поле, которое не отображается. Содержимое этого поля посылается серверу и может быть проанализировано

Оператор <TEXTAREA>XE "<TEXTAREA>"

Оператор <INPUT> с параметром TYPE, имеющим значение TEXT, позволяет вставить в форму поле редактирования текстовой строки. Если же вам нужно ввести многострочный текст, лучше воспользоваться оператором <TEXTAREA>, который применяется совместно с оператором </TEXTAREA>.

Заметим, что хотя параметр TYPE оператора <INPUT> позволяет задать многострочное поле редактирования с типом TEXTAREA, созданное таким образом поле работает как однострочное.

Для оператора <TEXTAREA> вы можете задать три параметра:

Параметр Описание
NAME Имя многострочного поля, которое посылается программе обработки формы и используется для получения введенных строк текста
ROWS Размер поля по вертикали (в строках)
COLS Размер поля по горизонтали (в символах)

Вот пример описания многострочного текстового поля:


<TEXTAREA NAME="multi" ROWS=54 COLS=60>
Это
  образец
	многострочного
          текста
</TEXTAREA>

Оператор <SELECT>

С помощью оператора <SELECT> вы можете вставить в форму заранее проинициализированный список произвольных текстовых строк. Выбранная строка пересылается серверу WWW наряду с содержимым других полей формы.

Для оператора <SELECT> определены два параметра - NAME и SIZE. Параметр NAME задает имя списка, которое передается серверу WWW в паре с выбранной строкой. С помощью параметра SIZE можно задать высоту списка в строках.

Ниже мы привели пример использования оператора <SELECT>:


<SELECT NAME="number">
  <OPTION>Первый
  <OPTION SELECTED>Второй
  <OPTION>Третий
  <OPTION>Последний
</SELECT>

Для записи строк в список здесь используется оператор <OPTION>. Строка, отмеченная параметром SELECTED, будет выбрана в списке по умолчанию.

Пример документа HTML с формой

Формы лучше изучать на конкретном примере. В этой главе мы создадим форму, содержащую почти все органы управления (рис. 7.2), и рассмотрим исходный текст соответствующего документа HTML.

Рис. 7.2. Форма в документе HTML с различными органами управления

Этот исходный текст приведен в листинге 7.1.

Листинг 7.1. Файл chap7\controls\controls.htm


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
 <TITLE>Органы управления в формах</TITLE>
</HEAD>
<BODY BGCOLOR=#FFFFFF>
<FORM METHOD=POST ACTION="http://www.someserver.ru/frolov-cgi/controls.exe">
  <TABLE>
    <TR>
      <TD VALIGN=TOP>Текстовое поле TEXT</TD>
      <TD><INPUT TYPE=text NAME="text1" VALUE="Sample of text1" SIZE=30></TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Текстовое поле PASSWORD</TD>
      <TD><INPUT TYPE=password NAME="pwd" VALUE="Sample of password"></TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Текстовое поле TEXTAREA</TD>
      <TD><TEXTAREA NAME="text2" ROWS=4 COLS=30>Sample of text</TEXTAREA></TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Переключатели CHECKBOX</TD>
      <TD>
        <INPUT TYPE=CHECKBOX NAME="chk1" VALUE="on" CHECKED>Первый<BR>
        <INPUT TYPE=CHECKBOX NAME="chk2" VALUE="on">Второй<BR>
        <INPUT TYPE=CHECKBOX NAME="chk3" VALUE="on" CHECKED>Третий<BR>
      </TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Переключатели RADIO</TD>
      <TD>
        <INPUT TYPE=RADIO NAME="rad" VALUE="on1" CHECKED>Первый<BR>
        <INPUT TYPE=RADIO NAME="rad" VALUE="on2">Второй<BR>
        <INPUT TYPE=RADIO NAME="rad" VALUE="on3">Третий<BR>
      </TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Список</TD>
      <TD>
        <SELECT NAME="sel" SIZE="1">
          <OPTION Value="First Option">First Option</OPTION>
          <OPTION Value="Second Option">Second Option</OPTION>
          <OPTION Value="None">None Selected</OPTION>
        </SELECT>
      </TD>
    </TR>
    <TR>
      <TD VALIGN=TOP>Скрытый орган управления</TD>
      <TD><INPUT TYPE=HIDDEN NAME="hid" VALUE="Hidden"></TD>
    </TR>
  </TABLE>
<BR><INPUT TYPE=submit VALUE="Send">&nbsp;
<INPUT TYPE=reset VALUE="Reset">
<P><INPUT TYPE=IMAGE SRC="send.GIF" BORDER=0>
</FORM>
</BODY>
</HTML>

Оператор <FORM> здесь имеет два параметра - METHOD и ACTION:


<FORM METHOD=POST ACTION="http://www.someserver.ru/frolov-cgi/controls.exe">

Параметр METHOD имеет значение POST и задает способ передачи данных программе CGI через стандартный поток ввода.

В параметре ACTION указан путь к загрузочному файлу программы CGI, которая находится в каталоге frolov-cgi сервера WWW с адресом http://www.someserver.ru.

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

Теперь займемся органами управления.

Мы разместили все органы управления в таблице. В первой строке этой таблицы находится однострочное поле для ввода текста, которое вставлено в форму оператором <INPUT> и имеет тип TEXT:


<TD><INPUT TYPE=text NAME="text1" VALUE="Sample of text1" SIZE=30></TD>

Имя поля указано в параметре NAME как “text1”. В качестве начального значения для поля параметром VALUE задана строка "Sample of text1". Эту строку можно будет редактировать после отображения формы. Мы также указали размер поля, равный 30 символам, для чего воспользовались параметром SIZE.

Во второй строке таблицы также при помощи оператора <INPUT> определено поле для ввода пароля:


<TD><INPUT TYPE=password NAME="pwd" VALUE="Sample of password"></TD>

При вводе символов в этом поле они не отображаются. Аналогично, не отображается и начальная строка, использованная для инициализации поля.

Для ввода многострочного текста в третьей строке таблицы при помощи оператора <TEXTAREA> мы разместили область ввода текста:


<TD><TEXTAREA NAME="text2" ROWS=4 COLS=30>Sample of text</TEXTAREA></TD>

Имя этого поля задано как “text2”. Поле имеет высоту, равную четырем строкам (параметр ROWS равен 4), и ширину, равную 30 символам (параметр COLS равен 30).

В четвертой строке таблицы мы расположили группу из трех независимых переключателей типа CHECKBOX:


<TD>
  <INPUT TYPE=CHECKBOX NAME="chk1" VALUE="on" CHECKED>Первый<BR>
  <INPUT TYPE=CHECKBOX NAME="chk2" VALUE="on">Второй<BR>
  <INPUT TYPE=CHECKBOX NAME="chk3" VALUE="on" CHECKED>Третий<BR>
</TD>

Каждый из этих переключателей имеет собственное имя, заданное параметром NAME. Оператор VALUE задает значение, которое будет послано в сервер WWW при включении переключателя. Кстати, если переключатель выключен, он не посылает в сервер WWW никаких данных.

Ниже, в пятой строке, находится группа из трех переключателей типа RADIO с зависимой фиксацией:


<TD>
  <INPUT TYPE=RADIO NAME="rad" VALUE="on1" CHECKED>Первый<BR>
  <INPUT TYPE=RADIO NAME="rad" VALUE="on2">Второй<BR>
  <INPUT TYPE=RADIO NAME="rad" VALUE="on3">Третий<BR>
</TD>

Все переключатели, относящиеся к одной группе, должны называться одинаково. В нашем случае при помощи параметра NAME мы задали для всех трех переключателей имя “rad”.

Первый переключатель включен по умолчанию, так как для него задан параметр CHECKED.

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

Шестая строка таблицы содержит список, состоящий из трех строк. Этот список определен при помощи операторов <SELECT> и <OPTION>, как это показано ниже:


<TD>
  <SELECT NAME="sel" SIZE="1">
    <OPTION Value="First Option">First Option</OPTION>
    <OPTION Value="Second Option">Second Option</OPTION>
    <OPTION Value="None">None Selected</OPTION>
  </SELECT>
</TD>

Имя списка задано как “sel”, а высота его равна одной строке. Содержимое строк списка задается при помощи параметра VALUE соответствующих операторов <OPTION>.

Последняя строка таблицы содержит скрытый орган управления, который не отображается в окне навигатора:


<TD><INPUT TYPE=HIDDEN NAME="hid" VALUE="Hidden"></TD>

Он посылает серверу WWW строку “Hidden”, заданную в параметре VALUE.

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


<BR><INPUT TYPE=submit VALUE="Send">&nbsp;
<INPUT TYPE=reset VALUE="Reset">
<P><INPUT TYPE=IMAGE SRC="send.GIF" BORDER=0>

Кнопка типа SUBMIT имеет надпись “Send” и предназначена для посылки данных из формы в сервер WWW для обработки программой CGI.

Кнопка типа RESET предназначена для того, чтобы пользователь, изменив данные в форме, мог снова вернуться к значениям, заданным по умолчанию при помощи параметра VALUE в операторах определения органов управления. Эта кнопка имеет надпись “Reset”.

Последняя кнопка имеет тип IMAGE. Ее изображение находится в файле send.GIF, адрес URL которого (в нашем случае это просто имя файла) указан в параметре SRC. Для того чтобы вокруг изображения кнопки не было рамки, мы указали нулевое значение параметра BORDER.

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

Передача данных программе CGI

Когда пользователь заполняет форму и нажимает на кнопку типа SUBMIT либо на графическую кнопку (которая выполняет аналогичную функцию), данные из полей формы вместе с именами этих полей передаются навигатором серверу WWW. Сервер, в свою очередь, анализирует эти данные и запускает соответствующую программу CGI, путь к файлу которой указан в операторе <FORM>.

Перед запуском программы CGI сервер WWW выбирает в зависимости от значения параметра METHOD оператора <FORM> один из двух способов передачи полученных данных для обработки. Это методы GET и POST.

Метод GET

Метод GET предполагает передачу данных программе CGI через переменные среды (environment variables). Это те самые переменные среды, которые устанавливаются в операционной системе MS-DOS командой SET.

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

Прежде всего, метод GET предполагает использование переменной среды с именем QUERY_STRING. Именно сюда попадают данные из полей формы. Эти данные находятся в следующем формате:


"Имя1=Значение1&Имя2=Значение2&Имя3=Значение3"

Здесь в качестве имен используются значения параметров NAME, задающих имена полей формы. Вместо значений подставляются данные из соответствующих полей. Сканируя содержимое текстовой строки переменной среды QUERY_STRING, программа CGI может найти в ней имя любого нужного поля и соответствующее этому имени значение. Заметим, что никакие данные от выключенных переключателей не передаются, поэтому не следует думать, что в полученной строке вы обязательно встретите имена всех полей, расположенных в форме.

Адрес заданной строки переменной среды в программе, составленной на C, легко получить с помощью функции getenv:


char * szQueryString;
szQueryString = getenv("QUERY_STRING");

Заметим, что если вы собираетесь модифицировать строку переменной среды, то ее следует скопировать во внутренний буфер. Операционная система сервера WWW может не допустить прямого редактирования блока памяти, содержащего переменные среды.

Строка, передаваемая в переменной среды QUERY_STRING, закодирована с использованием так называемой кодировки URL. В этой кодировке все символы пробелов заменяются на символы “+”. Кроме того, для представления кодов управляющих и некоторых других символов используется последовательность символов вида “%xx”, где символы “xx” представляют собой шестнадцатеричный код символа в виде двух символов ASCII. В нашей книге вы найдете исходные тексты функций, предназначенные для перекодирования информации, полученной из формы.

Метод POST

При использовании метода POST программа CGI получает данные из формы через стандартный поток ввода STDIN. Если программа CGI составлена на языке программирования C, то для получения данных она может воспользоваться такими функциями, как fread или scanf.

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

Ниже мы привели фрагмент кода для определения размера информации для ввода через стандартный поток STDIN:


int Size;
Size = atoi(getenv("CONTENT_LENGTH"));

Входные данные могут быть затем получены, например, следующим образом:


char szBuf[8196];
fread(szBuf, Size, 1, stdin);

Разумеется, буфер для чтения данных можно заказывать и динамически, для чего следует воспользоваться такой функцией, как malloc.

Если в операторе <FORM> не указан параметр ENCTYPE (тип MIME передаваемых данных) или этот параметр имеет значение application/x-www-form-urlencoded, данные, полученные через стандартный поток ввода, закодированы в кодировке URL. Перед использованием вы должны их раскодировать соответствующим образом.

Что лучше - GET или POST

Метод GET обычно используется для обработки небольших форм, так как навигаторы накладывают ограничения для размера данных, передаваемых через переменную среды QUERY_STRING.

В этом отношении метод POST является более предпочтительным, так как не накладывает на размер передаваемых данных никаких ограничений. Только метод POST пригоден для передачи файлов из локального компьютера через навигатор в сервер WWW.

Передача ответа из программы CGI

Вне зависимости от использованного метода передачи данных (GET или POST) результат своей работы программа CGI должна направить в стандартный поток вывода STDOUT. Если программа составлена на языке программирования C, для записи результат работы она может воспользоваться, например, функцией printf или fwrite.

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


Content-type: text/html

Сразу за этой строкой необходимо вывести еще одну пустую строку, которая послужит разделителем между заголовком HTTP и данными документа HTML.

Ниже мы привели фрагмент кода, в котором программа CGI динамически формирует документ HTML и выводит его в стандартный поток вывода STDOUT:


printf("Content-type: text/html\n\n");
printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">");
printf("<HTML><HEAD><TITLE>XYZ Incorporation</TITLE></HEAD>"
  "<BODY BGCOLOR=#FFFFFF>");
printf("<H1>Результаты обработки формы</H1>");
. . .
printf("</BODY></HTML>");

Обратите внимание на символы перевода строки “\n\n”. Первый из них закрывает строку заголовка HTTP, а второй нужен для создания пустой разделительной строки.

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

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

Изучение переменных среды мы начнем с того, что просмотрим их значение с помощью простейшей программы CGI, созданной с использованием языка пакетных заданий операционной системы Microsoft Windows NT, а проще говоря, с помощью обыкновенного файла с расширением имени bat.

Для запуска этой “программы” мы подготовили форму, исходный текст которой представлен в листинге 7.2.

Листинг 7.2. Файл chap7\viewenv\viewenv.htm


<HTML>
<HEAD>
 <TITLE>Просмотр переменных среды</TITLE>
</HEAD>
<BODY BGCOLOR=#FFFFFF>
<H1>Просмотр переменных среды</H1>
<P>Для просмотра переменных среды, передаваемых программе
CGI, нажмите кнопку &quot;View&quot;
<FORM METHOD=GET ACTION="http://saturn/frolov-cgi/test.bat?param1">
<INPUT TYPE=SUBMIT VALUE="View">
</FORM>
</BODY>
</HTML>

Форма выглядит так, как это показано на рис. 7.3.

Рис. 7.3. Форма для запуска программы CGI просмотра значений переменных среды

Обратите внимание, что после имени программы CGI через разделительный символ “?” указана строка параметров param1, которая может быть получена и проанализирована программой CGI.

Исходный текст программы CGI приведен в листинге 7.3.

Листинг 7.3. Файл chap7\viewenv\test.bat


echo Content-type: text/plain
echo
echo %0 %1 %2 %3
set

В первой строке программа выводит в стандартный поток вывода STDOUT строку заголовка HTTP. Эта строка описывает тип передаваемых данных как text/plain, то есть обычный текст без оформления с использованием операторов HTML.

Вторая строка выводит в стандартный поток вывода пустую строку, которая отделяет заголовок HTTP от передаваемых данных.

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

И, наконец, в последней строке вызывается команда SET, которая выводит в поток STDOUT значения всех переменных среды, определенных в системе перед запуском программы.

Результат работы нашей программы CGI показан на рис. 7.4.

Рис. 7.4. Результат работы программы CGI, отображающей значения переменных среды

Ниже мы привели полный листинг, полученный при работе нашей программы CGI:


g:\iisdir\wwwroot\guest>echo
ECHO is on.

g:\iisdir\wwwroot\guest>echo g:\iisdir\wwwroot\guest\test.bat param1?   
g:\iisdir\wwwroot\guest\test.bat param1?  

g:\iisdir\wwwroot\guest>set
AUTH_TYPE=
ComSpec=D:\WINNT35\system32\cmd.exe
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
HTTP_REFERER=file:C:\!websrv\Sample\Chap7\viewenv\viewenv.HTM
HTTP_ACCEPT_LANGUAGE=, en
HTTP_UA_PIXELS=1024x768
HTTP_UA_COLOR=color16
HTTP_UA_OS=Windows 95
HTTP_UA_CPU=x86
HTTP_USER_AGENT=Mozilla/2.0 (compatible; MSIE 3.0; Windows 95)
HTTP_HOST=frolov
HTTP_CONNECTION=Keep-Alive
CONTENT_LENGTH=0
CONTENT_TYPE=
PATH=D:\WINNT35\system32;D:\WINNT35;
PATH_INFO=/frolov-cgi/test.bat
PATH_TRANSLATED=g:\iisdir\wwwroot\guest\test.bat
PROMPT=$P$G
QUERY_STRING=param1?
REMOTE_ADDR=154.101.200.255
REMOTE_HOST=154.101.200.255
REMOTE_USER=
REQUEST_METHOD=GET
SCRIPT_NAME=/frolov-cgi/test.bat
SERVER_NAME=154.100.100.10
SERVER_PROTOCOL=HTTP/1.0
SERVER_PORT=80
SERVER_SOFTWARE=Microsoft-Internet-Information-Server/1.0
SystemRoot=D:\WINNT35
windir=D:\WINNT35

В этом листинге отображаются переменные среды, определенные специально для программы CGI сервером Microsoft Internet Information Server, а также переменные среды, стандартные для операционной системы Microsoft Windows NT.

Рассмотрим по отдельности назначение переменных среды. Заметим, что набор переменных, создаваемых при запуске программы CGI, зависит от конкретной реализации сервера WWW.

Технология WWW допускает защиту страниц HTML, когда доступ к отдельным страницам предоставляется только для отдельных пользователей при предъявлении пароля. При этом используется так называемая система аутентификации, или проверки подлинности идентификатора пользователя.

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

В этой переменной находится версия интерфейса CGI, с которой работает данный сервер. В нашем случае интерфейс имеет версию 1.1.

В этой переменной перечислены типы данных MIME, которые могут быть приняты навигатором от сервера WWW. Из приведенного выше листинга видно, что сервер Microsoft Internet Information Server может передать навигатору Microsoft Internet Explorer (который был использован для работы с программой CGI) графические изображения (image) в формате gif, jpeg, pjpeg, x-xbitmap. Подробно эти типы данных описаны в спецификации протокола MIME, рассмотрение которой выходит за рамки нашей книги.

В переменную HTTP_REFER записывается адрес URL документа HTML, который инициировал работу программы CGI. В нашем случае этот документ был записан на локальном диске компьютера в каталоге C:\!websrv\Sample\Chap7\viewenv.

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

Разрешение видеоадаптера, установленное в компьютере пользователя.

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

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

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

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

Имя узла, на котором работает сервер WWW.

Тип соединения.

Метод кодирования, который может быть использован навигатором для формирования ответа серверу WWW.

Информация авторизации от навигатора. Используется навигатором для собственной аутентификации в сервере WWW.

Имя пользователя в виде, как оно было зарегистрировано при настройке навигатора. Используется формат адресов электронной почты.

Специальные команды серверу WWW.

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

Тип данных, присланных навигатором.

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

Как правило, при настройке сервера WWW администратор выделяет один или несколько каталогов для хранения расширений сервера в виде программ CGI или ISAPI. Для файлов, записанных в такие каталоги, устанавливается доступ на запуск.

Администратор создает таблицу соответствия физических каталогов и виртуальных, определяя права доступа к виртуальным каталогам с помощью программы настройки параметров сервера WWW.

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

Строка параметров, указанная в форме после адреса URL программы CGI после разделительного символа “?”.

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

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

Имя пользователя, которое используется навигатором для аутентификации. Используется только в том случае, если сервер WWW способен работать с аутентификацией и программа CGI отмечена как защищенная.

Метод доступа, который используется для передачи данных от навигатора серверу WWW. В своих примерах мы используем методы доступа GET и POST, хотя протокол HTTP допускает применение и других методов доступа, например, PUT и HEAD.

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

Доменное имя сервера WWW или адрес IP сервера WWW, если доменное имя недоступно или не определено.

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

Номер порта, на котором навигатор посылает запросы серверу WWW.

Название и версия программного обеспечения сервера WWW. Версия следует после названия и отделяется от последнего символом “/”.

Имя, с которым пользователь подключился к серверу WWW. Используется только в том случае, если сервер WWW способен подключать пользователей по именам.

Примеры программ CGI

В этом разделе мы приведем примеры несложных программ CGI, демонстрирующих динамическое создание документов HTML и обработку данных, введенных при помощи форм.

Программа CGIHELLO

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

Эта программа хороша для проверки возможности запуска программ CGI на вашем сервере WWW или на сервере вашего поставщика услуг Internet. Так как она очень проста, существует немного причин, по которым она могла бы не работать. Это неправильная настройка прав доступа к виртуальному каталогу, содержащему загрузочный модуль программы CGI, а также неправильная ссылка на этот каталог в параметре ACTION оператора <FORM>.

Исходный текст документа, в котором определена форма, представлен в листинге 7.4.

Листинг 7.4. Файл chap7\cgihello\cgihello.htm


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
  <HEAD>
    <TITLE>CGI Script Test</TITLE>
  </HEAD>
  <BODY BGCOLOR=#FFFFFF>
    <H1>Запуск программы CGI</H1>
    <FORM METHOD=GET ACTION="http://frolov/frolov-cgi/cgihello.exe">
      <INPUT TYPE=submit VALUE="Send">
    </FORM>
  </BODY>
</HTML>

В этом документе определена форма, содержащая единственную кнопку, созданную оператором <INPUT> и имеющую тип SUBMIT.

В параметре ACTION оператора <FORM> мы указали путь к программе CGI, причем этот путь является виртуальным. Для передачи данных используется метод GET.

Внешний вид формы при ее просмотре навигатором Microsoft Internet Explorer представлен на рис. 7.5.

Рис. 7.5. Форма для запуска программы CGIHELLO

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

Рис. 7.6. Документ HTML, создаваемый динамически программой CGGIHELLO

Рассмотрим исходный текст программы CGIHELLO (листинг 7.5).

Листинг 7.5. Файл chap7\cgihello\cgihello.c


#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>"
     "XYZ Incorporation</TITLE></HEAD><BODY>");

  printf("<H1>Результат работы программы CGI</H1>");
  printf("<P>Эта страница создана динамически в результате"
    " работы программы CGI");
  printf("</BODY></HTML>");
}

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

В первый раз функция printf выводит заголовок HTTP и пустую строку-разделитель. Далее программа CGIHELLO записывает построчно в стандартный поток вывода STDOUT текст документа HTML.

Программа CONTROLS

Более сложная программа CGI называется CONTROLS и выполняет обработку данных, полученных из формы, которая была показана на рис. 7.2. Исходный текст документа HTML с этой формой представлен в листинге 7.1.

Программа CONTROLS отображает в динамически формируемом документе HTML метод, использованный для передачи (POST или GET), размер и тип данных, поступающих от формы. Принятые данные показываются как в исходном виде, так и после перекодировки. Кроме того, в документе HTML располагается список значений всех полей, определенных в форме (рис. 7.7).

Рис. 7.7. Фрагмент документа, сформированного динамически программой CONTROLS

Из рисунка видно, что навигатор прислал серверу WWW 135 байт информации. Так как при этом был использован метод POST, данные были направлены в стандартный поток ввода INPUT. Данные закодированы в кодировке URL, так как содержимое переменой среды CONTENT_TYPE равно application/x-www-form-urlencoded.

Обратите внимание на текстовое поле с именем text1. Все пробелы в соответствующей строке в кодировке URL заменены на символ “+”. Что же касается символов “&” и “,”, то они пришли в виде %26 и %2C. Функция перекодирования возвращает строку в исходный вид - “Sample of text1 &,.”.

Форма, показанная на рис. 7.2, имеет две кнопки, предназначенные для передачи данных серверу WWW. Это обычная кнопка и кнопка в виде графического изображения. Мы нажали графическую кнопку, поэтому от формы пришла информация о координатах курсора мыши в виде переменных с именами x и y.

Рассмотрим исходный текст программы CONTROLS (листинг 7.6).

Листинг 7.6. Файл chap7\controls\controls.c


// ===============================================
// Программа CGI controls.c
// Демонстрирует методы получения и обработки
// данных от форм, расположенных в документах HTML
//
// (C) Фролов А.В., 1997
// E-mail: frolov@glas.apc.org
// WWW:    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);

// ------------------------------------------------
// Функция main
// Точка входа программы CGI
// ------------------------------------------------
void main(int argc, char *argv[])
{
  int lSize;
  FILE * fileReceived;
  char * szMethod;
  char * szQueryString;
  char szBuf[8196];
  char szSrcBuf[8196];
  char * szPtr;
  char * szParam;

  // Вывод заголовка HTTP и разделительной строки
  printf("Content-type: text/html\n\n");

  // Вывод начального форагмента документа HTML,
  // формируемого динамически
  printf("<!DOCTYPE HTML PUBLIC"
    " \"-//W3C//DTD HTML 3.2//EN\">");
  printf("<HTML><HEAD><TITLE>XYZ Incorporation"
    "</TITLE></HEAD><BODY BGCOLOR=#FFFFFF>");

  // Определяем метод передачи данных
  szMethod = getenv("REQUEST_METHOD");

  // Обработка метода POST
  if(!strcmp(szMethod, "POST"))
  {
    // Определяем размер данных, полученных от навигатора
    // при передаче данных из полей формы
    lSize = atoi(getenv("CONTENT_LENGTH"));

    // Читаем эти данные в буфер szBuf из
    // стандартного потока ввода STDIN
    fread(szBuf, lSize, 1, stdin);

    // Создаем файл, в который будут записаны
    // принятые данные
    fileReceived = fopen("received.dat", "w");

    // Выполняем запись принятых данных
    fwrite(szBuf, lSize, 1, fileReceived);

    // Закрываем файл принятых данных
    fclose(fileReceived);

    // Отображаем значения некоторых переменных среды
    printf("<H2>Переменные среды</H2>");
    
    // Метод доступа
    printf("REQUEST_METHOD = %s", getenv("REQUEST_METHOD"));

    // Размер полученных данных в байтах
    printf("<BR>CONTENT_LENGTH = %ld", lSize);

    // Тип полученных данных
    printf("<BR>CONTENT_TYPE = %s", getenv("CONTENT_TYPE"));

    // Закрываем буфер данных двоичным нулем, 
    // превращая его таким образом в строку
    szBuf[lSize] = '\0';

    // Делаем копию принятых данных в буфер szSrcBuf
    strcpy(szSrcBuf, szBuf);

    // Отображаем принятые данные без обработки
    printf("<H2>Принятые данные</H2>");
    printf("<P>%s", szSrcBuf);

    // Выполняем перекодировку принятых данных
    DecodeStr(szSrcBuf);

    // Отображаем результат перекодировки
    printf("<H2>Данные после перекодировки</H2>");
    printf("<P>%s", szSrcBuf);

    // Выводим список значений полей формы
    printf("<H2>Список значений полей</H2>");
    
    // Дописываем в конец буфера принятых данных
    // символ "&", который используется в качестве
    // разделителя значений полей
    szBuf[lSize] = '&';
    szBuf[lSize + 1] = '\0';

    // Цикл по полям формы
    for(szParam = szBuf;;)
    {
      // Ищем очередной разделитель
      szPtr = strchr(szParam, '&');
      
      // Если он найден, раскодируем строку параметров
      if(szPtr != NULL)
      {
        *szPtr = '\0';
        DecodeStr(szParam);
      
        // Выводим в документ значение параметра
        printf("%s<BR>", szParam);

        // Переходим к следующему параметру
        szParam = szPtr + 1;

        // Если достигнут конец буфера, завершаем цикл
        if(szParam >= (szBuf + lSize))
          break;
      }
      else
        break;
    }
    
    // Выводим завершающий фрагмент документа HTML
    printf("</BODY></HTML>");
    return;
  }
  
  // Обработка метода GET
  else if(!strcmp(szMethod, "GET"))
  {
    // Получаем данные, полученные от формы.
    // При использовании метода GET эти данные 
    // передаются в переменной среды QUERY_STRING
    szQueryString = getenv("QUERY_STRING");
    
    // Записываем эти данные в выходной файл
    fileReceived = fopen("received.dat", "w");
    fwrite(szQueryString, strlen(szQueryString) + 1, 
       1, fileReceived);
    fclose(fileReceived);
    
    // Выводим в динамически формируемый документ HTML
    // значения некоторых переменных среды
    printf("<H2>Переменные среды</H2>");

    // Метод передачи данных
    printf("REQUEST_METHOD = %s", getenv("REQUEST_METHOD"));

    // Полученные данные
    printf("<BR>QUERY_STRING = %s", szQueryString);

    // Копируем принятые данные в буфер szSrcBuf
    strcpy(szSrcBuf, szQueryString);

    // Отображаем принятые данные
    printf("<H2>Принятые данные</H2>");
    printf("<P>%s", szSrcBuf);
    
    // Перекодируем данные и отображаем результат
    // перекодировки
    DecodeStr(szSrcBuf);

    printf("<H2>Данные после перекодировки</H2>");
    printf("<P>%s", szSrcBuf);

    // Выводим в документ список значений полей формы
    strcpy(szBuf, szQueryString);
    printf("<H2>Список значений полей</H2>");

    szBuf[strlen(szBuf)] = '&';
    szBuf[strlen(szBuf) + 1] = '\0';

    for(szParam = szBuf;;)
    {
      szPtr = strchr(szParam, '&');
      if(szPtr != NULL)
      {
        *szPtr = '\0';
        DecodeStr(szParam);
        printf("%s<BR>", szParam);

        szParam = szPtr + 1;
        if(szParam >= (szBuf + lSize))
          break;
      }
      else
        break;
    }

    printf("</BODY></HTML>");
  }
}

// ------------------------------------------------
// Функция DecodeStr
// Раскодирование строки из кодировки URL
// ------------------------------------------------
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;
    
    // Обработка шестнадцатеричных кодов вида "%xx"
    if(ch == '%')
    {
      // Выполняем преобразование строки "%xx"
      // в код символа
      szString[dst] = DecodeHex(&szString[src + 1]);
      src += 2;
    }
  }
  
  // Закрываем строку двоичным нулем
  szString[dst] = '\0';
}

// ------------------------------------------------
// Функция DecodeHex
// Раскодирование строки "%xx"
// ------------------------------------------------
char DecodeHex(char *str)
{
  char ch;

  // Обрабатываем старший разряд
  if(str[0] >= 'A')
    ch = ((str[0] & 0xdf) - 'A') + 10;
  else
    ch = str[0] - '0';

  // Сдвигаем его влево на 4 бита
  ch <<= 4;

  // Обрабатываем младший разряд и складываем
  // его со старшим
  if(str[1] >= 'A')
    ch += ((str[1] & 0xdf) - 'A') + 10;
  else
    ch += str[1] - '0';

  // Возвращаем результат перекодировки
  return ch;
}

Функция main программы CONTROLS вначале выводит в стандартный поток вывода STDOUT заголовок HTTP и начальные строки динамически формируемого документа HTML. Для вывода мы использовали функцию printf.

Далее функция main определяет использованный метод передачи данных, анализируя содержимое переменной среды REQUEST_METHOD. Это необходимо, так как при разных методах передачи необходимо использовать различные методы получения входных данных. Значение переменной среды программа получает при помощи функции getenv.

Если данные передаются методом POST, программа будет считывать их из стандартного потока ввода STDIN. Размер данных находится в переменной среды CONTENT_LENGTH. Соответствующая текстовая строка получается функцией getenv и преобразуется в численное значение функцией atoi.

Чтение данных из входного потока выполняется за один вызов функции fread:


fread(szBuf, lSize, 1, stdin);

Этой функции мы передаем адрес буфера для записи принятых данных, размер данных, количество буферов, которые нужно считать, и входной поток.

Программа CGI может сохранить принятые данные в файле для дальнейшей обработки. Наша программа создает в текущем каталоге файл с названием received.dat:


fileReceived = fopen("received.dat", "w");
fwrite(szBuf, lSize, 1, fileReceived);
fclose(fileReceived);

Текущим каталогом при запуске этой программы в среде сервера Microsoft Information Server будет каталог с загрузочным модулем программы CONTROLS. Заметим, что для того чтобы программа CGI могла создать файл в каталоге, необходимо соответствующим образом настроить права доступа. В последней главе нашей книги вы найдете информацию о том, как это можно сделать для сервера Microsoft Information Server.

После сохранения принятых данных в файле программа CONTROLS выводит в стандартный поток вывода содержимое некоторых переменных среды: REQUEST_METHOD, CONTENT_LENGTH и CONTENT_TYPE.

Далее наша программа выполняет перекодировку полученных данных из кодировки URL. Перед этим принятые данные копируются в буфер, где они будут обновляться по месту.

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

Перекодировка выполняется функцией DecodeStr,, определенной в нашем приложении. Эту функцию мы рассмотрим позже.

После перекодирования результат будет находиться в буфере szSrcBuf, откуда он и берется для отображения.

На завершающем этапе обработки данных, полученных от формы, программа CONTROLS записывает в выходной документ HTML значения отдельных полей. Напомним, что эти значения имеют формат &<Имя_поля>=<Значение>, при этом символ “&” используется как разделитель.

Наша программа закрывает исходный буфер с принятыми данными дополнительным символом “&” (для простоты сканирования), после чего запускает цикл по полям формы.

В этом цикле во входной строке с помощью функции strchr ищется символ разделитель. Если этот символ найден, он заменяется на двоичный нуль, после чего полученная текстовая строка значения параметра перекодируется функцией DecodeStr и выводится в выходной поток STDOUT.

Цикл завершается тогда, когда в процессе сканирования указатель текущей позиции выходит за границы буфера данных.

В конце программа CONTROLS закрывает документ HTML, записывая в него команды </BODY> и </HTML>.

Если данные передаются в программу CONTROLS методом GET, входные данные находятся в переменной среды QUERY_STRING, которую мы получаем следующим образом:


szQueryString = getenv("QUERY_STRING");

Обработка принятых данных выполняется аналогично тому, как это делается при методе POST. Разница заключается лишь в том, что в документе мы отображаем содержимое только переменных REQUEST_METHOD и QUERY_STRING.

Теперь займемся перекодировкой принятых данных, которая выполняется функцией DecodeStr.

Эта функция сканирует входную строку, заменяя символы “+” на пробелы. Если в перекодируемой строке встречается комбинация символов вида "%xx", она заменяются на однобайтовый код соответствующего символа с помощью функции DecodeHex.

Функция DecodeHex комбинирует значение кода символа из старшего и младшего разряда преобразуемой комбинации символов.

Программа AREF

До сих пор в наших примерах мы использовали программы CGI только для обработки данных из полей форм, указывая адрес URL загрузочного файла программы в параметре ACTION оператора <FORM>. Однако есть и другая возможность вызова программ CGI: вы можете указать их адрес в параметре HREF оператора ссылки <A>. В этом случае вы можете передать программе CGI параметры, указав их после имени файла загрузочного модуля через разделительный символ “?”. Программа получит строку параметров методом GET и сможет извлечь ее из переменной среды с именем QUERY_STRING.

Пример документа HTML, в котором демонстрируется вызов программы CGI указанным выше способом, приведен в листинге 7.7.

Листинг 7.7. Файл chap7\aref\aref.htm


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
  <HEAD>
    <TITLE>Ссылки на документы HTML</TITLE>
  </HEAD>
  <BODY BGCOLOR=#FFFFFF>

    <A HREF="http://frolov/frolov-cgi/aref.exe?page1">Домашняя страница</A><BR>
    <A HREF="http://frolov/frolov-cgi/aref.exe?page2">Книги</A><BR>
    <A HREF="http://frolov/frolov-cgi/aref.exe?page3">Статьи</A><BR>

  </BODY>
</HTML>

В этом документе есть три ссылки на программу CGI с именем aref.exe, причем каждый раз ей передаются разные параметры. Внешне документ выглядит так, как это показано на рис. 7.8.

Рис. 7.8. Документ HTML с ссылками на программу CGI

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

Рис. 7.9. Документ, который отображается программой CGI при выборе строки “Статьи”

Исходный текст программы AREF достаточно прост и приведен в листинге 7.8.

Листинг 7.8. Файл chap7\aref\aref.c


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

void main(int argc, char *argv[])
{
  char * szQueryString;

  szQueryString = getenv("QUERY_STRING");

  if(!strcmp(szQueryString, "page1"))
    printf("Location: home.htm\n\n");
  
  else if(!strcmp(szQueryString, "page2"))
    printf("Location: books.htm\n\n");
  
  else if(!strcmp(szQueryString, "page3"))
    printf("Location: capital.htm\n\n");

  else
    printf("Location: error.htm\n\n");
}

Программа получает значение переменной среды QUERY_STRING, пользуясь для этого функцией getenv. Далее она сравнивает значение параметра со строками “page1”, “page2” и “page3”. При совпадении программа возвращает навигатору адрес URL соответствующего документа HTML, формируя заголовок HTTP специального вида:


Location: <Адрес URL документа HTML или графического изображения>\n\n

Когда навигатор получает от сервера WWW такой заголовок, он отображает в своем окне документ или файл графического изображения, адрес URL которого указан в заголовке.

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

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

Программа COUNTER

Почти на каждом сервере WWW в сети Internet вы можете встретить счетчик посещений. По его показаниям можно судить о посещаемости сервера, что имеет, например, значение при выборе сервера для размещения рекламы.

Существуют различные методы создания счетчиков, доступность которых во многом определяется программным обеспечением и настройкой сервера WWW, а также доброй волей поставщика услуг Internet (если ваш сервер WWW виртуальный и физически расположен у поставщика). В нашей книге мы расскажем о том, как сделать счетчик посещений с помощью программы CGI и расширения ISAPI (в следующей главе).

Самый простой способ создания счетчика заключается в следующем. Те документы HTML, на которых необходимо разместить счетчик, преобразуется в файл шаблона. В этом файле в том месте, где должно располагаться текстовое значение счетчика, необходимо поместить последовательность заранее определенных символов, например, символы “ххххх” или “~~~~~”.

Пример такого файла шаблона вы можете найти в листинге 7.9.

Листинг 7.9. Файл chap7\counter\home.tm


<HTML>
  <BODY BGCOLOR="#FFFFFF">
    <H1>Главная страница фирмы XYZ Inc.</H1>
    <P>Добро пожаловать на нашу главную страницу!
    <HR>
    <P>Вы посетитель номер <B>~~~~~</B> с 1 января 1913 года
  </BODY>
</HTML>

Мы назвали файл home.tm, хотя вы можете выбирать для файлов шаблона любые имена.

Далее вам нужно сделать ссылку на документ, содержащий счетчик. Такая ссылка указывает на программу CGI, выполняющую подсчет посещений. В листинге 7.10 ссылка на программу счетчика выполнена с использованием оператора <A>.

Листинг 7.10. Файл chap7\counter\default.htm


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
  <HEAD>
    <TITLE>Счетчик посещений</TITLE>
  </HEAD>
  <BODY BGCOLOR=#FFFFFF>
    <H1>Посетите нашу главную страницу</H1>
    <A HREF="http://frolov/frolov-cgi/counter.exe?">Главная страница</A><BR>
  </BODY>
</HTML>

Внешний вид этого документа показан на рис. 7.10.

Рис. 7.10. Документ, который содержит ссылку на программу CGI, отображающую главную страницу

Обратите внимание, что вслед за именем программы мы разместили разделительный символ “?”, после которого вы можете указывать дополнительные параметры, например, номер счетчика. Это может пригодиться, если ваша программа CGI должна выполнять отдельный подсчет посещений для различных документов HTML.

Получив управление, программа счетчика может считать файл шаблона в оперативную память, найти там поле счетчика и заменить его на текущее количество посещений.

А как подсчитывать посещения?

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

После того как текущее значение счетчика посещений будет записано в соответствующее поле шаблона, программа счетчика может вывести шаблон в стандартный поток вывода STDOUT.

Документ HTML, полученный таким образом из нашего шаблона, показан на рис. 7.11.

Рис. 7.11. Документ HTML со счетчиком, созданный динамически на базе файла шаблона

Исходный текст программы COUNTER, работающей с использованием описанного выше алгоритма, приведен в листинге 7.11.

Листинг 7.11. Файл chap7\counter\counter.c


// ===============================================
// Программа CGI counter.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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>

void main(int argc, char *argv[])
{
  // Идентификатор файла шаблона главной страницы
  HFILE hSrcFile;

  // Идентификатор файла счетчика
  FILE *CounterFile;

  // Размер файла шаблона
  DWORD dwFileSize;

  // Адрес шаблона главной страницы
  LPSTR szTemplate;

  // Временный буфер для работы со счетчиком
  CHAR  szBuf[10];

  // Текущее значение счетчика
  INT   nCounter;

  // Указатель на поле счетчика в шаблоне
  LPSTR szCounterPtr;

  // -----------------------------------------------
  // Считываем файл шаблона в оперативную память
  // -----------------------------------------------
  
  // Открываем файл шаблона
  hSrcFile = _lopen("HOME.TM", OF_READ);
  
  // Определяем размер файла в байтах
  dwFileSize = _llseek(hSrcFile, 0, 2);

  // Устанавливаем указатель текущей позиции
  // на начало файла шаблона
  _llseek(hSrcFile, 0, 0);

  // Получаем память для буфера шаблона
  szTemplate = malloc(dwFileSize);

  // Загружаем шаблон в буфер
  _hread(hSrcFile, szTemplate, dwFileSize);
    
  // -----------------------------------------------
  // Увеличиваем значение счетчика в файле
  // -----------------------------------------------
  
  // Открываем файл счетчика для чтения
  CounterFile = fopen("CNTDAT.DAT", "r");

  // Читаем из файла строку значения счетчика
  fgets(szBuf, 7, CounterFile);

  // Закрываем файл счетчика
  fclose(CounterFile);
  
  // Преобразуем значение счетчика из текстовой
  // строки в численную величину
  sscanf(szBuf, "%d", &nCounter);
  
  // Увеличиваем значение счетчика
  nCounter++;
  
  // Записываем в буфер szBuf пять цифр нового
  // значения счетчика
  sprintf(szBuf, "%05.5ld", nCounter);

  // Сохраняем новое значение счетчика в файле
  CounterFile = fopen("CNTDAT.DAT", "w");
  fprintf(CounterFile, "%d", nCounter);
  fclose(CounterFile);

  // -----------------------------------------------
  // Заменяем 5 цифр значения счетчика на новые
  // в буфере шаблона
  // -----------------------------------------------

  // Ищем маркер поля счетчика
  szCounterPtr = strstr(szTemplate, "~~~~~");
  
  // Копируем в это поле новое значение счетчика
  if(szCounterPtr != NULL)
    strncpy(szCounterPtr, szBuf, 5);
  
  // Выводим заголовок HTTP и разделительную строку
  printf("Content-type: text/html\n\n");

  // Выводим шаблон с измененным значением
  // поля счетчика
  fwrite(szTemplate, dwFileSize, 1, stdout);

  // Освобождаем буфер шаблона    
  free(szTemplate);
}

Получив управление, программа COUNTER считывает файл шаблона в оперативную память. Для упрощения исходного текста программы мы применили для работы с файлом функции _lopen, _llseek и _hread, которые являются специфичными для операционной системы Microsoft Windows. Вы можете использовать здесь любые другие функции, предназначенные для работы с файлами.

Перед чтением файла шаблона мы определяем его длину в байтах, для чего используем известный прием с установкой текущей позиции на конец файла функцией _llseek. После того как размер файла будет сохранен в переменной dwFileSize, текущая позиция снова устанавливается на начало файла шаблона. Вслед за этим программа CGI динамически заказывает память для буфера и читает в этот буфер файл шаблона за один вызов функции _hread.

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

Далее новое значение счетчика сохраняется в файле счетчика и записывается в символьном виде в буфер szBuf, откуда будет выполняться вставка нового значения счетчика в шаблон документа.

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

После обновления шаблона в стандартный поток вывода записывается обычный заголовок HTTP (функцией printf) и шаблон (функцией fwrite). На последнем этапе освобождается буфер памяти, заказанный для буфера шаблона.