Часть B. Перл как средство для работы с CGI


Б. Perl и WWW


---------------------------------------------------------------------

(5) CGI-скрипты
(Q 5.1) Что такое CGI и как с ним работать
(Q 5.2) Как заставить браузеры не кешировать выдаваемый результат?
(Q 5.3) Как перенаправить клиента на другой URL?
(Q 5.4) Как загрузить файл на сервер?
(Q 5.5) Как получить файл по URL?
(Q 5.6) ! Как использовать плюшки (cookies)?

---------------------------------------------------------------------

(6) Web серверы
(Q 6.1) Как заставить WWW сервер исполнять CGI-программы?
(Q 6.2) Мой CGI-скрипт не работает! Как установить причину?
(Q 6.2) Ошибки HTTP 500 и 403. Что это значит и что с ними делать
(Q 6.3) ! Почему $ENV{REMOTE_HOST} пуста?
(Q 6.4) ! Почему $ENV{REMOTE_USER}} пуста?

* * *

(5) CGI-скрипты


(Q 5.1) Что такое CGI и как с ним работать

(A) CGI -- Common Gateway Interface. Cтандарт интерфейса внешних программ с http-сервером. 

Как работать:
HTTP -- клиент-серверный протокол, следовательно со стороны CGI, как серверного процесса, все взаимодействие выглядит следующим образом:
1. Получение данных от клиента
2. Обработка данных
3. Выдача ответа клиенту.
Пункты 1 и 3 я в кратце опишу здесь, а 2, надеюсь, сделате сами.
Начнем с п.3 как наиболее простого.

3. Выдача данных клиенту: Обычно клиенту выдают текст в формате HTML (ничто не мешает Вам отправить ему и картинку/видео/etc). Для того, чтобы сервер и клиент вас поняли, необходимо сказать, что вы выдаете, c помощью заголовка Content-Type: mime-type/mime-subtype. Обратите внимание на регистр и последовательность -- если вы скажите нечто типа Content_type, то сервер вас скорее всего не поймет. (Сообщение типа "500 Internal Server Error" будет симптомом).

Пример:

print "Content-Type: text/html\n"; # Мы выдаем текст в формате HTML. Также
# можно: text/plain -- простой текст, в
# брузере отобразится аналогично тексту, 
# заключённому между тегами <pre></pre>.
# image/gif -- Картинка, формат gif
# video/mpeg -- mpeg-видео
# И целая куча других форматов, см. 
# файл mime.types из apache
print "\n"; # <-- еще одна пустая строка, обозначает конец вывода наших 
# заголовков. ВАЖНО!

# Теперь мы можем написать свой текст клиенту
print "
<html>
<head>
<title>Моя первая CGI программа</title>
</head>
<body>
<h1>Моя первая CGI программа</h1>
</body>
</head>
";
## Все. 

1. Прием данных от клиента

Взаимодействие с клиентом обеспечивается так: Он заполняет форму своими значениями, нажимает на кнопку "submit", броузер кодирует данные соответствующим образом и отправляет их серверу.

Определение форм

Производится тегами <form> и </form>. Тег определения форм имеет следующие  аттрибуты
action -- скрипт на сервере, который будет запущен на сервере для обработки данных формы.
method -- тип взаимодействия с сервером. Может иметь значения GET и POST.
+ еще некоторые специальные, которые вы можете посмотреть в учебнике

Формы не могут быть вложенными.

Элементы ввода
Определяются тегами <input>, <textarea> и <select>
тег <input>:
<input type=checkbox> -- Элемен ввода "Опция"
<input type=hidden> -- Элемент ввода, который не виден пользователю
<input type=file> -- в Netscape Navigator позволяет загрузить файл на сервер
<input type=image> -- Изображение. Если по нему шелкнуть, это вызовет submit формы и серверу будут выданы две переменные вида name.x и name.y, где name
-- то, что вы пропишете в name=... тега <input>
<input type=password> -- Элемент ввода Пароль. Позволяет ввести строку,
которая на экране отображается звездочками. Никаких методов защиты при
передаче на сервер не применяется
<input type=radio> -- Радиокнопки
<input type=reset> -- Кнопка сброса значений формы на начальные
<input type=submit> -- Кнопка отправки формы на сервер 
<input type=text> -- строка ввода
Все теги <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=vauleN.
В случае POST аналогичная строка будет записана на стандартный ввод. Ее длину можно получить через переменную окружения CONTENT_LENGTH. 
В обоих случаях данные будут закодированы по следующему алгоритму:
Eсли ASCII код символа больше 32 и меньше 128, то он будет выдан без изменения.
если символ - пробел, то он заменится на "+" (плюсик, без кавычек) все остальное преабразуется в вид %xx, где xx -- шестнадцатеричный код сивола. Если вам повезло и у вас "Русский apache", то он преобразует его в нормальную кодировку. 
Декодирование на perl
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>
---- конец search.html

Программа:

---- начало search.pl
#!/usr/bin/perl -- поменяйте, как надо
use CGI qw(:standard); 

print "Content-Type: text/html\n\n"; # Не забудьте про два "\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>
";
---- конец search.pl

Да, кстати, если вы собираетесь писать в файлы с помощью CGI-программ, не забывайте про file locking.


(Q 5.2) Как заставить браузеры не кешировать выдаваемый результат?

(A)
Выдавайте заголовки в таком виде:

print "Content-Type: type/sub-type\n"; # Подставьте Ваш тип/подтип
print "Pragma: no-cache\n"; # Для HTTP/1.0 клиентов
print "Cache-Control: no-cache\n"; # Для HTTP/1.1
print "Expires: Thu Jan 1 00:00:00 1970\n\n"; # Это уже любой браузер должен понять

(Q 5.3) Как перенаправить клиента на другой URL?

(A)
В заголовке напишите: 
print "Status: 302\n";
# Или 301. Разница состоит в том, что по стандарту 301 значит "перемещён
# навсегда", а 302 -- "перемещён временно"
print "Location: ВАШ Новый URL\n";
# URL должен быть указан абсолютный
print "URI: ВАШ новый URL\n\n"; # Для http/1.0

Подробности: см. rfc1945(http/1.0), rfc2068(http/1.1)

(Q 5.4) Как загрузить файл на сервер?

(A)
В поставку CGI.pm входит пример file_upload.cgi.
Подробности CGI(3)

(Q 5.5) Как получить файл по URL?

(A)
Возьмите на CPAN библиотеку libwww-perl и смотрите lwpcook(3), там написаны основы использования библиотеки и есть примеры.

(Q 5.6) ! Как использовать плюшки (cookies)?
(A) 
Плюшки работают следующим образом: программа CGI добавляет в свой ответ директиву Set-Cookie, а клиент (если он поддерживает плюшки), при следующих запросах возвращает их. Сами плюшки представляют из себя пары
имя=значение. 
Ситаксис Set-Cookie: 
Set-Cookie: имя=значение; expires=дата; path=путь; domain=домен; secure
Сервер может установить не более 20 плюшек, размер всей плюшки не может  превышать 4Kb. 
* Имя=значение единственный обязательный параметр. И имя, и значение могут быть строками текста, не содержащими символов перевода строки, побелов, ; и табуляции. Если необходимо их использовать, можно эти символы закодировать при помощи стандартного url-кодирования.

* expires=дата, устанавливается дата истечения срока действия плюшки. После этой дата плюшка клиентом не возвращается. Если дата не установлена, то плюшка действует до завершения работы браузера.

* path=путь, обозначает путь на сервере, для которого возвращается  эта плюшка, если не указано, то только для того, который ее создал.

* domain=домен, домен, для которого возвращается эта плюшка. Может быть доменом уровня 2 и выше, т.е. .example.ru, а не .ru. Если не установлен, то плюшка возвращается только серверу, который выдал плюшку.

* secure, указывает, что плющка должна возвращаться только по защищенному соединению (SSL).

Синтаксис ответа клиента:
Cookie: имя1=значение1; имя2=значение2;....;имяn=значениеn
Программы CGI могут получить пары имя-значение через переменную окружения HTTP_COOKIE. В модуле CGI.pm значение можно получить при помощи метода cookie().

Пример:
1. Установка плюшки
---- начало setcookie.pl
#!/usr/bin/perl 

print "Content-Type: text/html\n";
print "Set-Cookie: mycookie=some+string\n\n";
print "<html><head><title>Установка плюшки</title></head>\n";
print "<body><h1>Установка плюшки</h1>\n";
print "<a href=getcookie.pl>Щелкни здесь,</a> чтобы посмотреть, что
получилось.
print "</body></html>";
---- конец setcookie.pl

2. Получение плюшки
файл getcookie.pl
---- начало getcookie.pl
#!/usr/bin/perl 
use CGI qw(:standard);

print "Content-Type: text/html\n\n";
print "<html><head><title>Получение плюшки</title></head>\n";
print "<body><h1>Плюшка</h1>\n";
print "mycookie = ", getcookie('mycookie');
print "</body></html>";
---- конец getcookie.pl


* * *

(6) Web серверы


(Q 6.1) Как заставить WWW сервер исполнять CGI-программы?

(A)
Apache для Unix/Win32 Надо поправить конфигурационные файлы (я расчитываю, что у вас default конфиги apache)

Способ 1
srm.conf
Директива ScriptAlias
ScriptAlias /cgi-bin/ /usr/local/apache/cgi-bin/
и в файле access.conf прописать
<Directory /usr/local/apache/cgi-bin/>
Options ExecCGI 
</directory> 
(если приглядется, там нужно только раскоментировать опции) 
Это позволит вам помещать программы в каталог
/usr/local/apache/cgi-bin/ и они будут видны по URL
http://you/cgi-bin/program_name

Способ 2
Добавить в srm.conf директиву AddHandler cgi-script .cgi  и вы сможете вызывать cgi-программу из любого каталога. Но она должна иметь окончание .cgi и для этого каталога должно быть разрешено исполнение CGI 
(Options ExecCGI в access.conf, написано выше).
Оба способа можно без проблем использовать совместно.

(Q 6.2) Мой CGI-скрипт не работает! Как установить причину?
Прежде чем читать дальше, убедитесь в том, что:
1) Вы запустили скрипт с командной строки (не через CGI!!!) и он работает, а не говорит 'compilation stopped due to errors'
2) Скрипт не только работает, но и выдает нужный заголовок с пустой  строкой после него
3) (Для многопользовательских систем) Вы сделали тоже под пользователем, под которым работает сервер (su -m имя_пользователя -c script в Unix) и он столь же благополучно работает

(Q 6.2) Ошибки HTTP 500 и 403. Что это значит и что с ними делать
(A) error_log для того и служит, чтобы туда смотреть :-)
Основные причины:
Ошибка 403 "Forbidden"
Возникает, если сервер не может отдать вам содержимое по причине отсутствия полномочий. 
Проверьте:
Может ли user (для apache под Unix это обычно nobody), под которым работает,  сервер исполнять файл? 
В частности, может ли сервер исполнять интерпретатор (в случае скриптовых языков)?
Можно ли для этого каталога исполнять CGI? (Options ExecCGI для apache) 

Ошибка 500 "Internal Server Error"
Возникает в случае внутренней ошибки.
Проверьте:
Выводите ли вы строку "Content-Type:"?
Правильно ли вы ее выводите? (Content-type, Content_Type -- неправильно)
Заканчиваете ли вы хедеры пустой строкой? (Нельзя написать 
print "Content-Type: text/html\n";
print "<HTML>\n";

Надо писать:
print "Content-Type: text/html\n\n"; # Два "\n"
print "<HTML>\n";)

Также, эта ошибка возникает, если CGI-программа завершилась с ненулевым кодом возврата, что часто встречается в случае некомпилируемости вашего скрипта perl'ом.

Совет: делайте  use CGI::Carp qw(fatalsToBrowser) во время отладки, тогда вам выдадут сообщение об ошибке perl.

(Q 6.3) ! Почему $ENV{REMOTE_HOST} пуста?
(A)
apache устнавливает переменную окружения REMOTE_HOST, если ему разрешено проводить dns запросы для определения имени. Для 1.3.x по-умолчанию это выключено. Включается/выключается директивой HostNameLookups, которая может принимать 3 значения: on -- проводить запросы, off -- не запрашивать dns и double -- делать двойные запросы: запрашивать имя хоста, а затем по имени запрашивать ip адрес, для безопасности.

(Q 6.4) ! Почему $ENV{REMOTE_USER}} пуста?
(A)
Переменная окружения REMOTE_USER устанавливается apache в случае, если скрипт защищен паролем. Как это сделать рассказано на http://www.apacheweek.com/features/userauth и http://www.apacheweek.com/features/dbmauth