Программирование на Delphi - обмен опытом / Интерфейс CGI

© Зайцев Олег 1998-2004
Лучшая портативная техника. Плееры Камеры Телефоны Компьютеры
Покупателям, пришедшим на www.porta.ru по этой ссылке - дополнительная скидка 1%
Железо | Система | WEB | Компоненты | Графика | Ссылки | Мультимедиа | Сети | Прочее | Реестр | Литература

Статистика

Интерфейс CGI

Рекомендую:
Главная страница \ WEB программирование \ Интерфейс CGI

  • Интерфейс CGI

    Интерфейс CGI

    Общие вопросы Задать вопрос Наверх
    Известно, что обычный Web-сервер способен лишь просто считывать и возвращать клиенту уже существующие статические HTML-страницы (или сообщения об ошибках, если запрошенные страницы недоступны). Следовательно, разработчик Web-сайта не мог предоставить пользователям ничего, кроме статических Web-страниц, заранее размещенных на сервере. Однако в большинстве случаев необходимо организовывать интерактивное взаимодействие с пользователем, причем легко выделить ряд типовых задач: Для решения перечисленных выше задач необходимо применение некоторого программного кода, который запускался бы на сервере, получал параметры пользователя и формировал страничку-ответ, которая передавалась бы пользователю.
    Однако вскоре стало ясно, что требуется более высокий уровень взаимодействия между клиентом и сервером, вследствие чего был разработан общий интерфейс маршрутизации (Common Gateway Interface - CGI). Интерфейс CGI позволяет Web- серверу запускать независимый процесс, и, основываясь на входных данных, полученных от пользователя, обрабатывать эту информацию и возвращать клиенту динамически создаваемую Web-страницу. При этом CGI-программа могла выполнять любой тип обработки данных, который требовался программисту, а также возвращать страницу любого вида, допустимого в рамках HTML.
    CGI программы принято называть скриптами, и далее я буду придерживаться этого термина.
    Стандартные CGI скрипты считывают информацию из стандартного потока ВВОДА (STDIN) и записывают результаты в стандартный поток вывода (STDOUT), а также считывают переменные окружения. С появлением интерфейса CGI Web сделала большой шаг вперед, поскольку сервера получили возможность предоставлять обработанные, уникальные ответы на запросы пользователей.
    Однако CGI скриптам присущ ряд недостатков. Самый существенный из них - это большая ресурсоемкость. Дело в том, что для каждого запроса клиента к CGI скрипту WEB сервер вынужден запускать отдельный процесс. Т.о. десяток-другой одновременных запросов могут привести к существенному замедлению работы сервера. Другим недостатком CGI является то, что если HTML не зависит от платформы сервера (и почему бы ему зависеть - HTML документ просто хранится на WEB сервере как файл и передается по запросу), то CGI скрипт является приложением, и, следовательно, должен работать под той платформой и ОС, что и сервер WEB. Следовательно, CGI скрипты, написанные на Delphi, будуть работать только под операционной системой Windows 9x/NT/2k.

    Алгоритм работы с использованием интерфейса CGI
    Рассмотрим алгоритм взаимодействия приложения клиента (для краткости будем называть его браузером) с сервером при использовании интерфейса CGI:

    1. Браузер устанавливает соединение с WEB сервером
    2. Браузер передает серверу HTTP запрос
    3. WEB сервер анализирует запрос и выясняет, что это не запрос статической странички, и запрос к CGI скрипту. Может возникнуть вопрос - как он отличает запрос о передаче файла от запроса на выполнение CGI приложения. Как правило, это делается на основе анализа расширения. Более того, для многих WEB серверов приложение должно лежать в особой папке (ее типовое имя - CGI-BIN). В Microsoft IIS такой папки нет, зато есть права на папку (и в том числе отдельное право Execute - выполнить).
    4. При обнаружении указанного в запросе CGI приложения и наличие прав на запуск WEB сервер запускает приложение. При этом WEB сервер передает через CGI скрипту параметры запроса и ряд дополнительных данных через переменные окружения и стандартный поток ввода.
    5. CGI скрипт выполняет любые действия, которые сочтет нужными и в результате формирует ответные данные и выводит их в стандартный поток ввода. WEB сервер передается эти данные клиенту. Причем GCI скрипт может передавать не только HTM файлы, но и бинарные данные (например картинку или архив). Кроме передачи данных CGI скрипт может целиком сформировать HTTP ответ (вместе с заголовком) или выполнить перенаправление - указать адрес файла, который должен быть передан в качестве ответа. Последнее особенно удобно для организации подсчета количества скачиваний программы или для огранизации навигационной системы.
    6. WEB сервер разрывает соединение, завершая тем самым обмен
    Легко заметить, что с технической точки зрения для приложения клиента (браузера, утилиты закачки и т.п.) нет никакой разницы между запросом статического документа и запросом к CGI скрипту. Эти различия существуют только на сервере.

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

    1. Установить WEB сервер, поддерживающий CGI
    2. Внимательно изучить настройки этого WEB сервера и уточнить, в каких каталогах следует размещать CGI скрипты, как их именовать, какие права необходимо предоставить пользователю, от имени которого будет работать WEB сервер
    3. Произвести настройки, необходимые на основании п.п. 2
    Следует отметить, что наиболее распространенной ошибкой начинающих разработчиков являются ошибки в настройках сервера.
    Возникает вопрос - какой сервер использовать. Тут я не советчик, я тестировал свои примеры на Microsoft IIS под Windows NT (если под рукой есть лицензионный NT и IIS, то грех его не использовать). Я бы рекомендовал для первых опытов использовать отменную программу Simple HTTP Server (http://www.wplus.net/pp/mrdoors/srv/srv.zip, разработчик М. Феоктистов) - при размере 9 кб это WEB сервер с поддержкой CGI, не содержащий не единой настройки. Правда, он не поддерживает метод POST, но для простейших примеров можно обойтись без него. Можно так же скачать что-то типа TinyWeb (http://www.ritlabs.com/tinyweb) или FolkWeb (www.ilar.com/folkweb), а еще лучше сходить например на www.listsoft.ru и посмотреть там. Есть очень хороший WEB сервер Apache, но я бы не рекомендовал его начинающим, т.к. это штука серьезная и требует настройки.

    Выбор языка программирования
    Скажу пару слов на извечную тему - "на чем писать". Я не хочу ничего рекомендовать, просто отмечу, что с точки зрения переносимости удобны C, Java, Perl, PHP; точки зрения быстродействия - C, Delphi; с точки зрения отвратительной читаемости и отменного набора возможностей по манипулированию строками - PERL и т.п.
    У Delphi есть у данном случаен минус - программа на Delphi работает только под Windows, что ограничивает его применение. С другой стророны, с появлением Kulix у разработчика появилась возможность переносить приложения Delphi на платформу Linux с минимальными переделками

    Теоретические основы CGI Задать вопрос Наверх
    Поговорим о теории. Для начала следует отметить, что существует две технологии CGI:

    Рассмотрим подробно первый вариант как более универсальный.

    Переменные окружения, устанавливаемые WEB сервером.
    ПеременнаяНазначение
    AUTH_TYPE Используется для идентификации пользователя (при условии, что WEB сервер сконфигурирован так, что поддерживает идентификацию пользователя)
    CONTENT_LENGTH Этот параметр содержит точный размер данных (в байтах), переданных в запросе. Пример: CONTENT_LENGTH = 14671
    CONTENT_TYPE Тип данных, переданных в запросе. Пример: CONTENT_TYPE = text/html. Если результат заполнения формы передается по методу POST, то CONTENT_TYPE как правило равно application/x-www-form-urlencoded
    GATEWEY_INTERFACEНомер выпуска используемой спецификации CGI. Имеет формат CGI/nnn, где nnn - номер выпуска. Пример: CGI/1.1
    PATH_INFO Возвращает дополнительную информацию о пути. Так, например, если вызывается скрипт zaitsev.smolen.elektara.ru/cgi-bin/test1.exe/path1/path2/1.htm, то PATH_INFO будет равно /path1/path2/1.htm
    PATH_TRANSLATED Путь, который пригоден для непосредственного использования в файловых операциях (учитывается то, что в запросе могут финурировать виртуальные директории и т.п.).
    QUERY_STRING Очень важный и часто используемый параметр. Содержит строчку, переданную в качестве запроса при вызове CGI скрипта. К примеру, URL имеет вид zaitsev.smolen.elektara.ru/cgi-bin/test1.exe?name=Oleg+Zaitsev. Особенности: все пробелы заменяются знаком "+", параметры разделяются знаком "&" и имеют формат <имя пареметра> = <значение>. Все непечатные символы заменяются их кодами в формате %dd, где dd - код символа. В данном примере QUREY_STRING= name=Oleg+Zaitsev
    REMOTE_ADDR IP адрес пользователя. Очень полезный параметр для фиксации в протоколе. В собственной сети большого предприятия компьютеры пользователей как правило имеют жестко закрепленные IP адреса, что может применяться для разграничения доступа. Пример: 192.20.97.28
    REMOTE_HOST Имя узла, с которого делается запрос. Пример: 172.20.97.28 или zaitsev
    REMOTE_IDENT Имя удаленного пользователя. Имеет формат имя.хост, например, zaitsev.www.smolen.elektra.ru
    REMOTE_USER То-же, что и REMOTE_IDENT, но содержит только имя. Пример: zaitsev
    REQUEST_METOD Позволяет определить тип запроса (GET или POST). Должен обязательно анализироваться, т.к. определяет дальнейший способ обработки информации
    SCRIPT_NAME Содержит путь к скрипту. Например, скрипт лежит на zaitsev.smolen.elektra.ru/cgi-bin/test1.exe, тогда SCRIPT_NAME = /cgi-bin/test1.exe
    SERVER_NAME Имя (или IP адрес) домена
    SERVER_PORT Номер порта, используемый браузером для связи с сервером. По умолчанию используется порт 80.
    SERVER_PROTOCOL Содержит версию протокола HTTP, используемую в запросе (см. протокол HTTP). Пример: HTTP/3.2
    SERVER_SOFTWARE Произвольная строчка, несущая информацию о названии WEB сервера, его версии и т.п. Ряд серверов позволяет запретить формирование этой строчки или подставить в нее любые данные
    HTTP_*** Переменные с именами HTTP_*** являются необязательными и содержат значения необязательных параметров HTTP запроса с именами ***. При формировании этих параметров есть особенность - все символы переноса "-" заменяются на "_". Примеры: HTTP_ACCEPT, HTTP _USER_AGENT, HTTP_REFERER
    Примечание: конкретные значения и их вид сильно зависят от сервера. Многие сервера не поддерживаю один или несколько параметров

    Формат ответа CGI скрипта
    Корректно написанный CGI скрипт должен сформировать ответ, содержащий заголовок. Заголовок отделяется от последующих данных пустой строкой (т.е. CR+LF, что достигается оператором writeln;). Если заголовок не содержит директив WEB серверу, то он считает, что скрипт сам формирует заголовок HTTP ответа и передает его клиенту как есть. В настоящее время определено три директивы:
    ДирективаНазначение
    Content-typeТип ответа скрипта. Например, если скрипт возвращает HTML документ, то возвращается Content-type: text/html
    LocationУказатель на документ, который должен быть возвращен в качестве ответа скрипта. Например, скрипт хочет переадресовать пользователя на некоторый документ. Тогда он формирует заголовок Location: http://www.chat.ru/~z_ol
    StatusВозвращает код статуса в стандартном формате. Применяется, если необходимо указание статуса, например Status: 404 Документ не найден
    Примечание: простейшие WEB сервера могут игнорировать последние две директивы

    От теории к практике - примеры программирования Задать вопрос Наверх
    Пример 1. Простейший CGI скрипт.
    Генерирует динамическую страничку вида:
    Hello, world
    Current date/time is 31.01.01 10:43:30
    Исходный текст этого скрипта:

    program test1;
    
    
    uses
     Windows, Sysutils;
    begin
     // Формирование заголовка
     Writeln('Content-Type: text/html');
     Writeln;
     // Формирование самого документа
     Writeln('<HTML><BODY>');
     Writeln('Hello, world 
    '); Writeln('Current date/time is ' + DateTimeToStr(Now)); Writeln('</BODY></HTML>'); end.
    Этот пример типовой, поэтому рассмотрим его подробно. Первая особенность - прагма компилятора {$APPTYPE CONSOLE}, указывающая но то, что это консольное приложение. Наличие этой пракмы обязательно, т.к. только у консольного приложения можно пользоваться операторами writeln.

    Пример 2. Простейший CGI, формирующий динамическую HTML страничку со значениями всех переменных окружения и параметров командной строки.
    Этот пример интересен не только в качестве примера, но и в качестве тестера - с его помощью легко посмотреть значения, передаваемые конкренным WEB сервером в различных случаях.
    Исходный текст примера:

    program test2;
    
    {$APPTYPE CONSOLE}
    uses
      Windows,
      Sysutils;
    
    // Получение переменной окружения по ее имени
    Function GetEnv(AName : string) : String;
    var
     Buf : array[0..16000] of char;
    begin
     GetEnvironmentVariableA(PChar(AName), Buf, SizeOf(buf));
     Result := Buf;
    end;
    
    var
     i : integer;
    begin
     Writeln('Content-Type: text/html');
     Writeln;
     Writeln('<HTML><BODY>');
     Writeln('Пример 2. Вывод всех переменных окружения, используемых для передачи параметров');
     Writeln('<hr>');
     Writeln('<pre>');
     Writeln('AUTH_TYPE         = ', GetEnv('AUTH_TYPE'));
     Writeln('CONTENT_LENGTH    = ', GetEnv('CONTENT_LENGTH'));
     Writeln('CONTENT_TYPE      = ', GetEnv('CONTENT_TYPE'));
     Writeln('GATEWEY_INTERFACE = ', GetEnv('GATEWEY_INTERFACE'));
     Writeln('PATH_INFO         = ', GetEnv('PATH_INFO'));
     Writeln('PATH_TRANSLATED   = ', GetEnv('PATH_TRANSLATED'));
     Writeln('QUERY_STRING      = ', GetEnv('QUERY_STRING'));
     Writeln('REMOTE_ADDR       = ', GetEnv('REMOTE_ADDR'));
     Writeln('REMOTE_HOST       = ', GetEnv('REMOTE_HOST'));
     Writeln('REMOTE_IDENT      = ', GetEnv('REMOTE_IDENT'));
     Writeln('REMOTE_USER       = ', GetEnv('REMOTE_USER'));
     Writeln('REQUEST_METOD     = ', GetEnv('REQUEST_METOD'));
     Writeln('SCRIPT_NAME       = ', GetEnv('SCRIPT_NAME'));
     Writeln('SERVER_NAME       = ', GetEnv('SERVER_NAME'));
     Writeln('SERVER_PORT       = ', GetEnv('SERVER_PORT'));
     Writeln('SERVER_PROTOCOL   = ', GetEnv('SERVER_PROTOCOL'));
     Writeln('SERVER_SOFTWARE   = ', GetEnv('SERVER_SOFTWARE'));
     Writeln('<hr>');
     Writeln('Параметры командной строки ');
     // Вывод параметров командной строки
     for i:=1 to ParamCount do
      Writeln(' ',inttostr(i),' = ',ParamStr(i));
    
     Writeln('</pre><hr>');
     Writeln('Current date/time is '+DateTimeToStr(Now));
     Writeln('</BODY></HTML>');
    end.
    
    В данном примере имеется функция , демонстрирующая, как читать значение переменной окружения через Windows API. Рассмотрим пример передачи бинарных данных (картинки, архива ...). Главная особенность (о которой не следует забывать !!) - необходимость корректного указания MIME типа передаваемых данных. Например, для передачи GIF картинки тип будет image/gif и т.п. Про это часто забывают. Исходный текст примера:
    program test3;
    
    {$APPTYPE CONSOLE}
    uses
      Windows,
      Sysutils;
    
    var
     i, NR, NW : integer;
     f : file;
     Buf : array[1..1024*5] of byte;
    begin
     Writeln('Content-Type: image/gif');
     Writeln;
     AssignFile(f, 'Delphi.gif');
     Reset(f,1);
     repeat
      BlockRead(f, Buf, SizeOf(Buf), NR);
      for i:=1 to NR do
       Write(Chr(Buf[i]));
     until NR=0;
     CloseFile(f);
    end.
    
    Легко заметить, что передача ведется дубовым способом - побайтным выводом данных в стандартный поток вывода оператором Write. Ну, на то он и пример.

    Пример 4. Обработка заполненной формы - передача по методу GET
    Итак, разработаем для примера простейшую форму:


    <html>
    <body>
    
    <form action="/cgi/test4.exe" method=GET>
    Имя пользователя <input type=text name="Name"><br>
    Пароль <input type=password name="passwd"><br>
    <hr>
    Некий переключатель с именем Radio1 
    <input type=radio name="Radio1" value="1">Пункт1
    <input type=radio name="Radio1" CHECKED value="2">Пункт2
    <input type=radio name="Radio1" value="3">Пункт3
    <p>
    input type=submit VALUE="Передать">
    </FORM>
    
    </body>
    </html>
    </code>
    

    Естественно, для Ваших опытов придется подстроить путь к программе test EXE.

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

     // Получение переменной окружения по ее имени
     Function GetEnv(AName : string) : String;
     // Декодировать параметр
     Function DecodeParam(AParam : string) : String;
    
    Первая читает значение указанной переменной окружения, а вторая декодирует значение параметра (учитывая всю специфику - замену пробела на +, непечатных символов на их шеснадцатеричные коды). Кроме того, в библиотеке описан класс
       // Класс, разбирающий переданные параметры
     TCGIParamsParser = class
       private
        FParamStr: string;
        procedure SetParamStr(const Value: string);
        protected
         FParamList : TStrings;
        public
         Constructor Create;  // Создание
         Destructor  Destroy; // Разрушение
         // Получение параметра по имени. Если параметр не описан или не имеет знечения,
         // то оно может быть заменено на 
         Function    GetParamVal(AName : string;DefVal : string = ''): string;
        published
         // Строчка параметров. При присвоении автоматически производится разбор
         Property ParamStr: string read FParamStr write SetParamStr;
       end;
    
    
    Итак, сам пример 4:
    program test4;
    
    {$APPTYPE CONSOLE}
    uses
      Windows, ZCGI;
    
    var
     CGIParamsParser : TCGIParamsParser;
    begin
     Writeln('Content-Type: text/html');
     Writeln;
     Writeln('<HTML><BODY>');
     Writeln('Пример 4. Работа по методу GET');
     Writeln('<hr>');
     // Создание класса и разбор строки параметров
     CGIParamsParser := TCGIParamsParser.Create;
     CGIParamsParser.ParamStr := GetEnv('QUERY_STRING');
     // Вывод ответа
     Writeln('<pre>');
     Writeln('Имя пользователя :'+ CGIParamsParser.GetParamVal('Name','Не введено'),'
    '); Writeln('Пароль :'+ CGIParamsParser.GetParamVal('passwd','Не введен'),'
    '); Writeln('В радио-переключателе Radio1 выбран пункт с значением :'+ CGIParamsParser.GetParamVal('Radio1'),'
    '); Writeln('</pre><hr>'); Writeln('</BODY></HTML'); end.
    Исходные тексты примеров можно скачать здесь примеры CGI.


    © Зайцев Олег, "Программирование на Delphi - обмен опытом" 1999-2004. При использовании любых материалов данного сайта необходимо указывать источник информации. Дата обновления: 22.11.2004. Сайт размещен на хостинге AGAVA - Хостинг от AGAVA.ru