http://beshenov.ru/perlfaq/9.html
perlfaq9 — работа в сети
Этот раздел FAQ отвечает на вопросы по работе в сети Internet и отчасти — в WWW.
Какой должен быть ответ CGI-скрипта?
(Отвечает Алан Флейвелл)
CGI определяет интерфейс между программой (CGI-скриптом) и веб-сервером (HTTPD). Это относится не только к Perl, и по CGI есть отдельные FAQ, руководства, а также группа новостейcomp.infosystems.www.authoring.cgi.
Спецификация CGI изложена в информационном RFC 3875.
Другая документация по теме перечислена в «CGI Programming MetaFAQ».
Эти FAQ по Perl очень избирательно покрывают некоторые вопросы CGI. Однако программирующим на Perl настоятельно рекомендуется использовать модуль
CGI.pm, чтобы позаботиться о деталях.Заголовки ответа CGI (определенные в спецификации CGI) намеренно схожи с заголовками ответа HTTP (определенными в спецификации HTTP, RFC 2616), но это может сбить с толку.
Спецификация CGI определяет два типа скриптов: с анализируемыми заголовками («Parsed Header»), и с неанализируемыми заголовками («Non Parsed Header», NPH). Посмотрите в документации своего сервера, что он поддерживает. Скрипты с анализируемыми заголовками во многих отношениях проще. Спецификация CGI допускает любое обычное представление перевода строки в ответе CGI (создание правильного ответа HTTP на основе CGI — уже дело сервера). Поэтому запись
"\n" в текстовом режиме технически корректна и рекомендована. Скрипты с неанализируемыми заголовками более сложны: они должны отдавать полный и правильный набор HTTP-заголовков; спецификация требует завершать записи с CR и LF, то есть, ASCII-символами \015\012, записанными в бинарном режиме.CGI.pm дает превосходную платформонезависимость, в том числе поддержку систем с EBCDIC.
CGI.pmвыбирает правильное представление перевода строки ($CGI::CRLF) и соответствующим образом устанавливает binmode.CGI-скрипт работает в командной строке, но не в браузере (ошибка №500)
Проблема может иметь различные источники. Посмотрите руководств «Troubleshooting Perl CGI scripts». Если вы его прочитали, но для проблемы не нашлось простого решения, то любезный совет можно получить вcomp.infosystems.www.authoring.cgi (если дело в протоколе HTTP или CGI). Сообщения, которые выдаются за вопросы по Perl, но на деле касаются CGI, обычно не очень хорошо принимаются в comp.lang.perl.misc.
Полезные FAQ, документы по теме и руководства по устранению проблем перечислены в «CGI Meta FAQ».
Как получить более хорошие сообщения об ошибках в CGI-программе?
Используйте модуль
CGI::Carp. Он заменяет warn и die, а также функции carp, croak и confess обычного модуля Carp более информативными и безопасными. Модуль также заносит сообщения в обычный журнал ошибок сервера.use CGI::Carp; warn "Это жалоба"; die "А это уже серьезная ошибка";
Следующее обращение к
CGI::Carp также перенаправляет ошибки в заданный файл, и помещено в блок BEGIN для перехвата предупреждений этапа компиляции:BEGIN { use CGI::Carp qw(carpout); open(LOG, ">>/var/local/cgi-logs/mycgi-log") or die "Не могу дописать в mycgi-log: $!\n"; carpout(*LOG); }Можете даже организовать вывод критических ошибок в браузере клиента, что удобно для отладки, но может сбить с толку конечного пользователя.
use CGI::Carp qw(fatalsToBrowser); die "Плохая ошибка";
Даже если ошибка случается до вывода HTTP-заголовка, модуль пытается перехватить ее, чтобы избежать вывода пугающих ошибок сервера №500. Простые предупреждения также будут заноситься в обычный журнал ошибок сервера (или куда-либо еще в соответствии с
carpout) с указанием имени приложения и времени.Как удалить HTML-разметку из строки?
Самый правильный, хотя и не самый быстрый способ — использовать
HTML::Parser из CPAN. Другой почти правильный подход — HTML::FormatText, который не только удаляет HTML-разметку, но и пытается немного отформатировать полученный простой текст.Многие пытаются обойтись простыми регулярными выражениями вроде
s/<.*?>//g, но это часто не работает, потому что теги могут разрываться переводами строки и содержать экранированные угловые скобки; также могут встречаться HTML-комментарии. Кроме того, при этом упускается преобразование сущностей вроде <.Вот один простой подход, работающий для большинства файлов:
#!/usr/bin/perl -p0777 s/<(?:[^>'"]*|(['"]).*?\1)*>//gs
Если нужно более полное решение, смотрите трехэтапную обработку в программе striphtml.
Вот некоторые хитрые ситуации, о которых нужно помнить при выборе решения:
<IMG SRC = "foo.gif" ALT = "A > B"> <IMG SRC = "foo.gif" ALT = "A > B"> <!-- <Комментарий> --> <script>if (a<b && a>c)</script> <# Просто данные #> <![INCLUDE CDATA [ >>>>>>>>>>>> ]]>
В HTML-комментариях с другими тегами эти решения также споткнутся на таком тексте:
<!-- Эта часть закомментирована. <B>Меня не видно!</B> -->
Как извлечь URL?
Все возможные URL из HTML можно извлечь при помощи
HTML::SimpleLinkExtor — поддерживаются a, img,object, frame и многие другие теги, которые могут содержать URL. Если нужно что-то более хитрое, то можете наследовать свой класс от HTML::LinkExtor или HTML::Parser. Можете даже использоватьHTML::SimpleLinkExtor в качестве примера для кода, заточенного под ваши нужды.Для извлечения URL из произвольного текстового документа можно использовать
URI::Find.Упрощенные решения на регулярных выражениях могут сэкономить много времени на обработке, если известно, что ввод простой. Это решение Тома Кристиансена работает в сто раз быстрее программ, использующих модули, но извлекает только ссылки из элементов
A с первым и единственным атрибутом HREF:#!/usr/bin/perl -n00 # qxurl - tchrist@perl.com print "$2\n" while m{ < \s* A \s+ HREF \s* = \s* (["']) (.*?) \1 \s* > }gsix;
Как скачать файл с пользовательского компьютера? Как открыть файл на другом компьютере?
Здесь «скачивание с компьютера» сводится к закачиванию, реализованному в HTML-формах. Вы позволяете пользователю указать файл для отправки на сервер. Для вас это выглядит как скачивание, а для пользователя это закачивание. Как ни называйте, это делается при помощи кодирования в
multipart/form-data. Модуль CGI.pm (распространяется с Perl как часть стандартной бибиотеки) поддерживает это в методе start_multipart_form() (не путать с startform()).За примерами и подробностями обратитесь к разделу документации
CGI.pm по закачке файлов.Как сделать раскрывающийся список HTML?
(Отвечает Брайан Д. Фой)
В модуле
CGI.pm (прилагается к Perl) есть функции для создания компонентов форм HTML.use CGI qw/:standard/; print header, start_html('Любимые животные'), start_form, 'Какое ваше любимое животное? ', popup_menu( -name => 'animal', -values => [ qw( Лама Альпака Верблюд Баран ) ] ), submit, end_form, end_html;Дополнительные примеры см. в документации
CGI.pm.Как скачать HTML-файл?
Один из подходов, если в системе установлен консольный браузер
lynx:$html_code = `lynx -source $url`; $text_data = `lynx -dump $url`;
Модули CPAN
libwww-perl (LWP) дают более мощное решение. Они не требуют lynx, но тоже могут работать через прокси:# Простейшая версия use LWP::Simple; $content = get($URL); # либо вывести документ по указанному URL use LWP::Simple; getprint "http://www.linpro.no/lwp/"; # либо вывести ASCII-версию документа по указанному URL # (требуется пакет HTML-Tree из CPAN) use LWP::Simple; use HTML::Parser; use HTML::FormatText; my ($html, $ascii); $html = get("http://www.perl.com/"); defined $html or die "Не могу загрузить документ с http://www.perl.com/"; $ascii = HTML::FormatText->new->format(parse_html($html)); print $ascii;
Как автоматизировать отправку HTML-форм?
Если вы делаете что-то сложное — например, работаете с многими страницами и формами, то можете использовать
WWW::Mechanize. Подробности смотрите в документации.Если вы отправляете значения при помощи метода GET, то создайте URL и кодируйте данные формы при помощи метода
query_form:use LWP::Simple; use URI::URL; my $url = url('http://www.perl.com/cgi-bin/cpan_mod'); $url->query_form(module => 'DB_File', readme => 1); $content = get($url);Если вы используете метод POST, то создайте собственный пользовательский агент ии кодируйте контент соответствующим образом.
use HTTP::Request::Common qw(POST); use LWP::UserAgent; $ua = LWP::UserAgent->new(); my $req = POST 'http://www.perl.com/cgi-bin/cpan_mod', [ module => 'DB_File', readme => 1 ]; $content = $ua->request($req)->as_string;
Как декодировать или создать эти % в URL?
(Отвечает Брайан Д. Фой)
Эти
% представляют специальные символы в URI, как описано во втором разделе RFC 2396. Такая кодировка заменяет специальные символы шестнадцатеричными значениями из таблицы. Например, вместо «:» используется «%3A».Если в CGI-скриптах используется
CGI.pm, то о декодировании URI не нужно заботиться. Обрабатывать URL вручную не требуется, ни на входе, ни на выходе.Если нужно самостоятельно кодировать строку, то никогда не кодируйте сформированный URI. Нужно обработать компоненты отдельно, а затем объединить. Для кодирования строки можно использовать модуль
URI::Escape; функция uri_escape возвращает экранированную строку:my $original = "Colon : Hash # Percent %"; my $escaped = uri_escape( $original ) print "$string\n"; # 'Colon%20%3A%20Hash%20%23%20Percent%20%25%20'
Для декодирования строки используйте функцию
uri_unescape:my $unescaped = uri_unescape( $escaped ); print $unescaped; # вернуться к исходной строке
Если хотите сделать это сами, то просто замените специальные символы их кодами. Одно из решений — глобальная подстановка
# кодировать $string =~ s/([^^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%0x", ord $1 /eg; # декодировать $string =~ s/%([A-Fa-f\d]{2})/chr hex $1/eg;Как перенаправить на другую страницу?
Укажите полный целевой URL (даже если это на том же сервере). Это один из двух возможных ответов CGI с заголовком
Location, определенный спецификацией для скриптов с анализируемыми заголовками. Другой возможный ответ — абсолютный URLpath — разрешается на стороне сервера без перенаправления по HTTP. Ни в первом, ни во втором случае, спецификации CGI не позволяют использовать относительные URL.Настоятельно рекомендуется использовать
CGI.pm. В следующем примере используется полный URL. Перенаправление производится веб-браузером.use CGI qw/:standard/; my $url = 'http://www.cpan.org/'; print redirect($url);
В примере ниже используется абсолютный URLpath. Перенаправление производится локальным веб-сервером.
my $url = '/CPAN/index.html'; print redirect($url);
Напрямую это можно записать следующим образом (последний
"\n" для ясности указан отдельно):print "Location: $url\n"; # заголовок ответа CGI print "\n"; # конец заголовков
$url может быть полным URL или абсолютным URLpath.Как защитить веб-страницы паролем?
Для включения аутентификации нужно настроить веб-свервер. Настройки могут быть разными, в этом Apache отличается от iPlanet, а iPlanet отличается от IIS. Подробности смотрите в документации к вашему серверу.
Как отредактировать в Perl файлы .htpasswd и .htgroup?
Подходящиий объектно-ориентированный интерфейс для работы с этими файлами, не зависимо от их расположения, предоставляют модули
HTTPD::UserAdmin и HTTPD::GroupAdmin. Базы данных могут быть в текстовом формате, DBM, Berkeley DB и любом другом виде, совместимом с DBI. HTTPD::UserAdminподдерживает файлы, используемые в схемах авторизации «Basic» и «Digest». Вот пример:use HTTPD::UserAdmin (); HTTPD::UserAdmin ->new(DB => "/foo/.htpasswd") ->add($username => $password);
Как убедиться в том, что пользователи не введут в форму значения, которые заставят CGI-скрипт сделать что-то нехорошее?
См. ссылки по вопросам безопасности в «CGI Meta FAQ».
Как разобрать заголовки почтового сообщения?
$/ = ''; $header = <MSG>; $header =~ s/\n\s+/ /g; # объединить продолжающиеся строки %head = ( UNIX_FROM_LINE, split /^([-\w]+):\s*/m, $header );
Это решение не сработает верно, если, например, вы попытаетесь обработать все строки с
Received. Более полный подход используется в модуле CPAN Mail::Header (часть пакета MailTools).Как декодировать CGI-форму?
(Отвечает Брайан Д. Фой)
Используйте модуль
CGI.pm, прилагающийся к Perl. Он быстрый, простой и берет на себя часть работы, чтобы удостовериться, что всё работает верно. Модуль поддерживает запросы GET, POST и HEAD, данныеmultipart/form, поля со множественными значениями, комбинации запросов и HTTP-сообщений и прочие вещи, о которых вам не хотелось бы заботиться.Проще некуда: модуль CGI автоматически разбирает ввод и делает каждое значение доступным через функцию
param().use CGI qw(:standard); my $total = param( 'price' ) + param( 'shipping' ); my @items = param( 'item' ); # несколько значений для одного поля
CGI.pm поддерживает и объектно-ориентированный подход.use CGI; my $cgi = CGI->new(); my $total = $cgi->param( 'price' ) + $cgi->param( 'shipping' ); my @items = $cgi->param( 'item' );
Кроме того, можете посмотреть облегченную версию
CGI::Minimal. Другие модули CGI::* из CPAN также могут подойти лучше.Многие пытаются написать собственный декодер (или скопировать из другой программы) и натыкаются на одну из многих тонкостей. Гораздо проще использовать
CGI.pm.Как проверить на корректность адрес электронной почты?
(Часть ответа от Аарона Шермана)
Это не так просто, как кажется. Есть два момента:
- Как проверить, что адрес правильно введен?
- Как проверить, что адрес указывает на существующего получателя?
Без отправки письма по адресу и проверки, что на другом конце вам ответят, на второй вопрос нельзя дать точный ответ, но модули
Email::Valid и RFC::RFC822::Address позволяют ответить на первый и частично на второй, насколько это возможно в реальном времени.Если хотите только проверить корректность адреса простым регулярным выражением, то можете столкнуться с пробемами, потому что существуют работающие адреса, не соответствующие RFC 2822(последнему стандарту на заголовки электронной почты), ровно как и соответствующие RFC адреса, по которым нельзя доставить почту. Следующий шаблон проверит адреса без комментариев, сворачивающихся пробельных символов и прочих устаревших или необязательных элементов. Этот шаблон соответствует только самому адресу:
my $atom = qr{[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+}; my $dot_atom = qr{$atom(?:\.$atom)*}; my $quoted = qr{"(?:\\[^\r\n]|[^\\"])*"}; my $local = qr{(?:$dot_atom|$quoted)}; my $domain_lit = qr{\[(?:\\\S|[\x21-\x5a\x5e-\x7e])*\]}; my $domain = qr{(?:$dot_atom|$domain_lit)}; my $addr_spec = qr{$local\@$domain};Просто проверьте адрес на сответствие
/^${addr_spec}$/, чтобы посмотреть, следует ли он RFC 2822. Впрочем, нельзя знать, что такой корректный адрес действительно используется определенным человеком, поэтому тут нужно быть аккуратнее.Наш совет для проверки адресов почты — попросите пользователей вводить их дважды, как при смене пароля. Это исключает опечатки. Если оба ввода совпадают, отправьте на этот адрес личное сообщение. Если на сообщение будет получен ответ, и пользователь выполнит инструкции в нем, то можно быть увереным, что адрес настоящий.
Схожее решенее, менее подверженное подлогам — закрепить за пользователями личные идентификационные номера (PIN), лучше случайные, и хранить вместе с адресами для последующей обработки. В отправляемых сообщениях пользователей нужно просить включить в ответ PIN. Если сообщение возвращается, либо скрипт отвечает, что получатель в отпуске, то PIN всё равно будет в ответе. Поэтому лучше просить пользователей отвечать с несколько измененным PIN, например, записанным в обратном порядке, с добавлением или вычитанием единицы из каждой цифры, и так далее.
Как раскодировать строчку из MIME/BASE64?
Пакет
MIME-Base64 из CPAN работает с MIME/BASE64 и MIME/QP. Декодирование BASE64 производится проще некуда:use MIME::Base64; $decoded = decode_base64($encoded);
Пакет
MIME-Tools из CPAN поддерживает выборку и декодирование вложений и самого текста сообщения.Если строка для декодирования небольшая (меньше 84 байт), то после незначительного преобразования можно использовать прямое решение с форматом u функции unpack():
tr#A-Za-z0-9+/##cd; # удалить символы не из base64 tr#A-Za-z0-9+/# -_#; # кодировать в UUE $len = pack("c", 32 + 0.75*length); # вычислить байт длины print unpack("u", $len . $_); # декодировать UUE и вывестиКак определить почтовый адрес пользователя?
В системах, которые поддерживают getpwuid, переменную $< и модуль Sys::Hostname (из стандартной поставки Perl), можно попробовать нечто вроде
use Sys::Hostname; $address = sprintf('%s@%s', scalar getpwuid($<), hostname);Правила компании относительно почтовых адресов могут быть такими, что код выше будет давать адреса, не принимаемые почтовой системой, поэтому адреса лучше спросить у самих пользователей. Кроме того, не все системы, на которых работает Perl, выдают подобную информацию, как это делает Unix.
Модуль
Mail::Util из CPAN (часть пакета MailTools) предоставляет функцию mailaddress(), которая пытается определить почтовый адрес пользователя. Используется более умный подход, чем в коде выше, с привлечением информации, полученной при установке модуля, но и это может не дать правильных результатов. Опять же, лучше спросить у пользователя.Как отправить почту?
Используйте напрямую программу
sendmail:open(SENDMAIL, "|/usr/lib/sendmail -oi -t -odq") or die "Не могу сделать fork для sendmail: $!\n"; print SENDMAIL <<"EOF"; From: Адрес автора письма <me\@host> To: Конечное назначение <you\@otherhost> Subject: Соответствующий заголовок Тело письма идет после пустой строки и продолжается нужное число строк. EOF close(SENDMAIL) or warn "sendmail завершился неудачно";
Опция -oi указывает sendmail не интерпретировать строку, содержащую одну точку, как конец сообщения. С опцией -t получатели определяются по заголовкам; -odq помещает сообщение в очередь. С последей опцией сообщение будет передано не сразу; уберите ее, если хотите отправить его незамедлительно.
Другие, менее удобные решения — прямой вызов mail (иногда называется mailx) или просто сокровенный разговор с удаленным SMTP-демоном на 25-м порту (возможно, с sendmail).
Или можно использовать модуль
Mail::Mailer из CPAN:use Mail::Mailer; $mailer = Mail::Mailer->new(); $mailer->open({ From => $from_address, To => $to_address, Subject => $subject, }) or die "Не могу открыть: $!\n"; print $mailer $body; $mailer->close();Модуль
Mail::Internet использует Net::SMTP — менее Unix-ориентированный, чем Mail::Mailer, но и менее надежный. Избегайте чистых SMTP-команд. Есть множество причин использовать агент передачи почты вроде sendmail, в том числе очереди сообщений, записи MX, безопасность.Как использовать MIME, чтобы добавить в сообщение вложение?
Ответ на вопрос взят из документации к
MIME::Lite. Создайте сообщение типа «multipart» (то есть, с вложениями).use MIME::Lite; ### Создать новое сообщение типа «multipart»: $msg = MIME::Lite->new( From =>'me@myhost.com', To =>'you@yourhost.com', Cc =>'some@other.com, some@more.com', Subject =>'Сообщение в двух частях...', Type =>'multipart/mixed' ); ### Добавить части (каждый "attach" вызывается с теми же аргументами, что и "new"): $msg->attach(Type =>'TEXT', Data =>"Вот GIF, который вы просили" ); $msg->attach(Type =>'image/gif', Path =>'aaa000123.gif', Filename =>'logo.gif' ); $text = $msg->as_string;
MIME::Lite также включает метод для отправки$msg->send;
По умолчанию используется sendmail, но можно настроить использование SMTP через
Net::SMTP.Как прочитать почту?
Хотя можно использовать модуль
Mail::Folder из CPAN (часть пакета MailFolder) или Mail::Internet (часть пакета MailTools), часто это перебор. Вот программа для сортировки почты.#!/usr/bin/perl my(@msgs, @sub); my $msgno = -1; $/ = ''; # считывание абзацами while (<>) { if (/^From /m) { /^Subject:\s*(?:Re:\s*)*(.*)/mi; $sub[++$msgno] = lc($1) || ''; } $msgs[$msgno] .= $_; } for my $i (sort { $sub[$a] cmp $sub[$b] || $a <=> $b } (0 .. $#msgs)) { print $msgs[$i]; }Или более коротко
#!/usr/bin/perl -n00 # bysub2 - сортировка по теме сообщения в духе awk BEGIN { $msgno = -1 } $sub[++$msgno] = (/^Subject:\s*(?:Re:\s*)*(.*)/mi)[0] if /^From/m; $msg[$msgno] .= $_; END { print @msg[ sort { $sub[$a] cmp $sub[$b] || $a <=> $b } (0 .. $#msg) ] }Как узнать имя узла, домена, IP-адрес?
(Отвечает Брайан Д. Фой)
Модуль
Net::Domain, входящий в стандартную поставку, начиная с perl5.7.3, может определить полностью уточнённое доменное имя (FQDN), имя узла или домена.use Net::Domain qw(hostname hostfqdn hostdomain); my $host = hostfqdn();
Мудуль
Sys::Hostname, входящий в стандартную поставку, начиная с perl5.6, также может получить имя узла.use Sys::Hostname; $host = hostname();
Для получения IP-адреса можно использовать встроенную функцию
gethostbyname, преобразующую имя в число. Для преобразования этого числа в привычную последовательность разделенных точкой октетов (a.b.c.d) используйте функцию inet_ntoa из модуля Socket, который тоже прилагается к perl.use Socket; my $address = inet_ntoa( scalar gethostbyname( $host || 'localhost' ) );
Как скачать сообщение из группы новостей или список активных групп?
Используйте
Net::NNTP или News::NNTPClient из CPAN. С ними задачи вроде получения списка групп новостей решаются очень просто:perl -MNews::NNTPClient -e 'print News::NNTPClient->new->list("newsgroups")'Как скачать/закачать файл по FTP?
С
LWP::Simple (есть в CPAN) можно скачать файл, но не закачать. Net::FTP (тоже есть в CPAN) более сложный, но поддерживает и закачивание, и скачивание.Как в Perl делать удаленный вызов процедур (RPC)?
(Отвечает Брайан Д. Фой)
Используйте один из модулей CPAN для RPC: http://search.cpan.org/search?query=RPC&mode=all.
No comments:
Post a Comment