ГЛАВА 9


Технология Common Gateway Interface — создаем серверные программы

 

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

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

 

Что такое CGI?

Аббревиатура CGI, от английского Common Gateway Interface, дословно переводится как "стандартный интерфейс обмена данных". Это понятие объединяет в себе набор технологических принципов, на основании которых происходит взаимодействие Web-сервера и внешних, по отношению к его программной среде, модулей. Технология CGI используется абсолютным большинством производителей серверов, поскольку она унифицирует процессы обмена данными и позволяет создавать серверные приложения, независимо от типа сервера, на котором предполагается их внедрение.

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

 

С чего начинать?

Для изучения технологии CGI лучше с самого начала применять получаемые знания на практике. Мне известны случаи, когда люди в течение года занимались освоением этой технологии, несмотря на то, что она, по своей сути, очень проста. Поэтому, чтобы ее изучение шло легко, давайте вместе, шаг за шагом, постигать сначала простые, а затем более сложные вещи. В этом вам поможет дискета с примерами, где есть исходные тексты программ и Web-страниц. Ее описание подробно изложено в приложении 3.

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

 

Рабочее место

Программное обеспечение Web-программиста, собирающегося создавать серверные модули в среде Delphi, должно включать прежде всего саму среду Delphi, причем не имеет значения, какой версии (желательно, 2.0 или новее, поскольку в первой версии язык Object Pascal имеет иной синтаксис). На данном этапе мы не будем использовать никаких визуальных средств, поэтому программы можно компилировать даже в режиме командной строки, но удобнее, конечно, делать программы в стандартной оболочке.

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

 

Настройка Apache Server для работы

Если у вас еще нет Web-сервера Apache, то его последнюю версию всегда можно загрузить с сайта www.apache.org.При инсталляции последних вариантов дистрибутивов пользователю придется обращаться к конфигурационным файлам только при необходимости изменения специфичных настроек, а название Web-сервера и домена, которые должен обслуживать Apache устанавливаются уже в процессе инсталляции. Все примеры этой и следующей главы рассчитаны на присвоение имени localhost ресурсу, обслуживаемому Apache, информационное наполнение которого мы будем создавать.

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

 

Настройка Delphi

Для удобства отладки серверных модулей, рекомендуем еще в начале работы по его созданию, установить путь для выходных (ехе) файлов, каталог cgi-bin сервера. Для этого запустите Delphi и зайдите на вкладку Project | Options | Directories/Conditionalsи в поле Output Directoryвпишите путь C:\Program Files\Apache Group\Apache\cgi-bin. Это позволит в дальнейшем моментально анализировать результаты работы.

Для создания Web-страниц потребуется любой простейший текстовый редактор.

 

Страница "Hello, World!"

Так уж принято в книгах по программированию, что первая программа всегда представляет собой радостное восклицание — здравствуй мир! Создадим серверный модуль, который будет генерировать Web-страницу с этим слоганом.

В своей работе создаваемый модуль будет использовать лишь консоль для обмена данными с Web-сервером. Это значит, что никаких элементов графического интерфейса использовать не нужно, а поток данных посылается на сервер стандартными процедурами write и writein.

Итак, предположим, что наш проект будет называться hello. После запуска Delphi создадим консольное приложение, используя пункты меню File | New | Console Application. Давайте сохраним его под именем hello.dpr в каталоге cgi-bin сервера или любом другом, но при использовании другого каталога желательно проделать описанную выше операцию переопределения выходного каталога.

Наше простейшее приложение не будет обрабатывать данные клиента и будет содержать код, приведенный в листинге 9.1.

Листинг 9.1. Исходный код серверного модуля Hello 

program hello;

  {$APPTYPE CONSOLE} 

begin

writeln('Content-Type: text/html');

writeln;

writeln('<HTML>');

writeln('<ТIТLЕ>Это динамически сгенерированная страница</ТIТLЕ>');

writeln('<Hl>Hello World!</Н1>');

writeln('</HTML>');

  end.

Скомпилируем приложение и сгенерируем ЕХЕ-файл, используя пункты меню Project | Build.Если код введен верно, то в папке cgi-bin должен появиться файл hello.exe. Теперь запустим браузер и наберем адрес http:// localhost/cgi-bin/hello.exe,который указывает путь к созданному файлу.

Замечание 

В случае, если имя вашего компьютера отлично от localhost, то нужно заменить это слово на имя компьютера.

После нажатия клавиши <Enter> в окне браузера должна появиться страница, приведенная на рис. 9.1.

Рис. 9.1.Динамически сгенерированная страница Она имеет HTML-код, приведенный в листинге 9.2.

 Листинг 9.2. HTML-код полученной страницы ;

<HTML>

<ТIТLЕ>Это динамически сгенерированная страница</ТIТLЕ>

<Hl>Hello World!</Н1>

</HTML>

Рассмотрим исходный код программы (листинг 9.1) подробнее. Строка {$APPTYPE CONSOLE) означает, что программа должна работать как консольное приложение. Такой тип программ работает подобно обычным программам, написанным для работы в текстовом режиме ДОС, но может использовать реестр, системную оболочку Windows, драйверы баз данных и т. д. Как отмечалось ранее, все серверные модули, использующие CGI, работают только в консольном виде. Оператор

writeln('Content-Type: text/html')

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

Очевидно, что последующие операторы выводят код страницы, что легко увидеть в листинге 9.2.

Разумеется, что получаемая страница всегда будет иметь одно и то же содержание, т.е. будет статична, однако генерируется она динамическими средствами: каждый раз, при обращении по вышеуказанному адресу, будет запускаться модуль hello.exe, и результаты его работы будут выведены на экран клиента.

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

 

Получение данных от клиента

Существует несколько способов передачи содержимого форм Web-страниц клиента серверному модулю, но наиболее распространенными являются POST и GET. Оба этих метода используют переменные окружения. Переменные окружения это набор именованных данных, хранящихся в зарезервированной области памяти. Если вы когда-нибудь настраивали файл autoexec.bat, то наверняка помните, что для установки, например, путей PATH к файлам использовалась процедура SET, как раз и работающая с этими переменными.

 

Работа с переменными окружения

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

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

Таблица 9.1.Переменные окружения серверного модуля

Имя переменной

Краткое описание

REQUEST_METHOD

Метод запроса (POST или GET)

SERVER_PROTOCOL

Протокол, используемый сервером (как правило, HTTP 1.0)

URL

URL, запрашиваемый у сервера

QUERY_STRING

Строка запроса в методе GET

PATH_INFO

Дополнительный путь, содержащийся в URL, после названия модуля

PATH_TRANSLATED

Информация о физическом расположении серверного модуля на диске сервера

CONTENT_LENGTH

Количество символов в данных, передаваемых стандартным потоком ввода при использовании метода POST

CONTENT_TYPE

Тип содержимого запроса. Это могут быть несколько MIME-типов, которые будут рассмотрены позже

GATEWAY_INTERFACE

Тип протокола CGI, используемого сервером

REMOTE_ADDR

IP-адрес клиента

REMOTE_ HOST

Если в системе DNS этому адресу сопоставлено доменное имя, то данная переменная обеспечивает доступ к нему

SCRIPT NAME

Название скрипта

SCRIPT_FILENAME

Имя файла скрипта, указанного в запросе

SERVER _NAME

DNS-имя сервера

SERVER_PORT

Порт сервера, который обслуживает протокол HTTP

SERVER _SOFTWARE

Название программы — Web-сервера

AUTH_TYPE

Тип аутентификации (если таковая определена для доступа пользователей к документам)

REMOTE_USER

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

HTTP_CACHE_CONTROL

Информация о поддержке управления кэша Web-сервера

HTTP_ DATE

Дата и время запроса

HTTP_REFERER

Адрес страницы, с которой перешел пользователь

HTTP_ACCEPT

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

HTTP_FROM

Аналогично REMOTE_ ADDR

HTTP_HOST

Аналогично REMOTE_HOST

HTTP_COOKIE

Содержимое поля cookie в строке запроса

HTTP_AUTHORIZATION

Аналогично AUTH _TYPE

HTTP_CONNECTION

Обеспечивает возможность удерживать долговременное HTTP-соединение с клиентом, что позволяет не запускать серверный скрипт для каждого запроса данного пользователя и немного иначе организовать обмен данными

HTTP_USER_AGENT

Название и марка браузера клиента

Не существует иных методов узнать, какие именно переменные окружения поддерживает конкретный Web-сервер, кроме как поискать в документации, либо непосредственно проверить. В начале выполнения любого проекта, предполагающего программирование серверных скриптов, желательно провести такую проверку. В гл. 5 мы запланировали использование в качестве сервера Apache. Давайте определим проверкой, какие переменные мы сможем в дальнейшем использовать (листинг 9.3).

Листинг 9.3. Проверка переменных 

program envir; 

{$APPTYPE CONSOLE}

 uses

Windows;

 const

CGIVar: array[0..27] of string =

 CREQUEST_METHOD', 

'SERVER_PROTOCOL' ,

 'URL',

// Для экономии места, здесь опущены переменные, приведенные в таблице //. . .

'НТТР_СООКIЕ',

'HTTP_AUTHORIZATION');

var

buffer:array[0..4095] of Char;

count:byte;

size:integer;

 begin

writeln('Content-Type: text/html');

writeln;

 writeln('<HTML>');

writeln('<ТIТLЕ>Список переменных и их значений, передаваемых скрипту Web-сервером Apache</TITLE>');

writeln('<Н4>Список переменных и их значений, передаваемых скрипту Web-сервером Apache</H4>');

for count:=0 to 27 do

begin

size:=GetEnvironmentVariable(Pchar(CGIvar[count]),Buffer, SizeOf(Buffer));

if size>l then writeln(CGIvar{count],'=',Buffer,'<BR>')

end;

writeln('</HTML>'); 

end.

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

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

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

Мы познакомились с базисом интерфейса CGI — переменными окружения. Теперь можно переходить к более детальному изучению возможностей обмена данными между клиентом и сервером.

Рис. 9.2.Список переменных окружения и их значения

 

Метод POST

Как уже было отмечено ранее, существует два различных способа передачи самого содержимого запроса от Web-сервера к серверному модулю, который должен эти данные обработать — POST и GET.

Метод POST предполагает получение серверным модулем клиентских сведений из стандартного потока ввода. При этом Web-сервер создает все те же переменные окружения с сопроводительной информацией, но при этом дополнительно посылает модулю в стандартный поток ввода само тело клиентского запроса, структура которого будет описана ниже. В дополнение к тем переменным, которые приведены на рис. 9.2, добавляется переменная CONTENT_LENGTH, содержащая число символов с клиентскими данными, которые необходимо считывать из потока ввода для последующей обработки. Если для получения данных программист использует функции вроде Readchar, которые возвращают один символ из потока ввода, то они должны вызываться то число раз, которое указано в переменной CONTENT_LENGTH. Также можно применять функции Read/Readin, поскольку сервер (по крайней мере, Apache) к концу посылаемых данных добавляет управляющий символ конца строки, что позволяет оператору программы успешно завершать чтение данных, и, следовательно, выполняться. Проще говоря, такая операция равносильна нажатию клавиши <Enter> в поле ввода строки в текстовом режиме ДОС.

Данные, посылаемые серверному модулю в потоке ввода, кодируются по следующим правилам.

Методы приведения данных к стандартному виду

Реализуем серверный скрипт, который получает, а затем отображает на Web-странице некоторые анкетные данные клиента.

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

Поскольку в соответствии с проектом анкета для физических и юридических лиц отличается, то реализуем поставленную задачу в два этапа:

1. Сначала пользователь указывает тип покупателя

2. Затем, в зависимости от его выбора, ему предоставляют анкету. Начальная Web-страница будет иметь код, приведенный в листинге 9.4.

Листинг.9.4 Анкета покупателя

<HTML>

<ТIТLЕ>Анкета покупателя</ТIТLЕ>

<Н4>Укажите, пожалуйста, следующую информацию:</Н4>

<PORM action=" http:\\localhost\cgi-bin\adduser.exe" method="POST">

<Р>Ваше имя:

<INPUT type="text" maxlength=30 name="Firstname"></INPUT>

<Р>Ваша фамилия:

<INPUT type="text" maxlength=30 name="Lastname"?></INPUT>

<Р>Вы представляете юридическое лицо при оформлении покупки?

<SELECT size="l" name="jud">

<OPTION selected value="l">fla, я представляю юридическое Jumo</OPTION>

<OPTION value="0">HeT, я действую от своего имени</ОРТIOМ> </SELECT>

<INPUT type="submit" value="Отправить данные данные" name="Bl"> 

</FORM>  

 </HTML>

Если теперь заполнить формы Web-страницы значениями начиная с первого ПОЛЯ формы, введя соответственно Sergey, Podolsky, "Нет, я действую от своего имени", то поток ввода, который передается серверному модулю Web-сервером, будет иметь вид:

Firstname=%Dl%E5%FO%E3%E5%E9+%28Sergey%29&Lastname=%CF%EE %E4%EE%EB%FC%Fl% EA%E8%E9+%28Podolsky%29&jud=0 &Bl=%CE%F2%EF%FO%EO%E2%E8%F2%FC+%E4%EO%ED%ED %FB%E5.

Как видим, модулю, для того чтобы получить интересующие данные и в дальнейшем их обрабатывать, необходимо проделать следующие операции:

1. После того как данные будут считаны, например, в строку content, нужно выделять подстроки, которые начинаются с названия элемента формы, а заканчиваются символом &.

2. Из получившихся строк убрать названия полей и знак =.

3. Преобразовать строки, заменив все шестнадцатеричные представления символов на обычные, а знаки + на пробелы.

4. При необходимости некоторым строковым значениям сопоставить числовые или булевские.

Проделаем эти операции над данными элемента Lastname.

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

 Листинг 9.5. Выделение подстроки, содержащей данные элемента Lastname 

function subst(s: string):string;

begin

subst:=copy(s, (pos('&Lastname=',s)+10), ((pos('&jud=', 

s))-(pos('&Lastname=',s)+10))) ;

end;

Данная функция отсекает данные, введенные клиентом в поле Lastname из всей строки, полученной в качестве параметра. Обратите внимание, что в качестве параметра функции pos, которая использовалась для установления номера позиций подстрок '&Lastname=' и '&jud=', а, следовательно, начала и конца интересующей области, использовались не только названия элементов HTML-форм, но и символы, обрамляющие их в потоке ввода. Сделано это для того, чтобы предотвратить неправильную работу скрипта, в случае, если пользователь преднамеренно или нет введет названия элементов в поля для текстового ввода. Даже если ввести символы & или = вместе с названием элемента, то сервер заменит их шестнадцатеричным представлением, что обеспечит корректную работу модуля.

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

 Листинг 9.6. Декодирование символов 

function russ(s: string):string;

 var

  st:string;

begin

while length(s)>0 do 

begin

if s[l]='+' then

 begin

st:=Concat(st,' ');

 delete (s,1,1); 

end;

if s [!]<>'%' then

 begin

st:=Concat(st,s[1]); 

delete (s,l,l); 

end

 else 

begin 

st:=Concat(st,char(strtoint(Concat('$',s[2],s[3]))));

delete(s,1,3};

 end;

  end;

Result:=st;

  end;

Приведенная функция последовательно "отрезает" от начала строки s, содержащей данные, которые необходимо декодировать, блок, размером один или три символа, для предварительной обработки. Если на первом месте строки стоит символ %, то следующие за ним два символа преобразуются в строковое представление шестнадцатеричного числа, с последующим переводом в тип integer и получением соответствующего символа. Преобразованные символы последовательно прибавляются к строке st. Знак + на первом месте указанного выше блока приводит к прибавлению к строке st пробела, а все остальные символы переносятся без изменений. В конце выполнения функции содержимое строки st передается переменной Result. Таким образом, мы создали весьма полезную функцию, которая может использоваться во всех серверных модулях, при необходимости декодировки данных.

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

Листинг 9.7. Тело программы 

begin

readln(i); //i — строковая переменная

 writeln('Content-Type: text/html'); 

writeln;

writeln('<HTML>');

writeln('<ТIТIЕ>Анкета покупателя</ТIТLЕ>'); 

writeln('<Р>Добрый день, уважаемый'); 

writeln(russ(subst(i) ) ) ;

writeln('<Р>Пожалуйста, заполните анкету покупателя:');

 if pos('jud=0',i)>0 then

begin

writeln('<Р>Анкета для физических лиц');

// Вывод тела анкеты для частных клиентов

//...

end

 else

begin

writeln('<Р>Анкета для юридических лиц');

//Вывод тела анкеты для юридических лиц //...

 end;

writeln('</HTML>');

  end.

Итак, мы научились создавать серверные модули, использующие в своей работе для получения данных с заполненных клиентом форм Web-страницы метод POST. Существует альтернативный метод передачи данных серверному скрипту — метод GET.

 

Метод GET

Данный метод также служит для передачи данных запроса серверному модулю, однако, в отличие от ранее изученного метода POST, не использует потока ввода. Вся строка запроса в методе GET передается в переменной окружения QUERY_STRING. Для доступа к ней, как и ранее, может использоваться функция GetEnvironmentVariabie. В данном методе существует ограничение на число посылаемых символов, поэтому его лучше использовать только для передачи заведомо коротких наборов данных. Кроме того, данный метод использует иной способ посылки данных от клиента к Web-серверу. Вместо передачи их в теле исходящего от клиента HTTP-запроса, содержимое формы вместе с названиями элементов добавляется к URL запрашиваемого документа. Например, если мы заменим в листинге 9.4 параметр формы action с POST на GET, то адрес документа, запрашиваемого при нажатии на кнопку Отправить данные ,будет не

http:\\localhost\cgi-bin\adduser.exe,

а

http:\\localhost\cgi-bin\adduser.exe?Firstname= %Dl%E5%FO'%E3%E5%E9+%28Sergey%29&Lastname= %CF%EE%E4%EE%EB%FC%Fl%EA%E8%E9+% 28Podolsky%29&jud=0&Bl= %CE%F2%EF%FO%EO%E2%E8%F2%FC+%E4%EO%ED%ED%FB%E5.

Как видим, непосредственно к адресу Web-документа добавляется символ ?, за которым следует то же закодированное содержимое формы. (Разумеется, для получения приведенного выше URL-запроса форму сначала нужно соответствующим образом заполнить.)

Воспользуемся методом GET для подачи запроса на поиск товара в базе данных магазина.

Пусть исходная страница имеет код, приведенный в листинге 9.8.

Листинг 9.8. Подача запроса на сервер 

<HTML>

<FORM action="http:\\localhost\cgi-bin\search.exe" method=GET>

<Р>Введите название товара для поиска по базе данных магазина. 

<INPUT type="text" name="Search"></INPUT> <INPUT'type="submit" value="OTnpaBMTb данные" name="Bl">

 </FORM> 

</HTML>

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

Итак, функция, приведенная в листинге 9.9, получает из переменной окружения содержимое запроса, затем извлекает слово для поиска, декодирует символы (для этого используется описанная выше, в методе POST, функция russ) и возвращает готовую строку запроса.

 Листинг 9.9. Извлечение посланной методом GET строки запроса 

function GetSearch:string;

 var

buffer:array[0..4095] of Char;

size:integer;

s:string; begin

size:=GetEnvironmentVariable(Pchar('QUERY_STRING') , Buffer,

  SizeOf(Buffer));

s:=string(buffer);

s:=copy(s, (pos('&Search=',s)7+8), ( (pos('&B1=',s))-(pos('4Search=',s)+8)));

Result:=russ(s); 

end;

Работа всех операторов аналогична рассмотренным выше примерам. Сначала функция GetEnvironmentvariabie заполняет массив Buffer значением переменной окружения QUERY_STRING. После этого массив передается строке s, из которой с использованием функции сору выделяется непосредственно введенное пользователем выражение. После декодировки символов функцией russ, реализованной выше, работа рассматриваемой функции заканчивается возвращением результата полученного методом GET в программу для последующей обработки.

 

Использование протокола HTTP

Протокол HTTP (HyperText Transfer Protocol, протокол передачи гипертекста) — это соглашение о формате передачи данных посредством сети Интернет. Именно используя этот протокол, браузер посылает запрос серверу о передачи определенной Web-страницы (конкретно — HTML-файла), изображения и т. д. В свою очередь, сервер отправляет клиенту данные в формате, который соответствует протоколу HTTP. Следует отличать протоколы низкого уровня типа TCP/IP, которые устанавливают правила работы для компонентов сети от рассматриваемого протокола HTTP или FTP, например, которые определяют, как должны взаимодействовать между собой программы в рамках этой сети. Протокол передачи гипертекста определяет, как должен послать запрос браузер, чтобы любой сервер, поддерживающий данный протокол, смог понять, что от него хочет клиент. То же самое касается и возврата данных. В следующей части главы будут описаны некоторые аспекты HTTP для того, чтобы вы, дорогой читатель, смогли создавать серверные модули, умеющие отправлять клиенту данные напрямую, минуя их обработку Web-сервером.

 

Типы данных

Наверное, вы уже обратили внимание, что в предыдущих примерах первой строкой в потоке вывода от сервера к клиенту была Content-Type: text/htmi. Как очевидно, в ней содержится информация о типе последующих данных. Может возникнуть вопрос, а какие данные вообще можно посылать? Ведь современная Web-среда оперирует с десятками типов объектов: графические файлы, Flash-ролики, звуковые и видеопотоки и т. д. Типы данных, которые браузер ожидает принять от сервера в ответ на свой запрос, находятся в переменной окружения НТТР_АССЕРТ и никакие другие виды информации не будут гарантированно корректно интерпретированы им.

До сих пор нас не заботили эти моменты, потому что работа шла лишь с простым HTML-кодом, и мы предположили, что, поскольку Web-браузер предназначен для просмотра HTML-страниц, проблем быть не должно. Так оно и случилось. Давайте проверим, какие типы данных был готов принять браузер, получая ответ сервера на посланные анкетные данные. Для этого в конце листинга 9.7 допишем строки, приведенные в листинге 9.10.

Листинг 9.10. Извлечение данных из переменной окружения НТТР_АССЕРТ 

size:=GetEnvironmentVariable('HTTP_ACCEPT',Buffer, SizeOf(Buffer)); writeln('HTTP_ACCEPT',' = ',Buffer, '<BR>');

Нужно ввести тип данных size и Buffer, подобно тому, как мы делали это ранее. В результате мы получим, что браузер Microsoft Internet Explorer 5.0 был готов принять следующие типы данных: 

image/gif, image/x-xbitraap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, */*,

а браузер Netscape Navigator— */*, что, впрочем, равносильно. Не трудно догадаться, что тип text/htmi, который мы всегда указывали, подпадает в раздел */*, поэтому этот тип принимается и корректно интерпретируется браузером.

Вернемся к вопросу о возможных типах передаваемых данных, которые может принимать клиент и передавать сервер. Существует стандарт, регламентирующий этот набор, который называется MIME (Multimedia Internet Mail Extension, мультимедийные расширения почты Интернета). В случае, когда серверный модуль пытается вывести в поток неизвестный серверу тип данных, генерируется внутренняя ошибка сервера, и сообщение об этом отсылается клиенту. Список всех типов данных, которые готов передавать Apache клиенту, находится в файле mime.types подкаталога conf, того каталога, в котором установлена программа. Если вы хотите использовать иной тип данных, то необходимо дописать в этот файл соответствующее добавляемое название. Наиболее употребительные типы данных приведены в табл. 9.2.

Таблица 9.2.Некоторые MIME-типы данных

Название MIME-типа

Тип данных

application/msword

Файл Microsoft Word

application/pdf

Документ Acrobat Reader

application/x-javascript

JavaScript-модул b

application/x-shockwave- flash

Flash-ролик

audio/mpeg

Звук в формате MPEG (MP3, например)

audio/x-pn-realaudio

Потоковые аудиоданные в формате Realaudio

image /bmp

Точечный рисунок

image/gif

GIF-изображение

image /jpeg

JPEG-изображение

text/html

HTML-документ

text/plain

Простой текст

text/rtf

Документ в формате RTF

multipart /form-data

Содержимое смешанного типа

 Замечание 

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

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

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

 

HTTP-заголовок

Указание описанных выше типов данных представляет собой лишь часть сопроводительной информации к набору данных, посылаемому пользователю. В приведенных примерах сервер сам дополнял отсутствующие сведения для формирования полного HTTP-заголовка (а именно в нем и помещается эта информация).

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

Формат заголовка приведен в листинге 9.11.

 Листинг 9.11. Вид заголовка HTTP 

НТТР/1.1 200 Ok

Date: Fri, 14 Jun 2001 23:18:01 GMT

Server: Apache/1.3

MIME-version: 1.0

Last-Modified: Fri, 14 Jun 2001 23:18:00 GMT

Content-Type: text/html

Content-Length: 500

Такого рода заголовки предшествуют любой отправляемой клиенту информации, будь то HTML-документ или двоичный файл.

Рассмотрим структуру заголовка более подробно.

Таблица 9.3.Некоторые коды результата обработки запроса клиента

Код

Статус выполнения операции

200

Операция запроса выполнена успешно

202

Запрос клиента принят

400

Ошибка запроса

401

Запрос не авторизован

402

Ресурс требует оплаты

403

Доступ закрыт

404

Документ запроса не найден

500

Внутренняя ошибка сервера (как правило — CGI-модуля)

502

Сервер перегружен

503

Сервер временно недоступен

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

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

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

 

Режим прямого вывода

Как уже отмечалось, спецификацией CGI предусмотрен режим работы Web-сервера, позволяющий напрямую выводить ответ, минуя процедуру автоматического добавления заголовков. Это так называемый nph-режим (non parsed headers). Для его включения достаточно имя файла — CGI-модуля сделать начинающимся с выражения nph-. Например — nph-module.exe. При работе в этом режиме все заголовки необходимо формировать самостоятельно, а в обычном режиме — нужно лишь указать тип посылаемых данных.

 

Отправка на сервер данных различного типа. Передача файлов

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

В этом случае все содержимое POST-запроса объявляется МIMЕ-типом multipart/form-data. Этот тип данных предназначен для передачи в рамках одного запроса данных нескольких типов.

POST-запрос при этом состоит из нескольких частей, которые представлены простыми типами и разделяются с помощью ключевого параметра boundary.

Ранее, в гл. 7, посвященной формам, описание возможностей установки типов передаваемых на сервер данных было опущено, поскольку все формы, кроме как для отправки простого текста не используются и по умолчанию передают содержимое типа text/plain. Исключение составляет лишь форма для отправки файлов. При одновременном их наличии на странице необходимо не только знать тип, но и уметь выделять содержимое каждого поля. Рассмотрим все это на примере.

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

Листинг 9.12. Код HTML-документа, предоставляющего возможность для отправки файла на сервер

<HTML>

<FORM action="resume.exe" method="POST" enctype="multipart/form-data">

<Р>Укажите свое имя<ВК>

<INPUT type="text" name="name"XBR>

<Р>Укажите свою фамилию<ВR>

<INPUT type="text" narae="lastname"><BR>

<Р>Введите, пожалуйста, путь и название файла, в котором содержится отправляемое резюме.

<INPUT type="file" name="resumefile"><BR>

<P><INPUT type="Submit" value="Отправить"><INPUT type="Reset" value="Сброс">

</FORM>

</HTML>

Рис. 9.3.Отображение Web-страницы

Отображение такого HTML-документа приведено на рис. 9.3.

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

 Листинг 9.13. Содержимое HTTP-запроса (включая заголовок) 

Content-Type: multipart/form-data;

 boundary=-------7dl21c31450

----------------------------7dl21c31450 

Content-Disposition: form-data; name="name"

Sergey

-------------------------------7dl21c31450 

Content-Disposition: form-data; name="lastname"

Podolsky

-------------------------------7dl21c31450

Content-Disposition: form-data; name="resumefile";

filename="C:\MoM документы\гезшпе.с1ос" Content-Type: application/msword

Содержимое файла

-----------------------------7dl21c31450--

Как видим, запрос состоит из нескольких частей, которые разделены одинаковой строчкой, указанной в параметре boundary заголовка HTTP (дополнительно, к этому параметру слева дописываются две горизонтальные черты). В конце запроса также находится указанная строка, но теперь уже вслед за ней стоят две горизонтальные черты.

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

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

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

Приведенный способ передачи данных является наиболее оптимальным, т. к. не использует кодирования и декодирования не ASCII-символов. Применительно к передаче файлов это имеет ключевое значение, вследствие того, что при кодировании каждый кодируемый символ увеличивает число передаваемых данных втрое (помните, что каждый кодируемый символ заменяется знаком % и шестнадцатеричным числом, состоящим из двух цифр). В случае, если расширение передаваемого на сервер файла сопоставлено некоторому типу данных, то этот тип автоматически передается в параметре Content-Type.

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

1. Прочитать из потока ввода число символов, которое хранится в переменной Окружения CONTENT_LENGTH.

2. Определить, какое сочетание символов является разделителем частей запроса. Это делается путем выделения подстроки, с которой начинается тело запроса и которая заканчивается первым пробелом в первой строке (помните, что в листинге 9.13 представлен запрос полностью, а в поток ввода, заголовок (первая строка) не посылается).

3. Путем "отсечения" нужного числа частей тела запроса выделяется необходимый блок. Данные элементов формы посылаются в порядке их следования на Web-странице, поэтому, зная код HTML-документа, содержащего исходную форму, можно легко пропустить нужное число составляющих запроса, чтобы получить доступ к части формы, содержащей файл. Далее, по значению, установленному в поле value, получаем путь и имя файла.

4. Полученное имя файла записывается в некоторую строковую переменную.

5. На диске сервера создается новый файл в некотором каталоге.

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

7. Файл закрывается, и производятся дальнейшие операции.

 

"Вытягивание" информации или Pull-метод

Этот метод позволяет указывать браузеру необходимость загрузки заданного ресурса через заданный интервал времени посредством введения перед заголовком дополнительной директивы Re fresh: время; 1жъ=адрес, где время указывается в секундах, а адрес — это обычный URL.

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

 Листинг 9.14. Пример использования метода Pull 

Begin

writeln('HTTP/1.1 200 Ok');

writeln('Refresh:10; URL=http://localhost/') ;

writeln('Content-Type: text/html');

writeln('');

writeln('<HTML>') ;

writeln('<H1> Адрес страницы изменился на www.someplace.com</Hl>'};

writeln('</HTML>');

end.

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

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

 

Получение информации о клиенте

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

Рассмотрим их более подробно.

Тип агента пользователя (браузера)

Указывается в переменной HTTP_USER_AGENT. В ней содержится как название, так и версия браузера. Наиболее распространенными программами просмотра на сегодняшний день являются Microsoft Internet Explorer и Netscape Navigator. Вот какую информацию сообщают о себе пятая и шестая версии этих программ соответственно:

Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)

И

Mozilla/5.0 (Windows; U; Win98; en-US; m!8) Gecko/20001108 Netscape6/6,0.

Как видим, выражения в скобках дополнительно указывают марку операционной системы клиента и некоторые другие сведения. Собирая эти сведения, можно определять тип содержимого страницы, которое может быть послано клиенту. Например, Netscape Navigator не поддерживает стилевой атрибут hover для ссылок, существуют различия и в поддержке графических возможностей JavaScript. Кроме того, сравнивая отображения одинаковых страниц в разных браузерах, можно заметить различия, которые необходимо учитывать. Многие Web-дизайнеры, при использовании языка JavaScript для организации интерфейса, проверяют тип браузера непосредственно в режиме выполнения скрипта. Это приводит к тому, что браузер загружает код, отвечающий за различные варианты поведения, но интерпретирует только тот, который предполагает сценарий JavaScript. При этом, увеличивается время на загрузку каждой страницы. Более оптимально это делать непосредственно на стадии HTTP-запроса, сокращая время на загрузку и интерпретацию документов.

Установление IP-адреса клиента

Иногда бывает полезно знать IP-адрес клиента. Многие создатели чатов отслеживают пользователей, употребляющих ненормативную лексику, и добавляют их адреса в базу данных запрещенных пользователей (хотя, следует заметить, что в сетях с динамической адресацией это бессмысленно). Кроме того, по IP-адресу в рамках сессии работы можно идентифицировать клиента. Это также используется для анализа статистики посещаемости ресурсов сайта. Например, даже учитывая динамичность предоставления IP-адресов, можно отслеживать такие параметры, как последовательность просмотра страниц сайта, показатель возвращения на сайт в течение некоторого промежутка времени и т. д.

Доступ к IP-адресу осуществляется через переменную окружения REMOTE_ADDR или HTTP_FROM.

Доступ к переменным осуществляется путем использования функции GetEnvironmentvariabie, применение которой описано выше.

 

Использование Cookie

Поскольку каждый информационный ресурс сети Интернет создается с целью потенциального многократного использования клиентами, бывает необходимо передавать какие-нибудь сведения между сессиями работы с ресурсом. Так, многие системы, в процессе работы предлагающие клиенту заполнить анкету, при повторном запросе уже вписывают в ее поля данные предыдущего заполнения. Сайты, которые обладают возможностью изменять свое оформление в зависимости от пользовательских предпочтений, также восстанавливают настройки при их повторных посещениях. Примеров можно привести много. Главное, что их объединяет, — это сохранение данных на достаточно большие промежутки времени. Причем, такого рода данные не вносятся в базу данных сервера, поскольку существует метод их сохранения и извлечения на клиентской стороне. Это значит, что все сведения, которые укажет серверный модуль, браузер автоматически записывает в файл и сохраняет его в специальной папке. В последующем, при необходимости, данные могут запрашиваться этим модулем, в результате чего браузер их возвращает для дальнейшего использования. Такая технология называется cookie.

Передача сохраняемых значений

Отправка cookie-данных браузеру осуществляется стандартным для CGI способом — через переменные, которые добавляются к заголовку HTTP-ответа на клиентский запрос. Однако следует заметить, что многие браузеры принимают cookie-информацию посредством как HTTP-заголовков, так и тега <МЕТА> HTML-документа. В данном разделе будет описан способ сохранения данных обоими способами. Итак, передаваемые параметры имеют следующий вид:

имя_переменной1=значение!; имя_переменной2=значение2; ЕХР1КЕЗ=деньнедели дата-месяц-год час:минута:секунда GMT; РАТН=путь;

Переменных, значения которых необходимо сохранить, может быть одна или несколько. В разделе 'EXPIRES хранятся дата и время, до которого данная информация является актуальной. PATH обозначает путь к модулю (документу), для которого предназначена данная информация. Можно задавать также и просто каталог, в случае, если необходимо предоставить cookie- сведения ко всем документам, хранящимся в нем. Если переменная PATH опущена, то данная информация считается предназначенной для файлов из того каталога, откуда пришел запрос.

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

 Листинг 9.15. Пример cookie-строки 

Set-Cookie: ClientID=0056Jud; EXPIRES=Fri 22-06-2001 00:00:00 GMT; PATH=localhost/cgi-bin/cookie.exe;

В данном примере указывается значение переменной clientID, равное 0056Jud, срок актуальности информации истекает в полночь на 22 июня 2001 года, пятница. Кроме того, указано, что эта информация относится только к файлу cookie.exe и получена с домена locaihost.

Для того чтобы передать эту информацию браузеру, можно использовать тег <МЕТА>. Пример такой реализации способа передачи cookie-данных приведен в листинге 9.16.

 Листинг 9.16. Пример передачи cookie-строк браузеру внутри тега <МЕТА> 

begin

writeln('Content-Type: text/html');

writeln;

writeln('<HTML>');

writeln('<META HTTP-EQUIV="Set-Cookie" CONTENT^" ClientID=0056Jud;

EXPIRES=Fri 22-06-2001 00:00:00 GMT; PATH=/cgi-bin/cookie.exe;">');

writeln('<ТIТLЕ> Web-страница, сохраняющая cookie</TITLE>');

writeln('<р>Любое содержимое HTML-документа');

writeln('</HTML>');

end.

Альтернативный способ передачи сведений представлен в листинге 9.17.

 Листинг 9.17. Передача cookie-сведений внутри заголовка HTTP 

begin

writeln('Content-Type: text/html');

writeln('Set-Cookie: ClientID=0056Jud; EXPIRES=Fri 22-06-2001 00:00:00

GMT; PATH=/cgi-bin/cookie.exe;') ;

writeln;

writeln('<HTML>');

writeln('<ТIТLЕ>ЯеЬ-страница, сохраняющая cookie</TITLE>');

writeln('<р>Любое содержимое HTML-документа');

writeln('</HTML>');

end.

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

Извлечение данных

Когда браузер запрашивает страницу у сервера (вообще говоря, посылает HTTP-запрос), он проверяет наличие cookie-файлов, предназначенных для данного документа. В случае если искомая информация присутствует, она извлекается из файла и оправляется вместе с HTTP-запросом серверу. Тот, в свою очередь, полученную информацию опускает в переменную окружения HTTP_COOKIE в виде пар имя_переменной=значение. Доступ 'к переменной окружения может осуществляться посредством стандартной функции GetEnvironmentvariabie, аналогично приведенным выше примерам.

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

 

Создание CGI-программ с использованием библиотек Delphi

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

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

Исходные коды этих библиотек находятся в подкаталоге Source\INTERNET того каталога, куда установлена Delphi. В Delphi версии 3.0 находятся следующие модули: cgiapp.pas, copyprsr.pas, dbweb.pas, httpapp.pas, isapiapp.pas, masks.pas, ntois.pas, scktcomp.pas, syncobjs.pas. Дополнительно к этим файлам, последние версии Delphi содержат новые подключаемые модули, которые описаны в приложении 1.

Многие объекты, описанные в этих библиотеках, представляют собой невизуальные компоненты, которые доступны на вкладке Internet Component Palette. Это позволяет редактировать их свойства, используя Object Inspector в визуальном режиме. Вся следующая глава посвящена работе с невизуальными объектами, и поэтому здесь они описаны не будут. Если вы планируете использовать их, то переходите к изучению очередной главы.