Версия для печати

Архив документации на OpenNet.ru / Раздел "Perl" (Многостраничная версия)
CGI, Программирование CGI-скриптов на Перле

Владивостокский государственный университет экономики и сервиса

Кафедра КТС

Максим Мамаев

Технологии Интернет
Лабораторный практикум

Тема 6. CGI


Литература и ссылки

Используемое ПО:

См. архив на Уране.



Интерфейс CGI

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

CGI определяет каким образом данные, предоставленные клиентом в запросе, передаются программе, как программа возвращает сгенерированный HTML-контент серверу, и какие переменные окружения устанавливаются сервером при запуске программы. Переменные окружения несут дополнительную информацию о сервере и запросе (например, тип сервера, IP-адрес клиента и др.).

Данные из заполненной клиентом HTML-формы могут передаваться на сервер двумя методами: GET и POST, это определяется параметром method соответствующего тэга <form method=... action=...>. В первом случае (GET) данные присоединяются после вопросительного знака в конец URL, указанной в параметре action, во втором случае - передаются в теле запроса - в секции, предназначенной для данных (следует после всех заголовокв и пустой строки). В обоих случаях данные кодируются одинаково - см. след. пункт.

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

CGI-программа выдает содержимое ответа (как правило, HTML-контент) на свой стандартный вывод, который перехватывается веб-сервером с тем, чтобы отослать эти данные клиенту. Предварительно CGI-программа должна напечатать заголовок "Content-Type" и отделить его от данных пустой строкой. Например, вывод CGI-программы, генерирующей HTML, может выглядеть следующим образом:

Content-Type: text/html

<HTML>
  <BODY>
    <H1>Hello, world</H1>
  </BODY>	
<HTML>  

Конфигурирование сервера Apache для исполнения CGI-скриптов

Для того, чтобы Apache воспринимал все файлы, находящиеся в некотором каталоге как CGI-скрипты, нужно использовать директиву


ScriptAlias /виртуальный/путь/ /путь/к/каталогу/
ScriptAlias /cgi-bin/ /usr/local/www/cgi-bin/

Это означает, что для обработки запроса URL вида http://your.server.com/cgi-bin/dir/script будет взят не файл script из каталога DocumentRoot/cgi-bin/dir/, а запущена программа /usr/local/www/cgi-bin/dir/script.

Для смешанного хранения файлов, подлежащих просмотру, и CGI-скриптов в одном каталоге внутри дерева DocumentRoot следует присвоить CGI-скриптам одинаковые расширения (например, ".cgi") и указать серверу, что интерпретировать такие файлы следует как CGI-скрипты:


AddHandler cgi-script .cgi

Директива AddHandler может быть использована в любом контексте конфигурации Apache.

Структура URL и кодирование данных запроса

Для работы CGI-программ важное значение имеют части URL, называемые PATH_INFO и QUERY_STRING. Рассмотрим запрос с URL вида

http://my.server.com/cgi-bin/dir/prog/a/b?A=1&B=qwerty

Используя директиву ScriptAlias, приведенную в предыдущем пункте, сервер определяет что произошло обращение к CGI-программе и для поиска этой программы заменяет начальное /cgi-bin/ на /usr/local/www/cgi-bin/. Следуя запрошенному URL, сервер обнаруживает в этом каталоге подкаталог dir, однако подкаталога prog в каталоге /usr/local/www/cgi-bin/dir не обнаружено. В таком случае сервер предполагает, что prog - имя CGI-программы, подлежащей выполнению. Если программа /usr/local/www/cgi-bin/dir/prog не найдена или не может быть исполнена, сервер возвращает клиенту ошибку 403, 404 или 500. В противном случае программа prog запускается, а оставшаяся часть пути из URL - /a/b - передается программе prog в переменной окружения PATH_INFO. Таким способом можно передать в CGI-программу дополнительные параметры.

Все, что находится после вопросительного знака - A=1&B=qwerty - передается программе prog в переменной окружения QUERY_STRING. Это могут быть данные из заполненной пользователем формы, отправленные на сервер методом GET, либо какая-то другая информация (сервер не делает никаких предположений об интерпретации данных в QUERY_STRING, это задача вызываемой программы).

Данные из полей формы, заполненной пользователем - независимо от метода (POST или GET), которым они пересылаются на сервер - кодируются следующим образом:

имя_поля=значение_поля&имя_поля=значение_поля...

Пары имя-значение разделяются амперсандом. Алфавитно-цифровые символы и некоторые знаки препинания, не имеющие специального значения (тире, подчеркивание) передаются как есть. Остальные символы кодируются в виде "%NM", где NM - двузначный шестнадцатеричный код символа. Пробел может передаваться как "%20" или как символ "+". Кириллические символы также должны кодироваться указанным способом. Кодировка производится броузером при отправке полей заполненной формы.

Например:

http://my.server.com/cgi-bin/dir/prog?birthday=11%2F05%2F73&name=John+Smith
означает, что в поле birthday пользователь внес "11/05/73", а в поле name - "John Smith".

Декодирование данных формы является задачей CGI-программы.

При пересылке данных формы, закодированных вышеописанным способом, методом POST клиент должен установить заголовок запроса Content-Type следующим образом:


Content-Type: application/x-www-form-urlencoded

Переменные окружения CGI

При запуске CGI-скрипта веб-сервер устанавливает дополнительные переменные окружения:
Переменная Значение
AUTH_TYPE

Метод аутентифицирования, использованный для опознания пользователя. См. также REMOTE_USER и REMOTE_IDENT.

CONTENT_LENGTH

Длина данных запроса в байтах, переданных CGI-скрипту через стандартный ввод.

CONTENT_TYPE

MIME-тип данных запроса.

DOCUMENT_ROOT

Корневой каталог дерева документов веб-сервера (определяется директивой DocumentRoot).

GATEWAY_INTERFACE

Используемая версия CGI.

HTTP_ACCEPT

Список MIME-типов данных, которые клиент может принять.

HTTP_FROM

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

HTTP_REFERER

URL документа, в котором находилась ссылка, вызвавшая настоящий запрос.

HTTP_USER_AGENT

Броузер клиента.

PATH_INFO PATH_INFO (если есть) - см. выше "Структура URL и кодирование данных запроса"
PATH_TRANSLATED

PATH_INFO, преобразованное в полный путь в файловой системе сервера (PATH_INFO, добавленное к DOCUMENT_ROOT).

QUERY_STRING

Данные запроса, переданные в составе URL вслед за вопросительным знаком - см. выше "Структура URL и кодирование данных запроса".

REMOTE_ADDR

IP-адрес клиента.

REMOTE_HOST Имя DNS клиента.
REMOTE_USER Аутентифицированное имя пользователя.
REQUEST_METHOD

Метод запроса (GET, POST, HEAD и т.д.).

SCRIPT_NAME Виртуальный путь (например, /cgi-bin/program.pl) к исполняемому CGI-скрипту.
SERVER_NAME DNS-имя сервера или, при невозможности определить имя, его IP-адрес.
SERVER_PORT

Номер порта сервера.

SERVER_PROTOCOL Имя и версия протокола, через который был сделан запрос (например, HTTP/1.1).
SERVER_SOFTWARE

Тип и номер версии ПО веб-сервера.

С Apache поставляется стандартный тестовый скрипт test-cgi, выводящий занчения переменных окружения CGI.

Cookies и другие методы сохранения состояния

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

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

Существует несколько методов сохранения состояния:

  1. cookies - сохранение на компьютере клиента,
  2. скрытые поля - сохранение внутри формы, посылаемой клиенту,
  3. сохранение в файле какого-либо формата на сервере,
  4. сохранение в параллельно работающей базе данных.

Два последних метода реализуют сохранение состояния на стороне сервера.

База данных

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

Также существует решение в виде демона, который запускается параллельно с http-сервером, и сохраняет требуемую информацию в своей оперативной памяти в виде переменная=значение. Для записи или извлечения данных скрипт соединяется с демоном по заранее оговоренному порту TCP или UDP, идентифицирует себя и использует набор простых команд типа "save name=value" и "extract name" (возвращается value).

Интересно, что несмотря на сложность реализации, такое решение (или использование СУБД с возможностью доступа по сети) позволяет разделять данные между скриптами работающими на различных серверах (если реализуется какая-то сложная распределенная интерактивная веб-система), при этом не вовлекается сохранение данных на стороне пользователя.

Файл

Основным недостатком сохранения данных в файле, кроме использования дискового пространства и накладных расходов на файловые операции, является операция записи на диск как таковая. Запись на диск может быть источником серьезных проблем в плане безопасности, так как работа CGI-скрипта фактически управляется воздействием внешних пользователей, которые могут иметь враждебные намерения. Путем посылки каких-либо специальных данных неаккуратно написанному скрипту можно вызвать серьезный сбой в работе сервера. Если же скрипт имеет право записи на диск, то последствия могут быть гораздо серьезнее, поэтому обычно CGI-скрипты, как и сам веб-сервер, работают с минимальными привилегиями от имени пользователя nobody без права записи на диск.

Сохранение состояния на стороне пользователя

Сохранение данных состояния на стороне пользователя (cookies и, технически, скрытые поля) существенный недостаток: пользователь имеет полный доступ к сохраняемым данным и может их несанкционированно изменить (например, прочитать правильный ответ теста или изменить идентификатор пользователя). Достоинством является простая реализация.

Cookies

Cookies - это данные вида имя=значение, которые, будучи получены от сервера, сохраняются броузером на диске пользователя для их возврата серверу при последующих запросах к этому или другому URL. Поскольку данные сохраняются на диске, они могут быть использованы после перезапуска броузера.

Сервер передает cookie через специальное поле заголовка HTTP-ответа "Set-Cookie". Броузер возвращает cookie также через специальное поле в загловке HTTP-запроса - "Cookie". На стороне сервера cookie формируется, как правило, скриптом, который просто выводит в STDOUT соответствующий заголовок. Передача данных, полученных через cookie, от броузера в скрипт производится сервером через установку переменной окружения HTTP_COOKIE, которая доступна внутри скрипта и содержит пары имя=значение, которые броузер передал внутри поля "Cookie" в заголовке своего запроса.

Формат поля Set-Cookie (HTTP-ответ)

Set-Cookie: имя=значение; Max-Age=секунды; Comment=текстовый_комментарий; Path=URI_или_часть_URI; Domain=домен_сервера; Secure ; Version=1

Все элементы, кроме имя=значение и Version, не являются обязательными. В заголовке одного ответа сервера может содержаться несколько полей Set-Cookie.

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

Max-Age=секунды
устанавливает срок годности данных (в секундах с момента получения cookie); по умолчанию - до окончания работы данного проуесса броузера.

Comment=текстовый_комментарий
комментарий сервера по поводу предназначения cookie; предполагается, что пользователь может отказаться работать с этим cookie, если комментарий ему не понравится.

Domain=домен_сервера
домен, для которого действительно данное cookie (броузер должен возвращать cookie при обращении ко всем серверам данного домена, с учетом параметра Path [см. ниже]); домен должен начинаться с точки; данный сервер должен находиться в этом домене. Если параметр Domain не указан - возвращать cookie только данному серверу.

Path=URI_или_часть_URI
путь от корня дерева документов сервера (URI); броузер должен возвращать cookie при обращении к данному URI и ко всем URI, начинающимся с данного; по умолчанию - URI, при запросе которого было сгенерировано cookie, минус имя файла.

Пример: при обращении на "http://s.vvsu.ru/a/b/c" сервер выдал ответ с установленным полем в заголовке:

SetCookie: X=5; Version=1
Это значит, что cookie должно возвращаться броузером при обращении на все URL вида "http://s.vvsu.ru/a/b/какое-то_имя_файла".
Если же SetCookie в ответе сервера выглядит вот так:
SetCookie: X=5; Domain=.vvsu.ru; Path=/a/; Version=1
то броузер должен присоединять это cookie ко всем запросам URL вида: "http://имя_без_точки.vvsu.ru/a/b/некий_путь_или_никакого".

Secure
если это параметр присутствует, то броузер должен возвращать cookie серверу только через защищенный канал связи; стандарт не специфицирует конкретный механизм защиты данных при передаче, но предполагается, что это SSL.

Формат поля Cookie (HTTP-запрос)

Cookie: имя=значение; Path=URI_или_часть_URI; Domain=домен_сервера; Version=1

Параметры Path и Domain включаются только если они были установлены в заголовке Set-Cookie. Если несколько cookie удовлетворяют параметру Path, то они указываются в одном заголовке Cookie друг за другом (через точку с запятой) в следующем порядке: первыми передаются cookie с более длинным параметром Path. Порядок следования при равенстве параметров Path не определяется.

Скрытые поля

Скрытое поле создается внутри формы с помощью тега
<input type=hidden name=name1 value=value1>

Когда броузер получает документ с этой формой, содержимое полей типа "hidden" не отображается и пользователь не знает об их существовании (если только не посмотрит в HTML-текст присланного документа). После того, как пользователь отправляет форму на сервер, пара "name1=value1" присоединяется к данным формы, которые будут обработаны вновь запущенным скриптом. Таким образом скрипт может получить данные о предыстории своей работы с пользователем. Например, при электронном шоппинге в скрытых полях может сожержаться список товаров, выбранных для покупки в других отделах, которые пользователь уже посетил в данном сеансе работы.

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

Server Side Includes

SSI представляет собой механизм разбора HTML-документов на стороне сервера с целью обнаружения в документе и выполнения директив, добавляющих в документ дополнительную информацию.

Все директивы вставляются внутрь тэгов HTML-комментариев, что позволяет клиенту, в случае, если сервер не поддерживает SSI, игнорировать эти директивы. Директивы имеют следующий формат:


<!--#директива параметр(ы)="значение"-->

Ниже следует список основных директив SSI и их параметров.

echo
Подставляет в документ значение указанной в качестве параметра переменной окружения (см. также список CGI-переменных) или специальной переменной SSI (см. ниже):
        <H1>Вы пришли на сервер, находящийся по адресу
        <!--#echo var="SERVER_NAME"-->...</H1>

include
Вставляет в документ текст другого файла. Параметры: file - указывает путь к вставляемому файлу относительно расположения данного документа; virtual - указывает виртуальный путь (как он указывался бы в URL) к вставляемому файлу.
       <!--#include file="stuff.html"-->
       <!--#include virtual="/personal/stuff.html"-->
Эта директива очень удобна для создания стандартных шапок и подвалов веб-страниц.

fsize
Вставляет размер указанного в параметре файла (путь к файлу виртуальный):
        Размер файла archive.zip - 
        <!--#fsize file="/archive.zip"--> bytes.

flastmod
Вставляет в документ дату и время последней модификации указанного в параметре файла (путь к файлу виртуальный):
        Дата последнего изменения: 
        <!--#flastmod file="/this_dir/this_file.html"-->bytes.
Формат вывода даты и времени может быть специфицирован параметром timefmt директивы config.

exec
Выполняет внешнюю программу, указанную параметром, и вставляет вывод этой программы в документ. Параметры: cmd - выполняемая программа является неким обычным приложением; cgi - выполняемая программа является CGI-скриптом
       <!--#exec cmd="/bin/finger $REMOTE_USER@$REMOTE_HOST"-->
        На эту страницу заходили
        <!--#exec cgi="/cgi-bin/counter.pl"--> 
        раз.
		
В первом примере используется подстановка значений переменных окружения (см. CGI-переменные).

config
Модифицирует различные аспекты работы SSI. Параметры:

Специальные переменные SSI

Ниже приведены переменные SSI, которые можно использовать в директиве echo в дополнение к переменным CGI.

DOCUMENT_NAME
Имя данного документа. Например:
         Вы читаете файл под названием:
         <!--#echo var="DOCUMENT_NAME"-->

DOCUMENT_URL
Виртуальный путь к данному документу. Например:
        Ссылка на этот документ:
        <!--#echo var="DOCUMENT_URL"-->

QUERY_STRING_UNESCAPED
Декодированные данные из QUERY_STRING (см "Структура URL и кодирование данных запроса"), при этом все метасимволы шелла экранированы обратным слэшем (\).

DATE_LOCAL
Текущие дата и время по местному времени. Например:
        Сейчас <!--#echo var="DATE_LOCAL"-->

DATE_GMT
Текущие дата и время по Гринвичу.

LAST_MODIFIED
Дата и время последней модификации данного документа. Например:
       Этот файл был последний раз изменен 
       <!--#echo var="LAST_MODIFIED"-->

Задание

Написать CGI-скрипт для игры в виселицу (угадывание слова по буквам).

Правила игры

Сервер загадывает слово из словаря и показывает его пользователю в замаскированном виде (буквы заменены звездочками). Пользователь имеет некоторое число попыток; во время каждой попытки он может угадать одну букву. Если пользователь правильно угадывает букву или называет букву, которую он уже использовал, попытка не засчитывается. Иначе число попыток уменьшается на единицу.

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

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

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

Реализация

Слова выбираются случайным образом из заданного текстового файла.

Соотношение числа попыток и длины слова разумно определяется программистом. Например, число попыток есть заданная функция от длины слова; число попыток жестко привязано к каждому слову в словаре; длина слов в словаре и число попыток являются константами; при определении числа попыток используется заявленный пользователем уровень сложности.

При первом обращении к скрипту выдается заставка и регистрационная форма вида:


Ваше имя:


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

Например, форма 6-й попытки при угадывании слова ПАРОВОЗ может выглядеть так:


Игрок: Сидоров

Слово: ПА*О*О*

Использованные буквы: АОЕПС

Осталось попыток: 5 из 10


При завершении игры сервер выводит соответствующее сообщение и предлагает начать следующую игру.

Список использованных букв выводится по алфавиту. Все операции нечувствительны к регистру. Для облегчения задачи допускается создание игры на английском языке.

Программа скрипта может быть написана на любом языке программирования под Unix (немедленно доступные в классе языки: С, С++, Перл, и - для любителей острых ощущений - shell/awk).

Обсуждение

При написании скрипта следует непрерывно помнить следующее основное правило CGI-программиста:

С каждым новым HTTP-запросом скрипт начинает свою работу с нуля. В данном случае: в игру может играть несколько пользователей одновременно; каждый из них последовательно формирует несколько HTTP-запросов; но для обработки каждого очередного запроса, поступившего на сервер, неважно к какому этапу игры или пользователю он относится, связан ли он с предыдущими или другими поступившими одновременно запросами, скрипт запускается с нуля и не имеет никаких знаний о своих предыдущих запусках и о состоянии игорного процесса.

В связи с вышесказанным требуется решить две проблемы при обработке каждого вновь поступившего запроса (или, что тоже, при каждом запуске программы):

  1. Идентификация пользователя
  2. Получение информации о состоянии игры (слово, использованные буквы, число попыток)

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

О методах сохранения состояния в CGI-программировании см. соответствующий раздел выше.

В результате анализа информации о состоянии игры и содержимого заполненной пользователем формы (если таковое имеется в поступившем запросе) программа разветвляется для формирования одного из следующих ответов (HTML-текстов):

После выдачи сформированного HTML-текста и сохранения (каким-либо способом) нового состояния, программа прекращает свою работу.

Дополнительное задание

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

Также для каждого пользователя ведется таблица его результатов; слова, уже предлагавшиеся пользователю, независимо от того, угадал он их или нет, исключаются из списка предложений.




Следующая тема - Вопросы безопасности в Интернет

Курс "Технологии Интернет"
Кафедра КТС
Максим Мамаев




Программирование CGI-скриптов на Перле

Владивостокский государственный университет экономики и сервиса

Максим Мамаев


Предварительные требования: знание языка С, работа в Unix, понятие о регулярных выражениях. И, естественно, темы 5 и 6 курса "Технологии Интернет".

Литература и ссылки





Установка Перла и библиотек (Unix)

Архивы самого языка Перл и его библиотек распространяются свободно и доступны через сайты CPAN. Библиотеки Перла называются модулями (стандартное расширение .pm), а сами программы - скриптами. И модули, и скрипты являются текстовыми файлами.

Для установки языка Перл необходимо иметь в системе компилятор С (желательно, gcc). Распаковать архив с языком (на текущий момент 27.11.99 это perl5.005_03.tar.gz). В каталоге, куда распаковался архив, выполнить следующие команды:

./configure
# configure задаст много вопросов, на все можно ответить нажатием Enter (т.е. принять значение по умолчанию)
make
# перейти в режим суперпользователя
make install

По умолчанию perl установится в /usr/local (исполнимые файлы - в /usr/local/bin/, библиотеки - в /usr/local/lib/perl5/, документы справочника man - в /usr/local/man (по самому языку) и в /usr/local/lib/perl5/5.00503/man (по библиотекам). Следует добавить соответстующие пути в переменные окружения PATH и MANPATH (в файле .profile).

Для запуска программ, написанных на Перле надо подать команду

perl имя_файла_с_программой
или просто ввести имя файла с программой, если в первая строка этого файла выглядит:
#!/usr/local/bin/perl

Перл поставляется с несколькими стандартными модулями. Для работы с CGI требуется взять из CPAN и установить следующие библиотеки (в указанном порядке):

  1. Digest-MD5
  2. MIME-Base64
  3. URI
  4. HTML-Parser
  5. libnet
  6. libwww-perl
  7. CGI

Установка каждой библиотеки производится командой

perl Makefile.PL && make && make test && make install
поданной в каталоге, куда распаковаля архив библиотеки. Для выполнения последней части команды (make install) требуются привилегии суперпользователя.

Библиотеки устанавливаюися в дерево /usr/local/lib/perl5/. Документы man по установленным библиотекам находятся в /usr/local/lib/perl5/5.00503/man.

Минимальное введение в Перл

В этом разделе дана минимальная необходимая информация по тем элементам Перла, которые чаще всего используются при написании скриптов. Перл здесь рассматривается как С-подобный язык, поэтому акцентируются отличия от С. Этот раздел ни в коей мере не претендует на описание языка Перл (многие моменты сознательно опущены); для этой цели обратитесь к Camel Book, Llama Book или книге Маслова.

Комментарий "отсюда и до конца строки" - символ "#".

Типы переменных

Переменные бывают трех типов: скаляры, списки и хэши. Описывать переменные не нужно, если только вы не используете директиву "use strict"; иначе каждая переменная должна быть описана с помощью "my имя_переменной".

Скаляры

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

Повтор специально для программистов на С: строковые значения не оканчиваются на символ "\0". Строковых значений как таковых вообще не существует, строка - это один из двух способов интерепретации значения скаляра, который сам по себе ни на что особенное не заканчивается.

Имена всех скалярных переменных обязаны начинаться на $ ($x, $my_variable_1).

  • Имеется специальное скалярное значение undef - "неопределенность". Такое значение имеют, например, переменные, которым не было присвоено никакого значения. В логических и арифметических операциях undef считается нулем, в строковых операциях - пустой строкой. Функция defined(выражение) возвращает истину, если выражение не равно undef (хотя может быть равно нулю).
    my $x;       # $x создается, равно undef
    $x;          # ложно
    defined($x); # ложно
    $x=0;        
    $x;          # ложно
    defined($x); # истинно
    $x=5;
    $x;          # истинно
    defined($x); # истинно
    $x=undef;    # опять undef!
    

    Списки и массивы

    Списком (list) называется упорядоченная последовательность скалярных значений; порядковые номера (индексы) начинаются с нуля. Отдельно стоящие списки заключаются в скобки:

    ($x, "abc", 15)
    
    Обращение к элементу списка осуществляется путем указания индекса этого элемента в квадратных скобках:
    $y=($x, "abc", 15)[1];      # $y="abc"
    

    Переменная, значением которой является список, называется массивом (array). Имена всех массивов обязаны начинаться с @ (@array). При обращении к элементу массива знак @ заменяется на $:

    $array[0]
    $array[$x]
    $array[-2]      # второй элемент с конца
    
    (смысл: элемент массива - это скаляр, следовательно он начинается с $). Примеры формирования массивов:
    @array=($x, "abc", 15);
    @array=($x, $y, @another_array);
    @array=(); #пустой список
    
    Количество элементов в массиве: scalar @array (см. также п. "Контексты" ниже); индекс последнего элемента: $last_index = $#array.

    Извлечение части массива:

    ($a, $b, $c) = @array[3,4,5];
    @sublist=@array[3,4,5];
    
    Соответственно, наоборот:
    @array[3,4,5]=($a,$b,"xyz");
    производит присвоение значений части массива @array (если элементы @array[0-2] ранее отсутствовали, они создаются со значениями undef).

    Двух- и более мерные массивы в явном виде не поддерживаются.

    Функции для работы с массивами:

    @list = reverse @array
    @list = reverse список
    - возвращает список, состоящий из значений массива @array, взятых в обратном порядке (сам массив @array не изменяется). Аргументом функции reverse может быть не только массив, но любое выражение, имеющее списочное значение.

    push @array, $x
    push @array, список
    - помещает значение или список значений в конец массива. Список может быть как явным перечислением значений через запятую, так и любым выражением, имеющим списочное значение. Функция push возвращает новую длину массива @array.

    $x = pop @array
    - извлекает из массива последний элемент (элемент из массива удаляется). Если массив не указан - подразумевается список аргументов функции (т.е. массив @_), если вызов произведен внутри какой-либо функции, или аргументы командной строки скрипта (т.е. массив @ARGV), если вызов произведен из основной части скрипта.

    $x = shift @array
    - извлекает из массива первый элемент (элемент из массива удаляется). Если массив не указан - подразумевается список аргументов функции (т.е. массив @_), если вызов произведен внутри какой-либо функции, или аргументы командной строки скрипта (т.е. массив @ARGV), если вызов произведен из основной части скрипта.

    unshift @array, $x
    unshift @array, список
    - аналогично push, но новые элементы помещаются в начало массива @array.

    @list = splice @array, $from, $length, список
    @list = splice @array, $from, $length
    @list = splice @array, $from
    - удаляет из массива @array $length элементов, начиная с индекса $from. Если присутствует список, то вместо удаленных элементов помещаются значения из списка; если их больше, чем удаленных, то массив увеличивается, если меньше - то соответственно уменьшается. Если $length отсутствует, то удаляются все элементы, начиная с индекса $from. Индекс $from может быть отрицательным - тогда счет производится с конца, т.е. splice(@array,-2) удаляет два последних элемента из массива @array, а splice(@array,-2,1) удаляет предпоследний элемент. Функция splice возвращает список удаленных из массива элементов. Функции push, pop, unshift и shift являются частными случаями функции splice.

    Хэши

    Переменная-хэш представляет собой массив, элементы которого индексируются строками (ассоциативный массив). Порядок хранения элементов в хэше не определен. Имена всех переменных-хэшей обязаны начинаться на % (%my_hash). При обращении к элементу списка знак % заменяется на $ и используются фигурные скобки:

    $hash{"key_A"}
    $hash{$x}
    
    Хэши могут быть сформированы с помощью объединения четного числа скаляров в скобки:
    %hash=("abc", $x, "def", 15); # $hash{abc}=$x и $hash{def}=15
    #запятые для удобо читаемости могут быть заменены на "=>"
    %hash=("abc" => $x, "def" => 15);
    %hash=();                          #пустой хэш
    
    Вообще, любой список (массив) может быть рассмотрен в качестве хэша, т.е. %hash=@array. При этом нечетные элементы списка станут ключами хэша, а четные - соответствующими значениями. Если число элементов в списке нечетное, то последний элемент сконструированного таким образом хэша будет существовать со значением undef ("неопределенность"). Если в списке есть одинаковые элементы на нечетных позициях, то в хэш будет внесено значение, ключом которого является последний из одинаковых нечетных элементов списка.
    
    @array=('a',1,'b',2,'c');             # для 'c' нет пары
    %hash=@array;                         # получилось: $hash{c}=undef
    
    %hash=('a' => 1, 'b' => 2, 'a' => 3); # получилось: $hash{a}=3
    

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

    Функции для работы с хэшами:

    @array = keys %hash
    - возвращает список ключей хэша (порядок неопределен).

    @array = values %hash
    - возвращает список значений хэша (порядок неопределен, но для одного и того же хэша всегда совпадает с порядком выдачи ключей функцией keys).

    ($key, $value) = each %hash
    - возвращает список из двух скаляров: ключ и значение очередного элемента хэша; используется в циклах для прохода по всем элементам хэша.

    exists $hash{$key}
    - возвращает истинное (ненулевое) значение, если указанный элемент хэша существует (хотя, может быть, и равен нулю или имеет неопределенное значение).

    delete $hash{$key}
    - удаляет указанный элемент хэша.

    %rev_hash = reverse %hash
    - меняет местами ключи и значения элементов хэша; работает корректно в случае, если в хэше %hash не было элементов с одинаковыми значениями.

    Операторы, выражения и операции

    Операторы, выражения и операции Перла во многом аналогичны языку С. Ниже рассмотрены отличия.

    Контексты

    Каждое выражение в Перл вычисляется в списочном, либо скалярном контексте. То есть в списочном контексте предполагается, что значение выражения должно быть списком, а в скалярном - скаляром. Контекст определяется, например, типом переменной, стоящей в левой части оператора присвоения. Многие функции определяют контекст, в котором они были вызваны и возвращают разные результаты в зависимости от контекста. Например, функция localtime (текущее местное время) в скалярном контексте возвращает строку вида "Fri Nov 26 20:36:33 1999", а в списочном контексте - список из чисел: секунды, минуты, часы, день месяца, месяц и т.д. Для того чтобы форсировать скалярный контекст, можно использовать оператор scalar.

    $x = localtime;                       # скалярный контекст
    @time = localtime;                    # списочный контекст
    ($sec,$min,$hour) = localtime[0,1,2]; # списочный контекст
    print scalar localtime;               # скалярный контекст
    

    Значением списка в скалярном контексте является длина списка:

    $x=@array; # в переменную $x помещается длина списка @array
    
    Значением скаляра в списочном контексте является список, состоящий из одного этого скаляра.

    Значения выражений и операции

    • Большинство арифметических и логических операций аналогично операциям языка С (в том числе операции типа $x++; ++$x; $x += 5; $x=$y=5). Различия рассмотрены ниже.

    • Результат многих операций может быть рассмотрен как истинное (ненулевое) или ложное (нулевое или неопределенное) значение. Можно использовать операции "И" ("&&" или "and") и "ИЛИ" ("||" или "or") для выстраивания цепочки действий, которые выполняются в зависимости от успеха (истинного значения) или неуспеха предыдущего действия. Это позволяет сократить запись (иначе пришлось бы использовать оператор if).
      # открыть файл myfile.txt, присвоив ему дескриптор FILE,
      # или выйти из программы с сообщением об ошибке
      open (FILE, "myfile.txt") or die ("Cannot open file");
      
      # вывести сообщение в случае существования элемента хэша
      exists $hash{"mykey"} and print "My key exists!";
      
      # присвоить переменной $x значение 5, если $x имеет неопределеное или нулевое значение
      $x ||= 5; 
      

    • Имеется ряд операций для работы со скалярами как со строками. Операция "." (точка) вызывает конкатенацию строк:
      $x = "Now is " . scalar localtime;
      $x .= " (time is local)."; # то же что $x = $x . " (time is local).";
      
      
      Сравнения скаляров как строк выполняются следующими операциями:
      $a eq $bИстинно, если $a и $b одинаковы
      $a ne $bИстинно, если $a и $b неодинаковы
      $a lt $bИстинно, если $a раньше по алфавиту, чем $b
      $a gt $bИстинно, если $a позже по алфавиту, чем $b
      $a le $bИстинно, если $a раньше по алфавиту или совпадает с $b
      $a ge $bИстинно, если $a позже по алфавиту или совпадает с $b
      $a cmp $b-1, если $a раньше по алфавиту, чем $b; 0, если $a и $b одинаковы; 1, если $a позже по алфавиту, чем $b

      Внимание, распространенная ошибка! Операции "== != > < >= <=" выполняют сравнение скаляров как чисел. Использование их в "скалярно-строковом" контексте приведет к неверным результатам. Например, "11">"2", но "2"gt"11". Более того, $a="xyz"; $b="qwerty"; $a==$b - истинно, поскольку как числа обе этих переменных равны нулю.

    • Дополнительная операция для сравнения скаляров как чисел: $x<=>$y равно -1, если $x<$y; 0, если $x=$y; 1, если $x>$y.

    • Если в выражении не указано, откуда берутся или куда возвращаются данные, то подразумевается, что речь идет о переменной $_ (в скалярном контексте) или о @_ (в списочном контексте).
      # читать из файла с дексриптором FILE по одной строке (оператор <FILE>)
      # и удалять символ перевода строки из конца прочитанной строки (функция chomp)
      while ($a=<FILE>) { chomp $a; }
      # то же самое, используется переменная $_
      while (<FILE>) { chomp; }
      
      

      Операторы

    • Имеются аналогичные языку С операторы
      if (...) {...};
      условие ? выражение_да : выражение_нет ;
      while(...) {...};
      do {...} while (...);
      for (...;...;...;) {...};
      
      Отличия и дополнения рассмотрены ниже.

    • Фигурные скобки, определяющие блок операторов, выполняемый после оператора ветвления или цикла, должны быть всегда, даже если блок операторов состоит из одного оператора:
      if ($a<$b) { $a=$b; }
      while ($a<$b) { some_function($a); }
      
      Однако для операторов ветвления в таком случае есть сокращенная "постфиксная" форма записи:
      $a=$b if ($a<$b);
      

    • Скобки вокруг аргументов стандартных функций не требуются (если только не возникает неоднозначностей):
      print "a=",$a;
      НО: join ':', split ('/', $a);
      или
          join ':', split ('/'), $a;
      

    • Оператор foreach производит цикл по всем элементам списка:
      foreach $i (@array) {
        print $i;
      }
      # то же самое с использованием переменной $_
      foreach (@array) {
        print;
      }
      

    • Оператор unless - это оператор if с отрицанием:
      # присвоить переменной $x значение 5, если $x имеет неопределеное или нулевое значение
      unless ($x) { $x=5; } # то же, что и $x ||= 5; 
      
      Будьте внимательны при программировании сложных условий с помощью unless - помните теорему де Моргана: отрицание произведения есть сумма отрицаний; отрицание суммы есть произведение отрицаний.

    • Оператор until - это оператор while с отрицанием:
          until ($array[$i++] == $x) {;} 
      
      соответственно и do {...} until(...).

    • Оператор switch отсутствует. Пользуйтесь конструкцией if () ... elsif () ... elsif () ... else ... или изобретайте что-то свое.

      Управление циклами:

      • досрочный переход на следующую итерацию ближайшего объемлющего цикла - next (аналог оператора continue в С):
               foreach $i (@array) {
        	      next if ($i<0);
        		  $sum += $i;
        	   }
        
      • досрочный выход из ближайшего объемлющего цикла - last (аналог оператора break в С):
              # считывать строки из файла и выводить на печать, пока не встретится пустая строка
              while ($a=<FILE>) {
        	     chomp $a;
        	     last unless ($a);	  
        	     print $a, "\n";
        	  }
        
      • каждый цикл может быть снабжен меткой, на которую могут ссылаться операторы next и last; это позволяет производить выход сразу из нескольких объемлющих циклов:
               # это не самый лучший алгоритм поиска одинаковых слов в двух списках,
               # зато здесь используется выход сразу из двух циклов
               M1: foreach $i (@array1) {
        	          foreach $j (@array2) {
        			     if ( $i eq $j ) { last M1; }
        			  }
        		   }	  
        
        

      Кавычки и интерполяция

      Строковые выражения заключаются в одинарные (') или двойные (") кавычки. Везде внутри двойных кавычек производится подстановка переменных - скаляров, элементов списков и элементов хэшей; целые списки подставляются как подстрока в которой все элементы списка следуют друг за другом без разделителя; хэши целиком не подставляются. Для экранирования спецсимволов внутри двойных кавычек используется обратный слэш: \$, \@, \", \\; также распознаются все спецсимволы языка С: \n, \t и т.п.

      $x="abc";
      @array=('c','d','e');
      %hash=( a => "A", b=> "B");
      print "this is \$x: \"$x\"; \nthis is element 2 of \@array: \"$array[2]\";\n",
            "and \$hash{a} is \"$hash{a}\"\n";
      ВЫВОД:
      this is $x: "abc";
      this is element 2 of @array: "e";
      and $hash{a} is "A"
      

      Строки, употребляемые как ключи хэшей, если это буквальные строки без подстановок ("abc"), в кавычки можно не заключать: $hash{abc}.

      Внутри одинарных кавычек никакие подстановки не производятся, спецсиволы типа "\n" не интепретируются и все символы воспринимаются буквально; исключение: комбинация \' (обратный слэш - одинарная кавычка), которая интепретируется как одинарная кавычка, являющаяся частью строки. Одинарная кавычка внутри двойных кавычек воспринимается буквально.

      Работа с регулярными выражениями

      Регулярные выражения

      Регулярные выражения (РВ) Перла - надмножество РВ grep/awk, используемых в Unix. В РВ следующие символы имеют специальное значение:

      \ | ( ) [ { ^ $ * + ? . /

      Символы "]" и "}" имеют специальные значения, только когда встречаются после соответственно "[" и "{". Все прочие символы в РВ воспринимаются буквально. Символ "/" имеет специальное значение не в регулярном выражении, а как признак начала и конца регулярного выражения (см. ниже примеры). Ниже кратко описаны наиболее часто используемые метасимволы РВ и их значения.

      . (точка)
      - любой единичный символ, кроме \n (если только не используется модификатор s - см. ниже).

      *
      - ноль или более появлений РВ, непосредственно предшествующего этому символу.

      +
      - одно или более появлений РВ, непосредственно предшествующего этому символу.

      ?
      - ноль или одно появлений РВ, непосредственно предшествующего этому символу.

      ^
      - начало строки.

      $
      - конец строки.

      [символы]
      [^символы]
      	
      - один символ из (не)перечисленных внутри скобок. Все метасимволы внутри квадратных скобок теряют свое специальное значение, кроме символа "^", если он находится непосредственно после открывающей скобки (в этом случае он обозначает отрицание набора символов); если символ "^" находится не сразу после открывающей скобки, он воспринимается буквально. Если символ "]" находится непосредственно за открывающей скобкой или непосредственно за комбинацией "[^", он воспринимается буквально, а не как закрывающая скобка; в этом случае должна быть еще одна закрывающая скобка.

      (РВ)
      - группировка регулярного выражения РВ. Используется в том числе и для последующей ссылки на часть строки, которая соответствует РВ.

      |
      - операция "ИЛИ" - требуется срабатывание хотя бы одного из РВ, находящихся слева и справа от символа "|".

      \s
      - любой пробельный символ (то же, что [ \t\n\r\f]).

      \w
      - любой алфавитно-цифровой символ (то же, что [a-ZA-Z_0-9]).

      \d
      - любая цифра (то же, что [0-9]).

      \b
      - граница слова.

      \S, \W, \D, \B
      - отрицание регулярных выражений, соответственно, \s, \w, \d, \w.

      $переменная
      - подстановка значения переменной.

      \
      - экранирует следующий метасимвол так, что он воспринимается буквально.

      Регулярные выражения заключаются в пару слэшей: /РВ/, после которых могут следовать модификаторы:

      • i - игнорировать различие прописных и строчных букв (в общем случае - только для латиницы),
      • s - воспринимать скалярное выражение, подлежащее обработке, как состоящее из нескольких строк, разделенных символами \n (т.е. метасимволы ^ и $ будут срабатывать на символах \n, которые возможно встретятся внутри обрабатываемого скалярного выражения),
      • m - воспринимать скалярное выражение, подлежащее обработке, как одну строку, независимо от того, встречаются ли внутри этого выражения символы \n или нет (т.е. метасимволы ^ и $ не будут срабатывать на символах \n, но метасимвол . будет срабатывать на \n).

      Обратите внимание, что метасимволы $, ^, \b не поглощают символы обрабатываемой строки, т.е. например, ^ - это не первый символ строки, а начало строки как таковое; аналогично, \b - это не первый или последний символ слова, а граница между символами типа \W и \w. Иначе говоря, слово cat соответствует РВ /^c/, а слово act ему не соответствует; или в строке "a yellow cat" регулярному выражению /\byellow\b/ соответствует подстрока "yellow", а не " yellow " или "ello".

      Оператор сопоставления

      Поиск РВ в строке:

      $s =~ /РВ/модификаторы;
      $s !~ /РВ/модификаторы;
      
      Операция возвращает в скалярном контексте "Истинно", если строка $s (не) соответствует РВ, иначе возвращается "Ложно". Если $s не указано, используется $_.
      if ( $d =~ /vvsu\.ru$/ ) { ... }
      if ( /^From: /i) { ... }
      

      Оператор замены

      Замена части строки:

      $s =~ s/РВ/замена/модификаторы;
      
      Операция производит поиск подстроки строки $s, соответствующей РВ, после чего заменяет эту подстроку на замену. Если указан модификатор g, то заменяются все подстроки, соответствующие РВ, иначе заменяется только первая найденная подстрока. Операция возвращает количество произведенных замен. Если $s не указано, используется $_. Подстановку переменных можно использовать и в РВ, и в замене. В части замена можно использовать также спецсимволы $1,$2,..., которые произведут подстановку соответствующей части РВ, взятой в скобки. Примеры:
      #в строке $x поменять первые два поля местами;
      #$1 ссылается на первое выражение в скобках в первой части оператора, $2 - на второе
      $x =~ s/([^ ]*) *([^ ]*)/$2 $1/;
      
      #убрать точку с конца строки $_
      s/\.$//;
      
      #заменить все слова white на black в переменной $_
      s/\bwhite\b/black/ig;
      
      #заменить в $_ все выражения #include_date на текущую дату
      #используется модификатор e, говорящий о том,
      #что вторая часть оператора должна быть воспринята как Перл-код
      s/#include_date/localtime/eg;
      
      #то же самое, но текст "#include_date" хранится в переменной $date_token
      s/$date_token/localtime/eg;
      

      Работа с файлами и запуск других программ

      Чтение и запись файлов осуществляется через переменную-дескриптор файла. Имя такой переменной не имеет специального префикса и, как правило, записывается прописными буквами. Дексриптор создается при вызове функции open. Дескрипторы файлов не требуется предварительно объявлять даже при использовании "use strict".

      Открытие файла

      open (FILE, "filename") or die ("Cannot open filename: $!");
      
      Функция open возвращает "ложно" в случае невозможности открыть файл, в этом случае выполняется функция die, которая выводит свой аргумент на печать и завершает работу скрипта. Переменная S! - стандартная переменная Перла, содержит описание последней возникшей системной ошибки.

      FILE - дескриптор файла, он будет использован ниже в операциях чтения, записи и закрытия файла.

      В приведенном примере файл filename открывается для чтения. Для открытия файла на запись с нуля его имя нужно предварить символом ">" (если файл до этого существовал, его содержимое пропадет); для открытия на добавление в конец - предварить символами ">>":

      open (FILE, ">filename") or die ("Cannot open filename: $!");
      open (FILE, ">>filename") or die ("Cannot open filename: $!");
      #открыть для чтения и записи
      open (FILE, "+>filename") or die ("Cannot open filename: $!");
      
      Для любого скрипта при его запуске по умолчанию открываются дескрипторы STDIN, STDOUT и STDERR.

      Чтение из файла

      Оператор <FILE> производит чтение из файла с дескриптором FILE. В скалярном контексте (например, в условии цикла while) оператор при кадом вызове возвращает очередную строку текста до следующего символа перевода строки включительно. В списочном контексте возвращается список всех текстовых строк от текущей позиции до конца файла.

      # скалярный контекст
      while (<FILE>) {
        chomp;
        if ($_ eq "this text") {
          do_someting();
        }
      }  
      
      # списочный контекст
      @all_lines=<FILE>;
      

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

      Еще одна распространенная ошибка! Оператор <> (внутри скобок пусто) считывает не из стандартного ввода, а из файлов, имена которых указанны как аргументы командной строки. При завершении одного файла ввод продолжается с начала следующего файла в том порядке, в каком имена файлов указаны в командной строке. Открывать файлы при использовании оператора <> не требуется.

      Запись в файл

      print FILE "эта строка печатается в FILE\n";
      

      Закрытие файла

      close FILE;
      
      Хотя при выходе из скрипта Перл и закрывает все незакрытые файлы, настоятельно рекомендуется делать это явным вызовом функции close во избежание различных побочных эффектов.

      Вывод в программу

      Для передачи данных в стандартный ввод другой программы нужно создать с помощью open соответствующий дескриптор файла (при этом программа будет запущена) и вывести в него данные. Для создания такого дескриптора вторым аргументом в функцию open передается путь к программе, предваренный символом "|":

      open (MAIL, "|/usr/bin/mail $user") or die($!);
      print MAIL "Subject: Happy birthday!\n\n",
                 "Hi, $user! Happy birthday!";
      close MAIL;		   
      

      Ввод из программы

      Для приема данных из стандартного вывода другой программы нужно создать с помощью open соответствующий дескриптор файла (при этом программа будет запущена) и ввести из него данные. Для создания такого дескриптора вторым аргументом в функцию open передается путь к программе, после которого следует символ "|":

      open (LS, "/usr/bin/ls $dir |") or die($!);
      @dir_listing= <LS>;
      close LS;		   
      

      Переименование, удаление файла и изменение его атрибутов

      # переименовать файл
      rename $oldname, $newname or die($!);
      
      # удалить файлы (возвращает количество успешно удаленных файлов)
      unlink $this_file, $that_file, @and_whole_list_of_files;
      
      # изменить атрибуты файлов
      # (возвращает количество файлов у которых атрибуты были изменены)
      chmod 0755, @array_of_filenames;
      
      # изменить владельцев файлов ($uid и $gid должны быть числовыми)
      # (возвращает количество файлов у которых владельцы были изменены)
      chown $uid, $gid, @array_of_filenames;
      
      Запуск другой программы

      Для выполнения другой программы из скрипта используется функция system, аргументом которой является список argc[] (т.е. командная строка вывываемой программы, начинающаяся с имени самой программы).

      system $program_to_run, $argument1, $argument2, ...;
      
      Функция возвращает 0 при успешном завершении вызванной программы и X*256, если статус выхода программы был X.

      Работа со строками

      В этом пункте описаны некоторые полезные функции Перла для работы со строками (функции для работы с регулярными выражениями рассмотрены выше).

      Разбор строки

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

      @список = split;
      @список = split разделитель;
      @список = split разделитель, строка;
      @список = split разделитель, строка, лимит;
      

      Лимит указывает, что строка будет разбита не более, чем на лимит частей (иными словами, будут восприняты только первые лимит-1 разделителей; весь остаток будет возвращен в последнем элементе списка). Если лимит отсутствует, то все встретившиеся в строке разделители будут участвовать в разбиении.

      Если строка отсутствует, обрабатывается $_.

      Разделитель может быть регулярным выражением (заключается в слэши: /РВ/) или буквальной строкой (заключается в кавычки: 'строка'). Если разделитель отсутствует, то подразумевается /\s+/ ("один или более пробельных символов подряд"); т.е. split без аргументов экивалентен "split /\s+/, $_".

      Пример:

      
      # вывод списка всех пользователей системы с их идентификаторами UID
      open (PASSFILE, "/etc/passwd") or die($!);
      while (<PASSFILE>) {
        chomp;
        ($user, $junk, $uid) =split ':'; #ненужные элементы списка отбрасываются
        print "User $user has UID $uid\n";
      };
      

      Формирование строки из списка элементов

      join

      join 'разделитель', @список;
      

      Функция join формирует скаляр-строку, состоящую из элементов списка, отделяя значения элементов друг от друга разделителем.

      $_ = join ' | ', "a", "b", "c";
      print;
      #ВЫВОД: "a | b | c"
      
      # заменить разделители с двоеточия на запятую с пробелом
      $_ = join ', ' (split, ':');
      

      sprintf
      Функция sprintf работает так же, как в языке С.

      Поиск подстроки в строке

      index

      $позиция = index $строка, $подстрока, $начальная_позиция;
      $позиция = index $строка, $подстрока;
      
      Функция index возвращает позицию в строке, с которой начинается подстрока (имеется в виду первое вхождение подстроки, если их несколько). Если указана начальная позиция, то поиск начинается с этой позиции, иначе - с начала строки. Позиции в строке нумеруются, начиная с нуля. Если подстрока не найдена, функция возвращает -1.
      # последовательный поиск всех вхождений $lookfor в строку $string
      $pos = -1;
      while (($pos=index($string, $lookfor, $pos)) > -1) {
        print "Found at position $pos\n";
        $pos++;
      }
      
      

      rindex

      Функция rindex аналогична index, но возвращает позицию последнего вхождения подстроки в строку. В этом случае третий аргумент, если он указан, определяет позицию, на которой поиск следует прекратить.

      Извлечение подстроки из строки в известной позиции

      $подстрока = substr $строка, $смещение, $длина;
      $подстрока = substr $строка, $смещение;
      
      Функция substr извлекает из строки подстроку длиной длина, начинающуюся с позиции смещение. Если длина не указана, то подстрока извлекается от смещения до конца строки. Позиции в строке нумеруются, начиная с нуля.

      Если смещение отрицательно, то оно отсчитывается не от начала, а от конца строки. Если длина отрицательна: длина=-N, то это значит, что извлекается подстрока такой длины, что она заканчивается за N символов до конца строки.

      # все нижеследующие операции присваивают переменной $_ значение "abc"
      $_ = substr "xyzabcqqqq", 3, 3;
      $_ = substr "xyzabcqqqq", 3, -4;
      $_ = substr "xyzabcqqqq", -7, 3;
      $_ = substr "xyzabcqqqq", -7, -4;
      

      Вставка/замена части строки в известной позиции

      Для вставки подстроки в строку или замены одной подстроки другой подстрокой используется функция substr в левой части оператора присвоения. В этом случае первый аргумент функции substr обязан быть скалярным выражением, которому можно присвоить значение - скалярной переменной, элементом массива или элементом хэша. Примеры (перед каждым примером предполагается, что $a="abcdef"):

      
      # 1) добавление подстроки "HELLO" в начало значения переменной $a
      substr($a,0,0) = "HELLO";
      # в $a лежит "HELLOabcdef"
      
      # 2) замена второго и третьего символов в строке $_ словом "HELLO"
      substr($a, 1, 2) = "HELLO";
      # в $a лежит "aHELLOdef"
      
      # 3) вставка слова "HELLO" между третьим и четвертым символами в строке $a
      substr($a, 3, 0) = "HELLO";
      # в $a лежит "abcHELLOdef"
      
      
      # 4) замена последнего символа в строке $a словом "HELLO"
      substr($a, -1, 1) = "HELLO";
      # в $a лежит "abcdeHELLO"
      

      Вышеприведенные операции можно понимать так: из переменной $a извлекается указанная подстрока, а потом вместо нее в этой же позиции вставляется слово "HELLO".

      Прочие функции

      # удаляет один символ с конца строки $a; возвращает удаленный символ
      chop $a;
      
      # удаляет с конца строки символ перевода строки, если он там есть,
      # иначе строка остается без изменений;
      # возвращает количество удаленных символов
      chomp $a;
      
      #возвращает длину строки $a
      $l = length $a;
      
      #переводит все символы строки $a в верхний регистр
      uc $a;
      #переводит первый символ строки $a в верхний регистр
      ucfirst $a;
      
      #переводит все символы строки $a в нижний регистр
      lc $a;
      

      Если аргументы вышеприведенных не указаны, подразумевается $_.

      Написание функций

      Функции описываются в любом месте скрипта с помощью конструкции

      sub имя_функции {
        тело_функции;
      }
      
      и вызываются обычным образом:
      имя_функции(аргумент1, аргумент2, ...); # со списком аргументов
      имя_функции();                          # без аргументов
      
      
      Скобки при вызове написанных пользователем функций обязательны.

      Количество и тип аргументов не декларируются и могут быть любыми и различными при последовательных вызовах одной и той же функции; все аргументы при передаче в функцию объединяются в единый список; внутри функции все аргументы доступны через массив @_ в том порядке, в каком они были указаны при вызове функции. Если аргументов не было, то массив @_ пуст. Хэш, переданный в качестве аргумента, преобразуется в список (о взаимоотношениях списков и хэшей см. выше п. "Типы переменных. Хэши").

      
      sub function1 {
         # абсолютно бессмысленная функция - просто иллюстрация, как взять аргументы
         my $x = shift;       # при этом первый аргумент удаляется из списка @_
                              # если @_ пуст, то $x=undef
         my ($y,$z) = @_;     # а здесь аргументы из списка @_ не удаляются
                              #(если они там вообще есть, иначе $y=$z=undef)
         print "Our first argument is ", defined $x ? $x : "empty", "\n";
         $y ||=1;             #  а $y и $z присвоим значения по умолчанию
         $z ||=1;   
      }
      
      sub function2 {
        # жутко полезная функция
        # предполагается, что в качестве аргументов поступают скаляр $k и хэш %h;
        # функция возвращает $h{$k}
        
        my $k = shift;
        # если $k пусто или в списке аргументов ничего не осталось - вернуть undef и выйти
        (defined $k and @_) or return undef; 
        my %h=@_;
        return $h{$k};
      }
      # вызов функции (например):
      $value=function2("my_key",%hash);
      
      

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

      Каждая функция вызывается либо в скалярном, либо в списочном контексте; соответственным образом вызывающая программа интерпретирует возвращаемый функцией результат:

      # ф-я f() возвращает список
      @array=f(); # списочный контекст - все тривиально
      $x=f();    # скалярный контекст - в $x помещается длина возвращаемого списка, а сам он пропадает
      
      # ф-я s() возвращает скаляр
      @array=s(); # списочный контекст - список @array теперь состоит из одного возвращенного значения
      $x=s();    # скалярный контекст - все тривиально
      

      Для определения того, в каком контексте была вызвана функция, внутри нее используется оператор wantarray, возвращающий истину, если данная функция была вызвана в списочном контексте. Рассмотрим пример функции, которая возвращает список в списочном контексте и первый элемент списка - в скалярном.

      sub f {
        # здесь как-то создается список @array
        # ...
        return wantarray ? @array : $array[0];
      }
      push @biglist, f(); # списочный контекст, т.к. вторым аргументом функции push ожидается список
      $first=f(); # скалярный контекст
      
      

      Внимание, распространенная ошибка! Если в функцию в качетсве аргументов переданы 2 массива: f(@ar1,@ar2), то внутри функции нет никаких способов определить, где в списке аргументов @_ кончается первый массив и начинается второй. Если внутри функции требуется различать эти массивы, то нужно передавать их в функцию в виде ссылок. Тоже самое касается и хэшей.

      Библиотека CGI.pm

      Строение скрипта






      Курс "Технологии Интернет"
      Кафедра КТС
      Максим Мамаев




      CGI, Программирование CGI-скриптов на Перле

      Владивостокский государственный университет экономики и сервиса

      Кафедра КТС

      Максим Мамаев

      Технологии Интернет
      Лабораторный практикум

      Тема 6. CGI


      Литература и ссылки

      Используемое ПО:

      • Unix (Solaris 2.x),
      • Russian Apache (Apache 1.3.6PL28.16) (распространяется свободно).
      • Язык программирования Perl v. 5.005_03, библиотека CGI.pm и сопутствующие библиотеки (распространяется свободно).
      • Модуль mod_perl v. 1.21 (распространяется свободно).

      См. архив на Уране.



      Интерфейс CGI

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

      CGI определяет каким образом данные, предоставленные клиентом в запросе, передаются программе, как программа возвращает сгенерированный HTML-контент серверу, и какие переменные окружения устанавливаются сервером при запуске программы. Переменные окружения несут дополнительную информацию о сервере и запросе (например, тип сервера, IP-адрес клиента и др.).

      Данные из заполненной клиентом HTML-формы могут передаваться на сервер двумя методами: GET и POST, это определяется параметром method соответствующего тэга <form method=... action=...>. В первом случае (GET) данные присоединяются после вопросительного знака в конец URL, указанной в параметре action, во втором случае - передаются в теле запроса - в секции, предназначенной для данных (следует после всех заголовокв и пустой строки). В обоих случаях данные кодируются одинаково - см. след. пункт.

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

      CGI-программа выдает содержимое ответа (как правило, HTML-контент) на свой стандартный вывод, который перехватывается веб-сервером с тем, чтобы отослать эти данные клиенту. Предварительно CGI-программа должна напечатать заголовок "Content-Type" и отделить его от данных пустой строкой. Например, вывод CGI-программы, генерирующей HTML, может выглядеть следующим образом:

      Content-Type: text/html
      
      <HTML>
        <BODY>
          <H1>Hello, world</H1>
        </BODY>	
      <HTML>  
      
      

      Конфигурирование сервера Apache для исполнения CGI-скриптов

      Для того, чтобы Apache воспринимал все файлы, находящиеся в некотором каталоге как CGI-скрипты, нужно использовать директиву

      
      ScriptAlias /виртуальный/путь/ /путь/к/каталогу/
      ScriptAlias /cgi-bin/ /usr/local/www/cgi-bin/
      
      
      Это означает, что для обработки запроса URL вида http://your.server.com/cgi-bin/dir/script будет взят не файл script из каталога DocumentRoot/cgi-bin/dir/, а запущена программа /usr/local/www/cgi-bin/dir/script.

      Для смешанного хранения файлов, подлежащих просмотру, и CGI-скриптов в одном каталоге внутри дерева DocumentRoot следует присвоить CGI-скриптам одинаковые расширения (например, ".cgi") и указать серверу, что интерпретировать такие файлы следует как CGI-скрипты:

      
      AddHandler cgi-script .cgi
      
      
      Директива AddHandler может быть использована в любом контексте конфигурации Apache.

      Структура URL и кодирование данных запроса

      Для работы CGI-программ важное значение имеют части URL, называемые PATH_INFO и QUERY_STRING. Рассмотрим запрос с URL вида

      http://my.server.com/cgi-bin/dir/prog/a/b?A=1&B=qwerty
      

      Используя директиву ScriptAlias, приведенную в предыдущем пункте, сервер определяет что произошло обращение к CGI-программе и для поиска этой программы заменяет начальное /cgi-bin/ на /usr/local/www/cgi-bin/. Следуя запрошенному URL, сервер обнаруживает в этом каталоге подкаталог dir, однако подкаталога prog в каталоге /usr/local/www/cgi-bin/dir не обнаружено. В таком случае сервер предполагает, что prog - имя CGI-программы, подлежащей выполнению. Если программа /usr/local/www/cgi-bin/dir/prog не найдена или не может быть исполнена, сервер возвращает клиенту ошибку 403, 404 или 500. В противном случае программа prog запускается, а оставшаяся часть пути из URL - /a/b - передается программе prog в переменной окружения PATH_INFO. Таким способом можно передать в CGI-программу дополнительные параметры.

      Все, что находится после вопросительного знака - A=1&B=qwerty - передается программе prog в переменной окружения QUERY_STRING. Это могут быть данные из заполненной пользователем формы, отправленные на сервер методом GET, либо какая-то другая информация (сервер не делает никаких предположений об интерпретации данных в QUERY_STRING, это задача вызываемой программы).

      Данные из полей формы, заполненной пользователем - независимо от метода (POST или GET), которым они пересылаются на сервер - кодируются следующим образом:

      имя_поля=значение_поля&имя_поля=значение_поля...
      

      Пары имя-значение разделяются амперсандом. Алфавитно-цифровые символы и некоторые знаки препинания, не имеющие специального значения (тире, подчеркивание) передаются как есть. Остальные символы кодируются в виде "%NM", где NM - двузначный шестнадцатеричный код символа. Пробел может передаваться как "%20" или как символ "+". Кириллические символы также должны кодироваться указанным способом. Кодировка производится броузером при отправке полей заполненной формы.

      Например:

      http://my.server.com/cgi-bin/dir/prog?birthday=11%2F05%2F73&name=John+Smith
      
      означает, что в поле birthday пользователь внес "11/05/73", а в поле name - "John Smith".

      Декодирование данных формы является задачей CGI-программы.

      При пересылке данных формы, закодированных вышеописанным способом, методом POST клиент должен установить заголовок запроса Content-Type следующим образом:

      
      Content-Type: application/x-www-form-urlencoded
      
      

      Переменные окружения CGI

      При запуске CGI-скрипта веб-сервер устанавливает дополнительные переменные окружения:
      Переменная Значение
      AUTH_TYPE

      Метод аутентифицирования, использованный для опознания пользователя. См. также REMOTE_USER и REMOTE_IDENT.

      CONTENT_LENGTH

      Длина данных запроса в байтах, переданных CGI-скрипту через стандартный ввод.

      CONTENT_TYPE

      MIME-тип данных запроса.

      DOCUMENT_ROOT

      Корневой каталог дерева документов веб-сервера (определяется директивой DocumentRoot).

      GATEWAY_INTERFACE

      Используемая версия CGI.

      HTTP_ACCEPT

      Список MIME-типов данных, которые клиент может принять.

      HTTP_FROM

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

      HTTP_REFERER

      URL документа, в котором находилась ссылка, вызвавшая настоящий запрос.

      HTTP_USER_AGENT

      Броузер клиента.

      PATH_INFO PATH_INFO (если есть) - см. выше "Структура URL и кодирование данных запроса"
      PATH_TRANSLATED

      PATH_INFO, преобразованное в полный путь в файловой системе сервера (PATH_INFO, добавленное к DOCUMENT_ROOT).

      QUERY_STRING

      Данные запроса, переданные в составе URL вслед за вопросительным знаком - см. выше "Структура URL и кодирование данных запроса".

      REMOTE_ADDR

      IP-адрес клиента.

      REMOTE_HOST Имя DNS клиента.
      REMOTE_USER Аутентифицированное имя пользователя.
      REQUEST_METHOD

      Метод запроса (GET, POST, HEAD и т.д.).

      SCRIPT_NAME Виртуальный путь (например, /cgi-bin/program.pl) к исполняемому CGI-скрипту.
      SERVER_NAME DNS-имя сервера или, при невозможности определить имя, его IP-адрес.
      SERVER_PORT

      Номер порта сервера.

      SERVER_PROTOCOL Имя и версия протокола, через который был сделан запрос (например, HTTP/1.1).
      SERVER_SOFTWARE

      Тип и номер версии ПО веб-сервера.

      С Apache поставляется стандартный тестовый скрипт test-cgi, выводящий занчения переменных окружения CGI.

      Cookies и другие методы сохранения состояния

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

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

      Существует несколько методов сохранения состояния:

      1. cookies - сохранение на компьютере клиента,
      2. скрытые поля - сохранение внутри формы, посылаемой клиенту,
      3. сохранение в файле какого-либо формата на сервере,
      4. сохранение в параллельно работающей базе данных.

      Два последних метода реализуют сохранение состояния на стороне сервера.

      База данных

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

      Также существует решение в виде демона, который запускается параллельно с http-сервером, и сохраняет требуемую информацию в своей оперативной памяти в виде переменная=значение. Для записи или извлечения данных скрипт соединяется с демоном по заранее оговоренному порту TCP или UDP, идентифицирует себя и использует набор простых команд типа "save name=value" и "extract name" (возвращается value).

      Интересно, что несмотря на сложность реализации, такое решение (или использование СУБД с возможностью доступа по сети) позволяет разделять данные между скриптами работающими на различных серверах (если реализуется какая-то сложная распределенная интерактивная веб-система), при этом не вовлекается сохранение данных на стороне пользователя.

      Файл

      Основным недостатком сохранения данных в файле, кроме использования дискового пространства и накладных расходов на файловые операции, является операция записи на диск как таковая. Запись на диск может быть источником серьезных проблем в плане безопасности, так как работа CGI-скрипта фактически управляется воздействием внешних пользователей, которые могут иметь враждебные намерения. Путем посылки каких-либо специальных данных неаккуратно написанному скрипту можно вызвать серьезный сбой в работе сервера. Если же скрипт имеет право записи на диск, то последствия могут быть гораздо серьезнее, поэтому обычно CGI-скрипты, как и сам веб-сервер, работают с минимальными привилегиями от имени пользователя nobody без права записи на диск.

      Сохранение состояния на стороне пользователя

      Сохранение данных состояния на стороне пользователя (cookies и, технически, скрытые поля) существенный недостаток: пользователь имеет полный доступ к сохраняемым данным и может их несанкционированно изменить (например, прочитать правильный ответ теста или изменить идентификатор пользователя). Достоинством является простая реализация.

      Cookies

      Cookies - это данные вида имя=значение, которые, будучи получены от сервера, сохраняются броузером на диске пользователя для их возврата серверу при последующих запросах к этому или другому URL. Поскольку данные сохраняются на диске, они могут быть использованы после перезапуска броузера.

      Сервер передает cookie через специальное поле заголовка HTTP-ответа "Set-Cookie". Броузер возвращает cookie также через специальное поле в загловке HTTP-запроса - "Cookie". На стороне сервера cookie формируется, как правило, скриптом, который просто выводит в STDOUT соответствующий заголовок. Передача данных, полученных через cookie, от броузера в скрипт производится сервером через установку переменной окружения HTTP_COOKIE, которая доступна внутри скрипта и содержит пары имя=значение, которые броузер передал внутри поля "Cookie" в заголовке своего запроса.

      Формат поля Set-Cookie (HTTP-ответ)

      Set-Cookie: имя=значение; Max-Age=секунды; Comment=текстовый_комментарий; Path=URI_или_часть_URI; Domain=домен_сервера; Secure ; Version=1
      

      Все элементы, кроме имя=значение и Version, не являются обязательными. В заголовке одного ответа сервера может содержаться несколько полей Set-Cookie.

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

      Max-Age=секунды
      устанавливает срок годности данных (в секундах с момента получения cookie); по умолчанию - до окончания работы данного проуесса броузера.

      Comment=текстовый_комментарий
      комментарий сервера по поводу предназначения cookie; предполагается, что пользователь может отказаться работать с этим cookie, если комментарий ему не понравится.

      Domain=домен_сервера
      домен, для которого действительно данное cookie (броузер должен возвращать cookie при обращении ко всем серверам данного домена, с учетом параметра Path [см. ниже]); домен должен начинаться с точки; данный сервер должен находиться в этом домене. Если параметр Domain не указан - возвращать cookie только данному серверу.

      Path=URI_или_часть_URI
      путь от корня дерева документов сервера (URI); броузер должен возвращать cookie при обращении к данному URI и ко всем URI, начинающимся с данного; по умолчанию - URI, при запросе которого было сгенерировано cookie, минус имя файла.

      Пример: при обращении на "http://s.vvsu.ru/a/b/c" сервер выдал ответ с установленным полем в заголовке:

      SetCookie: X=5; Version=1
      Это значит, что cookie должно возвращаться броузером при обращении на все URL вида "http://s.vvsu.ru/a/b/какое-то_имя_файла".
      Если же SetCookie в ответе сервера выглядит вот так:
      SetCookie: X=5; Domain=.vvsu.ru; Path=/a/; Version=1
      то броузер должен присоединять это cookie ко всем запросам URL вида: "http://имя_без_точки.vvsu.ru/a/b/некий_путь_или_никакого".

      Secure
      если это параметр присутствует, то броузер должен возвращать cookie серверу только через защищенный канал связи; стандарт не специфицирует конкретный механизм защиты данных при передаче, но предполагается, что это SSL.

      Формат поля Cookie (HTTP-запрос)

      Cookie: имя=значение; Path=URI_или_часть_URI; Domain=домен_сервера; Version=1
      

      Параметры Path и Domain включаются только если они были установлены в заголовке Set-Cookie. Если несколько cookie удовлетворяют параметру Path, то они указываются в одном заголовке Cookie друг за другом (через точку с запятой) в следующем порядке: первыми передаются cookie с более длинным параметром Path. Порядок следования при равенстве параметров Path не определяется.

      Скрытые поля

      Скрытое поле создается внутри формы с помощью тега
      <input type=hidden name=name1 value=value1>

      Когда броузер получает документ с этой формой, содержимое полей типа "hidden" не отображается и пользователь не знает об их существовании (если только не посмотрит в HTML-текст присланного документа). После того, как пользователь отправляет форму на сервер, пара "name1=value1" присоединяется к данным формы, которые будут обработаны вновь запущенным скриптом. Таким образом скрипт может получить данные о предыстории своей работы с пользователем. Например, при электронном шоппинге в скрытых полях может сожержаться список товаров, выбранных для покупки в других отделах, которые пользователь уже посетил в данном сеансе работы.

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

      Server Side Includes

      SSI представляет собой механизм разбора HTML-документов на стороне сервера с целью обнаружения в документе и выполнения директив, добавляющих в документ дополнительную информацию.

      Все директивы вставляются внутрь тэгов HTML-комментариев, что позволяет клиенту, в случае, если сервер не поддерживает SSI, игнорировать эти директивы. Директивы имеют следующий формат:

      
      <!--#директива параметр(ы)="значение"-->
      
      

      Ниже следует список основных директив SSI и их параметров.

      echo
      Подставляет в документ значение указанной в качестве параметра переменной окружения (см. также список CGI-переменных) или специальной переменной SSI (см. ниже):
              <H1>Вы пришли на сервер, находящийся по адресу
              <!--#echo var="SERVER_NAME"-->...</H1>
      

      include
      Вставляет в документ текст другого файла. Параметры: file - указывает путь к вставляемому файлу относительно расположения данного документа; virtual - указывает виртуальный путь (как он указывался бы в URL) к вставляемому файлу.
             <!--#include file="stuff.html"-->
             <!--#include virtual="/personal/stuff.html"-->
      
      Эта директива очень удобна для создания стандартных шапок и подвалов веб-страниц.

      fsize
      Вставляет размер указанного в параметре файла (путь к файлу виртуальный):
              Размер файла archive.zip - 
              <!--#fsize file="/archive.zip"--> bytes.
      

      flastmod
      Вставляет в документ дату и время последней модификации указанного в параметре файла (путь к файлу виртуальный):
              Дата последнего изменения: 
              <!--#flastmod file="/this_dir/this_file.html"-->bytes.
      
      Формат вывода даты и времени может быть специфицирован параметром timefmt директивы config.

      exec
      Выполняет внешнюю программу, указанную параметром, и вставляет вывод этой программы в документ. Параметры: cmd - выполняемая программа является неким обычным приложением; cgi - выполняемая программа является CGI-скриптом
             <!--#exec cmd="/bin/finger $REMOTE_USER@$REMOTE_HOST"-->
              На эту страницу заходили
              <!--#exec cgi="/cgi-bin/counter.pl"--> 
              раз.
      		
      
      В первом примере используется подстановка значений переменных окружения (см. CGI-переменные).

      config
      Модифицирует различные аспекты работы SSI. Параметры:
      • errmsg - сообщение об ошибке, выдаваемое при невозможности выполнить директиву:
                  <!--#config errmsg="Файл не найден"-->
        
      • sizefmt - устанавливает формат вывода размера файла (подставляемого директивой fsize; значения: bytes - выводит в байтах; abbrev - округляет до целого числа килобайт.
                <!--#config sizefmt="abbrev"-->
        		Размер файла archive.zip - примерно 
                <!--#fsize file="/archive.zip"--> bytes.
        
      • timefmt - устанавливает формат вывода даты и времени, подробнее см. здесь.

      Специальные переменные SSI

      Ниже приведены переменные SSI, которые можно использовать в директиве echo в дополнение к переменным CGI.

      DOCUMENT_NAME
      Имя данного документа. Например:
               Вы читаете файл под названием:
               <!--#echo var="DOCUMENT_NAME"-->
      

      DOCUMENT_URL
      Виртуальный путь к данному документу. Например:
              Ссылка на этот документ:
              <!--#echo var="DOCUMENT_URL"-->
      

      QUERY_STRING_UNESCAPED
      Декодированные данные из QUERY_STRING (см "Структура URL и кодирование данных запроса"), при этом все метасимволы шелла экранированы обратным слэшем (\).

      DATE_LOCAL
      Текущие дата и время по местному времени. Например:
              Сейчас <!--#echo var="DATE_LOCAL"-->
      

      DATE_GMT
      Текущие дата и время по Гринвичу.

      LAST_MODIFIED
      Дата и время последней модификации данного документа. Например:
             Этот файл был последний раз изменен 
             <!--#echo var="LAST_MODIFIED"-->
      

      Задание

      Написать CGI-скрипт для игры в виселицу (угадывание слова по буквам).

      Правила игры

      Сервер загадывает слово из словаря и показывает его пользователю в замаскированном виде (буквы заменены звездочками). Пользователь имеет некоторое число попыток; во время каждой попытки он может угадать одну букву. Если пользователь правильно угадывает букву или называет букву, которую он уже использовал, попытка не засчитывается. Иначе число попыток уменьшается на единицу.

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

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

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

      Реализация

      Слова выбираются случайным образом из заданного текстового файла.

      Соотношение числа попыток и длины слова разумно определяется программистом. Например, число попыток есть заданная функция от длины слова; число попыток жестко привязано к каждому слову в словаре; длина слов в словаре и число попыток являются константами; при определении числа попыток используется заявленный пользователем уровень сложности.

      При первом обращении к скрипту выдается заставка и регистрационная форма вида:


      Ваше имя:


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

      Например, форма 6-й попытки при угадывании слова ПАРОВОЗ может выглядеть так:


      Игрок: Сидоров

      Слово: ПА*О*О*

      Использованные буквы: АОЕПС

      Осталось попыток: 5 из 10


      При завершении игры сервер выводит соответствующее сообщение и предлагает начать следующую игру.

      Список использованных букв выводится по алфавиту. Все операции нечувствительны к регистру. Для облегчения задачи допускается создание игры на английском языке.

      Программа скрипта может быть написана на любом языке программирования под Unix (немедленно доступные в классе языки: С, С++, Перл, и - для любителей острых ощущений - shell/awk).

      Обсуждение

      При написании скрипта следует непрерывно помнить следующее основное правило CGI-программиста:

      С каждым новым HTTP-запросом скрипт начинает свою работу с нуля. В данном случае: в игру может играть несколько пользователей одновременно; каждый из них последовательно формирует несколько HTTP-запросов; но для обработки каждого очередного запроса, поступившего на сервер, неважно к какому этапу игры или пользователю он относится, связан ли он с предыдущими или другими поступившими одновременно запросами, скрипт запускается с нуля и не имеет никаких знаний о своих предыдущих запусках и о состоянии игорного процесса.

      В связи с вышесказанным требуется решить две проблемы при обработке каждого вновь поступившего запроса (или, что тоже, при каждом запуске программы):

      1. Идентификация пользователя
      2. Получение информации о состоянии игры (слово, использованные буквы, число попыток)

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

      О методах сохранения состояния в CGI-программировании см. соответствующий раздел выше.

      В результате анализа информации о состоянии игры и содержимого заполненной пользователем формы (если таковое имеется в поступившем запросе) программа разветвляется для формирования одного из следующих ответов (HTML-текстов):

      • регистрационная форма,
      • форма попытки,
      • сообщение об окончании игры.
      После выдачи сформированного HTML-текста и сохранения (каким-либо способом) нового состояния, программа прекращает свою работу.

      Дополнительное задание

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

      Также для каждого пользователя ведется таблица его результатов; слова, уже предлагавшиеся пользователю, независимо от того, угадал он их или нет, исключаются из списка предложений.




      Следующая тема - Вопросы безопасности в Интернет

      Курс "Технологии Интернет"
      Кафедра КТС
      Максим Мамаев




      Технологии Интернет
      Лабораторный практикум

      Тема 5. Информационные сервисы


      Литература и ссылки

      Используемое ПО:

      • Unix (Solaris 2.x),
      • Russian Apache (Apache 1.3.6PL28.16) (распространяется свободно).
      • Язык программирования Perl v. 5.005_03 (распространяется свободно).
      • Модуль mod_perl v. 1.21 (распространяется свободно).

      См. архив на Уране.



      Протокол FTP

      Создание анонимного FTP-сервера

      Протокол HTTP

      HTTP (HyperText Transfer Protocol) - прикладной клиент-серверный протокол типа "запрос-ответ" работающий поверх TCP (стандартный порт 80). Клиентом является броузер; сервером - WWW-сервер. В первой версии протокола (1.0) на одно TCP-соединение приходился один запрос и ответ, после чего соединение закрывалось. В настоящее время протокол позволяет выполнить несколько итераций в рамках одного соединения, что позволяет избежать накладных расходов на установление/закрытие соединений, снизить нагрузку на сеть и уменьшить время получения данных.

      Последняя версия HTTP 1.1 описана в RFC-2616,2617.

      HTTP-запрос

      Запрос (Request) имеет следующий формат:

      Строка запроса
      Заголовки запроса
      Заголовки данных*
      Пустая строка
      Данные*
      Поля, помеченные звездочкой (*), присутствуют только, если в запросе использован метод POST или PUT (см. ниже).

      Строка запроса имеет вид:

      метод   URI  протокол/версия
      
      Примеры:
      
      GET / HTTP/1.1
      
      POST /cgi-bin/sript?X=1&Y=a%20b HTTP/1.1
      
      HEAD http://www.vvsu.ru/index.html HTTP/1.1
      

      Основные методы:

      GET
      запрос документа, идентифицируемого указанным URI.

      HEAD
      запрос заголовков документа, идентифицируемого указанным URI (аналогично GET, но само содержимое документа не возвращается).

      POST
      пересылка данных на сервер (используется при заполнении форм). URI в этом случае обычно идентифицирует CGI-скрипт, который будет обрабатывать присланные данные, и результат этой обработки будет возвращен в HTTP-ответе.
      Существуют также методы PUT и DELETE, предназначенные для управления документами на сервере, но используются они крайне редко.

      URI является указателем на запрашиваемый ресурс. При непосредственном обращении к серверу, содержащему запрашиваемый ресурс, URI имеет неполный вид (см. выше примеры GET и POST). При обращении через прокси-сервер URI должен быть полным (см. выше пример HEAD).

      Заголовки, передаваемые вслед за строкой запроса, делятся на собственно заголовки запроса и на заголовки данных, передаваемых в запросе (разумеется, если такие данные присутствуют). Каждый заголовок начинается с новой строки и состоит из ключевого слова, за которым следует двоеточие, и данных, например:

      Accept: text/html, text/plain;q=0.5
      

      Порядок следования заголовков не имеет значения.

      Ниже приведены примеры заголовков запроса (за полным списком и описаниями обращаться к RFC-2616). Заголовки данных рассмотрены ниже в отдельном пункте.

      Host: athena.vvsu.ru
      Доменное имя сервера, к которому производится обращение. Этот заголовок позволяет создавать на одном компьютере несколько виртуальных веб-серверов с различным содержимым; при этом запрошенный URI относится к пространству документов того виртуального сервера, имя которого указано в заголовке "Host:". Разумеется, имена всех виртуальных серверов должны быть прописаны в DNS как псевдонимы того компьютера, на котором они размещены. Заголовок "Host:" - единственный, являющийся обязательным в HTTP-запросе версии 1.1.

      Accept: text/html, text/plain
      MIME-типы данных, которые клиент согласен принять в ответ на свой запрос. Множественные значения перечисляются через запятую. Обычно броузер указывает среди значений "*/*", что означает, что он готов принять данные любого типа (в крайнем случае он предложит пользователю сохранить их на диск). Значение "*/*" также подразумевается, если заголовок отсутствует. О MIME-типах см. тему 4. Фактически, этот заголовок полезен, только если сервер может предоставить один и тот же документ в разных форматах (например, как текст, HTML-файл или документ Word). Если сервер не может выдать документ ни в одной из приемлемых кодировок, то рекомендуется, чтобы он возвратил ошибку с кодом 406, однако допускается выдача запрошенного документа в какой-то другой кодировке.

      Accept-Encoding: compress, gzip
      Представление данных (в данном случае, сжатие), которое клиент понимает. Может быть использовано для запроса передачи документа в сжатом виде, если сервер это поддерживает.

      Accept-Language: ru, en;q=0.2
      Язык документа, который клиент согласен принять. Заголовок может быть использован, если сервер может предоставить один и тот же документ на разных языках.

      User-Agent: Netscape/5.40; Solaris 2.5
      Тип клиента (броузера).

      Referer: http://athena.vvsu.ru/index.html
      URL документа, в котором находилась ссылка на запрашиваемый документ. (То есть, если пользователь просматривает документ, находящийся по адресу http://athena.vvsu.ru/index.html, и выбирает в нем одну из ссылок, то броузер формирует запрос нового документа, на который указывает ссылка, а поле Referer в заголовке этого запроса имеет значение http://athena.vvsu.ru/index.html.) Обратите внимание, что написание заголовка противоречит правилам орфографии (по-английски правильно - "referrer").

      Authorization: Basic d2VibWFzdGVyOnpycW1hNHY=
      Этот заголовок используется в запросах, для выполнения которых сервер требует аутентификацию клиента. В заголовке указывается схема аутентификации (Basic - обычная аутентификация по имени пользователя и паролю), после чего следует строка "имя:пароль", преобразованная по алгоритму base64. Подробнее о процедуре аутентификации доступа к закрытым ресурсам см. ниже п. "Обработка запроса клиента".

      Connection: Keep-Alive
      Клиент прдлегает не закрывать TCP-соединение после обработки запроса, с тем чтобы это соединение могло быть использовано для последующих запросов и ответов на них. Если новые запросы не последовали в тчечение некоторого времени (несколько секунд), сервер закрывает соединение. Значение заголовка "Connection: close" указывает на то, что соединение будет закрыто после обработки данного запроса.

      If-Modified-Since: Wed, 03 Nov 1999 19:43:11 GMT
      Условный запрос (только метод GET). Сервер вернет документ, только если он был изменен после указанного времени. Иначе сервер вернет код 304 "Not Modified" (о кодах ответа сервера см. след. пункт) и содержимое документа передано не будет. Такой условный запрос используется, если броузер (или прокси-сервер) уже имеет в своем кэше копию запрашиваемого документа.

      Range: bytes=0-499
      Запрос части документа (только метод GET). Сервер возвращает код 206 "Partial content" (а не как обычно 200 "OK"), заголовок ответа и документа, а в качестве данных передается только указанная в запросе "Range:" часть документа (в данном примере: первые 500 байт). Другие примеры: "bytes=500-" - от 501 байта до конца документа, "bytes=-500" - последние 500 байт, "bytes=0-0,-1" - первый и последний байты. Сервер может проигнорировать заголовок "Range:", в этом случае он возвращает полный документ с кодом 200 "OK".

      HTTP-ответ

      Ответ (Response) имеет следующий формат:

      Статусная строка
      Заголовки ответа
      Заголовки данных*
      Пустая строка
      Данные*
      Поля, помеченные звездочкой (*), могут отсутствовать - это зависит от типа запроса. Например при запросе методом HEAD возвращаются только статусная строка с кодом 200, заголовки ответа и данных, а сами данные не передаются; а при условном запросе If-Modified-Since, в случае если документ не модифицирован, возвращаются только статусная строка с кодом 304 и заголовок ответа без заголовков данных и самих данных.

      Статусная строка имеет вид:

      протокол/версия код   статус  
      
      Статус - текстовая строка, комментирующая код, предназначена для человека; программное обеспечение анализирует только числовое значение кода. Примеры статусных строк:
      
      HTTP/1.1 200 OK
      
      HTTP/1.1 304 Not Modified
      
      

      Код ответа является трехзначным числом. Коды разделены по группам в зависимости от первой цифры:

      1**
      Промежуточные информационные сообщения (практически не используются).

      2**
      Успешная обработка запроса. Примеры:
      200
      OK - наиболее общий ответ: запрос обработан, запрошенный документ передан клиенту (или только его заголовки - в случае запроса HEAD).
      206
      Partial Content - клиенту передана часть документа в соответствии с заголовком "Range:", имевшимся в запросе.

      3**
      Для получения документа требуются дополнительные действия со стороны клиента. Примеры:
      301
      Moved Permanently - запрошенный документ перемещен. Новый URI документа возвращается в заголовке "Location:". В качестве данных возвращается краткий комментарий со ссылкой на новое расположение документа. В последующих запросах этого документа клиенту следует использовать новый URI. Как правило броузеры автоматически генерируют новый запрос с указанным URI при получении кода 301.

      Сервер конфигурируется для возврата ответов с кодом 301 при реструктуризации его пространства документов - с тем, чтобы клиенты, использующие старые ссылки, перенаправлялись к новому расположению документов, а не получали ошибку 404 Not Found.

      302
      Found - запрошенный документ временно перемещен. Новый URI документа возвращается в заголовке "Location:". В качестве данных возвращается краткий комментарий со ссылкой на новое расположение документа. В последующих запросах этого документа клиенту следует использовать старый URI.
      304
      Not Modified - документ был запрошен с помощью условного GET-запроса и условие не выполнено (например, документ не был модифицирован с момента, указанного в запросе в заголовке "If-Modified-Since:"). Возвращаются только статусная строка и заголовки ответа, заголовки данных (документа) и сам документ не возвращаются.

      4**
      Ошибка клиента. Примеры:
      400
      Bad Request - ошибка в формате запроса.
      401
      Unauthorized - для доступа к ресурсу требуется аутентификация, но заголовок "Authorization:" либо отсутствует, либо содержит неприемлемые аутентификационные данные. Заголовок "WWW-Authenticate:" ответа в этом случае должен содержать информацию, необходимую для того, чтобы клиент определил, какая требуется аутентификация. Подробнее о процедуре аутентификации доступа к закрытым ресурсам см. ниже п. "Обработка запроса клиента".
      403
      Forbidden - север понял запрос, но намеренно отказался его выполнять. Аутентификация в этом случае не поможет. Причина отказа может быть передана в качестве данных HTTP-ответа. Если сервер не желает раскрывать причину отказа, он может использовать вместо кода 403 код 404.
      404
      Not Found - запрошенный ресурс не найден. Это наиболее общий ответ в случае невозможности передать клиенту запрошенный документ; при этом не делается никаких предположений о том, постоянно или временно ресурс недоступен, а также о причине его недоступности.
      405
      Method Not Allowed - в запросе использовался не разрешенный сервером метод (например, DELETE). Список разрешенных методов должен быть помещен в заголовке "Allow:" HTTP-ответа.
      406
      Not Acceptable - в заголовках "Accept..." клиент указал параметры перезентации документа, которые не могут быть выполнены сервером для данного документа (например, нет такой кодировки символов, какая указана в "Accept-Charset:").
      410
      Gone - аналогично 404 "Not Found", однако подразумевается, что документ существовал ранее, но умышленно удален навсегда (сделан недоступным). Полезно для временных презентаций, более не актуальных, для персональных страниц сотрудников, более не работающих в организации и т.п.

      5**
      Ошибка сервера. Примеры:
      500
      Internal Server Error - при обработке запроса произошла ошибка в программном обеспечении сервера. Типичный случай - ошибка в CGI-скрипте.
      501
      Not Implemented - сервер не обладает функциональностью, требуемой для выполнения запроса. Например, метод, указанный в запросе, не известен серверу.
      502
      Bad Gateway - сервер, действующий в качестве прокси-сервера, получил ошибочный (неадекватный) ответ от сервера, которому он перенаправил запрос клиента.
      503
      Service Unavailable - сервер временно не в состоянии обработать запрос (перегружен или находится на техобслуживании [maintanance]). Если известно время, через которое сервер вернется в рабочее состояние, оно может быть указано в заголовке "Retry-After:". Заметим, что в случае невозможности обслуживания запросов сервер не обязан выдавать ответ с кодом 503, а может просто отказывать в установлении TCP-соединения.
      504
      Gateway Timeout - сервер, действующий в качестве прокси-сервера, не получил за некоторое установленное время ответ от сервера, которому он перенаправил запрос клиента. Этот же код прокси-сервер должен возвращать, если произошел тайм-аут при опросе сервера DNS, однако некоторые существующие прокси-серверы возвращают при этом код 400 или 500.

      Заголовки, передаваемые вслед за статусной строкой, делятся на собственно заголовки ответа и на заголовки данных, передаваемых в ответе (если такие заголовки требуются). Каждый заголовок начинается с новой строки и состоит из ключевого слова, за которым следует двоеточие, и данных. Порядок следования заголовков не имеет значения.

      Ниже приведены примеры заголовков ответа (за полным списком и описаниями обращаться к RFC-2616). Заголовки данных рассмотрены ниже в отдельном пункте.

      Server: Apache/1.3.6 (Unix) rus/PL28.16
      Тип WWW-сервера, его версия, дополнительные модули и подобная информация.

      Date: Mon, 13 Mar 2000 07:38:48 GMT
      Время ответа.

      Accept-Ranges: bytes
      Сервер может выдавать документ по частям (по байтам).

      WWW-Authenticate: Basic realm="SysAdmin"
      Этот заголовок возвращается с ответом 401 Authorization Required, в котором указана схема аутентификации и ее контекст ("System Administration"). Последнее - комментарий для пользователя, который броузер показывает в окне запроса имени и пароля.

      Location: http://new.url.com/
      Новый URL документа, возвращенный вместе с ответом типа 301 Moved Permanently, 302 Found.

      Connection: close
      См. заголовок Connection в заголовках HTTP-запроса выше.

      Allow: GET, HEAD, POST
      Перечисляются HTTP-методы, поддерживаемые сервером. Отправляется в ответ на запрос, содержавший недопустимый метод. Ответ сервера имеет кодом 501 Not Implemented).

      Заголовки данных

      Заголовки данных описывают данные, посылаемые в HTTP-запросе или ответе. В запросе обычно посылаются данные заполненных форм (метод POST). Заголовки данных следуют в заголовочной части HTTP-ответа или запроса вместе с заголовками ответа или запроса; порядок следования заголовков различныз типов не определен. Сами данные всегда отделяются от заголовочной части пустой строкой.

      Ниже приведены примеры заголовков данных (за полным списком и описаниями обращаться к RFC-2616).

      Content-Type: text/html; charset=koi8-r
      MIME-тип содержимого, дополнительным параметром указывается, например, кодировка символов. Подробнее о типах MIME см. тему 4. У данных заполненных форм, отправляемых в HTTP-запросе методом POST заголовок "Content-Type:" имеет значение "application/x-www-form-urlencoded". Подробнее об этом см. тему 6 "CGI".

      Last-Modified: Sat, 11 Dec 1999 11:21:19 GMT
      В НТТP-ответах: время последней модификации запрошенных данных на сервере.

      Expires: Mon, 14 Mar 2000 07:38:48 GMT
      В HTTP-ответах: время истечения срока годности данных (до каких пор они могут храниться в кэшах прокси-сервера и(или) броузера). Для того, чтобы документ не кэшировался, нужно поставить время, совпадающее с временем в поле Date HTTP-ответа.

      ETag: "8512-28a-3835276d-koi8-r"
      Некоторый уникальный идентификатор, присваиваемый сервером передаваемому содержимому.

      Content-Length: 295
      Размер данных в байтах.

      Content-Encoding: gzip
      Заголовок указывает представление данных (в данном случае - сжатие с помощью программы gzip) - то есть дополнительное преобразование, произведенное над данными перед передачей. Если данные передаются "как есть", этот заголовок отсутствует.

      Content-Range: 0-294/1234
      При передаче документа по частям (см. заголовок "Range:" HTTP-запроса) - номера первого и последнего байтов и общая длина документа. В данном случае - первые 295 из 1234 байт.

      WWW-сервер Apache

      Apache - самый распространенный в Интернет WWW-сервер. Программа распространяется бесплатно в исходных текстах для Unix и Windows; проект поддерживается организацией Apache Software Foundation. Кроме собственно WWW-сервера ASF поддерживает еще ряд проектов, связанных с Apache, например, интеграция Apache и Перл (модуль mod_perl, см. ниже), проект Java-Apache.

      Одним из основных достоинств Apache является его модульность и открытость API (Application Programming Interface), что позволяет

      • подключать только необходимые модули, гибко регулируя соотношение между функциональностью и размером программы сервера;
      • создавать дополнительные модули (яркий пример - модуль mod_charset, обеспечивающий обслуживание кириллических кодировок - проект Russian Apache);
      • изменять стандартное поведение сервера на той или иной стадии обработки запроса (например, производить аутентификацию не по файлу с паролями, а путем запроса в базу данных; подробнее см. ниже п. "Perl API и модуль mod_perl").

      Общая схема работы Apache (на примере версии для Unix) такова:

      1. Запуск сервера - этот процесс запускается с большими привилегиями (например, root - для доступа к стандартному HTTP-порту 80). Непосредственно запросы клиентов этим процессом не обслуживаются; он управляет работой сервера. Запустившись, сервер производит разбор директив в конфигурационном файле (файлах), открывает лог-файлы, перенаправляет вывод стандартной ошибки в файл, указанный директивой ErrorLog. Этими же лог-файлами будут пользоваться все порожденные процессы.
      2. Инициализация модулей.
      3. Запуск дочерних процессов - главный процесс ветвится с помощью вызова fork, порождая несколько дочерних процессов, которые собственно и будут обслуживать запросы клиентов. В целях безопасности порожденные процессы запускаются с низким уровнем привилегий (по умолчанию - как пользователь nobody). Число порождаемых процессов определяется директивой StartServers.
      4. Запрос, поступающий от клиента, отправляется на обработку одному из дочерних процессов. Цикл обработки запроса является основным смыслом деятельности сервера, он разобран отдельно в следующем пункте. После завершения обработки запроса процесс ожидает поступления следующего запроса. При достижении максимального числа обработанных запросов (директива MaxRequestsPerChild) дочерний процесс прекращает свою работу.
      5. Главный процесс осуществляет мониторинг дочерних процессов. Если число процессов падает ниже установленного директивой MinSpareServers, то запускаются дополнительные дочерние процессы. При одновременном поступлении большого числа запросов главный процесс также запускает дополнительные процессы, но после прохождения пика лишние процессы будут удалены так, чтобы общее число ожидающих запроса процессов не превышало установленного директивой MaxSpareServers. Максимальное количество порожденных процессов определяется директивой MaxClients, что и является ограничением на число одновременно обслуживаемых сервером запросов.
      6. При получении команды "stop" (сигнал TERM) главный процесс останавливает дочерние процессы, а потом выходит сам. При получении команды "restart" (сигнал HUP) главный процесс останавливает дочерние процессы, но сам не выходит; заново производится разбор конфигурационных директив и инициализация модулей, потом запускаются дочерние процессы. При получении команды "graceful" (сигнал USR1) действия сервера аналогичны команде "restart", однако дочерние процессы не прерываются принудительно, а им дается возможность обслужить текущий запрос (если таковой имеется) и только после этого выйти. Сигналы должны отпраляться только главному процессу, его идентификатор содержится в файле, указанном директивой PidFile; предпочтительнее всего пользоваться утилитой apachectl (см. ниже п. "Установка и работа с сервером").

      Обработка запроса клиента

      Цикл обработки запроса клиента состоит из следующих фаз:

      • Разбор URI (URI translation)
      • Разбор заголовков HTTP-запроса (Header parsing)
      • Контроль доступа (Access control)
      • Аутентификация (Authentication)
      • Авторизация (Authorization)
      • Определение MIME-типа документа (Checking MIME-type)
      • Генерация ответа (Response)
      • Регистрация (Logging)
      • Освобождение ресурсов (Clean-up)

      Конфигурирование сервера

      Конфигурирование сервера выполняется путем внесения директив в файл httpd.conf, расположенный в каталоге conf дерева установки сервера (см. ниже п. "Установка и работа с сервером") и считываемый сервером при запуске (перезапуске). Директивы могут находиться также и в других файлах, которые подключаются с помощью директивы "Include" в файле httpd.conf. Обычно Apache поставляется с файлом httpd.conf, уже содержащим примерную рабочую конфигурацию, причем каждая директива снабжена подробным комментарием на английском языке.

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

      • server config - директива применяется при конфигурировании основного сервера, т.е. относится ко всем поступающим запросам или ко всему серверу как таковому. Все директивы, не попадающие в ниже описанные контексты, находятся к контексте server config.

      • virtual host - директива применяется внутри раздела, определяемого директивой
        
        <VirtualHost имя_виртуального_сервера>
        .....
        #директивы, находящиеся здесь относятся только к запросам, в
        #заголовке Host которых указан данный виртуальный сервер
        .....
        </VirtualHost>
        
        
        Сама по себе директива VirtualHost может находиться только в контексте server-config.

      • directory - директива применяется внутри разделов типа
        
        <Directory путь_к_каталогу>
        ....
        #директивы, находящиеся здесь относятся только к запросам файлов,
        #находящихся в указанном каталоге (или его подкаталогах); путь
        #отсчитывается от каталога, указанного директивой DocumentRoot
        ....
        </Directory>
        
        <Location начало_URI>
        ....
        #директивы, находящиеся здесь относятся только к запросам URI, #начинающихся со строки, указанной в директиве Location
        ....
        </Location>
        
        <Files рег._выражение_для_файлов>
        ....
        #директивы, находящиеся здесь относятся только к запросам файлов, #имена которых удовлетворяют указанному регулярному выражению
        ....
        </Files>
        
        

        Сами по себе директивы Location, Directory и Files могут находиться как в контексте server config, так и в контексте virtual host. Более того, директива Files может находиться внутри раздела Directory, а также в контексте htaccess.

      • htaccess - директива применяется в файле .htaccess (имя файла начинается с точки), расположенном в каком-либо каталоге внутри дерева документов сервера. Директива срабатывает для всех запросов файлов из этого каталога и его подкаталогов (если там не находится другой файл .htaccess - в этом случае последний будет иметь приоритет). Директивы файла .htaccess имею приоритет над директивами раздела <Directory ...>...</Directory>, относящимися к тому же каталогу.

      Если одна и та же директива в разных контекстах имеет разные значения, то действует значение в наиболее узком применимом к данному запросу контексте.

      Управление доступом, аутентификацией и авторизацией

      Apache предоставляет следующие возможности по контролю доступа к ресурсам веб-сервера:

      • контроль адреса клиента,
      • аутентификация клиента по имени и паролю,
      • авторизация - т.е. определение прав опознанного клиента на доступ к тому или иному ресурсу.

      Для реализации этого механизма применяются директивы allow, deny. order, AuthName, AuthType, AuthUserFile, AuthGroupFile, require, satisfy.

      Возможности контроль доступа могут быть существенно расширены дополнительными модулями, написанными c использованием Perl-API (модуль mod_perl).

      Выполнение CGI-скриптов

      На стадии разбора URI сервер определяет файл, который был запрошен клиентом. Если на стадии определения MIME-типа документа было обнаружено, что файл должен быть интерпретирован как исполнимая программа, то для генерации ответа Apache запускает этот файл, в соответствии со спецификацями интерфейса CGI.

      SSI (Server Side Includes)

      В Apache реализован механизм SSI, который представляет собой разбор HTML-документов на стороне сервера с целью обнаружения в документе и выполнения директив, добавляющих в документ дополнительную информацию. Директивы могут вставлять в HTML-документ другой HTML-файл или результат работы CGI-программы; вставлять характеристики документа (размер файла, дату последней модификации), текущее время и т.п.

      Перекодировка кириллицы

      Модуль mod_charset производит переобразование кириллических документов в кодировку, требуемую клиенту. При этом сначала рассматриваются заголовки запроса (Accept-Charset), потом, если требуемую кодировку определить не удалось, - конфигурационные директивы модуля mod_charset (CharsetSelectionOrder и др.), а в случае неудачи документ возвращается в кодировке, определенной директивой CharsetDeafult.

      Кодировка, в которой документ хранится на диске, задается директивой CharsetSourceEnc, которую можно применять во всех конфигурационных контекстах.

      Perl API и модуль mod_perl

      Модуль mod_perl реализует интерфейс API к серверу Apache на языке Perl, что позволяет модифицировать поведение сервера на любой фазе обработки запроса в соответствии с конкретными требованиями пользователя (например производить аутентификацию не по файлу с именами пользователей и их паролями, а по базе данных). Кроме того, модуль позволяет существенно ускорить исполнение CGI-скриптов, написанных на Perl: теперь не запускается отдельного процесса Perl-интепретатора для каждого скрипта; интерпретатор находится внутри процесса веб-сервера; более того, однажды исполненный скрипт кэшируется в компилированном виде.

      Apache и Java

      Модуль mod_jserv является интерфейсом сервера Apache к исполнителю сервлетов (servlet engine), написанных на языке Java. Интерфейс jserv сходен по функциональности с CGI, но обладает повышенной гибкостью.

      Apache и SSL

      Модуль mod_ssl реализует в Apache слой SSL, осуществляющий шифрование всего потока данных между клиентом и сервером. Для всех остальных частей веб-сервера модуль mod_ssl является прозрачным. Для работы в этом режиме, требуется броузер, поддерживающий мехнизм SSL (этому условию удовлетворяют все современные распространенные броузеры).

      Установка и работа с сервером

      Установка сервера осуществляется в следующей последовательности:

      • распаковать дистрибутив Apache;
      • если требуется установка дополнительных модулей (например, mod_perl, mod_jserv, mod_ssl) - распаковать их дистрибутивы в том же каталоге; при конфигурировании сервера следовать дополнительным инструкциям по установке модулей, приведенных в их дистрибутивах;
      • сконфигурировать исходный код сервера командой
                  ./configure --prefix=/путь/установки/Apache
        
        
        (возможны различные дополнительные ключи, свзяанные с добавлением или удалением модулей);
      • скомпилировать и установить сервер командами "make" и "make install".

      Все компоненты сервера устанавливаются в дерево, корнем которого является каталог, указанный в параметре "--prefix"; вне этого дерева никакие компоненты программы не располагаются (это может быть неверно для дополнительных модулей - см. документацию к этим модулям). Этот же каталог должен быть значением конфигурационной директивы ServerRoot. Конфигурационный файл httpd.conf находится в каталоге ServerRoot/conf.

      Запуск, перезапуск и останов сервера выполняются командой ServerRoot/bin/apachectl с параметрами start, stop, restart, graceful.

      Задание

      1. Запустить на своем компьютере (Unix) анонимный FTP-сервер. Создать каталог с "невидимым" содержимым для входящих файлов.

      2. Сконфигурировать на своем компьютере WWW-сервер Russian Apache для перекодировки кириллицы по номеру порта (осуществлять перекодировку в win, koi, iso, dos и транслитерацию). В дереве документов организовать два каталога: в одном хранить документы в оригинальной кодировке win, в другом - в koi. Обеспечить разумный порядок определения выходной кодировки при обращении на стнадартный порт 80.

      3. На один из каталогов назначить авторизованный доступ: с "разрешенных" компьютеров - доступ открытый; со всех остальных - по паролю.




      Следующая тема - CGI

      Курс "Технологии Интернет"
      Кафедра КТС
      Максим Мамаев