Вездесущий Common Gateway Interface (CGI)

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

Картина использования систем управления базами данных в Web выглядит аналогично. Если оставить за рамками дискуссии Web-компоненты, входящие в состав СУБД, то, как правило, наиболее обсуждаемой темой окажутся интерфейсы JDBC и ODBC.

Признавая важность и мощь перспективных средств разработки программного обеспечения для World Wide Web и механизмов подключения информационных ресурсов в системах intranet, следует однако отметить, что не они являются основным инструментом в "Паутине" сегодняшнего дня.

Если внимательно присмотреться к тому, что пользователь Web получает на экран своего монитора, то окажется, что 70% страниц, которые "закачивает" браузер, - это страницы, генерируемые "на лету" (on the fly). Таких страниц нигде в сети реально не существует, они создаются из отдельных заготовок или генерируются программами. Для русскоязычной части Web эта цифра заведомо выше, так как примерно две трети информации требуют перекодировки из базового символьного кода в коды браузеров пользователей.

В большинстве случаев для генерации HTML-страниц используются не модные нынче Java-приложения или программы на языке Visual Basic for Applications, а проверенные временем скрипты CGI (Common Gateway Interface) или модули Web-серверов.

В отличие от языка JavaScript, который достаточно широко распространен в Сети (причем, как это ни странно, в российской части Web он используется интенсивнее, чем в среднем по Internet), все остальные современные Web-технологии еще только борются за место под солнцем. Да и применение JavaScript зачастую обусловлено стремлением разработчика продемонстрировать свою квалификацию (или как теперь говорят, "крутизну"), нежели реальной необходимостью. Не исключено, что демонстрация "мощи" в немалой степени происходит вследствие избытка имеющегося у Web-мастеров образования - в большинстве случаев применение JS на отечественных узлах Web с чисто практической точки зрения ничем не обосновано. Чтобы убедиться в этом, достаточно посмотреть на страницы www.infoart.ru.

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

Perl - лидер интерактивной "Паутины"

Хотя CGI-скрипт можно написать на любом языке программирования, тем не менее пальму первенства держат языки Perl и С. Если последний известен практически каждому, кто так или иначе сталкивался с программированием, то Perl приобрел свою популярность во многом именно благодаря Web. С большой долей уверенности можно утверждать, что языком номер один в Web являются не Java и не JavaScript вместе с VBScript. Место лидера по праву принадлежит Perl.

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

Показателем популярности языка может служить тот факт, что когда компания OpenMarket стала разрабатывать спецификацию FastCGI, позволяющую существенно улучшить временные показатели при обращении к внешним программам через Web-сервер, она создала библиотеку разработчика FastCGI-скриптов (FastCGI Development Kit) для C (типовой среды разработки), Perl (наиболее популярной инструментальной среды) и Java (самой модной среди разработчиков).

Какие же свойства языка обеспечивают Perl преимущества перед остальными языками программирования?

Если С можно условно назвать языком программистов-"системщиков", то Perl - это язык программирования, предназначенный для администраторов систем. Именно для этой цели Ларри Уолл и придумал Perl в 1986 году.

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

Реализации Perl - это продукты, относящиеся к категории public domain. Исходные тексты интерпретатора Perl и библиотеки можно найти практически в любом FTP-архиве. В силу этого Perl перенесен на все Unix-платформы, а также на 32-разрядную Windows-платформу. Программа, написанная на Perl и представляющая собой ASCII-файл, будет исполняться на любой машине, где есть интерпретатор языка.

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

Рассмотрим подробнее сильные с точки зрения программирования в Web свойства Perl. При этом будем опираться на последовательность действий, которая возникает при обработке запроса от клиента через сервер к скрипту и обратно от скрипта к клиенту.

Анализируем переменные окружения

Основным способом ввода данных пользователем являются прямые гипертекстовые ссылки и HTML-формы. В первом случае информация помещается за символом "/" после имени скрипта. Она транслируется в переменную окружения PATH_INFO. Остаток пути, отмеченный в URL после символа "?", помещается в переменную окружения QUERY_STRING. Такого рода обращение к скрипту появляется тогда, когда автор страницы либо указал его непосредственно в атрибуте href контейнера гипертекстовой ссылки, либо вспомнил о старом добром контейнере ISINDEX (что случается крайне редко).

Форма - гораздо более распространенный способ обращения к скрипту. С ее помощью можно не только ввести практически любые данные (вплоть до целого файла), но и управлять методом доступа, а также MIME-типом передаваемого сообщения. Кроме указанного выше способа получения через PATH_INFO и QUERY_STRING, существует возможность ввода данных из формы на стандартный поток ввода программы, запускаемой на Web-сервере (такой программой может быть, в частности, скрипт). При этом сервер сам не закрывает стандартный поток ввода, и данные из него следует читать точно в соответствии с числом байтов в этом потоке, которое указывается в переменной окружения CONTENT_LENGTH.

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

Сначала о переменных окружения. Для работы с ними в Perl есть хэш-таблица %ENV. Для получения значения из этой хэш-таблицы достаточно указать в качестве ключа имя переменной окружения:

$length = $ENV{'CONTENT_LENGTH'};

Полезным Web-приложением, которым часто пользуются Web-программисты, является скрипт, который печатает все переменные окружения. На Perl такое приложение пишется в несколько строк:

#!/usr/local/bin/perl
print "Content-type: text/html

";
print "
"; print "
" while(($name,$value) = each %ENV) { print "
$name$value
"; } print "
"

Уверен, даже на языке С программа, подобная этой, будет выглядеть существенно сложнее.

Если программа получает данные из формы, то следует определить метод доступа - GET или POST, который указывается в переменной окружения REQUEST_METHOD. В этом случае для обработки данных применяют не только чтение самой переменной, но и механизм подстановки:

if($ENV{'REQUEST_METHOD?} =~ /POST/i)
	{
	...
	}
elsif($ENV{'REQUEST_METHOD?} =~/GET/i)
	{
	...
	}

В правой части оператора подстановки, точнее оператора поиска подстроки, есть модификатор "i", позволяющий найти подстроку, невзирая на регистр букв, которыми искомая последовательность была указана автором HTML-документа.

Особая ценность этого механизма проявляется при трансляции символов, не входящих в американский набор символов ASCII (символы 0-127), в частности, букв русского алфавита. Проблема заключается в том, что браузер представит их в шестнадцатиричной нотации и в таком виде отправит серверу, а сервер, в свою очередь, передаст скрипту. Конечно, любой программист сразу составит себе таблицы перекодировки, однако Perl позволяет найти более изящное решение:

$query = $ENV{'QUERY_STRING'};
$query =~ /%(ww)/pack('c',$1)/ge;

В данном выражении две буквы после символа "%" помещаются в переменную $1, после чего весь образец (например %65) заменяется на букву, которая соответствует этому шестнадцатиричному числу (A). Аналогичным образом для преобразования одного варианта кода в другой достаточно использовать функцию трансляции tr. Например, чтобы перекодировать koi8 в cp1251, достаточно включить в текст скрипта следующую строку:

$query =~ tr/[набор кои8]/
[набор cp1251]/;

Как видим, вопрос трансляции запросов в Perl решается довольно просто.

Выделяем имена полей

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

Предположим, что в форме указаны два поля с именами S и P. Выделить эти поля и их значения из запроса можно следующим образом:

$query =~ /(S)=(.*)&(P)=(.*)&/;
print "FIELD NAME=$1, FIELD
VALUE=$2.
"; # S и его значение
print "FIELD NAME=$3, FIELD
VALUE=$4.
"; # P и его значение

Таким образом, разбор информации, передаваемой в Perl-скрипты, не представляет большой проблемы.

Другой немаловажный момент, который следует учитывать при программировании CGI-скриптов, - это возможность фильтрации вывода внешних по отношению к скрипту программ, которые запускает сам скрипт. Здесь в качестве эталона следует привести командный интерпретатор sh, в котором организация конвейеров строится наиболее просто. Например, если требуется передать запрос в командный интерпретатор psql, входящий в состав СУБД Postgres95, то в sh нужно будет набрать следующую команду:

echo QUERY_STRING | psql database

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

В Perl такой фильтр можно организовать в виде одной программы. Правда, для этого потребуется Perl5 и библиотека IPC::Open2:

#!/usr/local/bin/perl
use IPC::Open2;
use Symbol;
$WTR =gensym();
$RDR = gensym();
print STDOUT "Content-type: text/html

";
print "
"; print "
";
$pid = open2 $RDR, $WTR, /users/postgres/postgres95/bin/psql edu?;
print $WTR "d";
close $WTR;
while(<$RDR>)
   {
   print STDOUT "$_
";
   }
close $RDR;
print "
";

Если отвлечься от рассмотрения команд, которые формируют начало и конец HTML-отклика, то можно заметить, что функциональная часть не сильно отличается от sh по размеру кода. А работает все это так. Сначала открывается двунаправленный канал приема/передачи данных между программой psql и скриптом, который идентифицируется дескрипторами $RDR и $WTR. Первый дескриптор используется для чтения данных из psql, а второй - для передачи в psql запроса к базе данных. Затем внутри цикла обработки отклика psql применяется блок преобразования вывода, позволяя распечатать отчет по базе данных в виде HTML-страницы.

Как много в Perl вещей хороших!

Теперь рассмотрим Perl с точки зрения администрирования самого сервера и анализа статистики посещений. Если посмотреть на библиотеки CGI-скриптов, которые доступны через сеть, то легко убедиться, что большинство скриптов заняты подсчетом числа посещений страниц данного Web-узла и анализом журналов посещений.

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

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

Коротко поясню, о чем идет речь. Под хэш-таблицей в Perl понимают массив пар "ключ-значение". При этом значение можно найти, непосредственно указав значение ключа точно так же, как это было сделано для организации доступа к значениям переменных окружения. При стандартной работе с этими таблицами используются алгоритмы, встроенные в Perl-интерпретатор. Однако существует возможность использования и других пакетов, которые предоставляют более эффективные средства вычисления ключей или ранжирования данных. Один из таких пакетов - DB_File. В операционной системе FreeBSD он является стандартным и поставляется вместе с системой, в других ОС он, как правило, устанавливается отдельно.

В Perl существует функция tie, которая отображает все операции с хэш-таблицей на функции пакета. В результате программист, не переделывая программы, может существенно повысить эффективность ее исполнения. Заметим, что DB_File является не единственным пакетом, который можно применять таким образом.

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

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

Еще одним доказательством неординарных возможностей этого языка является пакет GlimpseHTTP, который является расширением брокера запросов (broker) Glimpse фирмы Harverst. В сущности, Glimpse - это поисковая машина, которая достаточно широко применяется для организации поиска на Web-узле по ключевым словам. По возможностям обработки больших массивов данных Glimpse уступает таким монстрам, как, скажем, Altavista, но по языковым средствам и способности работать с локальными массивами вполне может заменить коммерческую поисковую машину. WebGlimpse и GlimpseHTTP - это Perl-фильтры, которые принимают запросы от интерфейсной страницы Glimpse, разбирают их и передают на выполнение поисковой компоненте, а после завершения ее работы преобразуют результаты поиска из формы, обычной для программы grep, в HTML-документ с осмысленным набором полей.

В заключение, чтобы еще раз подчеркнуть, насколько популярен Perl в Web, напомню один маленький факт. У самого популярного сервера World Wide Web Apache, который, начиная с версии 1.3, может быть установлен на любую платформу (в том числе и на Windows NT), имеется модуль интерпретации Perl. Это означает, что Perl-программа будет исполняться самим сервером без порождения дополнительного процесса (потока). Задумайтесь над этим и начинайте учить Perl.


Павел Храмцов - руководитель группы РНЦ "Курчатовский Институт". С ним можно связаться по электронной почте: paul@kiae.su.