Учебное пособие по CGI-программированию от Леши

 

Оглавление:


Пару слов от автора
Краткое лирическое отступление насчет CGI
Итак ...приступим...
Переменные среды CGI
Прекрасный язык Perl
Заголовки запросов и ответов
Права Доступа
Генерация ответа
Обработка Форм
Изображения ismap
Анимация
Несколько советов по отладке
Trics and traps
Примеры приложений:
Кто посещает мою страничку?
Гостевая книга
Сщетчик посещений

Вместо заключения


Пару слов от автора


Что меня заставило взятся за этот нелегкий труд написания данного учебного пособия. Ну во первых то что практически НЕТ ничего по CGI-програмированию на русском языке, а большинству тех,кто хотел бы изучить CGI, документация на английском в отличии от тех немногих типа меня практически недоступна для понимания.Чтоб помочь им преодолеть этот в первую очередь языковый барьер я и сел писать эту книгу...
Еще причина ,отчасти перекликающаяся с первой, это то что когда говорят об интернет-программировании обычно излагают HTML со всеми тэгами, которые всем уже по ночам в кошмарах снятся ,ну а после чего начинают долго охать и ахать над прелестями нового аппаратно и платформо-независимого,переносимого,безопасного.....и.т.д. языка Java.Иногда в еще и могут тонким краешком затронуть JavaScript.Видя эту не побоюсь этого слова безнадежную ситуацию, я как доблестный CGI-программист решил хоть что-то поправить к лучшему. Если у меня это хоть немного удалось, то напишите мне.
Если также у вас есть какие-то вопросы -тоже пишите, я с радостью постараюсь ответить на них всех.

Леша.
paaa@uic.nnov.ru
http://www.uic.nnov.ru/~paaa/cgi-bin/contact.cgi

Краткое лирическое отступление насчет CGI

Итак что такое CGI- скрипты и вообще подобные вещи. Начнем с того что ваш браузер (когда вы набрали URL) соединяется по протоколу HTTP с указаным сервером и просит у него нужный файл,примерно так:

GET /~paaa/cgi-bin/guestbbok.cgi HTTP/1.0

Вот это самое главное в запросе

Ну тут дальше идет посылаемая браузером информация о себе и о том что более подробно ему надо.(Например Accept: */*)

Ну и если запрошен простой файл например .html то если если такой файл есть, То сервер отошлет браузеру ответ:

HTTP/1.0 200 Okay
Content-Type: text/html

<HTML>
<BODY>
.......
</BODY></HTML>



        В ответе , состоящем из зоголовка и тела, в заголовке содержится код возврата и информация о типе содержимого. Далее после пустой строки (она нужна чтоб отделить заголовок от тела) идет информация из самого документа , по заданому URL <HTML><BODY>...
Вот в принципе и весь WWW ....ходишь от ссылки к ссылке....
       А что если Нужно внести в этот унылый процесс что-нибудь по настоящему интерактивное , динамическое,прекрасное и великолепное....? Чтож есть ответ и на этот вопрос. Просто что если в запрашиваемом URL указать специальную программу (CGI,программа Common Gateway Inteface - Общего Шлюзового Интерфейса) и то что эта прога выдаст то и отправиться браузеру....Сервер запускает .cgi программу и она например обработав данные формы заносит вас куда-нибудь в свою базу данных,а вам сообщит что вы большой молодец :)
Ну надеюсь я вас заинтриговал......?


Итак ...приступим...



Краткие сведения о том что надо знать чтоб писать CGI скрипты:
Ну во-первых надо знать что такое интернет и как он работает (а вы знаете? ;))) ) Ну и чуть-чуть умения программировать (это самое главное)

    На кого ориентировано данное учебное пособие -спросите вы ? Ну в принципе на достаточно широкую аудиторию тех, кто занимается Интернет-программированием и кто хотел бы освоить премудрости интерфейса CGI. Данная книга будет весьма полезна для web-дизайнеров, системных администраторов интернет-серверов, программистов и для простых пользователей интернет, которые хотели бы сделать свой сайт по-настоящему достойным называться хорошим сайтом.
Так как интернет в основном строится на операционной системе UNIX , то изложеный сдесь материал может быть без особых модификаций реализован на практически любой UNIX-системе.
Кроме того, я также делаю предположение , что ваш web-сервер поддерживает интерфейс CGI и для вас эта поддержка включена. (на "халявных" серверах администраторы отключают CGI и SSI для пользовательских директорий - просто это такая политика - предоставлять только ОЧЕНЬ МИНИМАЛЬНЫЙ сервис.) Так что если вы хотите изучать CGI то вам нужет нормальный ,полнофункциональный сервер. Если же вы сами являетесь системным администратором на своем сервере , то для вас, естественно нет проблем, ведь включить CGI для какой-нибудь директории - это просто подправить одну строчку в файле конфигурации сервера.

Замечание:

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


Давайте вместе писанем какой нибудь простенький скриптик а потом я вам расскажу где сдесь собака порылась....
Ну сначала в своем домашнем каталоге создайте директорию cgi-bin:

cd public_html
mkdir cgi-bin
chmod 0777 cgi-bin

Последняя строчка будет очень важна.
Возьмите редактор и наберите:

#!/usr/bin/perl
#first.cgi
print "Content-Type: text/html\n\n";
print "<HTML><BODY>";
print "<H1>Hello you!!!</H1>";
print "</BODY></HTML>";


Сохраните его в директории cgi-bin под именем first.cgi .Ну как сохранили?
А теперь сделайте его исполняемым(ведь это программа):

chmod +x first.cgi

Ну вот,подходим к торжественному моменту.... наберите в строке браузера http://www.ваш.сервер.ru/~ваш_логин/cgi-bin/first.cgi
и посмотрите чо будет. Будет одно из двух ,либо скрипт заработает и вы увидите сгенерированую им страничку (поздравляю,в нашем полку прибыло!) либо Internal Server Error -тогда не расстраивайтесь,вы что-то сделали не так.
Вам тогда пригодится пособие по ловле блох.
Ну вопервых проверку синтаксиса можно осуществить следующим образом:

perl -с first.cgi

Perl вам сразу выдаст либо сообщения об ошибках(ну бывает,точку с запятой пропустили, скобочки или кавычки забыли закрыть...) это сразу по ходу дела поправимо.
Более грубая с логической точки зрения это пропустить вывод пустой строки, которая отделяет заголовок от тела:

print "Content-Type: text/html\n\n"; #Все Правильно
print "Content-Type: text/html\n";   #ОШИБКА!!!


Разберем скрипт:
Первая строка #!/usr/bin/perl Просто указывает где в системе расположен компилятор Perl. Обычно он находится /usr/bin/perl или /usr/local/bin/perl ,выяснить это можно одной из комманд which perl или whereis perl ну или (что очень долго) запустить полный поиск find / -name perl -print.
Вторая строка это просто коментарий -вы можете тыкать чо угодно после знака #, однако я пишу обычно во второй строке название скрипта, что очень удобно.
Затем идет print "Content-Type: text/html\n\n"; Это заголовок указывающий тип содержимого.
Все что скрипт печатает в свой стандартный вывод STDOUT идет на обработку к серверу. Пустая строка отделяет заголовок от тела,которое в нашем случае представляет собой

<HTML><BODY>
<H1>Hello you!!!</H1>
</BODY></HTML>

Сервер обработает ответ скрипта и на базе него сформирует и пошлет браузеру ответ.(Сервер обычно не изменяет тела сообщения,он только дополняет заголовок нужными для работы протокола HTTP полями)

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

 

Переменные среды CGI


     Предыдущий скрипт не содержал ничего особенно замечательного,так просто вываливал HTMLый текст который благополучно и отбражался на экране браузера.Но По настоящему мощь придает CGI возможность обработки параметров,которые переданы скрипту.например вы можете набрать
http://www.somehost.ru/somedir/cgi-bin/my_cgi.cgi?param=value
то есть вы хотите чтоб скрипт my_cgi.cgi обработал для вас параметер param со значением value (ну это например) или когда вы заполнили запрос в форме (в например yahoo или altavista).Ну это с точки зрения пользователя... А на сервере при запуске CGI-скрипта сервер формирует среду окружения в которой скрипт может найти всю доступную информацию о HTTP-соединении и о запросе.
Вот эти переменные:

REQUEST_METHOD

Это одно из самых главных поле используемое для определения метода запроса HTTP Протокол HTTP использует методы GET и POST для запроса к серверу.Они отличаются тем что при методе GET запрос является как-бы частью URL т.е. http://www..../myscript.cgi?request а при методе POST данные передаются в теле HTTP-запроса (при GET тело запроса пусто) и следовательно для CGI тоже есть различие при GET запрос идет в переменную QUERY_STRING а при POST подается на STDIN скрипта.
Пример:REQUEST_METHOD=GET

QUERY_STRING

Это строка запроса при методе GET. Вам всем известно что запрос из формы кодируется браузером поскольку не все символы разрешены в URL некоторые имеют специальное назначение. Теперь о методе urlencode: неплохо бы чисто формально напомнить,что все пробелы заменяются в URL на знак '+', а все специальные и непечатные символы на последовательность %hh ,где hh-шестнадцатиричный код символа,разделитель полей формы знак '&',так что при обработке форм надо произвести декодирование.
Пример:QUERY_STRING= name=quake+doomer&age=20&hobby=games

CONTENT_LENGTH

Длина в байтах тела запроса.При методе запроса POST необходимо считать со стандартного входа STDIN CONTENT_LENGTH байт,а потом производить их обработку.Обычно методом POST пользуются для передачи форм,содержащих потенциально большие области ввода текста TEXTAREA.При этом методе нет никаких ограничений,а при методе GET существуют ограничения на длину URL .
Пример:CONTENT_LENGTH=31

CONTENT_TYPE

Тип тела запроса(для форм кодированых выше указаным образом он application/x-www-form-urlencoded)

GATEWAY_INTERFACE

Версия протокола CGI.
Пример:GATEWAY_INTERFACE=CGI/1.1

REMOTE_ADDR

IP-Адрес удаленого хоста,делающего данный запрос.
Пример:REMOTE_ADDR=139.142.24.157

REMOTE_HOST

Если запрашивающий хост имеет доменное имя,то эта переменная содержит его, в противном случае -тот же самый IP-адресс что и REMOTE_ADDR
Пример:REMOTE_HOST=idsoftware.com

SCRIPT_NAME

Имя скрипта,исполизованое в запросе.Для получения реального пути на сервере используйте SCRIPT_FILENAME
Пример:SCRIPT_NAME=/~paaa/guestbook.cgi

SCRIPT_FILENAME

Имя файла скрипта на сервере.
Пример:SCRIPT_FILENAME=/home/p/paaa/public_html/cgi-bin/guestbook.cgi

SERVER_NAME

Имя серера ,чаще всего доменное как www.microsoft.com ,но в редких случаях за неимением такового может быть IP-адресом как 157.151.74.254
Пример:SERVER_NAME=www.uic.nnov.ru

SERVER_PORT

TCP-Порт сервера используюшийся для соединения .По умолчаниию HTTP-порт 80, хотя может быть в некоторых случаях другим.
Пример:SERVER_PORT=80

SERVER_PROTOCOL

Версия протокола сервера.
Пример:SERVER_PROTOCOL=HTTP/1.1

SERVER_SOFTWARE

Програмное обеспечение сервера.
Пример:Apache/1.0

AUTH_TYPE, REMOTE_USER

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

Переменные заголовка HTTP-запроса.
За исключением тех строк из заголовка HTTP-запроса которые были включены в другие переменные,сервер приделывает строкам префикс HTTP_ и заменяет знаки '-' на '_':
HTTP_ACCEPT

Давая запрос на сервер браузер обычно расчитывает получить информацию определеного формата,и для этого он в заголовке запроса указывает поле Accept:,Отсюда скрипту поступает cписок тех MIME,которые браузер готов принять в качестве ответа от сервера.
Пример:HTTP_ACCEPT=text/html,text/plain,image/gif

HTTP_USER_AGENT

Браузер обычно посылает на сервер и информацию о себе,чтоб базируясь на знании особеностей и недостатков конкретных браузеров CGI-скрипт мог выдать информацию с учетом этого. Например,разные браузеры могут поддерживать или не поддерживать какие-то HTMLые тэги.
Пример:HTTP_USER_AGENT=Mozila/2.01 Gold(Win95;I)

HTTP_HOST

Имя хоста к которому обращается браузер. Так как физически на одном сервере может находиться сразу много серверов (Виртуальные Хосты), то должен быть способ сообщить серверу к какому именно идет обращение. Скрипт же может тоже в зависимости от этой переменной производить различные действия, таким если он используется на сайтах сразу нескольких виртуальных хостов.
Пример:HTTP_HOST=www.nnov.city.ru



Ну,начнем применять на практике усвоеные уроки.

#!/usr/bin/perl
#vars.cgi
sub urldecode{    #очень полезная функция декодирования
 local($val)=@_;  #запроса,будет почти в каждой вашей CGI-программе
 $val=~s/\+/ /g;
 $val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>CGI-Variables</TITLE></HEAD>\n";
print "<BODY>\n";
print "Enter here something:<ISINDEX><BR>\n";
print "Your request is:$ENV{'REQUEST_STRING'}<BR>\n";
print "Decoded request is:urldecode($ENV{'REQUEST_STRING'})<BR>\n";
print "<HR>\n";
print "Variables:<BR>\n";
print "<I><B>REQUEST_METHOD</B></I>=$ENV{'REQUEST_METHOD'}<BR>\n";
print "<I><B>QUERY_STRING</B></I>=$ENV{'QUERY_STRING'}<BR>\n";
print "<I><B>CONTENT_LENGTH</B></I>=$ENV{'CONTENT_LENGTH'}<BR>\n";
print "<I><B>CONTENT_TYPE</B></I>=$ENV{'CONTENT_TYPE'}<BR>\n";
print "<I><B>GATEWAY_INTERFACE</B></I>=$ENV{'GATEWAY_INTERFACE'}<BR>\n";
print "<I><B>REMOTE_ADDR</B></I>=$ENV{'REMOTE_ADDR'}<BR>\n";
print "<I><B>REMOTE_HOST</B></I>=$ENV{'REMOTE_HOST'}<BR>\n";
print "<I><B>SCRIPT_NAME</B></I>=$ENV{'SCRIPT_NAME'}<BR>\n";
print "<I><B>SCRIPT_FILENAME</B></I>=$ENV{'SCRIPT_FILENAME'}<BR>\n";
print "<I><B>SERVER_NAME</B></I>=$ENV{'SERVER_NAME'}<BR>\n";
print "<I><B>SERVER_PORT</B></I>=$ENV{'SERVER_PORT'}<BR>\n";
print "<I><B>SERVER_PROTOCOL</B></I>=$ENV{'SERVER_PROTOCOL'}<BR>\n";
print "<I><B>SERVER_SOFTWARE</B></I>=$ENV{'SERVER_SOFTWARE'}<BR>\n";
print "<I><B>HTTP_ACCEPT</B></I>=$ENV{'HTTP_ACCEPT'}<BR>\n";
print "<I><B>HTTP_USER_AGENT</B></I>=$ENV{'HTTP_USER_AGENT'}<BR>\n";
print "<I><B>HTTP_HOST</B></I>=$ENV{'HTTP_HOST'}<BR>\n";
print "<HR>\n";
print "All enviroment:<BR>\n";
foreach $env_var (keys %ENV){
 print "<I>$env_var=$ENV{$env_var}</I><BR>\n";
 }
print "</BODY></HTML>\n";

Так как все ваши .cgi -файлы должны быть исполняемыми то чтоб облегчить себе жизнь заведите себе в директории cgi-bin командный файл mkcgi ,содержащий

#!/bin/sh
#mkcgi
chmod +x *.cgi

и сделайте его в свою очередь исполняемым chmod +x mkcgi -он сильно упростит вам жизнь.
Ну а теперь запускайте скрипт......
Изучив информацию,выдаваемую данным скриптом вы сможете лучше ориентироваться в переменных окружения CGI.

 

Прекрасный язык Perl


       Вы наверное обратили свое внимание что CGI скрипты пишутся обычно на языке Perl (Practical Extraction and Report Language)- очень удобном языке,впитавшем из других все лучшие черты.Может у вас возникнуть сомнение :Ну вот!Изучать новый язык программирования!? Спешу вас успокоить,изучение Perl не будет в тягость (я сужу по своему опыту!). Вы даже сами не заметите как выучите его.Если вы хоть когда-нибудь программировали скажем на C и использовали утилиту grep для поиска регулярных выражений в тексте,то вам будет еще легче. Для Perl родной платформой является Unix поэтому пользователям PC он мало известен. Мое целенаправленое доведение Perl до широкой публики началось с того что я скачал Perl под Windows (фирмы ActiveWare) К нему прилагается отличная гипертекстовая HTML- документация, даже быстрого просмотра которой хватит , чтобы начать хорошо и широко использовать его. Хоть он значительно уступает и по скорости и по эффективности своему Unix'ному аналогу, все равно самый лучший способ изучить язык это программировать на нем. Если вы как я дома используете большую часть времени не Windows а Unix то с изучением Perl у вас вообще не должно быть особых сложностей. Я же от себя могу сказать, что даже после небольшого опыта изучения его, он стал моим любимым языком программирования....

      Все в нем сделано для удобства программиста (в отличии например от Java ;( )
Начнем с переменных,они в Perl бывают 3х типов скаларные,списковые(массивы) и хэши(ассоциативные массивы). Для указания компилятору(да и для немалого удобства программиста) перед именем скалярной переменной стоит знак '$' перед массивом '@',перед хешем '%'. т.е. например $scalar_var,@array_var,%hash_var Скалярные переменные могут быть как числовые так и строковые,но это не надо указывать Perl сам по контексту в зависимости от операций может привести одно к другому.
Например: "123"+"4" будет 127 (или "127") так как операция '+' действует над числами а вот если применить операцию конкатенации строк '.' то строковое "test" . 1 будет "test1"
Ну а вот операции над скалярными переменными:

ОперацыиОписаниеПример
+ - * / %Арифметическиеprint 2*7+4/(8%3);
print int(127/15); #целая часть
**Возведение в степеньprint 2**16;
++ --Инкремент-декремент$i++;
& | ^ ~ << >>Побитовые$x=3;$y=4;
print $x|$y;
print $x&$y;
== != < > <= >= <=>Числовые операции сравненияif($x==9){print "Ok!";}
eq ne lt gt le ge cmpстрковые операции сравненияif($game eq 'doom'){print "You are doomer!\n";}
|| && !Логическиеif(($x==9)||($game eq 'doom')){print "hello you!\n";}
?:Условный оператор$x=($game eq 'quake'?9:8);
,Последовательное вычисление$x=10,$y=20;
.Конкатенация$x='http://'.'www.uic.nnov.ru';
xПовторение$x='1234'x5; #$x='12341234123412341234'
=~Сопоставление с образцомif($url=~/http/){print "HTTP";}
!~То же но с отрицаниемif($url!~/http/){print "No HTTP";}
= += -= *= /= %= **= |= &= ^= ~= <<= >>= .= x=Присваивание$x+=$y;

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

$x='qwerty';
print 'my var is $x'; #выведет my var is $x
print "my var is $x"; #выведет my var is qwerty

Списки: Спискочные переменные начинаются с символа '@' конструируются следующим образом

@List1=(1,2,5,70);
@List2=(12,23,@List1); #12,23,1,2,5,70
@Rgb=($r,$g,$b);

Также можно список использовать как lvalue:

@List=(1,2,3..8,15);
($x,$y,$z)=@List;        #$x=1,$y=2,$z=3
($x,$y,$z,@list2)=@List; #$x=1,$y=2,$z=3,@list2=(4,5,6,7,8,15);
($r,$g,$b)=@Rgb;

Можно обращаться к нескольким,выбраным элементам массива(срезу массива):

@list=(1..10);
@list[2,3,5,9]=(100,200,300,400); #@list=(1,100,200,4,300,6,7,8,400,10)
@list[1,10]=@list[10,1];#меняет местами элементы

Обратится к скаларному значению -элементу массива можно $имя_массива[индекс], сдесь обратите внимание на знак '$'- мы ведь обращаемся к скаляру-элементу.
Теперь немного о хешах:
хеш это такой массив который состоит из пар ключ-значение, весь хеш обозначается %хеш ,к отдельным элементам доступ $хеш{скалярное выражение} конструируется хеш так:

$my_hash{1}="doom";
$my_hash{'quake'}="www.idsoftware.com";
$my_hash{1+2}=100;

Хеш может быть также сконструирован из массива с четным числом элементов где пары превращаются в ключ-значение

%hash=(1,20,2,100);#аналогично $hash{1}=20;$hash{2}=100;

удаление из хеша -операция delete:

delete $hash{1};

есть функции выдающие ключи и значения соответственно.

%hash=(1,20,2,100,3,'doom');
@k=keys %hash;  #@k=(1,2,3);
@v=values %hash;#@v=(20,100,'doom');

Операторы:
Набор операторов в Perl Очень широк,многие из них прямые аналоги имеющихся в других языках,например if,for,while;но есть и значительные улучшения имеюшихся и конечно новые...
Тот же самый оператор if имеет две формы (как когда удобнее):

if(условие)оператор;
оператор if условие;

В пару к оператору if имеется оператор unless : означающий if с отрицанием:

unless(($method eq 'GET')||($method eq 'POST')){print "Unsupported method";}
print "Ok" unless $x < $y;

Также в пару while существует until
синтаксис оператора for полностью аналогичен C:

for($i=0;$i<10;$i++){
 print $i;
 }

новшеством(и приятным) является foreach позволяющий пройтись по всем элементам массива,присваивая по очереди его элементы какой-то переменной, его синтаксис такой:

foreach $переменная (@массив){
 блок операторов;
 }
или
foreach (@массив){
 операторы;
 }

      Последний пример особенно важен для упрощения вашего тяжкого труда програмиста и демонстрирует интересную особенность Perl-переменную по умолчанию $_: в оргомном количестве операторов и функций при опускании аргумента она подразумевается по умолчанию. Она также по умолчанию сопоставляется с регулярным выражением:

следующий пример
@Data=<STDIN>;
foreach(@Data){
 chomp;
 print if /^From:/;
 }
аналогичен такому:
@Data=<STDIN>;
foreach $_ (@Data){
 chomp($_);
 print $_ if $_ =~ /^From:/;

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

Регулярные выражения.
регулярное выражение записывается между двух слэшей /рег_выр/

if(/abc/){
 print '$_ содержит abc\n';
 }

это самый простой пример применения регулярного выражения а теперь посложнее вот тут в табличке (из того что я помню наизусть):

Символ