Дмитрий Леонов. Использование Perl в интернет-технологиях
---------------------------------------------------------------
© Copyright Дмитрий Леонов
Email:
[email protected]
WWW: http://www.hackzone.ru
Date: 09 Sep 2001
---------------------------------------------------------------
Введение в internet/intranet-технологии
Интернет (internet) -- это всемирное объединение сетей, шлюзов,
серверов и клиентских компьютеров, использующее для связи единый набор
протоколов TCP/IP. Основная черта Интернет -- предоставление глобального
доступа к информации и ресурсам. Информация, размещенная на
интернет-сервере, становится доступной из любой точки земного шара,
подключенной к интернет. Использование общих протоколов семейства TCP/IP и
единого адресного пространства позволяет говорить об Интернет как о единой
глобальной "метасети". Наряду с интернет выделяют понятие интранет. Интранет
-- это локальная сеть предприятия, основанная на тех же протоколах, что и
интернет. Эта технология позволяет широко использовать в рамках сети
предприятия все возможности и наработки глобальной сети, эффективно решая
при этом задачу ограничения доступа к информации. В то же время часть
интранет может быть открыта для внешнего доступа, становясь таким образом
частью Интернет.
Под протоколами в данном контексте понимаются правила, определяющие
последовательность действий, необходимых для обмена данными. Для доставки
информации на какой-либо компьютер он должен иметь уникальный идентификатор,
или адрес. Роль такого идентификатора в tcp/ip играет ip-адрес узла,
представляющий собой 32-разрядное число, записываемое тетрадой вида
216.122.167.55 (четыре десятичных числа от 0 до 255, разделенные точками). С
каждым ip-адресом связана 32-разрядная маска подсети, разбивающая адрес на
две части - на уникальный идентификатор сети, к которой принадлежит
компьютер, и на уникальный идентификатор узла в пределах этой сети. Маска
эта имеет вид 255.255.255.0, и при побитном умножении нашего ip-адреса из
примера мы получим сеть 216.122.167 и узел 55. Разбиение адреса на две части
обеспечивает большую управляемость сети. Компании, имеющей парк из
нескольких сотен машин, нет необходимости регистрировать адрес для каждой из
них - достаточно зарегистрировать на себя отдельную подсеть и раздавать
адреса внутри нее уже самостоятельно.
При простом подключении узла к сети, он получает уникальный адрес в
пределах всей Internet, что дает возможность обмениваться информацией с
другими узлами. Знание адреса компьютера, подключенного к сети, дает
возможность воспользоваться сервисами, на нем запущенными - с любого другого
компьютера, подключенного к сети.
Рис. 1. Схемы подключения к Internet
Достаточно часто используется схема отсечения локальной сети от
"большой Internet", когда компьютеры локальной сети не получают прямого
доступа к внешнему миру. В этом случае между локальной сетью и Internet
ставится отдельный сервер, доступный как из внешней, так и из внутренней
сети. Такой сервер носит название прокси-сервера (proxy, посредник).
Возможны различные способы реализации этой схемы - на уровне отдельных
служб, как это происходит в случае традиционных прокси, либо на уровне
пакетов tcp/ip, в случае использования NAT, Network Address Translator, но
идея в обоих случаях одна и та же: компьютер из локальной сети обращается к
прокси-серверу с просьбой передать запрос некоторому внешнему серверу.
Получив ответ от внешнего сервера, прокси переправляет ее локальному
компьютеру.
Подобная схема имеет несколько преимуществ перед прямым подключением к
сети. Во-первых, экономятся "настоящие" ip-адреса, которых через некоторое
время просто перестанет хватать (как выяснилось, 32 разряда, выбранные для
представления ip-адреса -- это вовсе не так уж и много, и в настоящее время
готовится к внедрению новая версия ip-адресации, ipv6). Во-вторых, из
внешней сети можно получить доступ только к промежуточному серверу, что
повышает защищенность всей сети. Наконец, если несколько компьютеров по
очереди обращаются к одному и тому же ресурсу, прокси может сохранить
результат первого обращения на своем жестком диске и выдавать его в ответ на
повторные запросы.
Ip-адресация позволяет точно идентифицировать компьютеры, подключенные
к сети. Однако, запоминать адреса вида 216.122.167.55 не слишком удобно.
Поэтому с самого начала развития сети каждый узел помимо цифрового ip имел
еще и символьное имя. Сперва все узлы были перечислены в одном текстовом
файле, но по мере роста сети возникла необходимость в механизме,
обеспечивающем, во-первых, уникальность имен, во-вторых, средства извещения
всех подключенных узлов об изменениях.
В настоящее время соответствие между цифровыми и символьными адресами
обеспечивается серверами имен (Domain Name Servers, DNS). Под доменом
понимается множество машин, которые администрируются и поддерживаются как
единое целое. Вся сеть представляет собой одну большую иерархию доменов,
позволяющую разграничить полномочия между администраторами разных сетей.
Иерархия доменов Интернет растет от корневого (root) домена, не
имеющего имени. Далее идет ограниченное количество доменов верхнего уровня,
в которых может быть зарегистрировано практически неограниченное количество
доменов второго уровня и т.д.
К доменам верхнего уровня относятся домены
.com (предназначенный для
коммерческих организация),
.edu (образовательные учреждения),
.net (сетевые
провайдеры, узловые компьютеры),
.org (организации, не попадающие ни в одну
из прочих категорий), администрированием которых до недавнего времени
занимался только InterNIC,
.gov (изначально предназначался для любых
государственных учреждений, позднее было принято решение о регистрации в нем
только федеральных правительственных учреждений США, регистрируются
US
Federal Government civilian agency),
.mil (военные учреждения США,
регистрируются
US military agency),
.int (международные организации). В
настоящее время ведется работа по введению дополнительных доменов верхнего
уровня. Кроме того, к доменам верхнего уровня относятся национальные домены
с двухбуквенными именами (например,
.ru,
.de), администрированием которых
занимаются национальные институты.
Ip-адрес позволяет точно идентифицировать компьютер, но этого
недостаточно. Дело в том, что на каждом узле могут быть запущены самые
разные службы Internet - обеспечивающие передачу электронной почты, файлов,
гипертекстовой информации и т.п. Каждая служба использует в своей работе тот
или иной протокол прикладного уровня. Для передачи файлов - это протокол FTP
(File Transfer Protocol), передачи web-страниц - протокол передачи
гипертекстовой информации HTTP(HyperText Transfer Protocol), для работы с
электронной почтой - протоколы SMTP, POP3, IMAP и т.д.
Для каждой службы отведен отдельный порт, представляющий собой число от
0 до 65534. Для наиболее популярных служб зарезервированы стандартные номера
портов. Так, для FTP это 21, для HTTP - 80, SMTP - 25, POP3 - 110. Впрочем,
это лишь значения по умолчанию, никто не мешает владельцу узла настроить эти
службы на работу с другими портами. Иногда это просто необходимо - как,
например, в случае с поддержкой различных кодировок кириллицы в WWW. Как
известно, одни и те же символы кириллицы в различных операционных системах
обозначаются разными кодами, и существует по крайней мере четыре популярные
кодировки: Windows-1251, KOI8, Mac, DOS. Поскольку одна и та же страница
может быть загружена пользователями различных систем, перед ее разработчиком
встает непростая задача -- как сделать ее читаемой для всех. Существует три
подхода к решению этой задачи. Во-первых, можно просто проигнорировать
существование нескольких кодировок и готовить страницу в самой популярной,
каковой на сегодня является Win1251. Во-вторых, готовить несколько копий
страниц - во всех кодировках. Недостатки этих подходов очевидны. Наиболее
популярным на сегодняшний день является решение, предусматривающее
автоматическую перекодировку документа на сервере -- в зависимости от того,
с каким портом общается клиентское приложение: например, на 8080 -- Win1251,
8083 -- Koi8 и т.п.
Унифицированные указатели ресурсов (Uniform Resource Locator, URL)
предназначены для адресации сетевых ресурсов - документов, файлов и т.п. В
самом общем виде URL записывается следующим образом:
[протокол]://[имя][:пароль]@[адрес][:порт][/путь/][документ][?дополнительная
информация]
Содержимое квадратных скобок является необязательным, любая часть URL
может быть опущена. Здесь
протокол - символьное обозначение протокола, используемого для доступа
к ресурсу (например, ftp, http);
имя - имя пользователя;
пароль - в сочетании с именем пользователя используется при работе с
ресурсами, доступ к которым ограничен;
адрес - адрес узла, в доменной или цифровой форме;
порт - номер порта, если он отсутствует, используется порт по умолчанию
для данного протокола;
путь - путь на сервере от его корневого каталога, либо относительно
текущего каталога;
документ - имя документа;
дополнительная информация - используется при работе с серверными
приложениями.
Средства разработки web-приложений
Обмен информацией в Интернет осуществляется с помощью протоколов
прикладного уровня, реализующих тот или иной прикладной сервис (пересылку
файлов, гипертекстовой информации, почты и т.д.). Одним из наиболее молодых
и популярных сервисов Интернет, развитие которого и привело к всплеску
популярности самой Интернет, стала World Wide Web (WWW), основанная на
протоколе HTTP (Hyper Text Transfer Protocol -- протокол передачи
гипертекстовой информации). Гипертекстовые документы, представленные в WWW,
имеют одно принципиальное отличие от традиционных гипертекстовых документов
-- связи, в них использующиеся, не ограничены одним документом, и более
того, не ограничены одним компьютером. Для подготовки гипертекстовых
документов используется язык HTML (Hyper Text Markup Language -- язык
разметки гипертекстовых документов), предоставляющий широкие возможности по
форматированию и структурной разметке документов, организации связей между
различными документами, средства включения графической и мультимедийной
информации. HTML-документы просматриваются с помощью специальной программы
-- броузера. Наибольшее распространение в настоящее время получили броузеры
Navigator компании Netscape (NN) и Internet Explorer компании Microsoft
(MSIE). Реализации NN доступны практически для всех современных программных
и аппаратных платформ, реализации MSIE доступны для всех Windows платформ,
Macintosh и некоторых коммерческих Unix-систем.
HTML-документ состоит из текста, представляющего собой содержание
документа, и
тегов, определяющих его структуру и внешний вид при отображении
броузером. Простейший html-документ выглядит следующим образом:
<html>
<head>
<title>Название</title>
</head>
<body>
<p>Тело документа
</body>
</html>
Данный код отображается в броузере следующим образом:
Рис. 2. Пример html-документа
Как видно из примера, тег представляет собой ключевое слово,
заключенное в угловые скобки. Различают одинарные теги, как, например,
<p>,
и парные, как
<body> </body>, в последнем случае действие тега
распространяется только на текст между его открывающей и закрывающей
скобкой. Теги также могут иметь параметры -- например, при описании страницы
можно задать цвет фона, цвет шрифта и т.д.:
<body bgcolor="white"
text="black">.
Текст всего документа заключается в теги
<html>, сам документ
разбивается на две части -- заголовок и тело. Заголовок описывается тегами
<head>, в которые могут быть включены название документа (с помощью тегов
<title>) и другие параметры, использующиеся броузером при отображении
документа. Тело документа заключено в теги
<body> и содержит собственно
информацию, которую видит пользователь. При отсутствии тегов форматирования
весь текст выводится в окно броузера сплошным потоком, переводы строк,
пробелы и табуляции рассматриваются как пробельные символы, несколько
пробельных символов, идущих подряд, заменяются на один. Для форматирования
используются следующие основные теги:
<p> -- начало нового абзаца, может иметь параметр, определяющий
выравнивание: <p align=right>;
<br> -- перевод строки в пределах текущего абзаца;
-- выделение текста полужирным шрифтом;
-- выделение текста курсивом;
<u></u> -- выделение текста подчеркиванием
Ссылка на другой документ устанавливается с помощью тега
<a
href="URL">...</a>, где URL -- полный или относительный адрес документа. При
этом текст, заключенный в тег <a>, обычно выделяется подчеркиванием и
цветом, и после щелчка мышью по этой ссылке броузер открывает документ,
адрес которого указан в параметре href. Графические изображения вставляются
в документ с помощью тега
<img src="URL">.
Средства разработки клиентских приложений
Использование приложений для броузеров позволяет широко использовать
все возможности WWW, поскольку приложение фактически становится составной
частью гипертекстового документа. Это приводит к существенному упрощению
системы поддержки -- клиентское ПО автоматически обновляется при подключении
к серверу, документация и система помощи легко реализуется с помощью
стандартных средств HTML, а некоторые учебные курсы, к примеру, могут быть
реализованы практически полностью средствами HTML.
Рис. 3. Схема взаимодействия пользователя, сервера
и серверных приложений
Пользователь взаимодействует с броузером (1), который запрашивает (2) и
получает (2) от веб-сервера страницу с включенным приложением. После этого
начинается взаимодействие пользователя с приложением (4), которое при
необходимости может связаться с серверным приложением (5,6) для
взаимодействия с СУБД и т.п.
В то же время средств HTML недостаточно для реализации более-менее
сложных клиентских приложений, что приводит к необходимости использования
дополнительных средств, написания вспомогательных программ.
Можно выделить следующие основные проблем в разработке клиентских
средств для WWW:
Проблема многоплатформенности. Поскольку к WWW подключены системы,
основанные на различных аппаратных и программных платформах, возникает
необходимость разработки для каждой платформы, которую предполагается
поддерживать, специализированной версии ПО. Что приводит либо к ограничению
поддерживаемых платформ, либо к возрастанию затрат на разработку и поддержку
ПО. Для корпоративных закрытых систем с однородной архитектурой аппаратных и
программных средств аппаратной эта проблема менее актуальна.
Проблема безопасности. Существует две стороны проблемы -- возможные
сбои в системе из-за ошибок в программном обеспечении и целенаправленные
действия программного обеспечения, направленные на нарушение
функционирования системы, копирование конфиденциальной информации и т.д.
Отсутствие в традиционных языках встроенных средств для выполнения
наиболее часто встречающихся при работе с Web операций -- взаимодействия с
удаленными серверами, загрузка файлов, работа с изображениями и т.д.
Значительный размер исполняемых файлов, генерируемых традиционными
средствами разработки, что затрудняет их передачу по сети.
Наибольшую популярность завоевали следующие подходы к реализации
вспомогательных приложений для клиентской стороны:
Реализация подключаемых модулей (plug-ins) Netscape;
Использование элементов ActiveX;
Использование Java-приложений;
Средства подготовки сценариев JavaScript, VBScript.;
Macromedia Flash.
Рассмотрим их более подробно.
Использование подключаемых модулей получило широкое распространение в
связи с популярностью броузера Netscape Navigator, предоставляющим такую
возможность. В настоящее время это путь является тупиковым, поскольку не
предоставляет адекватного решения ни одной из перечисленных проблем.
К достоинствам использования элементов ActiveX относится реализация на
основе OLE/COM-технологии, что позволяет достаточно легко перевести в эту
форму традиционное ПО, реализованное для Win32-платформ. Среди недостатков
-- поддержка в настоящее время только Win32-платформы и поддержка только
броузером Internet Explorer, хотя для Netscape Navigator существует plug-in,
позволяющий использовать элементы ActiveX. Размер элементов ActiveX
минимизирован за счет использования разделяемых динамических библиотек,
поставляемых вместе с броузером Internet Explorer, которые также
предоставляют все необходимые для сетевого взаимодействия функции. Проблема
безопасности решается с помощью введения института сертификатов -- при
загрузке пользователю предъявляется сертификат производителя, заверенный
независимой организацией (например, VeriSign Inc.), после чего он может
решить, доверять или нет этому компоненту. Защита от сбоев не предусмотрена.
Таким образом, проблема безопасности решается административными средствами.
Данная технология наиболее пригодна для разработки корпоративных
приложений для внутреннего пользования -- особенно в случае существования
готовых наработок в этой области.
Основным достоинством Java-приложений является их независимость от
клиентской платформы. В отличие от традиционных приложений, транслирующихся
в исполняемые коды процессора, Java-приложения транслируются в так
называемый байт-код, интерпретируемый в дальнейшем виртуальной Java-машиной.
При этом байт-код независим от платформы, на которой он в дальнейшем будет
выполняться -- достаточно, чтобы для этой платформы была реализована
Java-машина. Поскольку большая часть основных функций реализована на уровне
виртуальной Java-машины, это приводит к существенному уменьшению размеров
байт-кода. Это является как достоинством, так и недостатком Java-приложений
-- поскольку байт-код интерпретируются виртуальной машиной,
производительность Java-приложений уступает производительности традиционных
откомпилированных программ. Частично с этим удается бороться, применяя
компиляторы времени исполнения (JIT -- just in time compilers),
осуществляющие компиляцию приложения при его загрузке в "родной" для данного
процессора код. Также возможен вызов функций, реализованных на других языках
программирования (таких как С, С++) и откомпилированных для данной платформы
-- так называемый native code. Это применяется при реализации наиболее
критичных к времени исполнения фрагментов кода.
Другим достоинством Java-приложений является их защищенность -- как с
точки зрения программирования (из языка исключены средства, наиболее часто
приводящие к ошибкам при программировании -- такие как указатели, перегрузка
операторов и т.д., язык является строго объектно-ориентированным, в него
встроена "сборка мусора" и т.д.), так с точки зрения исполнения (значительно
ограничена возможность работы с файлами на локальных машинах, с
установлением сетевых соединений и т.д., программа выполняется в отдельном
адресном пространстве), что позволяет спокойно работать с приложениями,
полученными из сети, не опасаясь наличия в них опасного кода. Таким образом,
проблема безопасности полностью решена на уровне архитектуры.
Второй недостаток -- необходимость существования для данной платформы
виртуальной Java-машины. Java-машины реализованы для всех наиболее
распространенных платформ, но они остаются достаточно ресурсоемкими и
зачастую довольно нестабильными системами. Кроме того, остаются проблемы
несовместимости -- поскольку язык Java изначально проектировался для
написания многоплатформенных приложений, в него преимущественно входили
элементы, доступные на всех платформах, что привело к некоторой аскетичности
доступных средств. Некоторые разработчики виртуальных машин расширяли их
возможности для конкретной платформы, что может привести к тому, что
Java-приложение, использующее все эти возможности, утратит способность
запускаться на других платформах. Ошибки в реализации виртуальной машины
могут также привести к снижению безопасности системы, за последние несколько
лет тому было немало примеров.
Существенным достоинством Java является ее объектная ориентированность.
Программа на Java представляет собой набор взаимодействующих между собой
классов. С помощью классов осуществляется и доступ к основным сервисам
виртуальной машины. Стандартная библиотека классов достаточно обширна и
включает в себя классы для работы с сетевыми протоколами -- как на низком,
так и на прикладном уровне, с графикой, графическим пользовательским
интерфейсом, базами данных, строками и т.д.
Перечисленные достоинства делают Java-приложения лучшим выбором в
гетерогенных системах, для которых безопасность имеет большее значение чем
возможные потери в производительности.
JavaScript, VBScript и т.п. представляют собой упрощенные языки
подготовки сценариев, код которых встраивается непосредственно в html-файл и
выполняется броузером. Они непригодны для реализации серьезных приложений, в
них отсутствуют средства для работы с файлами, сетевого взаимодействия и
т.д. Но они широко используются во вспомогательных целях, в качестве
средства первоначальной обработки результатов, для оформления, "оживления"
html-документов (т.наз. Dynamic HTML) и т.д.
Macromedia Flash разрабатывалось как средство анимации, основанное на
векторной графике, и в последнее время практически заняло ту нишу в
web-дизайне, на которую еще несколько лет назад претендовала Java. Имеет
достаточно ограниченные возможности по программированию, но весьма широкие
-- по построению мультимедийных приложений в рамках заданной модели (реакция
на нажатие кнопок/движение мыши, анимация, векторная графика, слои). Для
работы flash-приложений необходимо наличие соответствующего проигрывателя,
существующего для большинства распространенных платформ (в том числе, в виде
ActiveX-модуля и Java-апплета).
Средства разработки серверных приложений
Клиентские приложения, лишенные серверной стороны, пригодны для решения
лишь ограниченного класса задач. Такие задачи как взаимодействие с базами
данных, централизованная обработка результатов и т.п. в большей или меньшей
степени требуют наличия серверной стороны.
Широкие возможности открываются при использовании комбинированных
систем с развитым пользовательским интерфейсом, реализованном средствами
клиентской стороны, и мощной серверной поддержкой.
В то же время определенный интерес представляют системы, основанные
преимущественно на серверных решениях. В этом случае все, что требуется от
клиентской стороны -- наличие средств для работы с WWW. Взаимодействие с
серверными приложениями осуществляется посредством динамически формирующихся
экранных HTML-форм.
В язык HTML включены базовые средства для взаимодействия с
пользователем -- кнопки, поля ввода, селекторные кнопки, списки. Они
группируются с помощью тега
<form>, в параметре
action которого указывается
адрес приложения, обрабатывающего результаты формы. Параметр
method
описывает метод передачи данных на сервер -- GET или POST.
HTML-код, описывающий форму с одним вопросом и тремя предлагаемыми на
выбор ответами, выглядит следующим образом:
<p align="center">
<form action="/cgi-bin/process.cgi" method="POST">
Вопрос 1<br>
<select name="answer" size="3">
<option value="answer1" selected>Ответ 1</option>
<option value="answer2">Ответ 2</option>
<option value="answer3">Ответ 3</option>
</select><br><br>
<input type="submit" value="Ответить">
</form>
Что приведет к отображению в броузере следующей страницы:
Рис. 4. Пример страницы с формой для взаимодействия с пользователем
Самый распространенный тег, используемый в формах -- <input>. В его
параметре
type указывается тип поля ввода:
submit/
text/password/checkbox/radio/hidden, соответствующий кнопкам, простым
текстовым полям ввода, полям ввода пароля, селекторным кнопкам, спрятанным
полям.
В обязательном параметре
name указывается имя поля ввода, в поле
value
можно задать его стартовое значение.
Списки и выпадающие списки создаются с помощью тегов
<select></select>,
описывающих характеристики всего списка, и тегов
<option>, описывающих
отдельные элементы списков.
В каждой форме должна присутствовать одна кнопка Submit, при нажатии
которой формируется http-запрос, включающий результаты заполнения формы, и
этот запрос направляется на вход приложения, указанного в параметре action.
Первым механизмом, обеспечивающим взаимодействие клиента с серверными
приложениями, стал
CGI (Common Gateway Interface, общий шлюзовой интерфейс).
В ответ на действия пользователя, используя CGI, Web-сервер вызывает внешнюю
программу (CGI-приложение) и передает ей информацию, полученную от клиента
(например, переданную Web-браузером). Далее CGI-приложение обрабатывает
полученную информацию, и результаты ее работы передаются клиенту.
Рассмотрим эти этапы чуть подробнее. Взаимодействие между клиентом и
серверным приложением осуществляется по схеме, представленной на рис.1.
Рис. 1. Схема взаимодействия браузера, www-сервера и cgi-приложения
Пользователь заполняет экранную форму, описанную в html-файле с помощью
тега <form>, и нажимает на кнопку "Submit". Возможен также запрос при
непосредственном использовании адреса CGI-приложения -- указывая его в
строке Location браузера, в тэге <img> с помощью средств включения сервера
(SSI) и т. д.
На основе информации из формы браузер формирует HTTP-запрос и
отправляет его серверу. Информация приводится к виду
param1=value1¶m2=value2...¶mN=valueN, где parami -- имя
соответствующего поля ввода, valuei -- введенное в него значение. Все
символы, за исключением букв латинского алфавита, цифр, символа
подчеркивания, дефиса и точки при этом передаются в закодированном виде %XX,
где XX -- шестнадцатеричное представление данного символа. Символ пробела
может быть заменен символом "+". Если указано, что при передаче должен
использоваться метод GET, эта строка передается непосредственно в URL
(например,
<u>http://www.somehost.com/cgi-bin/script.cgi?param1=value1¶m2=value2</u>).
При использовании метода POST через заголовок передается информация о типе
содержимого запроса (для форм это, как правило,
application/x-www-form-urlencoded), а также длина строки. Сама строка в этом
случае передается непосредственно в теле запроса (примеры приведены чуть
ниже). В заголовках запроса также передается значительное количество
вспомогательной информации: тип браузера, адрес страницы, с которой был
произведен запрос, и т. д. Вся эта информация передается в HTTP-заголовках,
имеющих вид "Имя: значение". Отделяются друг от друга заголовки с помощью
символа новой строки, завершается их список еще одним символов новой строки.
Сервер вызывает CGI-приложение, и в зависимости от метода запроса
передает информацию из формы через переменную окружения QUERY_STRING (в
случае GET) либо через стандартный ввод (в случае POST). Также формируются
другие переменные окружения, такие как HTTP_USER_AGENT, REMOTE_HOST и др.
Информация для 'njuj окружения берется из HTTP-заголовков.
CGI-приложение считывает строку с переданной информацией со
стандартного ввода (stdin) или из переменной окружения QUERY_STRING.
Обработав информацию, программа, как правило, либо переадресует браузер на
некоторый существующий документ с помощью http-заголовка Location, либо
формирует виртуальный документ, посылая информацию на стандартный вывод
(stdout). Телу документа предшествуют HTTP-заголовки, описывающие тип
возвращаемых данных, управляющие кэшированием, работой с cookies и т. д. Все
это передается серверу.
Сервер пересылает ответ CGI-приложения браузеру, дополняя их при
необходимости кодом возврата и вспомогательными заголовками. При этом
используется один из двух способов -- перенаправление броузера на новый
адрес с помощью http-заголовка Location, либо формирование виртуального
документа. В последнем случае значение, переданное в заголовке Content-type,
используется броузером для интерпретации идущей следом информации --
например, text/html для виртуальных html-документов.
Браузер, основываясь на заголовках HTTP, интерпретирует ответ
CGI-приложения и выводит его для просмотра пользователем.
Реализовать CGI-приложение можно на любом языке, способном генерировать
код для серверной платформы или для которого доступен интерпретатор. Так,
простейшее CGI-приложение может быть реализовано на языке пакетных файлов
DOS, на Delphi, С/С++, Tcl, Visual Basic, AppleScript, FoxPro, Perl и т. д.
Основные недостатки классического CGI -- каждое взаимодействие клиента
с сервером, во-первых, является независимым от предыдущих и последующих
взаимодействий, во-вторых, приводит к запуску на сервере отдельного
процесса. Первый недостаток является тяжелым наследием HTTP-протокола, не
имеющего гарантированно работающих средств идентификации клиентов, и все
существующие решения способны лишь слегка сгладить этот недостаток. Борьба
со вторым недостатком идет гораздо успешнее, и вполне приемлемые решения
существуют для обеих наиболее распространенных серверных платформ.
Для серверов, основанных на Unix-платформах, одним из самых популярных
решений является использование mod_perl -- модуля, интегрирующего в
web-сервер Apache интерпретатор языка Perl. Это позволяет резко уменьшить
нагрузку на сервер при запуске CGI-приложений, написанных на Perl. И это
только верхняя часть айсберга -- mod_perl позволяет разработчику писать на
Perl полноценные подключаемые модули Apache, что открывает перед
программистом довольно широкие возможности. Другим популярным решением
является PHP -- скриптовый язык, встраиваемый непосредственно в тело
html-страницы. Перед тем, как отослать html-документ клиенту, сервер
просматривает его на предмет нахождения в нем пары управляющих тегов PHP <?
и ?>. Текст, находящийся внутри этих скобок, рассматривается как код PHP,
передается на обработку интерпретатору, и клиенту возвращается результат его
работы. Синтаксис языка очень напоминает C, так что освоить его не
представляет особого труда.
Решения для Windows-платформ напоминают своих Unix-собратьев. В
Microsoft Internet Information Server (IIS) реализована поддержка технологий
ISAPI и Active Server Pages (ASP). ISAPI-приложения представляют собой
динамические библиотеки, загружаемые в адресное пространство сервера. При
вызове приложения не происходит запуск нового процесса, за счет чего
достигается рост эффективности и снижение накладных расходов. С другой
стороны, некорректно написанное ISAPI-приложение может стать причиной отказа
от работы всего web-сервера (впрочем, начиная с IIS4 появилась возможность
бороться с этим недостатком).
Технология ASP аналогична PHP -- как и там, текст, находящийся внутри
управляющих тегов ASP (<% и %>), передается на обработку интерпретатору,
реализованному, кстати, в виде ISAPI-приложения. Программа, размещенная на
активной странице, может быть составлена на одном из языков подготовки
сценариев -- таким как JavaScript или VBScript, знакомым нам по
динамическому HTML. Главное отличие от их использования в динамическом HTML
-- на этот раз они выполняются сервером и имеют гораздо большие возможности
по работе с данными. Возможно расширение возможностей ASP, например, для
обмена информацией с базой данных с помощью слоя серверных ActiveX-объектов.
Дальнейшее развитие эта технология получит в готовящейся к выпуску платформе
.NET, одна из трех основных составляющих которых так и называется, Active
Server Pages+, и вряд ли будет представлять из себя что-то принципиально
новое.
Перечисленные четыре технологии не исчерпывают список средств для
создания серверных web-приложений, призванных справиться с недостатками CGI
-- помимо них существуют такие технологии как Java Servlets, FastCGI,
ColdFusion и т.п. Однако все они затрагивают только одно звено цепочки
клиент-сервер-серверное приложение -- а именно способ передачи информации от
сервера к серверному приложению и обратно. Принципы построения самих
серверных приложений очень похожи, а взаимодействие между клиентом и
сервером осуществляется по-прежнему с помощью тега <form> и передачи
информации, собранной в одну строку.
Широкое распространение в качестве языка для CGI-приложений получил
Perl. Его синтаксис унаследован в первую очередь от С, в него добавлены
расширенные средства для работы со строками, регулярными выражениями,
ассоциативными массивами и т. д. Это интерпретируемый язык, изначально
созданный для Unix-систем, сейчас его интерпретаторы доступны для
большинства популярных архитектур, что делает особенно легким перенос
приложений. Было бы неверно говорить о Perl, как исключительно о средстве
разработки CGI. Встроенные в язык возможности, великолепная переносимость,
огромное количество существующих бибилиотек, доступных из Comprehensive Perl
Archive Network (CPAN, <u>http://www.perl.com/CPAN/)</u>, делают его
исключительно удобным средством для системного администрирования, сетевого
программирования, обработки текстов и т.п.
Основы языка: скаляры, массивы, хэши, ссылки
Perl-программа представляет собой файл, содержащий набор
Perl-операторов, и начинающийся со строчки вида
#!/usr/bin/perl ,
указывающей путь до интерпретатора Perl. Это справедливо для Unix-систем,
требующих, кроме того, установки бита исполнения в атрибутах данного файла,
для windows-реализаций подобная строчка необязательна, но желательна из
соображений переносимости (Apache/Win32, к примеру, не сможет запустить
CGI-скрипт с некорректным путем до интерпретатора).
Синтаксис языка очень похож на синтаксис C, но с незначительными
исключениями. Точки входа в виде функции с предопределенным именем нет,
переменные по умолчанию имеют глобальную область видимости и не должны быть
описаны перед использованием, что позволяет быстро "слепить" работающую
программу, которую практически невозможно поддерживать. Так что хорошим
решением все же является использование функций и описание переменных перед
использованием, а заодно и использование директивы
use strict, не
позволяющей программисту забыть о подобных мелочах. Параметры встроенных
функций могут заключаться в круглые скобки, а могут и не заключаться, так
что их использование -- лишь вопрос стиля, и следующие строчки эквивалентны:
print "Hello, World\n";
print ("Hello, World\n");
Комментарием служит символ #, его действие распространяется до конца
строки. Многострочных комментариев нет.
Скалярные величины в Perl бывают двух типов -- числового и строкового.
Для всех чисел используется одно и то же внутреннее представление -- число с
плавающей точкой двойной точности (то, что компилятор С, которым
компилировался Perl, понимал под
double). Строки представляют собой
последовательности символов, но не является массивом байтов и не
ограничивается каким-то специальным символом типа нулевого, как это
происходит в С. По мере необходимости осуществляется преобразование из
одного типа в другой -- в зависимости от контекста строка может превратиться
в число и наоборот:
print "2"*2; # выводит 4
Имя скалярной переменной всегда начинается с символа $:
$a = 1;
Скалярная величина может быть определенной и неопределенной, в
последнем случае ее значение равно
undef. В строковом контексте значение
undef интерпретируется как пустая строка "", для проверки определенности
переменной следует пользоваться функцией
defined. При использовании в
логических операциях ложными считаются число 0 и строки "" и "0".
Строки могут записываться с использованием одинарных и двойных кавычек.
При использовании одинарных кавычек их содержимое используется как есть:
print '12\n$a'; # печатает строку 12\n$a
При использовании двойных кавычек происходит так называемая
интерполяция переменных и спецсимволов:
print "12\n$a"; # печатает 12, делает перевод строки, печатает значение
переменной $a
Если в такой строке необходимо предотвратить подстановку значения
вместо имени переменной, то перед знаком доллара необходимо поставить
обратную косую черту: "\$a".
Операции над числовыми значениями ничем не отличаются от знакомых по С
(за исключением возведения в степень **, операций
not,
and,
or, дублирующих
операции !, &&, ||, но имеющих меньший приоритет, чем операции присваивания,
и операции
xor, означающей
логическое исключающее ИЛИ). Для строковых
переменных определены операция конкатенации "." (точка), операция повторения
"x", операции
ne (неравенство строк),
eq (равенство),
lt (строковое "больше
чем") и
gt (строковое "меньше чем"). Также определена операция сравнения
двух строк
cmp, возвращающая --1, 0 или 1, объединяющая проверки равенства и
сравнения, и аналогичная ей операция для чисел <=>.
Встроенная функция
chop удаляет последней символ строки, встроенная
функция
chomp удаляет последний символ, если он является символом
разделителя входных записей (значение по умолчанию -- символ новой строки
\n, которое может быть заменено присваиванием нового значения встроенной
переменной $\).
Поиск подстроки осуществляется функцией
index($строка, $подстрока,
$старт), возвращающей индекс первого вхождения подстроки в строку, большего
чем
$старт (третий параметр можно опустить) и функцией
rindex, делающей то
же самое, но просматривающей строку справа налево. Извлекается подстрока из
строки функцией substr ($строка, $начало, $длина). При отсутствии третьего
параметра будут извлечены все символы до конца строки. Эта функция может
стоять в левой части оператора присваивания, позволяя тем самым изменять
часть строки на лету.
Массивы в Perl могут хранить любое количество элементов и увеличиваются
по мере необходимости. Имена массивов начинаются с символа @:
@a, доступ к
отдельному элементу массива записывается в виде
$a[10] (нумерация элементов
массива начинается с 0, причем последний элемент массива имеет индекс --1,
предпоследний --2 и т.д.). В зависимости от списочного или скалярного
контекста разные функции могут вести себя по разному -- так, функция
chomp в
применении к массиву удалит последний символ у каждого элемента массива, а
@a в скалярном контексте вернет количество элементов массива.
При обращении к элементу, находящемуся за пределами массива, будет
возвращено значение
undef, при присваивании значения такому элементу массив
автоматически увеличится, а все промежуточные элементы получат значение
undef.
Инициализируются массивы списочными данными, которые записываются в
виде заключенной в круглые скобки последовательности значений, отделенных
друг от друга запятыми:
@a = (0, $a, 5, "123", @b);
При этом вставленные таким образом элементы массива
@b находятся на том
же уровне иерархии, что и другие элементы списка, т.е. просто дописываются в
конец массива
@a.
Присваивание списков позволяет осуществлять довольно любопытные
операции:
($a, $b, $c, @e) = ($b, $a, @g);
$a и
$b обменяются значениями,
$c будет присвоено значение первого
элемента массива
@g, остальные элементы будут скопированы в массив
@e.
Срез массива позволяет выбрать из него за один раз несколько элементов:
@a[0, 1, 10];
Эту же операцию можно применить к списку:
($a, $b, $c, $d)[0, 1];
Эта техника используется, к примеру, для получения информации о
времени: функция
localtime преобразует результат, возвращаемый функцией
time
(время в секундах от 1.01.1970), в зависимости от контекста, либо в строку
вида "Fri Sep 15 11:12:13 2000", либо в массив из девяти элементов (секунды,
минуты, часы, день, месяц, год, день недели, день года, признак летнего
времени):
($day, $month, $year) = (localtime(time))[3, 4, 5];
Следует отметить, что месяцы, дни года и дни недели начинаются с нуля,
причем нулевым днем недели считается воскресенье, а годы считаются от 1900
года. Таким образом, 2000 году соответствует значение 100 (из-за того, что
многие программисты забыли этот факт, в начале 2000 года на разных страницах
сети можно было наблюдать дату вида 1.01.100 или 1.01.19100).
При записи списков вместо операции "," можно воспользоваться => :
запись
("one", 1, "two", 2, "three", 3) эквивалентна
(one => 1, two
=>2, three => 3), что может сделать список более наглядным.
Для работы с массивом как со стеком используются функция
push,
добавляющая элементы (один или несколько) в конец массива, и функция
pop,
удаляющая последний элемент. Для работы с началом массива используются
аналогичные функции
unshift и
shift. По умолчанию все эти функции работают с
встроенным массивом
@_.
Функция
reverse меняет порядок следования элементов списка-аргумента на
обратный и возвращает результат. Функция
sort по умолчанию сортирует
аргументы по возрастанию, рассматривая их как строковые переменные.
Помимо простых массивов, в Perl существуют ассоциативные массивы, или
хэши. Их отличие от простых массивов в том, что индексами являются не
последовательные целые числа, а произвольные скалярные величины.
Инициализируются хэши списком, четные элементы которого (начиная с нуля),
являются индексом, четные -- значением, и начинаются с символа %:
%a = (one => 1, two =>2, three => 3);
Доступ к элементу хэша записывается как
$a{1} (в нашем примере вернет
"one").
Функция
key возвращает список ключей переданного ей хэша, функция
values -- список значений. Функция
each последовательно проходит по хэшу,
возвращая пару ключ-значение в виде списка из двух элементов. Удалить
элемент хэша можно функцией
delete:
delete $a{1};
Представить себе современный язык программирования, не имеющий
ссылочных типов данных, невозможно. Не обделен ими и Perl. Здесь можно
получить ссылку на любой из встроенных типов, и присвоить ее некоторой
скалярной переменной:
$ref1 = \$var;
$ref2 = \@array;
$ref3 = \%hash;
Для доступа к скалярным переменным, на которых ссылается ссылка, можно
воспользоваться следующими конструкциями:
$$ref1;
${$ref1};
$ref2->[12];
${$ref2}[12];
$$ref2[12];
$ref3->{$key};
${$ref3}{$key};
$$ref3{$key};
Как правило, предпочитают использовать конструкции с оператором ->,
делающие код более читабельным.
Можно также создать ссылку на анонимный массив или хэш:
$aref = [1, 2, 3];
$href = {1 => One, 2 => Two, 3 => Three};
С помощью ссылок можно формировать довольно сложные структуры данных
(например, массив ссылок на хэши).
Управляющие структуры и функции
Для управления потоком исполнения программы используются операторы
ветвления и цикла, аналогичные существующим в С. Причем обязательным
условием является использование блока операторов, заключенных в фигурные
скобки. Последний оператор в блоке может не иметь завершающей точки с
запятой:
if($a>$max) {$max = $a}
Общая форма оператора if:
if(условие 1)
{...}
elsif(условие 2)
{...}
elsif(условие 3)
{...}
...
else
{...}
Вместо конструкции
if(!условие) можно использовать
unless(условие), а
для упрощения записи вместо конструкции
if(условие){оператор} можно
использовать
оператор if условие.
В качестве управляющих структур часто используются операции && и ||:
Запись
if(выражение){оператор} эквивалентна записи
выражение &&
оператор, а запись
unless (выражение){оператор} -- записи
выражение ||
оператор. Выбор той или иной формы полностью зависит от настроения
программиста.
Операторы цикла также чуть более разнообразны, чем в С: вместо
while(!условие){} можно записать
until(условие){}, аналогично и для цикла с
пост-условием. Для сокращения записи также используется конструкция
оператор
while выражение.
Помимо оператора
for, ничем не отличающегося от С, существует оператор
foreach, который записывается как
foreach $i(@список)
{}
В этом случае скалярная переменная
$i последовательно принимает
значения элементов списка. Например, перебрать все элементы хэша,
отсортированные по ключам, можно так:
foreach $i(sort keys %a)
{
print $a{$i}."\n";
}
При проходе по большому хэшу эффективнее использовать функцию
each --
ценой потери сортировки:
while(($key, $value) = each(%a))
{
}
Для всех операторов цикла, кроме цикла с пост-условием, существуют
операторы
last, прерывающий выполнение цикла,
next, переходящий к следующей
итерации, и
redo, обеспечивающий переход в начало текущего блока без
проверки условия. Их можно использовать в сочетании с метками:
OUTER: while(условие 1)
{
INNER: while(условие 2)
{
if(условие 3)
{
next OUTER; #переход на следующую итерацию внешнего цикла
}
}
}
Функции записываются следующим образом:
sub f
{
}
и вызываются как f();
Значение из функции возвращается оператором return, при отсутствии его
возвращаемым значением является результат последней выражения, вычисленного
в функции. При передаче параметров в функцию они заносятся во встроенную
переменную @_, доступ к элементам которой можно получить как к элементам
обычного массива:
$_[0] и т.п., а также через функции shift, pop и т.п.
Таким образом, функции могут иметь переменное количество параметров.
Переменные, используемые в функциях, по умолчанию имеют глобальную
область видимости:
sub f
{
$a++;
}
$a = 1;
f();
print $a; # напечатает 2
Для того, чтобы сделать переменную локальной, надо объявить ее с
помощью оператора
my:
sub f
{
my $a;
$a++;
}
$a = 1;
f();
print $a; # напечатает 1
Распространенным приемом является инициализация параметров функции,
имеющих осмысленные имена:
my($param1, $param2) = @_;
При наличии директивы
use strict объявление переменных с помощью
операции
my является обязательным.
Кроме директивы
my существует похожая на нее директива
local. Разница
между ними следующая:
my ограничивает область действия переменной текущим
блоком,
local же делает эту переменную доступной и во всех функциях, которые
вызываются из текущего блока.
Теперь, научившись работать с функциями, мы можем использовать
сортировку с произвольным критерием.
sub by_num
{
return $a <=> $b;
}
foreach $i(sort by_num keys %a)
{
print $a{$i}."\n";
}
Функция
by_num определяет критерий сортировки, а переменные
$a и
$b,
передаваемые в нее, являются встроенными и локальными для нее переменными.
То же самое можно записать еще короче, используя анонимную функцию:
foreach $i(sort {$a <=> $b} keys %a)
{
print $a{$i}."\n";
}
Можно создать ссылку на функцию:
sub func{...}
$fref1 = \&func;
$fref2 = sub {...}; # Ссылка на анонимную функцию
Используется эта ссылка как
$fref1->(список аргументов).
Ввод-вывод
Ввод с консоли осуществляется с помощью оператора <STDIN>:
$a = <STDIN>; # считывает следующую строку до символа
#перевода строки
# (точнее, до значения, присвоенного переменной $/)
# либо undef, если строк больше нет
@a = <STDIN>; # считывает все строки до завершения ввода (обычно --
# нажатие Ctrl-Z); каждая строчка будет завершаться
# символом перевода строки
while(<STDIN>) {...} #последовательно считывает строки в
#встроенную переменную $_.
Вывод на консоль осуществляется с помощью функции print,
форматированный вывод -- с помощью printf, полностью аналогичной
соответствующей функции С. Если у print не указан параметр, выводится
содержимое встроенной переменной $_.
Для ввода из файлов, перечисленных в командной строке скрипта,
используется операция <>:
while(<>){...}
Если в качестве параметров передано несколько имен файлов, операция <>
считает их всех последовательно.
Для файлового ввода-вывода сначала необходимо связать с файлом
дескриптор. Стандартные дескрипторы STDIN, STDOUT, STDERR уже связаны со
стандартным вводом, стандартным выводом и стандартным выводом ошибок, кроме
того, существует специальный дескриптор DATA, позволяющий считать текст,
следующий после символов __END__ из файла, в котором находится сама
программа.
Для открытия дополнительных дескрипторов используется функция
open:
open(FILE1, "filename"); # открывает файл для чтения
open(FILE2 ">filename"); # открывает файл для записи
open(FILE3, ">>filename"); # открывает файл для добавления
При неудачном открытии файла функция open возвращает значение "ложь",
так что правилом хорошего тона является проверка этого значения:
open (FILE, "filename") || die "cannot open file: $!";
Функция die вызывает аварийное завершение программы, переменная $!
содержит строку с описанием последней ошибки.
После завершения работы файл необходимо закрыть:
close (FILE);
Имена дескрипторов не начинаются с каких-то специальных символов, и
общепринято вводить их заглавными буквами -- просто, чтобы отличить от
обычных переменных.
Далее их можно использовать в сочетании с операцией <> для ввода:
@a = <FILE>; # Cкладывает все строчки файла в массив строк. Может
# оказаться не самым лучшим решением, если файл имеет
# большой размер
Для вывода в открытый файл его дескриптор записывается сразу после
ключевого слова print. Запятой между дескриптором и другими аргументами быть
не должно:
print FILE "some text\n";
Для проверки существования файла используется операция "-e": if(-e
$filename){...}. Существуют также операции для проверки, доступен ли этот
файл для чтения (-r), доступен ли для записи (-w), является ли он каталогом
(-d) или обычным файлом (-f) и т.д.
Весьма удобной является встроенная в Perl поддержка DBM-файлом,
позволяющая связать ассоциативный массив с DBM-базой. Родная для Unix-систем
библиотека DBM предоставляет в распоряжение программиста простую и удобную
базу данных. Существуют различные реализации DBM, различающиеся возможным
размером записи, базы, скоростью работы и т.п. В простейшем варианте связь
хэша с DBM-файлом осуществляется функцией
dbmopen, а закрывается DBM-файл
функцией
dbmclose:
dbmopen(%A, "basename", 0644)
dbmclose(%A);
Третий параметр функции dbmopen указывает, с какими правами доступа
создавать файл, если его не существует. Если нет необходимости создавать
файл, вместо этого параметра можно передать
undef.
Для более полной информации воспользуйтесь командой
perldoc
AnyDBM_File.<u></u>
Регулярные выражения хорошо знакомы опытным пользователям Unix, они
используются для обработки текста во многих Unix-утилитах, таких как grep,
awk, sed, в редакторах (vi, emacs), в некоторых командных оболочках и т. д.
Регулярное выражение представляет собой образец, или шаблон, который
сопоставляется со строкой. Это сопоставление, или поиск по шаблону, может
закончиться успехом, или неудачей. Кроме того, совпадающий образец может
быть заменен другой строкой или скопирован во внешнюю переменную.
По умолчанию регулярные выражения используют встроенную переменную $_,
но можно сопоставить с шаблоном любую другую скалярную переменную с помощью
операторов =~ и !~:
/текст/; # возвращает истину, если в $_ содержится
# подстрока "текст"
$s =~ /текст/; # возвращает истину, если в $s содержится
# подстрока "текст"
$s !~ /текст/; # возвращает истину, если в $s нет подстроки "текст"
Для замены подстроки, соответствующей шаблону, используется запись вида
s/текст1/текст2/;
Возможно использование модификаторов, например:
/текст/i; # игнорировать регистр
s/$s/текст/g; # производить глобальную замену
В приведенных примерах мы использовали простейший образец, состоящий из
последовательности обычных символов. Однако возможны и более сложные
комбинации. Самый простой символ сопоставления -- точка ("."). Она
соответствует любому одиночному символу, кроме символа новой строки. Можно
задать
класс символов сопоставления с помощью списка, заключенного в
квадратные скобки:
/[абвгде]/;
Этому образцу соответствует строка, содержащая один из этих шести
символов. Диапазон символов задается с помощью дефиса (сам дефис вставляет в
список как \-), символ "^", стоящий сразу за открывающей скобкой, означает
отрицание -- такому классу символов соответствует любой символ,
отсутствующий в этом списке. Некоторые распространенные классы символов
имеют предопределенные обозначения:
\d [0-9] цифра
\w [a-zA-Z0-9_] обычный символ
\s [ \r\t\n\f] пробельный символ
\D [^0-9] нецифровой символ
\W [^a-zA-Z0-9_] специальный символ
\S [^ \r\t\n\f] непробельный символ
Однако самое интересное начинается при работе с образцами для групп
символов, или множителями. Два основных образца здесь -- звездочка "*" и
плюс "+". Звездочке соответствует ни одного или более экземпляров стоящего
перед ней символа или класса символов, плюсу -- один или более экземпляров.
Образцу "?" соответствует ни одного или один символ, стоящий перед ним в
шаблоне, наконец, с помощью фигурных скобок можно задать точное количество
повторений этого символа, или диапазон.
/ab*/; # строки, содержащие а, ab, abb, abbb и т.д.
/ab+/; # ab, abb, abbb и т.д.
/ab?/; # а, ab
/ab{2}/; # abb
/ab{2,4}/; # abb, abbb, abbbb
/ab{2,}/; # abb, abbb, abbbb и т.д.
Поиск по шаблону с множителями характеризуется тремя особенностями:
"жадностью", "торопливостью" и возвратом. "Жадность" означает, что если
шаблон может совпасть со строкой в нескольких вариантах, то выбирается самый
длинный:
$s = 'abbbbb';
$s =~ s/a.*b/c/; #результирующая строка будет содержать только "с".
Любой множитель можно превратить из "жадного" в "ленивый", поставив
сразу после него вопросительный знак:
$s = 'abbbbb';
$s =~ s/a.*?b/c/; #результирующая строка содержит "cbbbb".
"Торопливость" означает, что механизм поиска стремится обнаружить
совпадение как можно скорее -- так, шаблону /a*/ будет соответствовать любая
строка, поскольку * -- это 0 или более символов.
Наконец, возврат обеспечивает совпадение со строкой не только части
регулярного выражения, а всего шаблона. Т.е. если начало шаблона совпадает
со строкой, а одна из последующих частей -- нет, механизм поиска
возвращается к началу и пытается найти новое совпадение.
Если части шаблона заключены в круглые скобки, то включается механизм
запоминания, и части строки, которые им соответствуют, будут последовательно
присваиваться переменным $1, $2, $3 и т.п.:
$s = 'abbbbb';
$s =~ s/a(.*)b/c/; # в $1 будет находиться строка "bbbb"
Другой способ запоминания части строки -- использование кодов \1, \2 и
т.п. непосредственно в шаблоне:
$s = 'abbbbb';
$s =~ s/a(.*)b/\1c/; # результирующая строка содержит "bbbbc"
Возможно использование одного из нескольких шаблонов:
/текст1|текст2|текст3/
а также их комбинация с помощью скобок:
/(a|b)c/;
Чтобы не включать здесь режим запоминания, используют запись
/(?:a|b)c/;
Образец может быть зафиксирован относительно позиции в строке:
/^a.*b$/
соответствует строке, начинающейся с a и заканчивающейся b, а директива \b
требует, чтобы совпадение с образцом происходила только на границе слова.
Если в образце участвует переменная, то перед сопоставлением происходит
ее интерполяция, таким образом, можно строить регулярное выражение на
основании строки введенной пользователем. Чуть позже в этой главе будет
показана опасность этого подхода, пока же отметим, что для отмены
интерполяции используется управляющая последовательность \Q...\E,
предваряющая все спецсимволы в строке обратной косой чертой и превращающая
их тем самым в простые символы:
/\Q$var\E/
Директива tr позволяет заменить все вхождения символов из списка в
строке на другие:
$s =~ tr/abcde/ABCDE/;
Параметр /e заставляет рассматривать заменяющую строку директивы s как
Perl-выражение:
$s =~ s/(.)/sprintf("[%s]", $1)/eg;
(бесполезный пример, расставляющий квадратные скобки вокруг каждого
символа строки и демонстрирующий работу функции форматного вывода в строку
sprintf).
Регулярные выражения очень удобно использовать для разбивки строки на
составляющие -- например, значение, хранящееся в DBM-файле, удобно разбить
на поля, сымитировав более сложную таблицу. Для этого в Perl существует
функция
split. Первый ее параметр -- регулярное выражение, рассматривающееся
как разделитель строки, идущей вторым параметром. Все элементы строки, не
совпадающие с регулярным выражением, последовательно помещаются в массив,
возвращаемый функцией
split:
$s = 'abc:def:ghi';
@a = split(/:/, $s); # массив @a будет содержать три элемента -- "abc",
"def", "ghi".
Если не указан второй параметр, работа идет с переменной $_. Если не
указан и первый параметр, используется образец /\s+/.
Обратная функция --
join -- берет список значений и склеивает их,
вставляя между ними строку-связку, переданную ей в первом параметре:
$s = join(":", @a);
Регулярные выражения являются, пожалуй, самым мощным средством Perl,
предназначенным для обработки текстов, и в нашем кратком изложении мы далеко
не исчерпали все их возможности.
Пакеты, библиотеки, модули, классы и объекты
Пакеты предназначены для разделения глобального пространства имен. По
умолчанию все программы выполняются в пакете main, пока директива
package не
выбирает другой пакет. Для доступа к именам, описанным в другом пакете,
используется синтаксис $ИмяПакета::ИмяПеременной.
Модули представляют собой отдельные файлы, содержащие набор
взаимосвязанных функций. Каждый модуль имеет внешний интерфейс и, как
правило, описывает свои глобальные переменные и функции в отдельном пакете.
К основной программе модули подключаются с помощью директив
use и
require. Директива
use подключает модуль на этапе компиляции программы (хотя
Perl формально и является интерпретируемым языком, непосредственно перед
исполнением происходит компиляция исходных текстов программы), директива
require загружает модуль во время выполнения.
Формально в Perl нет средств для создания составных типов данных
наподобие структур или классов в С++, но имеющихся в нем средств вполне
достаточно для их довольно близкой имитации.
Обычные структуры имитируются в Perl с помощью анонимных хэшей:
$record = {
NAME => 'record1',
FIELD1 => 'value1',
FIELD2 => 'value2',
FIELD3 => 'value3',
};
print $record->{FIELD1};
$records{$record->{NAME}} = $record;
$records{"record1"}->{FIELD1} = 1;
Классы в Perl представляют собой пакеты, а объекты -- нечто (обычно все
та же ссылка на анонимный хэш), приведенное с помощью функции bless к
классу.
Внутри пакета обычно существует функция-конструктор, которая выполняет
всю эту работу. Типичный конструктор выглядит как
sub new
{
my $class = shift; # получаем имя класса
my $self = {}; # выделяем новый хэш для объекта
bless($self, $class); # приводим хэш к классу
$self->{FIELD1} = "value1";# инициализируем поля объекта
$self->{FIELD2} = "value2";
return $self;
}
Если данный конструктор описан в пакете Class, то использовать его
можно как
use Class;
$object1 = Class::new("Class");
$object2 = Class->new();
$object3 = new Class;
Все три записи эквивалентны.
В дальнейшем при вызове функций, описанных в пакете Class, через
объекты, возвращенные конструктором, в первом параметре им будет
передаваться ссылка на данные экземпляра:
sub f
{
my $self = shift;
$self->{FIELD1} = shift;
}
Фактически, Perl-программисту приходится вручную делать все то, что С++
от него скрывает за изящным синтаксисом.
Основные библиотеки, используемые в web-программировании
Одни и те же задачи web-программирования могут решаться на Perl
различными способами, выбор подходящего для конкретного приложения -- в
значительной степени дело вкуса. Главный лозунг Perl -- "Всегда есть больше
одного решения" (There's more than one way to do it, TMTOWTDI). В приложении
к материалу текущего раздела, одну и ту же работу вы можете сделать
самостоятельно, вручную разбирая строки или отсылая пакеты, а можете
доверить ее стандартным библиотекам, которые, впрочем, тоже можно
использовать по-разному. Профессиональная черта программистов -- лень -- как
правило, толкает нас по второму пути, но добросовестность и любопытство
принуждают посмотреть, как же это все устроено внутри.
Для начала рассмотрим задачу создания серверного приложения. Как было
описано выше, информация из формы собирается в строку вида
param1=value1¶m2=value2...¶mN=valueN, которая попадает в серверное
приложение либо через переменную окружения QUERY_STRING, либо через
стандартный ввод, в последнем случае переменная окружения CONTENT_LENGTH
содержит ее размер. Метод, которым передавались данный, задается переменной
окружения REQUEST_METHOD.
Доступ к переменным окружения в Perl осуществляется через ассоциативный
массив %ENV, для чтения строки заданного размера из входного потока
предпочтительней воспользоваться функцией read. Вся процедура получения
входной строки выглядит так (в реальной программе стоило бы добавить
ограничение на длину входной строки):
if($ENV{"REQUEST_METHOD"} eq 'POST')
{
read(STDIN, $query, $ENV{'CONTENT_LENGTH'});
}
else
{
$query = $ENV{'QUERY_STRING'};
}
Далее нам понадобится разбить входную строку на составляющие:
@params = split(/&/, $query);
Теперь @params содержит список строки вида param1=value1. Далее нам
придется выделить из них имена и значения, не забывая о необходимости
декодирования нестандартных символов:
foreach $p(@params)
{
($name, $value) = split(/=/, $);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$in{$name} = $value;
}
Впервые встретившаяся нам функция hex занимается преобразованием
"шестнадцатеричная строка->число", функция pack -- преобразует полученное
значение в бинарную форму, осуществляя в нашем случае преобразование "код
символа->символ".
По завершении цикла все параметры формы оказываются размещенными в
ассоциативном массиве %in, и их значения можно получить как
$in{'param1'}.
Далее следует содержательная часть нашей программы, обработка входных
параметров, выборка данных из баз и т.п. Наконец, обработанную информацию
необходимо вернуть пользователю. В первую очередь необходимо сообщить
клиенту, как именно он должен рассматривать передаваемую далее информацию.
Как мы помним, это осуществляется с помощью HTTP-заголовков.
Как правило, используется два способа -- перенаправление клиента на
новый адрес, или формирование виртуального документа. В первом случае все,
что от нас требуется -- вывести на стандартный вывод заголовок Location:
print "Location: <u>http://newurl/text.html\n\n"</u>;
Во втором случае мы сообщаем клиенту, что вся последующая информация
должна рассматриваться, к примеру, как html-файл:
print "Content-type: text/html\n\n";
print '<html><head><title>Ok</title></head><body>Results:
<br>...</body></html>';
Через HTTP-заголовки передается масса вспомогательной информации --
версия сервера, информация о клиенте, cookie, способ авторизации и т.п.
Как видите, ничего сложного в получении и передаче информации
CGI-приложением нет, но действия при этом выполняются типовые, и возникает
естественное желание написать их раз и навсегда и поместить в библиотеку. Мы
не первые, у кого возникло такое желание, так что теперь вместо переноса из
скрипта в скрипт типового кода можно воспользоваться стандартным (начиная с
версии Perl 5.004) модулем CGI.pm:
use CGI;
$Query = new CGI;
$val1 = $Query->param('param1'); # получаем значение параметра
$cookie1 = $Query->cookie('cookie1'); # получаем значение cookie
# Подготавливаем новый cookie:
$newcookie = $Query->cookie(
-name=>'new', # имя
-value=>'value', # значение
-expires=>"+1y", # прекращает действие через год
-domain=>'www.mydomain.ru' # определен для некоторого домена
);
Формируем и выводим HTTP-заголовки:
print $Query->header(
-type=>'text/html',
-cookie=>$newcookie,
-Pragma=>"no-cache"
# ...
);
Также CGI.pm содержит функции для формирования html-кода, сохранения
результатов запроса в файле и т.п. Дополнительную информацию о модуле можно
получить с помощью команды
perldoc CGI.
Написание клиентских web-приложений на Perl строится по обратной схеме
-- формируем строку параметров и HTTP-заголовки, соединяемся с сервером,
передаем ему запрос и ожидаем ответ. Как обычно, проделать это можно
несколькими способами.
1. Рецепт для любителей ручной работы. Используем низкоуровневые
функции для работы с сокетами, являющиеся минимальными обертками вокруг
соответствующих функций на С.
use Socket;
# подготавливаем строчку с параметрами формы
$forminfo = 'param1=val1¶m2=val2';
# подготавливаем и осуществляем соединение:
# выбираем работу через TCP
$proto = getprotobyname('tcp');
# открываем потоковый сокет
socket(Socket_Handle, PF_INET, SOCK_STREAM, $proto);
# подготавливаем информацию о сервере
$port = 80;
$host = "www.somehost.com";
$sin = sockaddr_in($port,inet_aton($host));
# соединяемся с сервером
connect(Socket_Handle,$sin) || die ("Cannot connect");
# передаем серверу команды, используя дескриптор сокета
# собственно команда GET
send Socket_Handle,"GET /cgi-bin/env.cgi?$forminfo HTTP/1.0\n",0;
# HTTP-заголовки
send Socket_Handle,"User-Agent: my agent\n",0;
send Socket_Handle,"SomeHeader: my header\n",0;
send Socket_Handle,"\n",0;
# начинаем чтение из дескриптора сокета аналогично
# тому, как читали из файла.
while (<Socket_Handle>)
{
print $_;
}
close (Socket_Handle);
При использовании нестандартных символов в параметрах формы их следует
преобразовать в вид %XX, где XX -- их шестнадцатеричное представление.
Кодирование выполняется следующим кодом:
$value=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
2. Чуть меньше ручной работы -- использование модуля IO::Socket.
Рассмотрим его на примере метода POST:
use IO::Socket;
$forminfo = 'param1=val1¶m2=val2';
$host = "www.somehost.com";
$socket = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => "http(80)")
or die ("Cannot connect");
$socket->autoflush(1);
$length = length($forminfo)+1;
$submit = "POST $path HTTP/1.1\n".
"Content-type: application/x-www-form-urlencoded\n".
"Content-length: $length\n".
"Host: $host\n\n"
"$forminfo\n";
print $socket $submit;
while(<$socket>)
{
print;
}
close $remote;
3. Наконец, наиболее комфортный для программиста вариант --
использование комплекса модулей libwww-perl, или LWP. LWP, как правило,
входит во все последние дистрибутивы Perl, кроме того, последняя версия
всегда доступна на CPAN и на <u>http://www.linpro.no/lwp/.</u>
Основные модули, используемые при работе с LWP (для получения
дополнительной инфомрации о каждом модуле воспользуйтесь командой
perldoc
<имя модуля>):
LWP::UserAgent
LWP::Simple
HTTP::Request
HTTP::Response
HTTP::Headers
HTTP::Cookies
LWP::Simple предназначен для простейших операций наподобие получения
информации о документе или получении документа методом GET:
use LWP::Simple;
$content = get('http://somehost/text.html');
LWP::UserAgent -- основной модуль для более тонкой работы с web. Его
назначение -- создание виртуального броузера, выполняющего всю работу по
взаимодействию с сервером:
# создание
$UserAgent = new LWP::UserAgent;
# задание строки с именем "броузера"
$UserAgent->agent("MoZilla 9.0 (non-compatible; MSIE 9.3; PalmOS)");
# работа через прокси
$UserAgent->proxy('http', $proxy);
HTTP::Request отвечает за формирование запроса серверу, HTTP::Headers
-- за формирование заголовков запроса:
# формируем заголовки
$Headers = new HTTP::Headers(Referer => $referer);
# формируем запрос
$Request = new HTTP::Request(POST => $url, $Headers);
# говорим, что передаваться будут данные формы
$Request->content_type('application/x-www-form-urlencoded');
# передаем данные
$Request ->content($forminfo);
Взаимодействие с сервером осуществляется функцией request, возвращающей
объект HTTP::Response:
$Response = $UserAgent->request($Request);
if($Response->is_success) # успешно выполненный запрос
{
# получаем информацию, возвращенную сервером
$text = $Response->content;
}
Для работы с cookie используется модуль HTTP::Cookie и функция
cookie_jar, сообщающая нашему виртуальному броузеру о необходимости
использовать объект Cookie:
my $СookieJar = new HTTP::Cookies;
$UserAgent->cookie_jar($CookieJar);
Для сохранения и загрузки cookie используются функции
$CookieJar->load($cookiefilename);
$CookieJar->save($cookiefilename);
Можно формировать их значения и вручную с помощью функции set_cookie.
Для доступа к ресурсам, защищенным средствами сервера, используется
HTTP-заголовок
Authorization. Его значение должно содержать тип авторизации
(обычно BASIC) и строку вида "имя_пользователя:пароль", в случае
basic-авторизации закодированную base64. Для кодирования можно
воспользоваться модулем MIME::Base64:
use MIME::Base64;
$authorization = MIME::Base64::encode_base64("$name:$password");
$Request->header(Authorization => "BASIC $authorization");
Работа с базами данных
Для работы с базами данных в Perl используется стандартный интерфейс
программирования DBI, обеспечивающий доступ к большинству существующих СУБД
с помощью подключаемых драйверов. Схемы подключения к различным СУБД
(точнее, правила формирования имени источника данных) могут незначительно
различаться, мы рассмотрим работу с использованием mySQL.
В первую очередь необходимо подключить модуль DBI:
use DBI;
Далее подключаемся к базе данных:
my $dbh = DBI->connect('DBI:mysql:hostname:base:port', 'user,
'password, { RaiseError => 1, AutoCommit => 1});
Здесь
$dbh -- дескриптор базы данных, используемый в дальнейшей работе,
DBI:mysql:hostname:base:port -- имя источника данных, включающее имя
драйвера, имя хоста, базы, к которой мы подключаемся, и номер порта, на
который настроен sql-сервер,
user/password -- имя и пароль пользователя,
имеющего доступ к базе, в последнем параметре передаются различные флаги.
По завершении работы желательно закрыть дескриптор:
$dbh->disconnect();
Возможно использование двух способов работы с базой. В случае, если нам
нужно только передать информацию в базу, используется метод
do, параметром
которого является строка SQL-запроса:
$dbh->do("insert into mytable values (1,1)");
Если же необходимо получить информацию из базы, используется следующая
процедура:
1. Получаем дескриптор команды с помощью метода prepare:
my $sth = $dbh->prepare ("select * from mytable where field1>1");
2. Выполняем команду:
$sth->execute();
3. Получаем данные. Используется один из четырех методов:
fetchrow_array
fetchrow_hash
fetchrow_arrayref
fetchrow_hashref
Методы возвращают соответственно массив, хэш, ссылку на массив, ссылку
на хэш, в которых хранятся значения полей текущей записи. Для выборки всех
записей используется цикл, после выборки всех записей функции возвращают
пустой список, воспринимаемый как значение false:
while(my $hash_ref = $sth->fetchrow_hashref)
{
foreach my $fieldname(keys %$hash_ref)
{
print "$fieldname: $hash_ref->{$fieldname }\n";
}
print "\n";
}
4. Освобождаем ресурсы:
$sth->finish();
При передаче текстовой информации в базу рекомендуется предварительно
обработать ее методом
$dbh->quote(), расставляющим кавычки и управляющие
символы в строке в соответствии с правилами используемой СУБД.
Кроме того, возможно использование
привязки параметров в виде:
$sth = $dbh->prepare("select * from mytable where field1=?");
$sth->bind_param(1, "значение параметра");
$sth->execute();
либо
$sth = $dbh->prepare("select * from mytable where field1=?");
$sth->execute("значение параметра");
В этом случае в методе
quote необходимости нет, он вызывается
автоматически.
Использование привязки параметров особенно эффективно при выполнении
нескольких однотипных запросов подряд. В этом случае достаточно один раз
подготовить запрос с помощью функции prepare, и выполнять его с помощью
функции
execute столько раз, сколько необходимо.
Задания для лабораторных работ
Цель работы.
Освоение базовых возможностей языка Perl, работы со структурами данных,
файловым вводом-выводом.
Постановка задачи.
Реализовать на Perl консольное приложение, позволяющее добавлять,
редактировать, выводить на экран, сохранять в файл список однотипных
объектов (студентов группы, служащих фирмы, жителей дома и т.п.), каждый из
которых обладает несколькими атрибутами (имя, фамилия, возраст и т.п.).
Требования к реализации.
использовать директиву
use strict;
взаимодействие с пользователем осуществляется через простое текстовое
меню, выбор действия -- ввод цифры;
меню включает в себя следующие пункты: добавить, редактировать, удалить
объект, вывести на экран весь список, сохранить в файл, загрузить из файла;
целесообразно реализовать обработку меню с помощью хэша ссылок на
функции;
данные сохраняются в dbm-файл;
при загрузке в память каждый объект хранится в виде ссылки на анонимный
хэш, вся картотека хранится в виде массива (либо хэша) ссылок.
2. Картотека с web-интерфейсом
Цель работы.
Освоение способов построения CGI-приложений на Perl.
Постановка задачи.
Преобразовать консольное приложение картотеки в CGI-приложение,
работающее совместно с web-сервером.
Требования к реализации.
для получения информации от клиента использовать стандартную библиотеку
CGI.pm;
сохранить функции работы с файлами и схему обработки меню,
реализованные в первой лабораторной работе, при выводе текстовой информации
осуществлять преобразование кодировки cp866->win1251.
3. Картотека с web-интерфейсом, использующая mySQL
Цель работы.
Освоение способов взаимодействия Perl-приложений с базами данных.
Постановка задачи.
Преобразовать CGI-приложение картотеки, основанное на работе с
dbm-файлами, в клиента базы данных mySQL.
Требования к реализации.
предусмотреть возможность конвертирования информации, хранящейся в
dbm-файлах, в формат mySQL;
использовать механизм привязки параметров;
добавить объект, обладающий расширенными атрибутами (управляющий,
староста и т.п.).
4. Клиентское приложение картотеки
Цель работы.
Освоение техники построения клиентских приложений, использующих
протокол http.
Постановка задачи.
Реализовать на Perl консольное клиентское приложение к картотеке,
считывающее из dbm-файла информацию об объектах, и передающее ее
CGI-приложению из третьей лабораторной работы по протоколу http для
занесения в базу.
Требования к реализации.
использовать библиотеку LWP;
при передаче строковой информации кодировать нестандартные символы в
формат %XX, где XX -- шестнадцатеричное представление данного символа, а
также осуществлять преобразование кодировки cp866->win1251.
Декарт А., Банс Т. Программирование на Perl DBI/Пер. с англ. -- СПб:
Символ-Плюс, 2000. -- 400 с.
Кристиансен Т., Торкингтон Н. Perl. Библиотека программиста/Пер с англ.
-- СПб.: "Питер", 2000. -- 736 с.
Найк Д. Стандарты и протоколы Интернета/Пер. с англ. -- М.:
Издательский отдел "Русская редакция" ТОО "Channel Trading Ltd.", 1999, --
384 с.
Спейнаур С., Куэрсиа В. Справочник Web-мастера/Пер. с англ. -- К.:
Издательская группа BHV, 1997. -- 368 с.
Шварц Р., Кристиансен Т. Изучаем Perl/Пер. с англ. -- К.: Издательская
группа BHV, 1998. -- 320 с.
Обращений с начала месяца: 341, Last-modified: Sun, 09 Sep 2001 17:58:10 GMT