Цель статьи – показать возможность построения программ автоматического разбора приходящей почты для случая Baikonur SuperServer.
Если у вас уже был опыт работы с Baikonur Enterprise Web App Server (BEWAS), то вы уже знаете возможности сервера приложений и то, что основной функцией сервера является обслуживание запросов удаленных или локальносетевых пользователей, запуск задач (.exe-модулей) по запросам, приходящим по различным протоколам, и коммутация потоков информации между пользователями и запущенными приложениями.
Baikonur SuperServer (BSS) в этом смысле не слишком отличается от BEWAS – он работает точно так же, просто в комплект поставки добавлены некоторые дополнительные возможности.
На рисунке
слева показаны основные отличия Baikonur SuperServer от
Baikonur Enterprise Web App Server. Из рисунка видно, что в
дополнение к уже существующим протоколам (http, ftp,
finger, gopher) BSS поддерживает еще и почтовые протоколы
SMTP, POP3, IMAP4.
Работа с этими протоколами аналогична работе с
протоколом HTTP. BSS можно использовать как штатный
почтовый сервер, но самое полезное его качество
– это возможность использования его как
почтовый сервер приложений.
Кроме стандартной обработки почтовых сообщений
и доступа пользователей к своим почтовым ящикам
для BSS можно конструировать специализированные
почтовые обработчики (роботы), которые позволяют
автоматически обрабатывать почтовые сообщения,
выделять присоединенную информацию (attachements),
проводить транзакции с серверами БД и
генерировать рассылку почтовых сообщений по
списку почтовых адресов.
Давайте создадим простейшее приложение
серверного слоя, которое, кроме стандартных
функций SMTP сервера, позволяет удаленному
пользователю запросить и получить содержимое
произвольного файла с сервера по почте.
Сценарий работы в нашем случае будет следующий:
Чтобы проверить, как это будет работать наш пример, сделаем все необходимые настройки:
А теперь обсудим особенности программирования.
Откроем новый проект в Delphi 3. Если у Вас
проинсталлирован package для работы с Baikonur Mail Server, то
на странице "Baikonur Mail" Вы увидите три
проинсталлированные компоненты TSmtpControl, Tpop3Control и
TForwarder.
В самом начале мы имеем пустую форму проекта.
Положим на форму компоненты TPanel, TGroupBox и TMemo на
TGroupBox.
Теперь добавим функциональность SMTP сервера. Для этого положим компонент TSmtpControl на форму.
Чтобы для каждого коннекта пользователя запускалась 1 копия программы, надо установить свойство ApplicationType компонента TSmtpControl равным atMultiuserRedirect.
Как только на форму был положен компонент
TSmtpControl, ваше приложение вместе с сервером Baikonur
стало обладать функциональностью SMTP
сервера.
Для решения нашей задачи (пересылка указанного в
письме файла по электронной почте) у компоненты
TSmtpControl нужно определить обработчики событий.
Определим обработчик
события OnWriteLog. Это событие отвечает за обработку
информационных и отладочных сообщений. Оно
происходит, когда появляется отладочная
информация и обычно используется для
формирования log-файла. Определим обработчик
события так, чтобы отладочные сообщения не
выводились.
procedure TForm1.SmtpControlWriteLog(Sender: TComponent;
Importance: TLogLevel; Msg: String);
begin
if Memo1<>NIL then
if Importance=llProcess then
begin
if Memo1.Lines.Count=0 then
Memo1.Lines.Add (Msg)
else
Memo1.Lines[Memo1.Lines.Count-1] :=
Memo1.Lines[Memo1.Lines.Count-1] + Msg;
end
else
Memo1.Lines.Add (Msg);
end;
Добавим модуль Dparsers в uses предложение Interface-ной
части формы.
Обработка приходящих писем производится при
помощи механизма фильтров. Для нашего случая нам
тоже потребуется создать такой фильтр.
Нажмите в Object Inspector кнопку "…" в свойстве
Filters для вызова его редактора. Перед вами
высветится диалог редактора фильтров. В нем
можно настроить порядок вызова фильтров, создать
новые, уничтожить старые или настроить условия
срабатывания фильтров.
Фильтры могут каскадироваться, поэтому почтовые
обработчики могут реализовывать довольно
сложную функциональность.
Создадим новый фильтр. Для настройки условий его срабатывания надо нажать кнопку Edit. В этом диалоге можно указать такие условия, как максимальный и минимальный размер файла, с какого поля заголовка письмо может начинаться или что должно содержать письмо в своем теле ( некоторая строка) для того, чтобы сработал обработчик.
Настроим фильтр, чтобы он срабатывал, если поле To начинается со строки send-mail.
Теперь вернемся к редактору фильтров. Выберем
только что созданный фильтр и страницу Events в Object
Inspector. Два раза щелкнем мышкой на обработчике
OnFilter.
В обработчике можно написать следующий текст:
procedure TForm1.SendViaMailFilter(Sender: TSmtpControl; Filter: TMailFilter;
Line, Command : String;
FPos : Integer;
FileName : String;
Receip : TStringList;
begin
// Выводим сообщение
SmtpControl.WriteLog (llMessage, 'GET filter started');
Msg := NIL;
try
Msg := TQueMessage.Create (NIL);
try
Msg.Top (MsgStream,1);
// чтение тела сообщения из потока. Сообщение
// может быть размером в несколько мегабайт,
// поэтому нецелесообразно читать его
// полностью в память
except
On E : Exception do
begin
SmtpControl.WriteLog (llError, 'In TOP : '+E.Message);
Exit;
end;
end;
if Msg.Body.Count>0 then
begin
Line := Msg.Body[0]+#13;
SmtpControl.WriteLog (llMessage, 'Message line is : '+Line);
// выделяем подстроку до символа ограничителя
FPos := 1;
Command := UpperCase (GetStringValueAt (Line, FPos, [' '],
''));
if Command='GET' then // запрос на файл
begin
SmtpControl.WriteLog (llMessage, 'Requesting file');
// создаем объект для хранения сообщения
OutMsg := TQueMessage.Create (NIL);
try
try
FileName := UpperCase (GetStringValueAt
(Line, FPos, [' ',#13], ''));
if FileName = '' then
raise Exception.Create ('Required
parameter (File name) for GET command is not defined' + CRLF +
'Use the following syntax : GET <file name>');
// В данном примере не
проверяется, где находится файл, так что
// сервер может прислать
любой файл, на доступ к которому у него
// будут права
if not FileExists(FileName) then
raise Exception.Create ('Requested
file ('+FileName+') not found');
// формируем заголовок письма
OutMsg.ContentType := 'text/plain';
OutMsg.Subject := 'Request processed';
OutMsg.Recipients.AddAddress
(Envelope.Sender,'');
OutMsg.Sender.Address :=
SendViaMail.HeaderData + Envelope.LocalHost;
// формируем тело письма
OutMsg.Body.Add (
'Dear friend!' + CRLF + CRLF +
'You have requested the following
file "' + FileName + '" from our server.' + CRLF +
'If you think that you got this
message by mistake, please sent us message to '+
'SUPPORT@' + Envelope.LocalHost
+ CRLF + // MUST EXIST!!!
'Thank you for using our service!' +
CRLF);
OutMsg.Attachments.AddFile (FileName); // ???
// формируем список
получателей письма. Посылаем ответ
// отправителю, а не
тому, кто указан в поле From
Receip := TStringList.Create;
Receip.Add (Envelope.Sender);
Try
// Посылка письма
SmtpControl.DirectSendMessage
(OutMsg,
Envelope.LocalHost,
SendViaMail.HeaderData + Envelope.LocalHost,
Receip);
finally
Receip.Free;
end;
// Этот блок создает
письмо, в случае неудачи при выполнении
// посылки письма (например
требуемый файл не найден)
// Его можно опустить
except
on E : Exception do
begin
SmtpControl.WriteLog (llError,
'Post failed!!!');
OutMsg.ContentType :=
'text/plain';
OutMsg.Subject := 'Request
processing failed';
OutMsg.Recipients.AddAddress
(Envelope.Sender,'');
OutMsg.Sender.Address :=
SendViaMail.HeaderData + Envelope.LocalHost;
OutMsg.Body.Add (
'Dear friend!' + CRLF +
CRLF +
'You have requested the
following file "' + FileName + '" from our server.' + CRLF +
'But server encounter the
following error :' + E.Message + CRLF +
'If you think that you
got this message by mistake, please sent us message to '+
'SUPPORT@' +
Envelope.LocalHost + CRLF + // MUST EXIST!!!
'Thank you for using our
service!' + CRLF);
OutMsg.Attachments.AddFile (FileName); // ???
Receip :=
TStringList.Create;
Receip.Add (Envelope.Sender);
try
SmtpControl.DirectSendMessage (OutMsg,
Envelope.LocalHost,
SendViaMail.HeaderData + Envelope.LocalHost,
Receip);
finally
Receip.Free;
end;
end; {On Exception}
end {try/except/end}
finally
OutMsg.Free; // Внимание!
Уничтожаем объект для хранения письма
end;
end; {if 'GET'}
end; {Msg.Body.Count>0}
finally
Msg.Free; // Внимание! Уничтожаем объект для
хранения письма
ContinueFiltering := False; // Запретить выполнять
другие фильтры над данным письмом
Action := faDelete; // Удалить письмо
end;
SmtpControl.WriteLog (llMessage, 'Filter is OK');
end;
Как всегда, при любом программировании, большая
часть кода ушла на обработку недопустимых или
ошибочных ситуаций.
Заметим, что мы абсолютно свободны в выборе того,
что должен делать обработчик, выполнять команду
посылки файла или запрашивать информацию из базы
данных.
Классическим случаем такого рода обработчиков
являются почтовые роботы типа ftpmail, которые
позволяют получать информацию с ftp-сервера через
почту.
Мы могли убедиться в том, что для реализации
подобного робота требуется сравнительно
несложное программирование.
Более того, требуются совсем небольшие усилия,
чтобы избавиться от назойливых мусорных
сообщений (спам) или для того, чтобы сделать
серьезную систему автоматического обмена
информацией, основанную на почтовых службах.