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: http://... /text.html\n\n";
Во втором случае мы сообщаем клиенту, что вся последующая информация должна рассматриваться, к примеру, как html-файл:
print "Content-type: text/html\n\n";
print 'Results: ...';
Через HTTP-заголовки передается масса вспомогательной информации -- версия сервера, информация о клиенте, cookie, способ авторизации и т.п.
Как видите, ничего сложного в получении и передаче информации CGI-приложением нет, но действия при этом выполняются типовые, и возникает естественное желание написать их раз и навсегда и поместить в библиотеку. Мы не первые, у кого возникло такое желание, так что теперь вместо переноса из скрипта в скрипт типового кода можно воспользоваться стандартным (начиная с версии Perl 5.
) модулем 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=>' .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 = " .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 ()
{
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 = " .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 и на .linpro.no/lwp/.
Основные модули, используемые при работе с LWP (для получения дополнительной инфомрации о каждом модуле воспользуйтесь командой perldoc <имя модуля>):
LWP::UserAgent
LWP::Simple
HTTP::Request
HTTP::Response
HTTP::Headers
HTTP::Cookies
LWP::Simple предназначен для простейших операций наподобие получения информации о документе или получении документа методом GET:
use LWP::Simple;
$content = get('http:// .....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 столько раз, сколько необходимо.