Java Apache Mail Enterprise Server(通常称为James )是由Apache组构建的便携式,安全且100%纯Java企业邮件服务器。 但是,由于其可插拔的协议体系结构和可用于电子邮件的servlet基础结构,它的潜力远不止于此 ,servlet可以为Web服务器提供服务。 自DARPA投入资金以来,电子邮件服务器一直存在,直到最终成为Internet,但James为通常被称为Internet的第一个杀手级应用程序提供了新的可能性。
这是探索James的两篇文章的第一篇。 它提供了James的概述和高层次的介绍,以让我们探索其可能性。 在第二篇文章中,我们将实现一个管理不可用消息的mailet应用程序。 如您所见,为James编写应用程序非常容易。 电子邮件每天在全球范围内被数百万人使用,因此,除了本系列提供的介绍性处理方法之外,其他可能性也很大。 但是,我希望这个基础能很好地为您服务,并帮助您开始想象各种可能性。
电子邮件如何运作
电子邮件原则上很简单。 您使用邮件用户代理 (MUA)构造具有一个或多个收件人地址的邮件 。 MUA有多种形式,包括基于文本,基于Web和GUI的应用程序。 Microsoft Outlook和Netscape Messenger属于最后一类。 每个电子邮件客户端都配置为将邮件发送到邮件传输代理 (MTA),并可用于轮询MTA来获取发送到用户地址的电子邮件。 为此,您需要在邮件服务器(技术上是MTA)上的电子邮件帐户,并且可以使用标准Internet协议,脱机处理电子邮件(使用POP3)或将电子邮件保留在服务器上(使用IMAP)。 用于将邮件从客户端发送到MTA以及在MTA之间发送邮件的协议是SMTP(简单邮件传输协议)。
MTA之间真正发生的事情只是稍微有趣一点。 电子邮件服务器严重依赖DNS和特定于电子邮件的记录,称为邮件传输 (或MX )记录。 MX记录与用于解析URL的DNS记录略有不同,其中包含一些用于更有效地路由邮件的其他优先级信息。 我不会在这里详细介绍这些细节,但重要的是要了解DNS是成功且高效地路由电子邮件的关键。 James是MTA,而JavaMail API提供了MUA的框架。 在本文中,我们将使用JavaMail为James安装设置一个测试。 在本系列的第二篇文章中,我们将使用James Mailet API展示如何开发自己的James应用程序。
詹姆斯设计目标
詹姆斯的设计目的是为了适应某些目标。 例如,它完全用Java语言编写,以最大程度地提高可移植性。 它被编写为安全的,并提供了许多功能,既可以保护服务器环境本身,又可以提供安全的服务。 James充当多线程应用程序,它利用了Avalon框架中提供的许多好处。 (Avalon的是一个Apache Jakarta项目,具有凤凰高性能服务器基础设施。了解Avalon的是有用的,但没有必要开发应用詹姆斯。请参阅相关主题以获得更多关于Avalon的部分。)
James提供了一套全面的服务,其中包括许多通常仅在高端或完善的电子邮件服务器中可用的服务。 这些服务主要是通过Matcher和Mailet API来实现的,它们可以协同工作以提供电子邮件检测和处理功能。 James使用松散耦合的插件设计来支持消息收发框架从协议中抽象出来,James支持其他一些标准的电子邮件协议(SMTP,POP3,IMAP)。 这是一个强大的想法,可以使James在将来充当更多的通用消息服务器,或支持替代消息协议,例如即时消息。
James设计小组提供的最后一个也是最有趣的目标是邮件的概念,它为开发电子邮件应用程序提供了组件生命周期和容器解决方案。 可以肯定的是,由于可以调用任何程序并且可以通过可执行文件将数据通过管道执行操作,因此始终可以使用其他MTA(例如Sendmail)来执行此操作,但是James提供了一个通用的简单API来实现这些目标由于对象易于操作,使工作变得轻松。 我们将在本文中仔细研究Matcher API和Mailet API。
安装和配置James
可以在Apache基础网站的主页上找到James(请参阅参考资料中的链接)。 您应该下载最新的生产版本以使用; 在撰写本文时,该版本为2.1.2。 您可以通过在James主页的左侧导航区域中选择“ 下载”>“二进制文件”来找到下载区域。 从那里,向下滚动到Release Builds部分,然后选择James 2.1链接之一。 根据您的喜好选择james-2.1.2.tar.gz或james-2.1.2.zip。
我们还将使用JavaMail API来测试我们的应用程序,因此您也需要下载它(请参阅参考资料 )。 当前可用的版本为1.3,文件名为javamail-1_3.zip。 在JavaMail主页上,您会注意到JavaMail API所需的指向JavaBeans激活框架(JAF)的链接。 (请参阅相关信息的直接链接到JAF)目前JAF版本是1.0.2,文件被称为jaf-1_0_2.zip。 一旦拥有所有这些文件,就可以设置系统与James一起使用。
我们将建立一个目录结构,其中包含开发所需的所有元素。 在生产中,设置必须完全不同,要根据安全性和功能性进行安排。 例如,出于我们的目的,我们可以在localhost上工作,但这在实际的电子邮件服务器部署中不是可行的选择。 关于将James配置为主要MTA或与Sendmail一起,有大量文档,如果您需要将服务器商业部署,则在邮件列表中有大量帮助。
将所有内容解压缩到James目录中后,我们的层次结构将类似于清单1。我删除了javadoc,src和JavaMail webapp演示下的一些子目录,以使事情更紧凑,更容易设想。
清单1. James,JavaMail和JAF目录
James
+---jaf-1.0.2
| +---demo
| \---docs
| \---javadocs
+---james-2.1.2
| +---apps
| +---bin
| | \---lib
| +---conf
| +---docs
| | +---images
| | \---stylesheets
| +---ext
| +---lib
| +---logs
\---javamail-1.3
+---demo
| +---client
| +---servlet
| \---webapp
+---docs
| \---javadocs
\---lib
我假设您已经设置了独立于James文件的Java平台1.4版。 James的配置说明表明,使用Java 1.3.0已观察到一些问题,因此您应使用1.3.1或更高版本。 原则上,James应该在支持合适的Java 1.4 VM的任何平台上都能很好地工作。
我们的第一步是启动James,因为配置文件只有在服务器运行一次后才会解压缩。 您可以在james-2.1.2 / bin目录中找到运行脚本(使用run.bat或run.sh,具体取决于您的操作系统)。 运行该脚本时,输出应类似于清单2(此示例输出来自Windows系统):
清单2.运行James的控制台输出
Using PHOENIX_HOME: D:\James\james-2.1.2
Using PHOENIX_TMPDIR: D:\James\james-2.1.2\temp
Using JAVA_HOME: c:\programming\java14
Phoenix 4.0.1
James 2.1.2
Remote Manager Service started plain:4555
POP3 Service started plain:110
SMTP Service started plain:25
NNTP Service started plain:119
Fetch POP Disabled
您可以使用Ctrl + C退出程序,然后从Phoenix容器中收到一条消息,提示您退出程序。 严格来说,让James退出的正确方法是使用远程管理界面。 我在自己的开发中使用了Ctrl + C,没有负面影响。 但是,在部署环境中,应始终使用shutdown命令。
首次关闭James之后,您将在james-2.1-2 / apps / james / SAR-INF文件夹中找到一个名为config.xml的文件,您应该查看一下。 通常您要更改的第一件事是管理员帐户,默认情况下将其设置为root,密码为root。 我们将其留给开发,但出于明显的原因,将其以这种方式配置在生产系统中是不明智的。 接下来要更改的通常是DNS服务器地址,如果James要用作完整的电子邮件服务器,则这是必需的。 鉴于我们所有的测试都将从本地主机运行,因此我们也将不再赘述,但这也是您应该注意的重要设置。 考虑到我们的开发目标,其余的默认设置都可以,但是了解配置文件很重要。 您可以在james-2.1.2 / docs目录中提供的文档中找到更多信息。
在继续之前,让我们添加一些用户。 为此,请使用命令telnet localhost 4555
在端口4555上telnet到localhost。 您可以使用root用户名和密码登录。 登录后,我们将添加一些用户。 adduser
命令需要用户名和密码组合。 我们将为此项目添加名为红,绿和蓝的用户,每个用户的密码均与用户名相同。 (我确定您知道在创建实际用户时这是个坏主意,但可以轻松配置我们的测试用例。)添加用户后,您可以使用listusers
命令来验证条目,然后退出通过输入quit
远程管理。 整个会话应类似于清单3。我突出显示了您要输入的文本。
清单3.使用远程管理添加用户
JAMES Remote Administration Tool 2.1.2
Please enter your login and password
Login id:
root
Password:
root
Welcome root. HELP for a list of commands
adduser red red
User red added
adduser green green
User green added
adduser blue blue
User blue added
listusers
Existing accounts 3
user: blue
user: green
user: red
quit
Bye
现在,我们都准备与运行的James服务器一起使用。 如您所见,部署James并使用远程管理器进行设置相对简单。 显然,如果希望邮件服务器安全,则需要更改几个配置参数,但这不是一个非常复杂的过程。 使用邮件服务器的真正关键与在多用户和多服务器环境中正确设置DNS有关。 这超出了本文的范围,但也没有使过程复杂化。
使用JavaMail测试James
为确保我们的设置正常运行,我们将编写一对快速的类,它们将发送消息并列出收件箱内容,以模拟典型电子邮件客户端的基本功能。 我们将使用两个类,因为清单4中所示的MailClient
类可被重用于测试更复杂的行为,这将在本系列的第二篇文章中开发James应用程序时进行。
清单4. MailClient:模拟电子邮件客户端的基本功能
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
public class MailClient
extends Authenticator
{
public static final int SHOW_MESSAGES = 1;
public static final int CLEAR_MESSAGES = 2;
public static final int SHOW_AND_CLEAR =
SHOW_MESSAGES + CLEAR_MESSAGES;
protected String from;
protected Session session;
protected PasswordAuthentication authentication;
public MailClient(String user, String host)
{
this(user, host, false);
}
public MailClient(String user, String host, boolean debug)
{
from = user + '@' + host;
authentication = new PasswordAuthentication(user, user);
Properties props = new Properties();
props.put("mail.user", user);
props.put("mail.host", host);
props.put("mail.debug", debug ? "true" : "false");
props.put("mail.store.protocol", "pop3");
props.put("mail.transport.protocol", "smtp");
session = Session.getInstance(props, this);
}
public PasswordAuthentication getPasswordAuthentication()
{
return authentication;
}
public void sendMessage(
String to, String subject, String content)
throws MessagingException
{
System.out.println("SENDING message from " + from + " to " + to);
System.out.println();
MimeMessage msg = new MimeMessage(session);
msg.addRecipients(Message.RecipientType.TO, to);
msg.setSubject(subject);
msg.setText(content);
Transport.send(msg);
}
public void checkInbox(int mode)
throws MessagingException, IOException
{
if (mode == 0) return;
boolean show = (mode & SHOW_MESSAGES) > 0;
boolean clear = (mode & CLEAR_MESSAGES) > 0;
String action =
(show ? "Show" : "") +
(show && clear ? " and " : "") +
(clear ? "Clear" : "");
System.out.println(action + " INBOX for " + from);
Store store = session.getStore();
store.connect();
Folder root = store.getDefaultFolder();
Folder inbox = root.getFolder("inbox");
inbox.open(Folder.READ_WRITE);
Message[] msgs = inbox.getMessages();
if (msgs.length == 0 && show)
{
System.out.println("No messages in inbox");
}
for (int i = 0; i < msgs.length; i++)
{
MimeMessage msg = (MimeMessage)msgs[i];
if (show)
{
System.out.println(" From: " + msg.getFrom()[0]);
System.out.println(" Subject: " + msg.getSubject());
System.out.println(" Content: " + msg.getContent());
}
if (clear)
{
msg.setFlag(Flags.Flag.DELETED, true);
}
}
inbox.close(true);
store.close();
System.out.println();
}
}
MailClient
类的主要目的是让我们发送消息并显示或删除服务器上给定用户可用的消息列表。 我已经声明了一些有用的常量,这些常量可以让我们SHOW_MESSAGES
, CLEAR_MESSAGES
或两者。 MailClient
类还实现了Authenticator
接口,以便在检索电子邮件时轻松管理登录过程。
我创建了两个构造函数,其中一个显式设置JavaMail调试标志。 这会将客户端/服务器协议交互输出到控制台,以便您查看正在发生的情况。 较短的构造函数将保留此标志。 其他两个参数是用户名和主机。 暗示地,电子邮件地址可以从用户和主机派生。 我们创建一个PasswordAuthentication
对象,该对象可以由Authenticator
接口中指定的getPasswordAuthentication()
方法返回。
其余的构造函数代码将设置JavaMail属性以反映指定的用户和主机,并显式指定我们打算使用的协议。 一旦填充了Properties
对象,就可以调用静态Session
方法getInstance()
来获取有效的Session
引用,并将其存储在本地变量中。 一旦将构造函数与指定的用户一起使用,我们就可以在指定的电子邮件主机上发送和检索该用户的电子邮件。
sendMessage()
方法也很简单。 它使用指定的收件人,主题和文本内容构建MimeMessage
,然后使用Transport
类中的JavaMail静态send()
方法将其send()
。 为了易于查看发生了什么,我们还向控制台打印了一条消息。
checkInbox()
方法可以完成更多工作,因为它需要列出消息并有选择地擦除它们。 根据您用于mode参数的标志,也可以只删除消息而不查看它们。 要实际获取消息,我们需要通过我们的会话对象获得对Store
的引用,连接到服务器,然后打开收件箱文件夹。 引用文件夹后,我们可以遍历消息并显示或删除它们。
现在我们有了可重用的MailClient
代码,我们准备为本地主机上的James服务器编写一个快速测试。 清单5中的JamesConfigTest
类通过创建三个MailClient
实例来做到这一点,每个实例都与我们的一个用户(红色,绿色和蓝色)相关联。 在运行此代码之前,请确保这些用户在电子邮件服务器上有效。
清单5. JamesConfigTest:James服务器的快速测试
public class JamesConfigTest
{
public static void main(String[] args)
throws Exception
{
// CREATE CLIENT INSTANCES
MailClient redClient = new MailClient("red", "localhost");
MailClient greenClient = new MailClient("green", "localhost");
MailClient blueClient = new MailClient("blue", "localhost");
// CLEAR EVERYBODY'S INBOX
redClient.checkInbox(MailClient.CLEAR_MESSAGES);
greenClient.checkInbox(MailClient.CLEAR_MESSAGES);
blueClient.checkInbox(MailClient.CLEAR_MESSAGES);
Thread.sleep(500); // Let the server catch up
// SEND A COUPLE OF MESSAGES TO BLUE (FROM RED AND GREEN)
redClient.sendMessage(
"blue@localhost",
"Testing blue from red",
"This is a test message");
greenClient.sendMessage(
"blue@localhost",
"Testing blue from green",
"This is a test message");
Thread.sleep(500); // Let the server catch up
// LIST MESSAGES FOR BLUE (EXPECT MESSAGES FROM RED AND GREEN)
blueClient.checkInbox(MailClient.SHOW_AND_CLEAR);
}
}
创建了三个MailClient
实例之后, JamesConfigTest
代码只需使用带有CLEAR_MESSAGES
模式的checkInbox()
方法清除每个邮箱,然后等待半秒钟以确保服务器已处理了删除操作。 接下来,我们将红色和绿色的邮件发送到蓝色,然后检查是否有邮件发送到蓝色帐户。 运行JamesConfigTest
,应该看到类似于清单6的输出:
清单6.运行JamesConfigTest的输出
Clear INBOX for red@localhost
Clear INBOX for green@localhost
Clear INBOX for blue@localhost
SENDING message from red@localhost to blue@localhost
SENDING message from green@localhost to blue@localhost
Show and Clear INBOX for blue@localhost
From: green@localhost
Subject: Testing blue from green
Content: This is a test message
From: red@localhost
Subject: Testing blue from red
Content: This is a test message
这证明我们的James装置可以正常工作。 在进行开发之前,您需要以这种方式设置系统。 但是,我们将推迟到本系列的第二部分。 在本文的其余部分,我们将研究Matcher和Mailet API,以及作为James发行的一部分提供的预先编写的Matcher和Mailet。 我们还将快速查看James支持的一些其他功能。
匹配器
James附带了许多标准的匹配器。 这些工具中的每一个都实现了清单7所示的Matcher API,并提供了现有MTA共有的功能以及其他有用的扩展。 界面非常简单。 它包括几个生命周期方法init()
和destroy()
,以及一对簿记方法getMatcherInfo()
和getMatcherConfig()
,以及主要方法match()
,该方法在Mail
对象上运行。 Mail
引用提供对容器状态,邮件消息和元数据进行处理的访问。
清单7. Matcher接口
public interface Matcher
{
void init(MatcherConfig config);
void destroy();
String getMatcherInfo();
MatcherConfig getMatcherConfig();
Collection match(Mail mail);
}
匹配器负责识别一组收件人,并返回一个String
对象的集合,这些对象表示要由邮件处理的收件人。 通过将匹配器识别和邮件处理相结合,可以开发可处理电子邮件的复杂应用程序。
作为James发行版的一部分提供的匹配器,使您无需开发自己的匹配器实现就可以做几件事。 在决定开发自己的匹配器之前,了解这些是很重要的; 在许多情况下,您正在考虑的工作可能已经为您完成。 您可以在表1中看到这些预写匹配器的列表:
表1.詹姆斯预写的匹配器
匹配器 | 描述 |
---|---|
All | 匹配所有正在处理的电子邮件并返回所有收件人 |
HasHeader | 匹配指定的标题(如果存在) |
HasAttachment | 如果消息是多部分消息,则匹配 |
SubjectStartsWith | 匹配主题以指定主题文本开头的邮件 |
SubjectIs | 匹配具有特定主题的邮件 |
HostIs | 将邮件与指定主机匹配 |
HostIsLocal | 匹配来自本地主机的消息 |
UserIs | 匹配指定的用户 |
SenderIs | 匹配指定的发件人 |
SenderInFakeDomain | 匹配无法解析主机地址的发件人 |
SizeGreaterThan | 匹配大小大于指定限制的邮件 |
Recipients | 将邮件与指定列表中的收件人匹配 |
RecipientsLocal | 将邮件与本地收件人匹配 |
IsSingleRecipient | 只与一位收件人匹配邮件 |
RemoteAddrInNetwork | 匹配来自IP地址,域等指定列表的消息 |
RemoteAddrNotInNetwork | 匹配不是来自IP地址,域等指定列表的消息 |
RelayLimit | 匹配通过多个指定服务器转发的邮件 |
InSpammerBlackList | 将地址与mail-abuse.org提供的列表匹配 |
NESSpamCheck | 使用从Netscape Mail Server派生的方法来匹配垃圾邮件 |
HasHabeasWarrantMark | 将邮件与Habeas认股权证相匹配 |
FetchedFrom | 匹配FetchPOP使用的X-fetched-from 标头 |
CommandForListserv | 匹配列表服务器的命令 |
从该列表中可以看到,无需编写任何新代码就可以完成许多任务,包括精细的操作(如匹配标头,主题或收件人)以及高级操作(如检测垃圾邮件和处理列表服务器命令)。
信箱
James的许多功能都是通过清单8中所示的Mailet API实现的,对于习惯于使用Servlet API的开发人员来说,这似乎很奇怪。 与Matcher API一样,Mailet接口支持两种生命周期方法来提供初始化( init()
方法)和关闭( destroy()
方法)。 另外两个方法返回信息。 第一个是getMailetInfo()
,它返回一个String
对象,该对象应包含与该Mailet相关的信息,例如作者,版本和版权。 第二个getMailetConfig()
,对于返回当前的mailet配置信息很有用。 init()
方法将MailetConfig
对象作为参数,因此,尽管它可能已被修改,但这通常是getMailetConfig()
方法返回的对象。
清单8. Mailet接口
public interface Mailet
{
void init(MailetConfig config);
void destroy();
String getMailetInfo();
MailetConfig getMailetConfig();
void service(Mail mail);
}
主要处理在services()
方法中完成,该方法带有一个Mail
对象参数。 该对象提供对容器状态,邮件消息和元数据的其他访问,以进行处理。
为了让您了解James支持的功能以及已经存在的mailet应用程序的种类,表2开箱即用地提供了James中可用的mailet实现的列表:
表2.预写的James信箱
Mailet | 描述 |
---|---|
Null | 结束对电子邮件的处理 |
AddHeader | 在邮件内容中添加文本标题 |
AddFooter | 在消息内容中添加文本页脚 |
Forward | 将邮件转发到收件人列表 |
Redirect | 提供可配置的重定向服务 |
ToProcessor | 将电子邮件处理重定向到指定的处理器 |
ToRepository | 将邮件副本放入指定目录 |
NotifySender | 将邮件作为附件转发到原始发件人 |
NotifyPostmaster | 将邮件作为附件转发到邮件管理员 |
RemoteDelivery | 管理SMTP主机传递 |
LocalDelivery | 将邮件传递到本地邮箱 |
JDBCAlias | 是否使用JDBC数据源进行别名转换 |
JDBCVirtualUserTable | 使用JDBC数据源进行更复杂的别名转换 |
UseHeaderRecipient | 从邮件头重新生成邮件收件人 |
ServerTime | 发送服务器时间戳消息 |
PostmasterAlias | 将postmaster@<domain> 邮件重定向到个人的地址 |
AddHabeasWarrantMark | 在消息中添加Habeas保修标记 |
AvalonListserv | 提供基本的列表服务器功能 |
AvalonListservManager | 进程列表服务器管理命令 |
从该列表中可以看到,借助Mailet API,James中已经提供了一些功能,其中包括复杂的列表服务器支持,别名以及存储和路由功能。
附加功能
James支持的其他功能超出了本系列的范围。 但是,在这里我们将简要提及它们,以便您可以更好地了解James的实际能力。 首先是NNTP支持,这使James可以充当Usenet服务器。 James还实现了FetchPOP协议以支持基于邮件的远程管理功能。 RemoteManager和SpoolManager提供的抽象允许存在多种类型的存储和管理支持。 出于开发目的,仅依赖基于文件系统的SpoolManager就足够了,尽管同时提供了部分和完全以数据库为中心的解决方案。
James提供了界面和服务,可以有效地管理用户,并且开箱即用地提供了邮件列表支持。 实际上,邮件列表功能是James提供的最常用的服务之一,并且通常是管理员选择James作为电子邮件解决方案的主要原因之一。
下一步是什么?
James基础结构旨在提供灵活性和便捷的应用程序开发。 电子邮件应用的可能性仅受您的想象力限制。 在本系列的后续部分中,我们将开发一个简单的应用程序,该应用程序允许用户将电子邮件发送到特定的James地址,以启用休假消息类型功能。 用户可以草拟一封将发送给所有传入发件人的电子邮件,直到用户将取消消息发送到另一个指定的James地址以将其关闭为止。 此解决方案模仿了通常由电子邮件客户端软件实现的机制,但是由于这种机制,它通常仅限于单个地理位置:如果关闭了邮件客户端,则该功能将无法使用。 通过在电子邮件服务器上实现此功能,我们仍然可以不受限制地从任何位置检查邮件,并且如果我们的计划发生变化,我们可以轻松地将“离开”消息更改为更适当的响应。
翻译自: https://www.ibm.com/developerworks/java/library/j-james1/index.html