每天我都要花费大量的时间在电子邮件相关的工作上,或者通过邮件来和其他工作伙伴联系,或者饶有兴致地分析,
索引,重新组织以及挖掘邮件内容。很自然的,Perl 协助我做这些事情。
在 CPAN 上有很多现成的模块可以用来处理电子邮件,我们将介绍其中几个主要的。同时我们也将关注由我和
Richard Clamp,Simon Wistow 以及其他伙伴所致力的 Perl 电子邮件项目(Perl Email Project),
该项目的目标是提供一系列简单的,有效的,精准的邮件处理模块。
邮件消息的处理
我们从一些比较简单的,用来描绘一封单独邮件,提供对邮件头和邮件体的访问,甚至修改它们的信息的那些模块
开始介绍。
所有的这些模块的曾祖父都是 Mail::Internet ,由 Graham Barr 创建,目前 Mark Overmeer 在维护。
该模块提供了通过数组(元素为字符串行)或者文件句柄来读取信件内容的构造器,并通过它返回一个描述该信件的
Mail::Internet 对象。在下面的例子中,我们使用变量 $rfc2822 来表示字符串形式的邮件信息内容。
my $obj = Mail::Internet->new( [ split /\n/, $rfc2822 ] );
Mail::Internet 从信件中提取构造出一个邮件头对象,并连带邮件体信息。邮件头对象的类为 Mail::Header 。
你可以通过该对象获取或者设置邮件头的信息:
my $subject = $obj->head->get("Subject");
$obj->head->replace("Subject", "New subject");而读取或者编辑邮件体内容的操作,则可以使用 body 方法:
my $old_body = $obj->body;
$obj->body("Wasn't worth reading anyway.");
到现在为止我还没有提到过任何关于 MIME 的东西。对于简单的任务来说,Mail::Internet 确实非常方便,
不过它并不完全支持对 MIME 的处理。谢天谢地,MIME::Entity 作为一个为 MIME 而考虑设计的
Mail::Internet 子类,允许你读取 MIME 消息的每一个独立的部分(part):
my $num_parts = $obj->parts;
for (0..$num_parts) {
my $part = $obj->parts($_);
...
}
如果 Mail::Internet 和 MIME::Entity 都不适合你,你可以试试 Mark Overmeer 自己的
Mail::Message 模块,该模块是令人印象深刻的 Mail::Box 模块中的一部分。Mail::Message
是个极富特色的、功能全面的模块,但这些优点并不总意味着褒扬。
Mail::Message 对象通常都是在 Mail::Box 读取一个电子邮件文件夹的时候,在内部构建的。
当然它也可以通过 read 方法来读取一封信件:
$obj = Mail::Message->read($rfc2822);
就像 Mail::Internet 一样,邮件消息被分割为邮件头和邮件体,而与 Mail::Internet 不同的是,
邮件体也是一个对象。我们如此读取邮件头:
$obj->head->get("Subject");或者,如果是 Subject 头信息以及其他常见的邮件头信息,可以如此读取:
$obj->subject;
我找不到直接设置头信息的方法,所以最终可能需要这样做:
$obj->head->delete($header);
$obj->head->add($header, $_) for @data;
读取邮件体内容作为字符串形式表达也仅有一点麻烦:
$obj->decoded->string
而设置邮件体内容的操作则绝对是恶梦 -- 我们不得不构建一个 Mail::Message::Body 对象来覆盖现有的。
$obj->body(Mail::Message::Body->new(data => [split /\n/, $body]));
Mail::Message 处理邮件的时候可能有点慢,也着实难用。它的体系也非常复杂,上面我们所看到的这些操作就
已经用到了 16 种类 (Mail::Address, Mail::Box::Parser, Mail::Box::Parser::Perl, Mail::Message,
Mail::Message::Body, Mail::Message::Body::File, Mail::Message::Body::Lines,
Mail::Message::Body::Multipart, Mail::Message::Body::Nested, Mail::Message::Construct,
Mail::Message::Field, Mail::Message::Field::Fast, Mail::Message::Head,
Mail::Message::Head::Complete, Mail::Message::Part, 以及 Mail::Reporter)和 4400 多行的代码。
尽管它确实拥有很多功能,我还是傻傻的觉得邮件的分析处理应该更为简洁。所以我坐下来决定自己着手编写尽
可能简洁的邮件处理函数库,结果就有了 Email::Simple 模块,它的交互界面如下所示:
my $obj = Email::Simple->new($rfc2822);
my $subject = $obj->header("Subject");
$obj->header_set("Subject", "A new subject");
my $old_body = $obj->body;
$obj->body_set("A new body\n");
print $obj->as_string;
它做的事情并不多,但却非常简单和高效。如果你需要 MIME 处理,可以使用它的子类 Email::MIME,
该类增加了 parts 方法。
实际上,选择哪一种邮件处理函数库完全取决于你,最终用户,不过并不总是这样的。有许多辅助性的模块,
帮助你在更高的应用层上处理邮件信息的,可能要求你提供特定的邮件表达对象。比如最近的 Mail::ListDetector
模块(稍后我们将解析),需要传给它的邮件为 Mail::Internet 对象,因为该对象的操作界面(API)是已知的。
而我不想用 Mail::Internet 对象,但我又需要 Mail::ListDetector 的一些功能,那我可以做些什么呢?
为了让用户也能够有这样的选择,我写了一个用于表达上面各个模块操作界面的抽象层,叫做 Email::Abstract 。
给出上面任何一种类型的对象,我们都可以说:
my $subject = Email::Abstract->get_header($obj, "Subject");
Email::Abstract->set_header($obj, "Subject", "My new subject");
my $body = Email::Abstract->get_body($obj);
Email::Abstract->set_body($message, "Hello\nTest message\n");
$rfc2822 = Email::Abstract->as_string($obj);
Email::Abstract 知道如何在这些主要的邮件表达对象上作相应的操作。它也抽象了构造邮件消息的过程,
并允许你通过类方法 cast 来改变邮件消息对象的操作界面:
my $obj = Email::Abstract->cast($rfc2822, "Mail::Internet");
my $mm = Email::Abstract->cast($obj, "Mail::Message"); 这样使得模块的作者得以使用“接口预先未知
(interface-agnostic)”的方式来撰写邮件处理函数库。我很感谢 Michael Stevens 立即在
Mail::ListDetector 中使用了 Email::Abstract 。现在我可以将 Email::Simple 对象传递给
Mail::ListDetector 了,而且它工作的非常好。
Email::Abstract 也给了我们对上面所有这些模块作基准测试(benchmarks)的机会。这里是我使用的测试代码:
use Email::Abstract;