Рубрика:
Веб /
Веб
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
Игорь Тетерин
Автоматизация веб-проектов через электронную почту
Если взглянуть на мир со стороны, становится абсолютно не понятно, кто и как управляет сайтами. Информации на этот счет крайне мало, хотя, думаю, существует достаточное количество интересных решений. Более-менее развитые движки сайтов предоставляют веб-интерфейс управления контентом, если, конечно, это не считается принципиальным аппендиксом. Про то, что я буду описывать, лично мне нигде пока слышать не доводилось.
Сразу же уточним, чего мы хотим добиться. Наша задача – сделать как можно более удобный и доступный интерфейс для наполнения сайта контентом, то есть переложить часть рутины на скрипты и сотрудников. Значит, нам предстоит заняться автоматизацией этого процесса.
Иногда дело доходит до смешного. Есть у человека свой проект, и даже есть свой движок. Но для каждого нового динамического раздела он пишет и рисует формы, пишет обработчики, вставляет проверки. Появляется новый раздел – берем шаблон предыдущего, корректируем, правим, редизайним, проверяем и выкладываем. И еще раз проверяем.
Такой вот обьем работы ради того, чтобы добавить один раздел. А кто-то ведь еще работает и делает это через dial-up. Любой, кто занимался этим, поймет – эта работа для орков. Мы же – программисты.
У нас есть свой проект, и тратить кучу времени на разработку каждого отдельно взятого раздела мы не хотим. Лучше мы помучаемся один раз, зато потом жизнь наша станет сладкой и динамичной.
Новый принцип автоматизации. Основы. Сравнения
Как-то давно, когда я писал ret 0.8 (ядро для интернет-сайтов), мой друг заметил, что было бы весьма удобно, если бы сайтом можно было управлять через электронную почту. Тогда мы не уделили этому вопросу должного внимания. Позднее – только вспоминали про эту технологию. И вот, наконец, я решил испробовать это на одном разделе своего сайта http://revda.biz.
Написал простой скрипт, который читает почту из рассылки и публикует все, что попадет, на сайт. И тут я понял, что это очень удобно! Люди, которые писали в рассылку, просто переписывались, при этом страница сайта была в постоянном движении. Те, кто заходил на эту страницу, знали и понимали – за новой информацией туда можно заходить каждый день. Да, пока это больше похоже на обычный интернет-трэд, но все же!
Как все это выглядит? Наша задача – обрабатывать письма, которые мы будем брать с заранее созданного почтового ящика. Этот же ящик мы можем подписать на рассылки или нечто подобное, дело даже не только в рассылках. Мы можем дать адрес ящика нужным нам людям для того, чтобы они присылали туда новую информацию почтой, и не приучать их пользоваться для этого каким-либо веб-интерфейсом. Кроме того, наш сайт может брать что-то из сети Интернет, а результат пересылать на тот же почтовый ящик.
Грубо говоря, наш почтовый ящик стал централизованным источником информации. Этого обычно не скажешь о веб-интерфейсе, где приходится (по большей части) кого-то напрягать, заставлять или самому работать ручками. Более того – мы можем автоматически обрабатывать не один ящик, а несколько, закрепив за каждым свою тематику.
Этот подход также решает вопрос вашего присутствия в Интернете. Так как мы работаем с почтовым ящиком, то для того, чтобы обработать материал, ответить кому-то или сделать что-то еще, нам достаточно просто скачать почту, поработать с ней и отослать ответы.
Пока все выглядит очень красиво. Однако, чтобы создать нечто подобное в реальности, нам придется ввязаться в битву с одним, а может, и двумя, самыми запутанными стандартами: MIME и HTML.
Победить в битве с HTML достаточно трудно – это довольно противоречивый и очень динамичный стандарт. Тут придется не один раз приложить и руки, и голову. В рамках нашей задачи мы коснемся его только слегка.
Выбор платформы, языка и оценка задачи
На чем будет работать наш обработчик почты? Ответ очевиден, если предположить, что все должно работать и на Windows, и на UNIX-платформах. Мы не станем ограничивать себя чем-то одним, поэтому после разработки обработчика сможем использовать его и там, и там. А это весьма удобно.
На чем будем писать? Немного подумаем. Учитывая, что PHP я не знаю и знать не хочу, остается один выбор – Perl. К тому же Perl, с моей точки зрения, концептуально более правильный язык и больше подходит на роль стандартного. Но не это важно, важны принципы и алгоритм, а реализовывать можно хоть на Ruby.
Ну и, наконец, перед нами стоит задача написать обработчик почты. То есть нам потребуется автоматизировать прием, отправку, разбор писем. Плюс к этому возникает проблема безопасности и защищенности такого решения.
Что же нам потребуется? Один почтовый ящик, хостинг (хотя можно и локально) с поддержкой Perl и несколькими дополнительными модулями с CPAN. Ну и остальное «огнестрельное оружие», вроде putty, far, TheBat и т. д.
Переходим к практике
Рассмотрим очень простой пример. Первое, что нам потребуется – подключение нужных модулей:
use Net::POP3;use MIME::Parser;
use MIME::Entity;
use MIME::Head;
use MIME::Body;
use MIME::Words qw(:all);
use MIME::QuotedPrint;
use MIME::Base64;
Определим основные объекты и переменные:
my $parser = MIME::Parser->new;
$parser->output_to_core(1);
$parser->tmp_to_core(1);
$mail_server='127.0.0.1';
$username='login';
$password='password';
Теперь получим список писем:
$pop = Net::POP3->new($mail_server)
or die "Can't open coonection to $mail_server :$!\n";
$pop->login($username, $password)
or die "Can't Authenticate: $!\n";
$messages = $pop->list
or die "Can't get listof undeleted messages: $!\n";
Начинаем обрабатывать каждое письмо:
foreach $msgid (keys %$messages)
{
$message = $pop->get($msgid);
unless (defined $message)
{
warn "Couldn't fetch $msgid from server: $!\n";
next;
}
Следующий метод изначально был взят у Рэндола Шварца. Если вы поищете в Сети, то найдете нечто вроде Perl-Column, мне встречался даже перевод, правда, неполный.
Далее следует примерно такой алгоритм: если в письме есть часть типа text/plain, то берется именно эта часть, а все остальное игнорируется.
Таким образом, если мы встречаем письмо, где есть HTML-часть и текст, то в качестве входящих данных берется именно текст. Если текстовой части нет – письмо игнорируется.
$pop->delete ( $msgid );
@message = @$message;
$ent = $parser->parse_data ( \@message );
$bodyCoding = $ent->head->mime_attr( 'Content-type.charset' );
$origType = $ent->head->get( 'Content-Transfer-Encoding',0 );
if ( $ent->effective_type eq 'text/plain' )
{
# письмо - только текст
$bodyCoding = $ent->head->mime_attr (
'Content-type.charset');
$origType = $ent->head->get(
'Content-Transfer-Encoding',0 );
$body = $ent->body_as_string;
}
elsif (
$ent->effective_type eq 'multipart/alternative'
and $ent->parts(0)->effective_type eq 'text/plain' )
{
# письмо, где первая часть мультипар - текст
$bodyCoding = $ent->parts(0)->head->mime_attr(
'Content-type.charset' );
$origType = $ent->parts(0)->head->get(
'Content-Transfer-Encoding',0 );
$body = $ent->parts(0)->body_as_string;
}
elsif (
$ent->effective_type eq 'multipart/alternative'
and $ent->parts(1)->ffective_type eq 'text/plain' )
{
# письмо, где вторая часть мультипарт - текст
$bodyCoding = $ent->parts(1)->head->mime_attr(
'Content-type.charset');
$origType = $ent->parts(1)->head->get(
'Content-Transfer-Encoding',0 );
$body = $ent->parts(1)->body_as_string;
}
else {next}
chomp $origType;
Чем универсален этот код, так это тем, что в нём происходят все стандартные MIME-декодировки – Base64 и Quoted-Printable – и перекодирование из ISO и KOI в Windows-1251.
# Если закодировано, декодируем его, во имя счастья
if (lc($origType) eq 'quoted-printable')
{ $body = MIME::QuotedPrint::decode($body); }
if (lc($origType) eq 'base64')
{ $body = MIME::Base64::decode($body); }
$bodyCoding = lc($bodyCoding);
# Перекодировка кирилицы у тела, если надо.
if ($bodyCoding ne '')
{
if ($bodyCoding eq 'koi8-r') {$bodyCoding = 'koi'}
if ($bodyCoding eq 'koi8r') {$bodyCoding = 'koi'}
if ($bodyCoding eq 'iso8859-5') {$bodyCoding = 'iso'}
if ($bodyCoding eq 'koi8-u') {$bodyCoding = 'koi'}
if ($bodyCoding eq 'koi' || $bodyCoding eq 'iso')
{ $body = encoder($body, $bodyCoding, 'win') }
}
$subj = join( "",
map {xcode( ${$_}[1], ${$_}[0])}
decode_mimewords(
$ent->head->get('Subject',0)
)
);
$date = $ent->head->get('Date',0);
}
Собственно, все. Переменная $subj содержит тему письма, $body – тело письма, а $date – дату. Остальные параметры письма вы сможете легко получить, используя уже подключенные в программе модули.
Теперь вы смело можете сохранить в базе данных полученные результаты. Я, например, сохраняю их таким образом:
use collector;
($r) = Add2Revorum ( \$subj, \$body, \$date );
где модуль collector.pm – часть моего движка сайта, которая создает необходимую структуру и, используя ядро ret WebOS и модуль Storable, пишет её базу (обычные плоские файлы).
О проблеме альтернативной СУБД я напишу в другой статье. Те, кого это заинтересовало, могут обратиться за подробностями по интернет-адресу: http://jkeks.far.ru/ret.
Подпрограммы или процедуры, ответственные за перекодировку:
sub xcode {
# определяем кодировку и вызываем перекодировщик, если нужно
my ($charset, $src) = @_;
my %charsets = (
'windows-1251' =>'win',
'iso8859-5' =>'iso',
'koi8-r' =>'koi',
'koi8r' =>'koi',
'koi8-u' =>'koi',
);
return $src unless ($charsets{lc($charset)});
return encoder($src, $charsets{lc($charset)}, 'win');
}
Огромная благодарность российскому разработчику библиотек pvd – Денису Познякову (Denis Poznyakov, pvdenis@usa.net) – за минимальный код перекодировки русских символов. Перекодировка основывается на данных полей письма, тут нет попытки создания какого-либо анализатора.
sub encoder {
my $enstring = shift; my $cfrom = shift; my $cto = shift;
my %codefunk = (
win => "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF",
koi => "\xE1\xE2\xF7\xE7\xE4\xE5\xF6\xFA\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF2\xF3\xF4\xF5\xE6\xE8\xE3\xFE\xFB\xFD\xFF\xF9\xF8\xFC\xE0\xF1\xC1\xC2\xD7\xC7\xC4\xC5\xD6\xDA\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD2\xD3\xD4\xD5\xC6\xC8\xC3\xDE\xDB\xDD\xDF\xD9\xD8\xDC\xC0\xD1",
iso => "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF",
dos => "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF",
koi_lc => "tr/\xB3\xE0-\xFF/\xA3\xC0-\xDF/",
koi_uc =>"tr/\xA3\xC0-\xDF/\xB3\xE0-\xFF/",
win_lc => "tr/\xA8\xC0-\xDF/\xB8\xE0-\xFF/",
win_uc =>"tr/\xB8\xE0-\xFF/\xA8\xC0-\xDF/",
alt_lc => "tr/\xF0\x80-\x9F/\xF1\xA0-\xAF\xE0-\xEF/",
alt_uc => alt_lc => "tr/\xF1\xA0-\xAF\xE0-\xEF/\xF0\x80-\x9F/",
iso_lc => "tr/\xA1\xB0-\xCF/\xF1\xD0-\xEF/",
iso_uc => "tr/\xF1\xD0-\xEF/\xA1\xB0-\xCF/",
dos_lc => "tr/\x80-\x9F/\xA0-\xAF\xE0-\xEF/",
dos_uc => "tr/\xA0-\xAF\xE0-\xEF/\x80-\x9F/",
mac_lc => "tr/\xDD\x80-\xDF/\xDE\xE0-\xFE\xDF/",
mac_uc => mac_lc => "tr/\xDE\xE0-\xFE\xDF/\xDD\x80-\xDF/"
);
if (!$enstring or !$cfrom or !$cto) {return 0}
else {
if ($cfrom ne "" and $cto ne "lc" and $cto ne "uc") {
$_ = $enstring;$cfrom = $codefunk{$cfrom};$cto = $codefunk{$cto};
eval "tr/$cfrom/$cto/"; return $_;
}
elsif (($cfrom ne "") and ($cto eq "lc" or $cto eq "uc")) {
$_ = $enstring; $cfrom = $codefunk{"$cfrom\_$cto"};
eval $cfrom; return $_;
}
}
return $enstring;
}
Если вы не поняли, как это работает, поясню. Данный пример скачивает с почтового ящика все письма, обрабатывает их и передает системе управления базами данных либо куда-то еще. Этот код является основой описываемой технологии. Как я и обещал, соблюдена полная кросс-платформенность между Windows и UNUX-системами. Все используемые в скрипте библиотеки легко доступны.
От редактора: в процессе редактирования статьи было замечено, что тот же самый Outlook Express нечасто кодирует русские буквы в заголовках сообщений. Все это, конечно, зависит от настроек, но мало кто из пользователей Outlook занимается дотошной настройкой своего почтового клиента.
Поэтому процедура xcode при работе с такими заголовками не диагностировала необходимость перекодирования. Немного подумав, была придумана небольшая модификация. Это не панацея, а скромная попытка перекрыть некоторые проблемы – угадать кодировку KOI8-R в заголовке. Для этого внесем только одну коррективу в процедуру xcode – чуть усилим проверку:
# закомментируем строку, возвращающую неизмененный
# заголовок и продолжим проверку:
# return $src unless ($charsets{lc($charset)});
unless ($charsets{lc($charset)})
{
# если кодировка неопределенна, считаем вхождение
# больших и маленьких русских букв
$upper = "ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ";
$lower = "ёйцукенгшщзхъфывапролджэячсмитьбю";
$ucount = eval("\$src =~ tr/$upper/$upper/;");
$lcount = eval("\$src =~ tr/$lower/$lower/;");
# если больших букв больше - скорее всего это KOI8
# и мы перекодируем это в Windows
return encoder($src, 'koi', 'win') if ($ucount > $lcount);
# иначе, как и ранее, возвращаем неизмененный заголовок
return $src;
}
Собственно, идея основана на том факте, что слово «Новость» в кодировке KOI будет восприниматься как «оПЧПУФШ» в кодировке Windows. Прием довольно спорный, но имеет право на существование. Абсолютно не применим на больших объемах текста – поверьте на слово.
Тема безопасности, аутентификация, вклинивания
Любое письмо, попавшее на ящик, может стать атакой или просто спамом. Как с этим бороться?
Прежде всего вы можете организовать интернет-рассылку, где сможете управлять регистрацией нужных вам подписчиков. Обычно в системах рассылки уже встроены свои собственные механизмы борьбы со спамом.
Описанный выше сценарий также подходит к делу принципиально. Принимаются только те сообщения, которые пришли в виде текста. Кроме того, вырезаются баннеры, расположенные обычно после символов «\n— \n» (это – стандарт TheBat). Также скрипт соблюдает логику: «можно только то, что разрешено», а не «можно то, что не запрещено».
Если проблемно организовать рассылку, скорее всего вам необходимо будет добавить проверку ключа (например, в теме письма должен быть соответствующий идентификатор). Идентификатор отбрасывает почти все проблемы с безопасностью, однако это не накладывает ограничения на входящий трафик.
Но самый безопасный вариант – принимать только текст, зашифрованный или подписанный при помощи программ семейства PGP. Это также исключит возможность прочитывания писем при случайном доступе к ящику.
Немного рекламы
Ранее мы коснулись темы хранения данных. Важно понимать, что кроме простых функций вроде публикации рассылки, со временем функциональность может обрасти связями с различными разделами, а приходящие данные смогут иметь сложную структуру. Плюс ко всему – не каждый может себе позволить профессиональный хостинг.
Ради этого всего появилось на свет ядро ret WebOS, которое (как одно из направлений) предлагает альтернативу серверу SQL. Код системы минималистичен и для своей работы требует лишь наличия модуля Storable (который входит в стандартную поставку с Perl 5.8).
В свою очередь модуль blogs.pm предоставляет возможность хранить структуры данных любой сложности и осуществлять к ним доступ как к разделам сайта. Данные хранятся в плоских файлах. Интернет-адрес проекта: http://jkeks.far.ru/ret. Добро пожаловать!
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|