Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  15:22[Войти] | [Зарегистрироваться]

Простые CGI приложения на Дельфи.

Алексей Еремеев
дата публикации 16-10-2000 00:00

Простые CGI приложения на Дельфи.

Прежде всего, что такое cgi? Это аббревиатура от английского Common Gateway Interface. Вкратце это интерфейс взаимодействия программы и WWW-сервера (точнее HTTP сервера). Для чего это надо? Что-бы создавать динамические HTML странички, которые не являются просто статичным текстом а зависят от ввода пользователя и внешних условий.
Таким образом можно создавать привлекательные сайты в интернете, добавить диалоги с посетителями, статистику, опросы, счетчики посещений и т.п. Все зависит от задач и фантазии разработчика.

Итак, как же работают cgi приложения?

Все что я буду писать далее, относится к платформе Win и http серверу IIS, хотя в общих чертах справедливо и для других платформ и серверов.
Cgi приложение это программа, выполняемая под управлением операционной системы. В нашем случае это будет просто скомпилированный исполнимый (exe) файл. Причем консольного (не GUI, т.е. без графической оболочки) типа. Взаимодействие с http сервером осуществляется при помощи стандартного ввода/вывода и переменных окружения, устанавливаемых для этого приложения. В принципе этого почти достаточно для понимания работы cgi :-). Что-же происходит на самом деле? Начнем с простого (ужас как набившего оскомину, но куда деваться) примера.

Что нам понадобится (и для последующих примеров тоже)

  • Работающий http сервер (IIS или Personal Web Server). Будем считать корневым входом на сервер (хост) адрес http://www.foo.com/
  • Директория, например cgi-bin в корне сервера и (в случае WinNT) с правом выполнения программ для программы сервера и с правом на запись - для Вас.
  • Среда разработки Дельфи. (я сам использовал версию 2, дешево и сердито :-)
  • Базовые сведения о языке HTML и протоколе HTTP - очень желательно

Пример 1:

Вывод простой страницы с надписью "Hello World" (пока не динамической).
  • Создаем новое приложение.
  • Убираем из проекта единственную форму (сохранять не надо).
  • Идем в меню View -> Project source. Убираем все между begin и end.
  • В uses убираем Forms и пишем Windows.
  • Перед begin пишем {$APPTYPE CONSOLE} (будет консольное).
Пока подготовка закончена, сохраните проект куда-нибудь, например, как hello.dpr. Это был базовый шаблон. Он нам пригодиться и в дальнейшем, поэтому можете его и продублировать как tmpl-cgi.dpr или убрать в репозиторий... как нравится.
Прежде чем двигаться дальше, давайте посмотрим, как будут развиваться события при запросе клиентом (броузером) вашей странички с сервера. Т.е. проследим всю цепочку от броузера через сервер до Вашей программы и обратно.

Из броузера страничка будет вызываться как http://www.foo.com/cgi-bin/hello.exe. Сервер её должен найти в указанной директории и запустить на выполнение. (Для этого в WinNT и IIS должны быть установлены соответствующие права) Таким образом сервер запускает дочерний процесс в лице Вашей программы. Любая программа имеет доступ к переменным окружения, таким как PATH, COMSPEC и т.д. Кроме того сервер устанавливает ряд переменных окружения специально для конкретного вызванного процесса. Например REMOTE_ADDR или HTTP_USER_AGENT. Но об этом позже. Если при вызове программы использовался метод POST то входная информация отправляется на стандартный ввод (STDIN) программы. Но об этом тоже позже. Нас пока интересует результат работы программы. Результат работы программы (в виде HTML файла, двоичный данных или чего-либо еще, что должно возвращатся броузеру клиента) должно выводится через стандартный вывод (STDOUT). Причем включая специальные заголовки для клиента типа Content-type. Итак, теперь вооружившись теорией заполним досадный пробел между begin и end в нашей первой программе:

...
begin
  WriteLn('Content-type: text/html');       // тип вывода
  WriteLn;                                  // необходимая пустая строка
  WriteLn('<HTML>');                        // начало HTML файла
  WriteLn('<HEAD>');                        // начало заголовка файла
  WriteLn('<TITLE>Hello world!</TITLE>');   // название документа
  WriteLn('</HEAD>');                       // конец заголовка
  WriteLn('<BODY>');                        // начало тела документа
  WriteLn('<H1>Hello World !!!</H1>');      // большими буквочками !!!
  WriteLn('</BODY>');                       // конец тела документа
  WriteLn('</HTML>');                       // конец HTML файла
end.

Скомпилируйте и положите в Вашу cgi-bin директорию на сервере, а потом попробуйте её вызвать из броузера. Если что-то не вышло, то придется поковырятся с настройками броузера, сервера и прав доступа. И перепроверить программу на предмет очепяток :-). Если это не заработает, то нельзя двигаться дальше, так что придется заставить это работать.
Заработало? Вуаля! Идем дальше. (Между прочим размер программы пока всего 8,5 килобайт)

Пример 2:

Какие все-таки переменные окружения получает наша программа? Хорошо-бы иметь список того, с чем мы можем оперировать при обработке запроса. Если покопаться в хэлпах, то можно обнаружить пару функций LPVOID GetEnvironmentStrings(VOID) и BOOL FreeEnvironmentStrings(LPTSTR lpszEnvironmentBlock); Это то, что нам нужно. Создадим процедурку для заполнения объекта TStrings парами переменная=параметр.

procedure GetEnvStrings(L: TStrings);
var P: PChar; z: string; i: integer;
begin
  if not Assigned(L) then Exit;   // если список неопределен, то уходим
  P := GetEnvironmentStrings;     // получаем указатель на блок переменных
  try                             // защищаемся
    L.Clear;                      // чистим список на всякий случай
    z := ''; i := 0;              // обнуляем переменные поначалу
    while True do begin           // бесконечный цикл (ой! нужен инвариант цикла)
      z := StrPas(@p[i]);         // получаем пару имя=значение до NULL
      if z <> '' then begin       // если непустая пара ...
        L.Append(Z);              // то добавляем в список
        inc(i, Length(z) + 1);    // едем дальше по строке
      end else Break;             // иначе выходим из цикла (инвариант)
    end;                          // закрываем цикл
  finally                         // и наконец-то
    FreeEnvironmentStrings(P);    // освобождаем блок
  end;                            // конец защищённой зоны
end;

Теперь в программу в раздел uses нужно добавить Classes и SysUtils. Она, правда, вспухнет более чем в пять раз, но куда деваться. Функции нам нужны. Не писать-же их всех заново? Ладно, в конце концов это будет исполнятся на сервере и по сети исходники не пойдут. Что еще надо? Добавить пару переменных. Вот текст программы:

program envar;

uses
  Windows, Classes, SysUtils;

{$R *.RES}
{$APPTYPE CONSOLE}

   // ### ТУТ ВАША НОВАЯ ФУНКЦИЯ GetEnvStrings ###

var
  i: integer;   // переменная перебора для цикла
  X: TStrings;  // определяем переменную для нашего списка (абстрактный класс)

begin
  WriteLn('Content-type: text/html');              // тип вывода
  WriteLn;                                         // необходимая пустая строка
  WriteLn('<HTML>');                               // начало HTML файла
  WriteLn('<HEAD>');                               // начало заголовка файла
  WriteLn('<TITLE>Environment variables!</TITLE>');      // название документа
  WriteLn('</HEAD>');                              // конец заголовка
  WriteLn('<BODY>');                               // начало тела документа
  WriteLn('<H1>Environment variables:</H1>');      // большими буквочками !!!
  WriteLn('<BR>');                                 // перевод строки

  X := TStringList.Create;            // создаем объект-список
  GetEnvStrings(X);                   // собираем список переменных
  for i := 0 to X.Count - 1 do        // и в цикле
    WriteLn(X.Strings[i] + '<BR>');   // их выводим (с переводом строки)
  X.Free;                             // освобождаем объект-список

  WriteLn('</BODY>');                        // конец тела документа
  WriteLn('<HTML>');                        // конец HTML файла
end.

Скомпилируйте и это и снова положите в Вашу cgi-bin директорию на сервере, а потом попробуйте её вызвать из броузера. http://www.foo.com/cgi-bin/envar.exe Если предыдущий пример заработал, то и этот обязан выполниться. Впечатляет? Оказывается сколько всего видно серверу о Вас и, возможно, о прокси сервере :-) Кстати, не всегда все поля доступны. Иногда присутствие/отсутствие поля зависит от настройки операционной системы, сервера и проходящих прокси-серверов (может и броузера).
Как просто разбирать пары, надеюсь объяснять не нужно. Ну почитайте в хэлпе о классе TStrings (подсказка - свойства Names и Values).

Пример 3:

Часы, которые показывают время на сервере.
Это уже динамическая страница, она меняет свой контент в зависимости от внешних условий (времени на сервере :-). Тут не должно быть никаких сложностей. Вот код программы. (GetEnvStrings тут не нужна)

var
  Z:  string ;

 begin
  WriteLn( 'Content-type: text/html' );             // тип вывода
  WriteLn;                                        // необходимая пустая строка
  WriteLn( '<HTML>' );                              // начало HTML файла
  WriteLn( '<HEAD>' );                              // начало заголовка файла
  WriteLn( '<TITLE>Simple clock</TITLE>' );         // название документа
  WriteLn( '</HEAD>' );                             // конец заголовка
  WriteLn( '<BODY>' );                              // начало тела документа
  WriteLn( '<BR>' );                                // перевод строки
  Z := FormatDateTime( 'hh:nn:ss' , Now);           // создаем строку со временем
  WriteLn( '<H1><CENTER>'  + Z +  '</CENTER></H1>' ); // большими буквочками !!!
  WriteLn( '</BODY>' );                             // конец тела документа
  WriteLn( '</HTML>' );                             // конец HTML файла
 end .

Модуль Classes тут не нужен. Размер программы 32 килобайта. Часы статичные в том смысле, что время на них само не "бежит" (идет, течет) и нужна перезагрузка страницы для обновления.

Модификация: автоматическое обновление часов. Используем специальную команду для броузера клиента. Для этого вставим следующую строку сразу после строки с
  
WriteLn('<META HTTP-EQUIV="REFRESH" CONTENT="1">'); // автоперегрузка каждую сек.
Правда перегружать страницу каждую секунду это очень расточительно. Сисадмины могут и убить за трафик :-) при такой страничке он небольшой, но все-же...
примерно 160 байт * 60 сек * 60 мин * 24 часа * 30 дней = 395 мегабайта в месяц!!!
Так что с REFRESH поосторожней :-)

Пример 4:

Разбор параметров вызова из броузера. То что нам передает броузер в строке запроса после вопросительного знака есть параметр. Например вот запрос с параметром http://www.foo.com/cgi-bin/watch.exe?abcd=1234 Тут параметром будет abcd=1234.
Это передается в любом запросе GET или POST. Мы можем получить эту строчку из переменной окружения по имени QUERY_STRING. Можно воспользоваться недавно написанной процедурой, но можно написать функцию для наших целей попроще, потому, что мы точно знаем имя переменной окружения:

function GetEnvVar(const Name: string): string;
var z: integer;
begin
  Result := '';                      // начальное обнуление результата
  if Length(Name) = 0 then Exit;     // если имя пустое, то уходим
  z := GetEnvironmentVariable(@Name[1], NIL, 0);  // первый пустой вызов
  SetLength(Result, z);              // устанавливаем длину буфера как требуется
  z := GetEnvironmentVariable(@Name[1], @Result[1], z);  // нормальный вызов
end;

Тут следует иметь в виду, что не всякие символы попадут сюда из строки запроса.
Например пробел транслируется в %20 а с русским языком вообще проблемы. Тут много нюансов, накладываемых протоколом HTTP, поэтому изучите внимательно спецификацию, иначе ваш cgi-bin будет дырявый. Для наших тестов вполне подойдет упрощенная модель без пробелов и исключений. Соорудим простенькую спецификацию параметров нашего приложения (используем старый вариант с часами):
  • Если есть параметр Type=simple, то часы выводяться без секунд
  • Если есть параметр Color=число, то текст цвета определяется числом
Пары параметров разделяются запятыми. Тут основная трудность в разборе параметров. Если (как мы договорились) абстрагироватся от потенциальных сложностях кодирования параметров, то может помочь опять-таки StringList с его свойством CommaText. Вот фрагменты, целиком программу смотрите в исходниках:

...
  Query := GetEnvVar(QUERY_STRING);                // получаем строку запроса
  TxtPar := TStringList.Create;                    // создаем список строк
  TxtPar.CommaText := Query;                       // присваиваем туда параметр
  IsSimple := CompareStr(TxtPar.Values[ 'Type' ],  'simple' ) =  0 ;  // часы простые?
  ColorNum := StrToIntDef(TxtPar.Values[ 'Color' ], - 1 );  // берем цвет
  TxtPar.Free;                                     // убиваем список
...
   // тут создаем строку со временем
   if  IsSimple  then  Z := FormatDateTime( 'hh:nn' , Now)
               else  Z := FormatDateTime( 'hh:nn:ss' , Now);
...
   if  ColorNum >=  0   then     // если цвет указан, открываем фонт
    WriteLn( '<FONT COLOR="#'  + IntToHex(ColorNum,  6 ) +  '">' );
  WriteLn( '<H1><CENTER>'  + Z +  '</CENTER></H1>' );  // большими буквочками !!!
   if  ColorNum >=  0   then     // если цвет указан, закрываем фонт
    WriteLn( ' ' );
...

Попробуйте разные варианты вызовов, и увидите разницу:
  • http://www.foo.com/cgi-bin/watch.exe
  • http://www.foo.com/cgi-bin/watch.exe?Type=simple
  • http://www.foo.com/cgi-bin/watch.exe?Color=16000000
  • http://www.foo.com/cgi-bin/watch.exe?Type=simple,Color=16000000

Пример 5:

Разбор входных данных по методу POST (чтение из STDIN). Обычно из формы.
Поступим следующим образом. При первом вызове приложения предложим форму для написания своего имени, а при подтверждении (нажатии на кнопку "Enter") выведем приветствие этому товарисчу. Тому кто знаком с HTML не составит труда написать HTML-код для вывода формы. Остальные - смотрите исходники. Параметр ACTION оставим пустым (сами себе рулить будем :-) То что пришло по методу POST (как укажем в описании формы) падает на стандартный ввод. Кроме того устанавливается переменная окружения CONTENT_LENGTH с числом принятых байт. Входной поток содержит пары имя_в_форме=значение разделенные амперсандами "&" пробелы заменяются плюсами, спец. символы (такие как %, &, +) заменяются на escape код вида %хх где хх - шестнадцатиричный код. Русские символы целиком заменяются на escape-коды. Опять-таки отсылаю Вас к спецификации протокола HTTP. Как и ранее, соорудим процедуру разбора и заполнения списка строк. Исходный текст смотрите в исходниках, тут я приведу лишь заголовок: procedure FillPostParams(S: string; L: TStrings);
Итак, если ничего не прислали, выводим форму, если форма была подтверждена, и содержимое ненулевое, то выводим приветствие.
Запуск: http://www.foo.com/cgi-bin/forma.exe

Продолжение (возможно) следует.
Ожидаемые темы: вывод картинок, генерация картинок на лету, доступ к базе данных, методы внутренней авторизации, куки (печенья) и всякое другое относительно Web-а. Присылайте свои темы для разбора или свои предложения и/или содержание. Несмотря на засилье Perl, ASP, PHP, ActiveX, Java, Java-script это тоже неплохой инструмент.

Еремеев Алексей
Специально для Королевства "Delphi"



К материалу прилагаются файлы:


Смотрите также материалы по темам:
[Консольные приложения] [CGI]

 Обсуждение материала [ 22-08-2010 03:06 ] 29 сообщений
  
Время на сайте: GMT минус 5 часов

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

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования