Алексей Еремеев дата публикации 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>');
WriteLn('<HEAD>');
WriteLn('<TITLE>Hello world!</TITLE>');
WriteLn('</HEAD>');
WriteLn('<BODY>');
WriteLn('<H1>Hello World !!!</H1>');
WriteLn('</BODY>');
WriteLn('</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]);
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;
var
i: integer;
X: TStrings;
begin
WriteLn('Content-type: text/html');
WriteLn;
WriteLn('<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>');
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>' );
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>' );
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"
К материалу прилагаются файлы:- Пример №1 (6.3 K) обновление от 11/3/2005 7:54:00 AM
- Пример №2 (28 K) обновление от 11/3/2005 7:55:00 AM
- Пример №3 (40.6 K) обновление от 11/3/2005 7:55:00 AM
- Пример №4 (30.6 K) обновление от 11/3/2005 7:55:00 AM
- Пример №5 (30.2 K) обновление от 11/3/2005 7:55:00 AM
[Консольные приложения] [CGI]
Обсуждение материала [ 22-08-2010 03:06 ] 29 сообщений |