Perl для CGI / Запросы и ответы

+ Введение Синтаксис Запросы и ответы Разное по теме MySQL в Perl Примеры
Реклама

Заголовки запросов и ответов

Даже если вы и знаете кое-что о HTTP все равно не лишне будет вспомнить о том как это все работает, тем более на эту информацию придется ориентироваться при написании CGI скриптов.

Этапы соединения

  1. Первый этап это когда HTTP-клиент (браузер) соединяется с сервером. Для этого он использует протокол TCP/IP, соединение происходит к известному клиенту TCP-порту (80 -номер порта HTTP) (другие сервисы сидят на других портах, например FTP и SMTP на 21 и 25).
  2. Вторым этапом идет запрос клиента. Клиент передает заголовок запроса и возможно (в зависимости от метода) тело сообщения запроса. В заголовке обязательно указывается метод, URL, и версия HTTP, и может быть еще несколько необязательных полей.
  3. Третий этап - ответ сервера, который опять таки состоит из заголовка, в котором сервер указывает версию HTTP и код статуса, который может говорить о успешном или неуспешном результате и его причинах. Далее идет тело ответа.
  4. Четвертым этапом происходит разрыв TCP/IP соединения.

HTTP -запрос

Запрос состоит из Строки запроса (она обязательна) и остальных полей. Синтаксис строки: МЕТОД <SP> URL <SP> HTTP/версия <CRLF>, где <SP> -пробел ,<CRLF> -переход на новую строку.

Методы HTTP

Сушествуют и другие, реже применяемые методы, например PUT -для сохранения передавемых данных в указаном URL и DELETE для удаления ресурса.

Поля заголовка запроса.

После строки запроса идут поля заголовка запроса. Поля общего (general-header) заголовка (он общий как для запросов так и для ответов):

Поля относящиеся к запросу (Request-Header):

Заголовок информации сообщения (Entity-Header) применяется как в запросах так и в ответах (при этом некоторые поля только в ответах):

Другие поля:

Примеры запросов

Простейший запрос:
GET /index.html HTTP/1.0

Посложнее:
GET /somedir/somedoc.html HTTP/1.0
User-Agent: Mozilla/2.0
Accept: text/html
Accept: text/plain
Accept: image/gif

Передача данных CGI- скрипту через метод GET:
GET /~paaa/cgi-bin/test.cgi?name=Dmitry&organization=%D3%ED%E8%E2%E5%F0%F1%E8%F2%E5%F2+%CD%E8%E6%ED%E5%E3%EE+%CD%EE%E2%E3%EE%F0%EE%E4%E0&Name=&email=&comment= HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif

Используя метод POST данные передаются в теле сообщения запроса:
POST /~paaa/cgi-bin/test.cgi HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif
Content-Type: application/x-www-form-urlencoded
Content-Length: 131

name=Lesha
&organization=%D3%ED%E8%E2%E5%F0%F1%E8%F2%E5%F2+%CD%E8%E6%ED%E5%E3%EE+%CD%EE%E2%E3%EE%F0%EE%E4%E0&Name=
&email=
&comment=

Ответы HTTP-сервера

Ответ идет от сервера. Состоит он из строки состояния и затем поля ответа. Общий заголовок (General-Header) и заголовок тела сообщения (Entity-Header) уже описаны при обсуждении запроса. И еще идет заголовок ответа (Response-Header). Строка состояния имеет следующий формат: HTTP/version <SP> Status-Code <SP> Status-Phrase, где HTTP/version версия, Status-Code - 3-х значный код, Status-Phrase текстовая фраза, поясняющая код, пример: HTTP/1.0 200 Ok, где 200 - код означающий успешную обработку запроса, что и поясняет "Ok". Заголовок ответа состоит из полей:

Коды ответов HTTP
Код статуса Значение
200 OK
201 Успешная команда POST
202 Запрос принят
203 Запрос GET или HEAD выполнен
204 Запрос выполнен но нет содержимого
300 Ресурс обнаружен в нескольких местах
301 Ресурс удален навсегда
302 Ресурс отсутствует временно
304 Ресурс был изменен
400 Плохой запрос от клиента
401 Неавторизованый запрос
402 Необходима оплата за ресурс
403 Доступ Запрещен
404 Ресурс не найден
405 Метод не применим для данного ресурса
406 Недопустимый тип ресурса
410 Ресурс Недоступен
500 Внутренняя ошибка сервера
(это по вашу душу, юные CGI-программисты ;( )
501 Метод не выполнен
502 Неисправный шлюз либо перегруз сервера
503 Сервер недоступен/тайм-аут шлюза
504 Вторичный шлюз/тайм-аут сервера

Более подробное описание всех кодов можно найти в RFC-1945.

Несколько примеров:

HTTP/1.0 200 Ok
Date: Wed, 25 Sep 1998 23:00:00 GMT
Server: Apache/1.1
MIME-version: 1.0
Last-Modified: Mon 15 Nov 1996 15:20:12 GMT
Content-Type: text/html
Content-Length: 2000

<HTML><HEAD><TITLE>Hello</TITLE></HEAD>
<BODY bgcolor="green" text="yellow">
......
</HTML>

А вот такое сервер выдаст в неудачном случае:

HTTP/1.0 404 Not Found

CGI-заголовок

В том случае когда запрашиваемый URL есть CGI-скрипт, сервер, базируясь на данных запроса, создает среду переменных CGI и передает управление скрипту. Скрипт должен выдать CGI-заголовок, после которого и идет тело ответа, сгенерированное скриптом. Заголовок (CGI-Header) состоит из полей:

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

Обычно такое выдает скрипт:
Content-Type: text/html

<HTML><HEAD>.......

Но иногда такое (когда он служит для перенаправления):
Location: http://www.mustdie.ru/

А вот пример возврата статуса:
Content-Type: image/gif
Status: 190 Its seems great like a playing doom! WOW!

GIF89a........

nph-скрипты

Иногда возникает необходимость чтобы CGI-скрипт сам отвечал напрямую клиенту, минуя разбор заголовка. Это во-первых уменьшает нагрузку на сервер, и во-вторых, что самое главное, такой прямой ответ клиенту позволяет скрипту полностью контролировать транзакцию. Для этого существуют nph-скрипты (Not Parse Header), имя скрипта должно начинаться с префикса "nph-". Например "nph-animate.cgi". Такие скрипты сами формируют HTTP-ответ клиенту, что полезно при анимации:

#!/usr/bin/perl
#nph-animate.cgi

$times = 20;
#Заготовьте несколько небольших gif-файлов для этой программы
@files = qw(img0.gif img1.gif img2.gif img3.gif);

select (STDOUT);
$|=1; #autoflush mode on
#Generate header
print "HTTP/1.0 200 Okay\n";
print "Content-Type: multipart/x-mixed-replace;boundary=myboundary\n\n";

print "--myboundary\n";
for ($num=1;$num<=$times;$num++) {
foreach $file (@files) {
print "Content-Type: image/gif\n\n";
open(PIC,"$file");
print <PIC>;
close(PIC);
print "\n--myboundary\n";
sleep(3);
}
}
print "\n--myboundary--\n";

Этот пример вам выдаст анимацию, составленую из нескольких .gif -файлов. Если же вы получили вместо анимации сообщение об ошибках, то вам следует, перейти к следующей главе, которая поведает вам о правах доступа - того, без чего Unix не был бы Unix'ом.

Права Доступа

Я бы ни за что не написал этот раздел, если бы он не был так важен. Сидя в пределах своей домашней директории и занимаясь только тем, что качаете с Инета всякую херню, вы возможно и не задавались некоторыми вопросами. А зря. Ведь немного надо, чтоб попортить нервы начинающему CGI -програмисту. Одна из таких вещей это права доступа.

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

Итак есть идентификатор пользователя. Также имеется идентификатор группы. Группа служит для выделения пользователей по группам. Например у пользователей группы users (обычные пользователи) не такие права как у группы wheels (административная группа). Каждый процесс который вами запущен (Будь то Netscape, терминал, или текстовый редактор) получают ваши идентификаторы пользователя и группы. Таким образом исполняются от вашего имени.

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

Следующее новшество по сравнению с DOS это права доступа к файлу. Их может сменить только тот пользователь которому принадлежит файл, или супервизор. Это в отличии от DOS, где каждая дрянь типа вируса может снять атрибут readonly читать и писать все файлы ;() Права доступа задаются обычно числом в восьмеричной записи и разбиты на 3 части по 3 бита. Каждая часть задает права доступа для конкретной группы:

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

Значения прав доступа
Бит Описание
8 Право на чтение для пользователя
7 Право на запись для пользователя
6 Право на исполнение для пользователя
5 Право на чтение для группы
4 Право на запись для группы
3 Право на исполнение для группы
2 Право на чтение для всех остальных
1 Право на запись для всех остальных
0 Право на исполнение для всех остальных

Изменяются права командой chmod, ее синтаксис такой:

chmod [u|g|o]{+|-}{r|w|x} file
chmod number file

где u-user, g-group, o-other, r-read, w-write, x-execute; --удалить, +-установить

Примеры:

chmod +r file.txt #разрешает всем право на чтения файла
chmod u+w file.txt #устанавливает для владельца файла право на запись в него
chmod +x gbook.cgi #право на исполнение для всех,как для ползователя,группы,и для других
chmod 0777 cgi-bin #Разрешает самые широкие права доступа для cgi-bin

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

Можно выполнить скрипт, только если есть права на его исполнение. Вот почему следует давать chmod +x *.cgi иначе ваши скрипты станут просто недоступными. Но и это еще не все. Ваш скрипт может обращатся к вашим файлам (например ведет базу данных гостевой книги). Все выглядит нормально, но только ничего не работает. Файл, в который вы намеревались писать, не открывается. Знакомая проблема ;(( ? Так вот чтобы вы не мучались в догадках, Ваш скрипт не может получить доступ к вашим файлам, потому что он выполняется не вами (не с вашим идентификатором), а от имени nobody (непривелигированый пользователь). Это мера предосторожности направлена на то, чтоб скрипты, "взбесившись" из-за неправильно переданых параметров (или вообще от глюков) не могли ничего повредить ценного и важного на сервере. Поэтому к тем файлам, к которым скрипт по смыслу должен обращатся нужно присвоить самые широкие права доступа 0777. Например в случае гостевой книги chmod 0777 guestbook.dat. Если также важно чтоб скрипты могли заводить новые файлы в cgi-bin то надо дать также права на это (chmod 0777 cgi-bin).

Если вы видите что ваш скрипт не может обратится к какому-то файлу, то это в 99% случаев из-за вашей забывчивости!!! На самый крайний случай воспользуйтесь setuid-скриптами (к этому делу, если вы на это решились, отнеситесь ОЧЕНЬ серьезно, так как целые тома по безопасности в Unix посвящены именно setuid-программам). Хочу сразу предупредить, сам я таких не так уж много писал, да и вам не особенно советую. Но для общего, как говорится, развития,имейте в виду следующую информацию:

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

Самый классический пример setuid-программ это программа passwd, предназначеная для смены пароля пользователя. Такие данные как пароль и прочие характеристики пользователей хранятся в специальном файле, который имеет огромное значение при входе в систему. Так как это системный файл, то открыть к нему доступ на запись всем-значит подвергнуть ВСЮ систему риску, ведь любое неправильное изменение его повлечет катастрофические последствия (в конце концов бывает просто хулиганство). Поэтому доступ к этому файлу закрыт для всех пользователей. А что если надо сменить пароль? Запускаем программу passwd! Если глянуть на ее аттрибуты, то видно что она принадлежит root -супервизору, и еще имеет установленый бит setuid. Так корректно обходится эта проблема. Если вы все-же решили попытаться, то знайте, что сделать программу setuid можно коммандой: chmod +s myprogramm

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

#!/usr/bin/perl
#listmydir.cgi
print "Content-Type: text/html\n\n";
if(!(-r '..')){
print ".. is not allowed for reading ;)))))\n";
}
else{
@list=glob('../*');
foreach(@list){
print "<A href=\"$_\">$_</A>";
print "&nbsp;readable" if -r;
print "&nbsp;writable" if -w;
print "&nbsp;executable" if -x;
print "<BR>\n";
}
}

Генерация ответа

Большую часть того что нужно знать о генерации ответа, я рассказал в разделе Заголовки запросов и ответов? Нет, не угадали! Я не буду здесь говорить о всяком дизайне того что вы выдаете. Этому вы успели напрактиковатся на HTML-страничках. Я поговорю о MIME (Multipurpose Internet Mail Extension). И о разных браузерах.

Стандарт MIME появился в электронной почте (e-mail) потому что остро стала проблемма пересылки по e-mail различных данных в различных форматах. Так как HTTP тоже работает с различными типами данных, то поэтому тоже использует MIME для своих нужд. Типы MIME состоят из Типа и подтипа (например text/plain, где text-указывает на наличие текстового содержимого, а plain-уточняет его как простой текст). Приведеный ниже список (он далеко не полн, типов MIME огромное количество) описывает некоторые часто встречающиеся типы: text/html text/plain text/richtext image/gif image/jpeg image/tiff audio/basic audio/32kadpcm audio/ video/mpeg video/quicktime multipart/mixed multipart/alternate multipart/ application/octet-stream application/msword application/postscript message/digest

Информация о MIME больше, возможно, пригодится вам в том случае если вы собираетесь работать из ваших скриптов с электронной почтой, но и для WWW она не повредит. Особенно знание Content-Type:.

Теперь поговорим о разных браузерах вы знаете что, браузеры бывают разные, разных версий, на разных платформах, поддерживают и не разные тэги и глюки у них тоже разные...;((( . Это могло попортить много нервов WEB-дизайнерам и конечно же нам, CGI-програмистам. Профессионально написаный сайт от просто хорошего отличается тем что хорошо выглядит Не только на экране того браузера, которым пользуется сам его автор, но и на других тоже.

Если вы используете JavaScript для своих страничек, то вы уже наверно использовали (или хотя бы вам в голову приходила мысль использовать) свойства navigator.AppName, navigator.AppCodeName, navigator.appVersion, navigator.userAgent:

<SCRIPT language="JavaScript">
if(navigator.AppName=="Netscape"){
/*Сделать чо-нибудь специфичное для Netscape*/
}
else if(navigator.AppName=="Microsoft Internet Explorer"){
/*Сделать чо-нибудь специфичное для Explorer*/
}
else{
/*Не делаем специфичных вещей-хрен его знает с каким браузером мы имеем дело*/
}
</SCRIPT>
или
<SCRIPT language="JavaScript">
if((navigator.AppName=="Netscape")&&(parseFloat(navigator.appVersion)<3.0)){
document.writeln("Пользуетесь слишком старым браузером");
}
</SCRIPT>

Ну не волнуйтесь вы так, мы CGI-программисты не в самых худших условиях на этот счет. Вспомните о том что браузер сам при запросе посылает вам данные о себе и о своей версии. И делает он это для того, чтобы эту информацию можно было учесть. В запросе он указывает User-Agent: которое и попадает на сервере в переменную среды HTTP_USER_AGENT, которую и можно использовать. Например если в ней содержится Mozilla/3.01Gold (Win95;I), то значит вы имеете дело с Netscape (Mozilla-кодовое название Netscape Navigator'а), версии 3.01Gold и далее после имени и версии может следовать необязательная информация, например как в приведеном примере о платформе Win95 и о том является ли версия U - для США (USA) или I - международной (International). Напомню, что такая информация необязательна. То есть если браузер послал информацию User-Agent:, то гарантировано расчитывать вы можете только на Название/Версия). Ну вот я слишком много развел демагогии, пора переходить к практическим примерам.

Допустим ваш скрипт генерирует какие-то тэги, которые слишком старые браузеры не поддерживают, причем без них не обойдешся, они составляют всю 'изюминку' сайта:

#!/usr/bin/perl
#oldbrowser.cgi
print "Content-Type: text/html\n\n";
if(defined ($ENV{'HTTP_USER_AGENT'})){
$browser=$ENV{'HTTP_USER_AGENT'};
($vers)=($browser=~/\/(\d+\.\d+)/);
if(($browser=~/mozilla/i)&&($vers<=2.0)){
print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
print "<BODY bgcolor=\"red\" text=\"black\">";
print "<CENTER><H1>Ваш Netscape Слишком старый для этого сайта";
print "(старость не радость;))</H1></CENTER>";
print "</BODY></HTML>";
exit;
}
if(($browser=~/msie/i)&&($vers<=3.0)){
print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
print "<BODY bgcolor=\"red\" text=\"black\">";
print "<CENTER><H1>Ваш Explorer устарел";
print "(а не пора ли сделать апгрейт хотя бы до 4.0 версии)</H1></CENTER>";
print "</BODY></HTML>";
exit;
}
}
print "<HTML><HEAD>.........";

Ну уже почувствовали, насколько это здорово. А вот еще примерчик. Это из разряда того, что тэги бывают разные. Например в Explorer есть тэг BGSOUND предназначеный для проигрывания музыки на страничке. В Netscape этого тега нет, и поэтому для втыкания музыки приходится использовать подключаемые модули plugin. Мутится с этими Плугинами Вам в облом, а хочется побаловать человека хорошей музыкой, если браузер позволяет.

...
...
if($ENV{'HTTP_USER_AGENT'}=~/msie/i){
print "<BGSOUND src=\"jmj00.mid\">";
}
elsif($ENV{'HTTP_USER_AGENT'}=~/mozilla/i){
#Оставлю сдесь коментарий, что воткну что-нибудь типа музыки, для Netscap'а ,
#Когда мне не будет так в облом это делать.......
}

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

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

#!/usr/bin/perl
#download.cgi

sub urldecode{
local($val)=@_;
$val=~s/\+/ /g;
$val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
return $val;
}
@Filelist=qw(index.html readme.txt jmj00.mid gunshot.wav foto.gif);
@Sel_list=();
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST'){sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query eq ''){
#Если никаких данных не подано на обработку,то сгенерируем форму,
#которую и предложим заполнить пользователю.
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>File Downloading</TITLE></HEAD>";
print "<BODY bgcolor=\"white\">";
print "Выберите файлы которые вы хотите загрузить:<BR>";
print "<FORM METHOD=\"POST\">";
print "<SELECT NAME=\"file\" size=4 multiple>";
foreach(@Filelist){
print "<OPTION value=\"$_\">$_";
}
print "</SELECT><BR>";
print "<INPUT TYPE=\"Submit\" value=\"Download!\">";
print "</FORM>";
print "</BODY></HTML>"
}
else{
@formfields=split(/&/,$query);
foreach(@formfields){
if(/^file=(.*)/){push(@Sel_list,urldecode($1));}
}
unless(@Sel_list){
print "Content-Type: text/html\n\n";
print "<HTML><BODY><CENTER><H1>Вы должны выбрать что-то из списка";
print "</H1></CENTER></BODY></HTML>";
}
else{
print "Content-Type: multipart/mixed;boundary=\"bhy3e23r4t34tnehtpo7678nneu4232y213vdg\"\n\n";
print "--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
foreach(@Sel_list){
print "Content-Type: application/x-qwerty; name=\"$_\"\n\n";
open(F,"$_");
print <F>;
close(F);
print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
}
print "Content-Type: text/html\n\n";
print "<HTML><H1>Thats all folks!</H1></HTML>";
print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg--\n";
}
}

Обработка Форм

Ну вот, вы уже знаете достаточно. Кое в чем уже успели приобрести опыт, пришло время перейти к очень важной теме - обработке форм. При всей простоте (кажушейся) это едва ли не самое главное предназначение всего стандарта CGI . Куда бы вы не зашли на любой уважающий себя сайт, везде вы встретите формы, которые вам предложат заполнить. В этом деле можно положится только на CGI, так как Java и JavaScript, выполняющиеся на страничке у клиента не имеют доступа к серверу, на котором находится сайт.

Коротко вспомним о том, что происходит при рассматриваемом процессе поближе, так сказать на трезвую голову ;). Итак браузер требует у сервера определенный URL (это может быть как простой документ, так и сгенерированный CGI), в этом документе может содержаться форма. Отображая такой документ браузер также выводит элементы формы (кнопки, поля ввода, поля ввода пароля, переключатели, радио-кнопки, списки, текстовые области, скрытые поля). И со всем этим добром пользователь может взаимодействовать. К форме естественно имеет доступ и встроеный язык программирования JavaScript - он может как использовать форму для своих нужд, не передавая CGI, так и помогать пользователю в заполнении формы.

После того, как пользователь заполнил форму он нажимат кнопку Submit, которая говорит, что форму надо отправить на сервер. Браузер собирает все имена и значения элементов формы, кодирует их методом urlencode и, в зависимости от указаного в тэге FORM метода, вызывает GET или POST с указаным URL, передавая ему данные. На сервере CGI-скрипту это попадает (в зависимости от метода) либо в переменную QUERY_STRING либо на STDIN. Скрипт может проверить данные, занести их в какую-нибудь базу данных, может как yahoo выполнить какой-нибудь поиск, может что-нибудь вычислить... да мало ли что он может. Все зависит только от нашей фантазии. В конце концов скрипт выдает браузеру ответ, который он и отображает. В этом ответе может содержаться все что вашей душе угодно, от сообщения об удачном или неправильном запросе до таких ответов, по сравнению с которыми yahoo и altavista подвиснут от зависти. Главное чтоб вам и тем кто посещает ваш сайт это нравилось.;)

Ну а теперь немного о синтаксисе элементов форм, их описании и, самое главное, особенностях при обработке CGI-скриптом.

Итак небольшой экскурс в HTML:

FORM
<FORM action="http://......cgi" method="GET"|"POST" enctype="encodingType"
name="formName" target="windowName" onSubmit="Handler">
</FORM>

Параметры тэга:

Пример типичной формы:

<FORM action="http://www.uic.nnov.ru/~paaa/cgi-bin/test.cgi" method="POST">
.........Поля формы.........
</FORM>

Форма может содержать элементы. Элементы имеют имена, которые используются для кодирования пар имя=значение. Некоторые Элементы не передаются CGI, а используются JavaScript для управления, например кнопки. Некоторые поля передаются только в тех случаях, когда в них что-то выбрано, например списки и переключатели. Остальные поля передаются всегда, даже когда они пустые.

Например:

<FORM action="http://www.doom/cgi-bin/test.cgi">
Your Name:<INPUT name="Name"><BR>
E-Mail:<INPUT name="Email"><BR>
Are you doomer:<INPUT type="checkbox" name="doomer" value="Yes">
<INPUT type="submit" value="Send Form!">
</FORM>

Допустим вы ввели имя lesha и адрес paaa@uic.nnov.ru, при этом выбрали переключатель. После нажатия кнопки будет отправлен вот такой запрос:

http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru&doomer=Yes

Если же вы не выбрали переключатель, то запрос будет таким:

http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru

как видите элемент doomer не вошел в строку запроса. Теперь попробуйте оставить поля редактирования пустыми:

http://www.doom/cgi-bin/test.cgi?Name=&Email=

Эти элементы (Name и Email) присутствуют и сообщают что они пустые.

Кнопки

Поля ввода

Скрытое поле

<INPUT type="hidden" name="hiddName" value="hidValue">

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

<FORM onSubmit="return false;">
Этого здесь вам не видно, поле-скрытое.
<INPUT type="hidden" name="formNum" value="3">
</FORM>

Переключатель

<INPUT type="checkbox" name="checkboxname" value="checkboxValue" [checked] onClick="Handler">Text

В отличии от кнопки, атрибут value здесь не задает надпись на переключателе, а его значение (внутреннее). Поэтому если надо что-то подписать, пишите рядом в ним. Может быть сразу выбраным если указан атрибут checked. Если value не указано то значение по умолчанию "ON". Передается только в том случае, когда выбран.

<FORM onSubmit="return false;">
<INPUT type="checkbox" name="inet" value="Yes"
checked>Доступ в Интернет
</FORM>

Доступ в Инернет

Радио-кнопка

<INPUT type="radio" name="radioName" value="radioVal1" [checked] onClick="Handler">Text

В отличие от checkbox может быть несколько радиокнопок с одинаковым параметром name, но с разными value. Из них передается только та, что выбрана. Одна из них может быть изначально выбрана по умолчанию checked. Например:

<FORM onSubmit="return false;">
Вы уверены?<BR>
<INPUT type="radio" name="Radbut" value="Yes" checked>Yes
<INPUT type="radio" name="Radbut" value="No">No
</FORM>

Вы уверены?
Yes No

Список

<SELECT name="SelectName" size=число [multiple] [обработчики] >
<OPTION value="optionValue1" [selected]>Опция 1</OPTION>
<OPTION value="optionValue2" [selected]>Опция 2</OPTION>
<OPTION value="optionValue3" [selected]>Опция 3</OPTION>
.....
<OPTION value="optionValueN" [selected]>Опция N</OPTION>
</SELECT>

Задает список, позволяющий выбрать одну (или несколько) опций из списка. Если атрибут multiple не указан, то можно выбрать только одну из опций. Его значение всегда передается, т.к. всегда хоть одно выбрано. Можно указать размер видимой части списка атрибутом size (Если опций больше появится скролинг). Если указан атрибут multiple, то передаются все выбраные опции, т.е. может передатся несколько раз ?SelectName=opt1&SelectName=opt2&SelectName=opt9 если выбраны несколько опций. А может и ни разу, если ничего не выбрано из списка. Для выбора нескольких элементов нужно удерживать Ctrl или Shift. Можно задавать обработчики событий: onBlur, onChange, onFocus.

<FORM onSubmit="return false;">
Ваш цвет:<BR>
<SELECT name="singleSel">
<OPTION value="white">Белый</OPTION>
<OPTION value="black">Черный</OPTION>
<OPTION value="magenta">Фиолетовый</OPTION>
<OPTION value="green">Зеленый</OPTION>
<OPTION value="red">Красный</OPTION>
</FORM>

Ваш цвет:

<FORM onSubmit="return false;">
Какие сорта пива вы пили:<BR>
<SELECT name="miltiSel" multiple size=4>
<OPTION value="Балтика">Балтика</OPTION>
<OPTION value="Толстяк">Толстяк</OPTION>
<OPTION value="Премьер">Премьер</OPTION>
<OPTION value="Хольстен">Хольстен</OPTION>
<OPTION value="Бавария">Бавария</OPTION>
<OPTION value="Coca-Cola ;)">Coca-Cola ;)</OPTION>
</SELECT>
</FORM>

Небольшая Помощь JavaScript

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

Вот например как можно применить JavaScript для предварительного контроля правильности. Допустим простейшая форма содержит имя и возраст. Имя не должно быть пустым, а возраст должен состоять из цифр:

<HTML><HEAD>
<SCRIPT language="JavaScript">
<!--
function IsNumber(data){
var NumStr="0123456789";
var ch;var count;
for(var i=0;i<data.length;i++){
ch=data.substring(i,i+1);
if(NumStr.indexOf(ch)!=-1)count++;
}
if(counter==data.length)return true;
else return false;
}
function IsEmpty(data){
if(data.length==0)return true;
else return false;
}
function IsFormOk(f){
if(IsEmpty(f.Name.value)){
alert('Имя не должно быть пустой строкой');
return false;
}
if(!IsNumber(f.Age.value)){
alert('Возраст должен состоять из цифр');
return false;
}
return true;
}
//--></SCRIPT></HEAD>
<BODY>
<FORM action="http://www.test.ru/cgi-bin/test.cgi" onSubmit="IsFormOk(this.form)">
Your Name:<INPUT name="Name"><BR>
Your age:<INPUT name="Age"><BR>
<INPUT type="submit" value="Послать Данные">
</FORM>
</BODY></HTML>

Ну вот. На этом можно закончить краткое введение в HTML. Итак, у нас на входе скрипта данные формы, закодированые методом urlencode положенные в переменную QUERY_STRING или подаваемые на STDIN. Мы должны во-первых их получить:

if($ENV{'REQUEST_METHOD'} eq 'GET'){#Анализируем метод,GET или POST
$query=$ENV{'QUERY_STRING'};
}
elsif($ENV{'REQUEST_METHOD'} eq 'POST'){
sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});
}

Вот,мы уже считали наш запрос в переменную $query. Теперь пришло самое время ее обработать. Мы знаем что поля разделены символом '&' значит используем его в качестве разделителя функции split:

@formfields=split(/&/,$query);

Вот разделили, а теперь организуем цикл foreach по полученым элементам @formfields:

foreach(@formfields){
if(/^Name=(.*)/){$name=urldecode($1);}
if(/^Age=(.*)/){$age=urldecode($1);}
}

Здесь выражение в регулярном выражении в круглых скобках (.*) после знака '=', запоминается в скалярную переменную $1, которая затем и декодируется нашей старой и знакомой функцией urldecode (я предупреждал, что она будет почти в каждой вашей CGI-программе):

sub urldecode{ #очень полезная функция декодирования
local($val)=@_; #запроса,будет почти в каждой вашей CGI-программе
$val=~s/\+/ /g;
$val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
return $val;
}

Так мы проходим по всем полям, которые нам переданы. Это стандартный подход, он годится в качестве шаблона.

У вас может возникнуть вопрос,а что делать если вам переданы данные от списка у которого задана возможность выбора нескольких элементов и данные поступают в таком виде: Sel=opt1&Sel=opt2&Sel=opt9. Тут тоже нет никаких проблем, просто запихиваем эти поступающие значения в массив:

foreach(@formfields){
.....
if(/^Sel=(.*)/){push @Sel,urldecode($1);}
.....
}

И потом спокойно оперируем с Полученым Массивом @Sel.

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

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

<HTML><!-- HTML файл с формой,можете повесить его себе на сайт! ->
<HEAD><TITLE>Социологический опрос насчет курения</TITLE></HEAD>
<BODY>
<CENTER><H1>Социологический опрос насчет курения</H1></CENTER>
<FORM action="cgi-bin/smoketest.cgi">
<TABLE>
<TR><TD>Ваш возраст:</TD><TD><INPUT name="age"></TD></TR>
<TR><TD>Вы курите(Y/N):</TD>
<TD><INPUT type="radio" name="smoke" value="Yes" checked>Да
<INPUT type="radio" name="smoke" value="No">Нет</TD></TR>
<TR><TD>Как вы относитесь если рядом кто-то курит?</TD>
<TD><SELECT name="sm_near">
<OPTION value="0">Резко негативно
<OPTION value="1">Негативно
<OPTION value="2" selected>Мне все равно
<OPTION value="3">Позитивно
<OPTION value="4">Резко позитивно
</SELECT>
</TD></TR>
<TR><TD>Сколько вы выкуриваете в день?</TD>
<TD><SELECT name="sm_day">
<OPTION value="0">Ни сколько
<OPTION value="1">1 сигарету
<OPTION value="2">2 сигареты
<OPTION value="5">около 5
<OPTION value="0.5pac">полпачки
<OPTION value="pac">пачку
<OPTION value="2pac">2 пачки
<OPTION value="more">больше
</SELECT>
</TD></TR>
<TR><TD>Как давно вы начали курить?</TD>
<TD><SELECT name="sm_stage">
<OPTION value="noatall">Не начинал
<OPTION value="onetime">Бросил
<OPTION value="0.5year">Полгода
<OPTION value="1year">Год
<OPTION value="2year">2 Года
<OPTION value="5year">5 Лет
<OPTION value="more">Больше
</SELECT>
</TD></TR>
<TR><TD>Считаете ли вы это опасным для своего здоровья?</TD>
<TD><SELECT name="sm_danger">
<OPTION value="0">Очень Опасно
<OPTION value="1">Думаю,что да
<OPTION value="2" selected>Не знаю
<OPTION value="3">Может самую малость
<OPTION value="4">Нет,Безопасно.
</SELECT>
</TD></TR>
<TR><TD>Хотите ли вы бросить?</TD>
<TD><SELECT name="sm_nosmoke">
<OPTION value="0">Уже бросаю
<OPTION value="1">Думаю бросить
<OPTION value="2" selected>Иногда
<OPTION value="3">Очень Редко
<OPTION value="4">Никогда.
</SELECT>
</TD></TR>
<TR><TD><INPUT type="submit" value="Послать Данные"></TD>
<TD><INPUT type="reset" value="Очистить Форму"></TD></TR>
</TABLE>
</FORM>
</BODY></HTML>

А вот скрипт для его обработки:

#!/usr/bin/perl
#smoketest.cgi
$datafile="smoke.dat";
sub urldecode{
local($val)=@_;
$val=~s/\+/ /g;
$val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
return $val;
}
sub print_err{
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Error!!</TITLE></HEAD>";
print "<BODY><CENTER><H1>@_</H1>";
print "</BODY></HTML>";
exit;
}
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST')
{sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query ne ''){
@formfields=split(/&/,$query);
foreach(@formfields){
if(/^age=(.*)/){$age=urldecode($1);}
if(/^smoke=(.*)/){$smoke=urldecode($1);}
if(/^sm_near=(.*)/){$sm_near=urldecode($1);}
if(/^sm_day=(.*)/){$sm_day=urldecode($1);}
if(/^sm_stage=(.*)/){$sm_stage=urldecode($1);}
if(/^sm_danger=(.*)/){$sm_danger=urldecode($1);}
if(/^sm_nosmoke=(.*)/){$sm_nosmoke=urldecode($1);}
}
if((!$age)||($age=~/\D/)){
print "Content-Type: text/html\n\n";
print "<HTML><BODY><H2>Возраст введен неправильно,должен состоять из цифр.</H2>";
print "<FORM><INPUT type=\"button\" value=\"Вернуться назад к Анкете\"";
print "onClick=\"history.back();\"></FORM>";
print "</BODY></HTML>";
}
$anket_str=join('\t',($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke));
open(DATA,">>$datafile") || print_err("Cannot open $datafile $!");
print DATA "$anket_str\n";
close(DATA);
}
open(DATA,"$datafile") || print_err("Cannot open $datafile $!");
@AllData=<DATA>;
close(DATA);
$total=$#AllData;
foreach(@AllData){
($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke)=split(/\t/,$_);
$smok_total++ if ($smoke eq 'Yes');
$nosmok_total++ if ($smoke eq 'No');
if($age<16){$age16_total++;
if($smoke eq 'Yes'){$age16_sm++;}else{$age16_nosm++;}
}
if(($age>16)&&($age<=18)){$age16_18_total++;
if($smoke eq 'Yes'){$age16_18_sm++;}else{$age16_18_nosm++;}
}
if(($age>18)&&($age<=20)){$age18_20_total++;
if($smoke eq 'Yes'){$age18_20_sm++;}else{$age18_20_nosm++;}
}
if($age>20){$age20_total++;
if($smoke eq 'Yes'){$age20_sm++;}else{$age20_nosm++;}
}
if($sm_near eq '0'){$near0++;}
if($sm_near eq '1'){$near1++;}
if($sm_near eq '2'){$near2++;}
if($sm_near eq '3'){$near3++;}
if($sm_near eq '4'){$near4++;}
if($sm_day eq '0'){$day0++;}
if($sm_day eq '1'){$day1++;}
if($sm_day eq '2'){$day2++;}
if($sm_day eq '5'){$day5++;}
if($sm_day eq '0.5pac'){$dayhalfpac++;}
if($sm_day eq 'pac'){$daypac++;}
if($sm_day eq '2pac'){$day2pac++;}
if($sm_day eq 'more'){$daymore++;}
if($sm_stage eq 'noatall'){$stagenoatall++;}
if($sm_stage eq 'onetime'){$statgeonetime++;}
if($sm_stage eq '0.5year'){$stagehalfyear++;}
if($sm_stage eq '1year'){$stage1year++;}
if($sm_stage eq '2year'){$stage2year++;}
if($sm_stage eq '5year'){$stage5year++;}
if($sm_stage eq 'more'){$stagemore++;}
if($sm_danger eq '0'){$danger0++;}
if($sm_danger eq '1'){$danger1++;}
if($sm_danger eq '2'){$danger2++;}
if($sm_danger eq '3'){$danger3++;}
if($sm_danger eq '4'){$danger4++;}
if($sm_nosmoke eq '0'){$stopsmoke0++;}
if($sm_nosmoke eq '1'){$stopsmoke1++;}
if($sm_nosmoke eq '2'){$stopsmoke2++;}
if($sm_nosmoke eq '3'){$stopsmoke3++;}
if($sm_nosmoke eq '4'){$stopsmoke4++;}
}
#########
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Результаты обработки данных</TITLE></HEAD>";
print "<BODY bgcolor=\"yellow\">";
unless($total){print "<H1>Еще нет данных</H1></BODY></HTML>";exit;}
print "<CENTER><H1>Результаты обработки данных</H1></CENTER>";
print "<BR>\n";
print "Обработано анкет: $total<BR>\n";
print "Общие данные Всего:<BR>\n";
print "Курящие:$smok_total (".(($smok_total/$total)*100) ."%)<BR>\n";
print "Некурящие:$nosmok_total (".(($nosmok_total/$total)*100)."%)<BR>\n";
print "<TABLE>\n";
print "<TR><TD colspan=4>Возрастные группы:(<16,16..18,18..20,>20)</TD></TR>\n";
print "<TR><TD>Возраст</TD><TD>Курящие</TD>
<TD>Некурящие</TD><TD>Всего</TD></TR>\n";
print "<TR><TD>&lt;16:</TD><TD>$age16_sm</TD>
<TD>$age16_nosm</TD><TD>$age16_total</TD></TR>\n";
print "<TR><TD>16..18:</TD><TD>$age16_18_sm</TD><TD>
$age16_18_nosm</TD><TD>$age16_18_total</TD></TR>\n";
print "<TR><TD>18..20:</TD><TD>$age18_20_sm</TD>
<TD>$age18_20_nosm</TD><TD>$age18_20_total</TD></TR>\n";
print "<TR><TD>&gt;20:</TD><TD>$age20_sm</TD>
<TD>$age20_nosm</TD><TD>$age20_total</TD></TR>";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Отношение когда кто-то курит рядом:(%)</TD></TR>\n";
print "<TR><TD>Резко негативно</TD><TD>".(($near0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Негативно </TD><TD>".(($near1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Мне все равно </TD><TD>".(($near2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Позитивно </TD><TD>".(($near3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Резко позитивно</TD><TD>".(($near4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>В среднем выкуривают:(%)</TD></TR>\n";
print "<TR><TD>не курят: </TD><TD>".(($day0/$total)*100)."</TD></TR>\n";
print "<TR><TD>1 сигарету:</TD><TD>".(($day1/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 сигареты:</TD><TD>".(($day2/$total)*100)."</TD></TR>\n";
print "<TR><TD>5 сигарет: </TD><TD>".(($day5/$total)*100)."</TD></TR>\n";
print "<TR><TD>полпачки: </TD><TD>".(($dayhalfpac/$total)*100)."</TD></TR>\n";
print "<TR><TD>пачку: </TD><TD>".(($daypac/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 пачки: </TD><TD>".(($day2pac/$total)*100)."</TD></TR>\n";
print "<TR><TD>больше: </TD><TD>".(($daymore/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Стаж курения:(%)</TD></TR>\n";
print "<TR><TD>Не начинал</TD><TD>".(($stagenoatall /$total)*100)."</TD></TR>\n";
print "<TR><TD>Бросил </TD><TD>".(($statgeonetime/$total)*100)."</TD></TR>\n";
print "<TR><TD>Полгода </TD><TD>".(($stagehalfyear/$total)*100)."</TD></TR>\n";
print "<TR><TD>Год </TD><TD>".(($stage1year /$total)*100)."</TD></TR>\n";
print "<TR><TD>2 Года </TD><TD>".(($stage2year /$total)*100)."</TD></TR>\n";
print "<TR><TD>5 Лет </TD><TD>".(($stage5year /$total)*100)."</TD></TR>\n";
print "<TR><TD>Больше </TD><TD>".(($stagemore /$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Курение опасно:(%)</TD></TR>\n";
print "<TR><TD>Очень Опасно </TD><TD>".(($danger0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю,что да </TD><TD>".(($danger1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Не знаю </TD><TD>".(($danger2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Может самую малость</TD><TD>".(($danger3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Нет,Безопасно. </TD><TD>".(($danger4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Хотели ли вы бросить:(%)</TD></TR>\n";
print "<TR><TD>Уже бросаю </TD><TD>".(($stopsmoke0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю бросить</TD><TD>".(($stopsmoke1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Иногда </TD><TD>".(($stopsmoke2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Очень Редко </TD><TD>".(($stopsmoke3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Никогда. </TD><TD>".(($stopsmoke4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "</BODY></HTML>";

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

Реклама
Карта сайта