__________________________________________________________
CGI -- Common Gateway Interface - Общий шлюзовый интерфейс. Стандарт интерфейса внешних программ с http-сервером.
HTTP -- клиент-серверный протокол, следовательно со стороны CGI-программы, как серверного процесса, все взаимодействие выглядит следующим образом
Получение данных от клиента
Обработка данных
Выдача ответа клиенту.
Взаимодействие с клиентом обеспечивается так: Он заполняет форму своими значениями, нажимает на кнопку "submit", броузер кодирует данные соответствующим образом и отправляет их серверу.
Обычно клиенту выдают текст в формате HTML. Для того, чтобы сервер и клиент вас поняли, необходимо сказать, что вы выдаете, c помощью заголовка Content-Type: mime-type/mime-subtype.
print "Content-Type: text/htmln";
# Мы выдаем текст в формате HTML. Также можно: text/plain -- простой текст, в
# браузере отобразится аналогично тексту, заключМнному между тегами
# <pre></pre>. image/gif -- Картинка, формат gif video/mpeg --
# mpeg-видео И целая куча других форматов, см. файл mime.types из apache
print "n";
# <-- еще одна пустая строка, обозначает конец вывода наших
# заголовков. ВАЖНО!
# Теперь мы можем написать свой текст клиенту
print qq{
<html>
<head>
<title>CGI программа</title>
</head>
<body>
<h1> CGI программа</h1>
</body>
</head>
};
Производится тегами <form> и </form>. Тег определения форм имеет следующие атрибуты
скрипт на сервере, который будет запущен на сервере для обработки данных формы.
тип взаимодействия с сервером. Может иметь значения GET и POST. Плюс, еще некоторые специальные, которые вы можете посмотреть в rfc
и др.
Формы не могут быть вложенными.
Определяются тегами <input>, <textarea> и <select> тег <input>:
Элемент ввода "Опция"
Элемент ввода, который не виден пользователю
в Netscape Navigator позволяет загрузить файл на сервер
Изображение. Если по нему щелкнуть, это вызовет submit формы и серверу будут выданы две переменные вида name.x и name.y, где name -- то, что вы пропишете в name=... тега <input>
Элемент ввода Пароль. Позволяет ввести строку, которая на экране отображается звездочками. Никаких методов защиты при передаче на сервер не применяется
Радиокнопки
Кнопка сброса значений формы на начальные
Кнопка отправки формы на сервер
строка ввода
Все теги <input> имеют атрибут name -- Наименование элемента. Служит для идентификации при передаче на сервер, а также другие типа value, width, etc, название и назначение которых можно опять-таки посмотреть в учебнике.
Тег <textarea> -- Поле многострочного ввода.
Тег <select> -- списочный выбор
<form action=/cgi-bin/myscript.pl method=GET>
Имя: <input type=string name=name><p>
Пол: <input type=radio name=gender value=male>Мужской
<input type=radio name=gender value=female>Женский
<input type=submit><input type=reset>
</form>
Это определяется методом формы, GET или POST
В случае GET сервер установит переменную окружения QUERY_STRING в виде name1=vaue1&name2=value2&..&nameN=valueN.
В случае POST аналогичная строка будет записана на стандартный ввод. Ее длину можно получить через переменную окружения CONTENT_LENGTH.
В обоих случаях данные будут закодированы по следующему алгоритму:
Если ASCII код символа больше 32 и меньше 128, то он будет выдан без изменения.
если символ - пробел, то он заменится на "+" (плюсик, без кавычек) все остальное преобразуется в вид %xx, где xx -- шестнадцатеричный код символа. Если вам повезло и у вас "Русский apache", то он преобразует его в нормальную кодировку.
tr/+/ /;
s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
(списано из CGI.pm)
На самом деле, можно не возится со всем этим, а использовать модуль CGI.pm (Имеется в поставке Perl 5.004 и более поздних. Если у вас perl версии 4 или 1, нужно срочно взять на CPAN новую версию perl)
Предположим мы делаем поисковую систему, тогда у нас должен быть HTML с формой и программа, которая будет выполнять поиск.
search.html
<html>
<head>
<title>Поиск</title>
</head>
<<body>
<h1>Чего искать?</h1>
<form action=/cgi-bin/search.pl method=get>
Строка для поиска: <input type=text name=string width=70><p>
Искать в <input type=checkbox name=searchin value=internet>Интернет
<input type=checkbox name=searchin value=intranet>Интранет
<input type=checkbox name=searchin value=extranet>Экстранет<p>
<input type=submit value=Давай!><input type=reset value="Нет, не надо">
</form>
</body>
</html>
Программа:
#!/usr/bin/perl -- поменяйте, как надо
use CGI qw(:standard);
print "Content-Type: text/htmlnn"; # Не забудьте про два "n"
$string = param("string");
@searchin = param("searchin"); # searchin это checkbox и его значения
# возвращаются списком
# Искать мы сегодня ничего не будем
print "<html>
<body>
<h1>Мы сегодня ничего не ищем</h1>
Но, если бы искали, то: <br>
Искали бы строку <b>$string</b><br>n";
print "В <b>" . join(" ", @searchin) . "</b>n";
print "
</body>
</html>
";
__________________________________________________________
Выдавайте заголовки в таком виде:
print "Content-Type: type/sub-typen"; # Подставьте Ваш тип/подтип
print "Pragma: no-cachen"; # Для HTTP/1.0 клиентов
print "Cache-Control: no-cachen"; # Для HTTP/1.1
print "Expires: Thu Jan 1 00:00:00 1970nn"; # Это уже любой броузер должен
# понять
__________________________________________________________
В заголовке напишите:
print "Status: 302n";
# Или 301. Разница состоит в том, что по стандарту 301 значит "перемещён
# навсегда", а 302 -- "перемещён временно"
print "Location: ВАШ Новый URLn";
# URL должен быть указан абсолютный
print "URI: ВАШ новый URLnn"; # Для http/1.0
Подробности: см. rfc1945(http/1.0), rfc2068(http/1.1)
__________________________________________________________
В поставку CGI.pm входит пример file_upload.cgi.
Учтите, что если у вас система, которая разделяет бинарные и текстовые файлы -- вам понадобится использовать binmode.
Также, помните, что "Русский Apache" не будет производить перекодировку multipart форм.
__________________________________________________________
Возьмите на CPAN библиотеку libwww-perl и смотрите lwpcook(3), там написаны основы использования библиотеки и есть примеры.
__________________________________________________________
Плюшки работают следующим образом: программа CGI добавляет в свой ответ директиву Set-Cookie, а клиент (если он поддерживает плюшки), при следующих запросах возвращает их в заголовке Cookie. Сами плюшки представляют из себя пары имя=значение.
Set-Cookie: имя=значение; expires=дата; path=путь; domain=домен; secure
Сервер может установить не более 20 плюшек, размер всей плюшки не может превышать 4Kb.
единственный обязательный параметр. И имя, и значение могут быть строками текста, не содержащими символов перевода строки, пробелов, ; и табуляции. Если необходимо их использовать, можно эти символы закодировать при помощи стандартного uri-кодирования.
устанавливается дата истечения срока действия плюшки. После этой дата плюшка клиентом не возвращается. Если дата не установлена, то плюшка действует до завершения работы браузера.
обозначает путь на сервере, для которого возвращается эта плюшка, если не указано, то только для того, который ее создал.
домен, для которого возвращается эта плюшка. Может быть доменом уровня 2 и выше, т.е. .example.ru, а не .ru. Если не установлен, то плюшка возвращается только серверу, который выдал плюшку.
указывает, что плюшка должна возвращаться только по защищенному соединению (SSL).
Cookie: имя1=значение1; имя2=значение2;....;имяn=значениеn
Программы CGI могут получить пары имя-значение через переменную окружения HTTP_COOKIE. В модуле CGI.pm значение можно получить при помощи метода cookie().
В деталях, механизм плюшек описан в rfc2109 (HTTP State Management Mechanism. D. Kristol, L. Montulli. February 1997.)
setcookie.pl
#!/usr/bin/perl
print "Content-Type: text/htmln";
print "Set-Cookie: mycookie=some+stringnn";
print "<html><head><title>Установка плюшки</title></head>n";
print "<body><h1>Установка плюшки</h1>n";
print "<a href=getcookie.pl>Щелкни здесь,</a> чтобы посмотреть, что
получилось.
print "</body></html>";
getcookie.pl;
#!/usr/bin/perl
use CGI qw(:standard);
print "Content-Type: text/htmlnn";
print "<html><head><title>Получение плюшки</title></head>n";
print "<body><h1>Плюшка</h1>n";
print "mycookie = ", getcookie('mycookie');
print "</body></html>";
Использование других языков для написания CGI-програм
__________________________________________________________
Да, можно. CGI не зависит от используемого языка программирования.
CGI-скрипты могут быть написанв на любом языке программирования (C, C++, PASCAL, FORTRAN и т.п.) или на командном языке (shell, cshell, командный язык MS-DOS, Perl и т.д.).
__________________________________________________________
Tom Boutell's cgic (C library) <URL:http://www.boutell.com/cgic/>
Eugene Eric Kim's cgihtml (C library) <URL:http://www.eekim.com/software/cgihtml/>
JemTek's (C Library) <URL:http://www.camtech.com.au/jemtek/cgi/itemb/>
Nick Kew's CGI++ (C++ library) <URL:http://www3.pair.com/webthing/cgiplusplus/>
Stephen F. Booth's Cgicc (C++ library) <URL:http://www.cs.hmc.edu/ sbooth/cgicc/>
S. Martin's CGI.h C++ library <http://www1.dpe.net/C++/>
http://www.pms.informatik.uni-muenchen.de/mitarbeiter/panne/haskell_itembs/CGI.html
http://www.cse.ogi.edu/~erik/Personal/cgi.htm
Java CGI package ftp://ftp.orbits.com/pub/software/default.htm. Лучше использовать сервлеты:
http://www.servletscentral.org/
http://www.javasoft.com/XXXXXX
http://java.apache.org/
__________________________________________________________
$fh=$query->upload($name);
open (OUTFILE,"$userpath/$username/$name.tmp.jpg");
Всегда нужно указывать направление потока в open, "<" отсутствует в данном примере.
Обязательно необходимо параноидально проверять каждую переменную в open().
как минимум:
$var ~ s/[^w-._d]//g;
$var =~ s/..//g;
Или лучше всего использовать перед каждым системным вызовом open(), system(),
exec(), `..` и т.д. функцию:
# check_file($var);
sub check_file{
my($str) = @_;
if (($str =~ /[^dw-_./]/) || ($str =~ /../)){
die "Detected filename mistification !!!."
}
return 0;
}
$fh=$query->upload($name);
check_file("$userpath/$username/$name.tmp.jpg");
open (OUTFILE,"<$userpath/$username/$name.tmp.jpg")|| die "Невозможно открыть файл.";
__________________________________________________________
Рекомендуется сохранять все документы на сервере в кодировке KOI8-R.
Клиенту документы в любом случае буду выдаваться в зависимости от его операционной системы, для пользователей Windows в cp1251, Unix - koi8-r.
В крайнем случае по запросу клиента возможно переключение на хранение документов в windows cp1251. KOI8-R является кодировкой сервера и использование чуждой кодировки совместно с CGI скриптами приведет к возникновению проблем с сортировкой, регулярными выражениями, строковыми операциями, разделения верхнего и нижнего регистра и т.д.
Для активации русской локали необходимо в скрипт поместить код следующего вида:
use POSIX;
use locale;
$loc = POSIX::setlocale( &POSIX::LC_ALL, "ru_RU.KOI8-R" );
__________________________________________________________
На этапе тестирования работы скриптов, при отсутствии доступа к лог файлам, необходимо представить вывод ошибок интерпретатора perl в виде читаемом из обычно браузера. Для установки данного режима, на время отладки, рекомендуется вписать в начале неработающего скрипта use CGI::Carp 'fatalsToBrowser';
Не забудьте правильно прописать путь к интерпретатору языка perl в первой строке вашего скрипта (#!/usr/bin/perl) и убедится в отсутствии символа возврата каретки (код 0x0D) в надписи "#!/usr/bin/perl" (должен быть только символ перевода строки 0x0A, сохраните файл как unix текст в FAR [Shift+F2]). Если скрипт так и не подает признаков жизни, проверьте права доступа на документы закаченные на сервер, например с помощью ftp клиента файлового менеджера FAR. Для html документов права должны быть "-rw-r--r--", для CGI скриптов - "-rwxr-xr-x".
!!! Две самые распространенные ошибки пользователей хостинга - это неправильно выставленные права доступа и присутствие символа возврата каретки (0x0D) в вашем скрипте.
__________________________________________________________
Кусок кода для закачки файла на сервер должен выглядеть примерно так:
#!/usr/bin/perl
use CGI qw/:standard/;
$in_file = param('file');
..............
if ( $in_file ) {
open (JPEG,">${http_dir}${pics_base}/${in_num}_${time}.jpg");
binmode(JPEG);
flock(JPEG, 2);
while ($bytesread=read($in_file,$buffer,1024)) {
print JPEG $buffer;
}
close(JPEG);
close $in_file;
# Если нужно сохранить закачиваемый
# объект во временный файл кусок кода будет выглядеть:
$tmp_file=tmpFileName($in_file);
open (JPEG,">$realimg_dir/$g_user_name/$file_name");
binmode(JPEG);
binmode($t_file);
flock(JPEG, 2);
$counter=0;
while (read ($t_file, $buff, 1024)) {
print JPEG $buff;
}
close(JPEG);
close $t_file;
unlink($t_file);
}
Форма для закачки файла должна выглядеть следующим образом:
<FORM METHOD="POST" ACTION="/cgi-bin/script.cgi" enctype="multipart/form-data">
<input type="file" name="file">
!!! В случае использования русского апача с активизированной автоматической перекодировкой, для соблюдения целостности закачиваемого бинарного объекта необходимо в директории со скриптом создать файл .htaccess содержащий директиву CharsetRecodeMultipartForms Off.
__________________________________________________________
Установка модуля:
Создайте у себя в домашнем каталоге директорию lib и в ней поддиректорию perl5 (cd ~;mkdir lib;cd lib;mkdir perl5).
Распакуйте устанавливаемый модуль (tar -xzf модуль.tar.gz).
Запустите "perl Makefile.pl prefix=$HOME;make;make test;make install".
В дальнейшем в скриптах, использующих установленный модуль, необходимо прописать:
use lib "HOME/lib/perl5";
use lib "HOME/lib/perl5/site_perl";
где, HOME - путь к вашей домашней директории (/usr/hosting/аккаунт/)
__________________________________________________________
perldoc Date::Calc
Напрмер, для вычисления дня недели по дате, нужно использовать следующую конструкцию:
use Date::Calc qw(Day_of_Week);
@weekday=('Вс','Пн','Вт','Ср','Чт','Пт','Сб','Вс');
$weekday = Day_of_Week($year,$month,$day);
$week_day = $weekday[$n_wday];
__________________________________________________________
Для получения значения cookie предлагаю использовать функцию fetch_cookie (имя_cookie). Вот пример кода:
use CGI qw/:standard/;
use CGI::Cookie;
..............
sub fetch_cookie {
my($name) = @_;
foreach (keys %cookies){
if ($_ eq $name){
return $cookies{$_}->value;
}
}
return "";
}
...............
%cookies = fetch CGI::Cookie; # $cookies{"name"}->value;
................
$shopbm = fetch_cookie("shopbm") || 0;
Отправить cookie еще проще:
На perl:
use CGI qw/:standard/;
use CGI::Cookie;
..............
$cookie .= new CGI::Cookie(-name => "ИМЯ cookie",
-value => "Значение",
-expires => '+5y',
-path => '/');
print "Set-Cookie: $cookien";
print "Content-type: text/htmlnn";
__________________________________________________________
Кодируем последовательность символов:
sub cgi_escape {
my $toencode = shift;
$toencode=~s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
return $toencode;
}
Раскодируем:
sub cgi_unescape {
my $fromencode = shift;
$fromencode =~ /%(..)/pack("c",hex($1))/ge;
return $fromencode;
}
Для преобразования в UNICODE
формат (например, для вставки русского текста в JavaScript блок) можно использовать
функцию:
sub koi2utf{
my($str)=@_;
$_=$str;
s/ё/ё/g;
s/Ё/Ё/g
s/ю/ю/g;
s/а/а/g;
s/б/б/g;
s/ц/ц/g;
s/д/д/g;
s/е/е/g;
s/ф/ф/g;
s/г/г/g;
s/х/х/g;
s/и/и/g;
s/й/й/g;
s/к/к/g;
s/л/л/g;
s/м/м/g;
s/н/н/g;
s/о/о/g;
s/п/п/g
s/я/я/g;
s/р/р/g;
s/с/с/g;
s/т/т/g;
s/у/у/g;
s/ж/ж/g
s/в/в/g;
s/ь/ь/g;
s/ы/ы/g;
s/з/з/g;
s/ш/ш/g;
s/э/э/g;
s/щ/щ/g;
s/ч/ч/g;
s/ъ/ъ/g;
s/Ю/Ю/g;
s/А/А/g;
s/Б/Б/g;
s/Ц/Ц/g;
s/Д/Д/g;
s/Е/Е/g;
s/Ф/Ф/g;
s/Г/Г/g;
s/Х/Х/g;
s/И/И/g;
s/Й/Й/g;
s/К/К/g;
s/Л/Л/g;
s/М/М/g;
s/Н/Н/g;
s/О/О/g;
s/П/П/g;
s/Я/Я/g
s/Р/Р/g;
s/С/С/g;
s/Т/Т/g;
s/У/У/g;
s/Ж/Ж/g;
s/В/В/g;
s/Ь/Ь/g;
s/Ы/Ы/g;
s/З/З/g;
s/Ш/Ш/g;
s/Э/Э/g;
s/Щ/Щ/g;
s/Ч/Ч/g;
s/Ъ/Ъ/g;
return $_;
}
__________________________________________________________
Вот пример одного из файлов .htaccess на www.opennet.ru:
# Перенаправим пользователей пришедших на http://man.opennet.ru
# на http://www.opennet.ru/man.shtml
RewriteEngine On
RewriteCond %{HTTP_HOST} ^man.opennet.ru$ [NC]
RewriteRule .* http://www.opennet.ru/man.shtml [R]
# Перенаправим пользователей пришедших на индексную страницу
# http://www.tyumen.ru/~mc/linux/ на http://www.opennet.ru/map.shtml
RewriteCond %{HTTP_HOST} ^www.tyumen.ru$ [NC]
RewriteCond %{REQUEST_URI} ^/~mc/linux/$ [NC]
RewriteRule .* http://www.opennet.ru/map.shtml [L]
# Перенаправим пользователей с поисковиков пришедших на
# http://www.tyumen.ru/~mc/linux/ на аналогичную страницу http://www.opennet.ru
RewriteCond %{HTTP_HOST} ^www.tyumen.ru$ [NC]
RewriteCond %{REQUEST_URI} ^/~mc/linux/(.*)$ [NC]
RewriteRule (.*) http://www.opennet.ru/$1 [R]
# Для IE запрашивающих favicon.ico выдадим Forbidden
RewriteCond %{REQUEST_URI} favicon.ico [NC]
RewriteRule .* / [F]
# Перенапривим программу зеркалирования FlashGet на http://chat.opennet.ru/stop.txt
RewriteCond %{HTTP_USER_AGENT} FlashGet [NC]
RewriteRule .* http://chat.opennet.ru/stop.txt [R]
# Запретим доступ (будет выдано Forbidden) для IP 192.168.1.13
RewriteCond %{REMOTE_ADDR} 192.168.1.13
RewriteRule .* / [F]
# Незаметно подсунем роботу "AgentName/0.1" приходящему с IP 212.24.43.60
# и запрашивающему скрипт navigator.cgi c параметром template=7
# документ /opennews/opennews_3.inc
RewriteCond %{REMOTE_ADDR} 212.24.43.60
RewriteCond %{HTTP_USER_AGENT} AgentName/0.1
RewriteCond %{REQUEST_URI} navigator.cgi
RewriteCond %{QUERY_STRING} template=7
RewriteRule .* /opennews/opennews_3.inc [L]
__________________________________________________________
Добавьте .htaccess (missing.html - документ не найден, access.html - в доступе отказано):
ErrorDocument 404 /missing.html
ErrorDocument 403 /access.html
__________________________________________________________
Для ограничения доступа ко всей директории для всех пользователей которые присутствуют в файле паролей, в .htaccess требуется прописать:
AuthName "Welcome to private network"
AuthType Basic
AuthUserfile /home/user/.htpasswd
require valid-user
Для ограничения доступа к отдельному документу private.html для пользователей master и robot, в .htaccess требуется прописать:
AuthName "Welcome to private network"
AuthType Basic
AuthUserfile /home/user/.htpasswd
require user master robot
Для создания файла паролей и занесения первого пользователя test наберите htpasswd -c /home/user/.htpasswd test, для добавления последующих пользователей используйте htpasswd /home/user/.htpasswd user
Для открытия доступа к директории только для сетей 192.168.4.0/24 и 192.168.3.0/24, пишем в .htaccess:
order allow,deny
allow from 192.168.4.0/24 192.168.3.0/24
Чтобы разрешить вход в директорию только пользователю greg с IP 192.168.4.11,
пишем в .htaccess:
order allow,deny
allow from 192.168.4.11
AuthType Basic
AuthName "WEMBAIL HQ"
AuthUserFile /home/user/.htpasswd
require valid-user
__________________________________________________________
$mail_prog="/usr/sbin/sendmail -t ";
open (SENDMAIL, "|$mail_prog") || die "Can not run sendmail";
print SENDMAIL "MIME-Version: 1.0n";
print SENDMAIL "Content-Type: text/plain; charset="koi8-r"n";
print SENDMAIL "Content-Transfer-Encoding: 8bitn";
print SENDMAIL "To: $emailn";
print SENDMAIL "From: $support_emailn";
print SENDMAIL "Subject: $subjnn";
print SENDMAIL "$body";
close (SENDMAIL);
__________________________________________________________
<!--#if expr=""$QUERY_STRING" != /section=00/ && "$QUERY_STRING" = /section=/ " -->
<!--#include virtual="/cgi-bin/groups.cgi?template=2&show=level1&$QUERY_STRING"-->
<!--#endif -->
<br>
<!--#include virtual="/cgi-bin/groups.cgi?template=2&show=level0&$QUERY_STRING"-->
<br>
<!-- поиск. начало -->
<!--#include file="search.inc"-->
<!-- поиск. конец -->
<!--#if expr=""$HTTP_COOKIE" = /shopkey/" -->
<!--#if expr=""$QUERY_STRING" = /mask=/" -->
<!-- сводные новости. начало -->
<!--#include virtual="/cgi-bin/goods.cgi?template=1&sub_section=on&$QUERY_STRING"-->
<!-- сводные новости. конец -->
<!--#else -->
<!--#if expr=""$QUERY_STRING" = /section=00/" -->
<!--#else -->
<!--#if expr=""$QUERY_STRING" = /subgroup=/" -->
<!--#include virtual="/cgi-bin/groups.cgi?template=0&show=header&$QUERY_STRING"-->
<!--#include virtual="/cgi-bin/goods.cgi?template=0&sub_section=on&$QUERY_STRING"-->
<!--#else -->
<!--#include virtual="/cgi-bin/goods.cgi?template=0&$QUERY_STRING"-->
<!--#endif -->
<!--#endif -->
<!--#endif -->
<!--#else -->
<!--#include virtual="/cgi-bin/goods.cgi?template=5&sub_section=on&$QUERY_STRING"-->
<!--#endif -->
|
А так же... |