ApacheJames 介绍(转),apachejames介绍


这篇文章是介绍ApacheJames邮件企业服务器的第一部分,也是James的介绍知识。它介绍了使用James开发服务端邮件程序的基础知识。它概括的介绍了Apache组织设计James目标以及如何安装配置一个可以运行的工作环境。你还可以了解到ApacheJames都提供哪些功能。你还将看到James提供匹配器(matcher)和mailet实现的有关描述,以及它与创通邮件服务器的不同。
Java Apache邮件服务器一般是指Apache组织开发的James,它是一个轻便的、安全的100%纯Java实现的邮件服务器。不仅如此James还可以提供更多的功能,感谢James给我们提供了插件化协议架构和mailet底层构造,它可以让我们利用web服务器的servlets处理邮件。Email服务到处都是,开始是DARPA(国防部高级研究计划局)的一个计划并最终发展为Internet,但是James成为了打破这一传统规则的第一个应用。

这篇文章的第一部分首先向我们概要的介绍了James并给出了James开发的高层定位。在第二部分我们将实现一个mailet应用用于处理无效的消息。你会惊讶的发现用James完成这个应用非常简单。每天有成千上万的人在使用Email,所以首先让我们来看看它是怎么工作的。

Email是如何工作的
总的来说email是简单的,你可以使用MUA(邮件用户代理)向一个或多个地址发送消息。MUA有多种表现形式,可以是文本格式的、网页格式的或者一个GUI应用程序。微软的outlook和网景的Messenger属于最后一种。每一个客户端软件都可以通过配置向一个MTA(邮件传输代理)发送邮件以及选择一个MTA来发送邮件消息到指定的邮件地址。为了完成这些你需要在邮件服务器上注册一个帐号,可以使用标准的网络协议,离线Email协议(pop3)或者在线Email协议(IMAP)。这些用户在客户端与MTA或MTA之间发送消息的协议称为SMTP(简单邮件传输协议)。
值得注意的是MTA之间到底是如何运行的。邮件服务器主要依赖于DNS和email规范记录或者教邮件传输(MX)记录。邮件传输记录于DNS解析URL的记录略有不同,它还包含一些附加的优先信息以便更有效的发送邮件。我不准备对此作更深入的介绍,但是了解这一点是很重要的,DNS是成功有效的发送邮件的关键。James是一个MTA,而JavaMail API为MUA提供了一个框架。在这篇文章中我们会用JavaMailAPI建立一个应用,以便测试我们的James,在这篇文章的第二部分我会向你展示如何使用James mailet API建立自己的James应用。

James的设计目标
James被设计为实现几个确定的目标。比如它完全使用Java开发以便试映最大的轻便行;它提供了很多安全特性用于保护服务器的运行环境安全还提供了安全服务。James是多线程的,它使用了很多Avalon架构提供的功能。
James提供了完善的服务,包括一个完全可以运行的Email服务器。这些服务主要是有Macther和MailetAPI实现的,这两个API提供了Email检查和处理功能。James支持标准的email协议(SMTP,POP3,IMAP),另外还提供了一些附加功能,它使用了松散耦合的插件设计方式使消息框架从协议中抽象出来。这种设计方式非常有用,你可以把James当作一个通常的消息服务器或者为即时消息传输提供支持。
James一个更重要的设计目标是提出了mailet概念,mailet提供了一个开发邮件应用程序的完整生命周期。当然你也可以在这里使用其他MTA,比如SendMail,要这样作的话你需要提供一个可调用的程序然后将数据传送给它来完成工作,不过,James提供了一套更简单、通用的API来完成这些工作。下面让我们来更进一步的了解Matcher和MailetAPI。

James的安装与配置
James位于Apache站点(你可以在资源中找到它的连接),你应该下载最新的发行版本,写这篇文章时的最新版本是2.1.2。你可以在James的主页左边找到下载连接,然后选择Download->Binaries,然后选择2.1发行版本根据你的需要下载james-2.1.2.tar.gz 或 james-2.1.2.zip。
我们还会用到JavaMailAPI来测试我们的应用程序,所以你还需要下载JavaMail。当前的发现版本是1.3文件名为javamail_1_3.zip。在Javamail的主页上你应该注意到一个JAF的连接,这是JavaMail必须的支持包。JAF的当前版本是1.0.2文件名为jaf-1_0_2.zip,下载了所有这些资源之后你就可以安装配置你的James了。
我们将把开发所需的所有文件放到一个目录中,从产品化的角度来说,这里的安装有很大的不同,首先需要考虑安全性和功能性两个方面。对于我们来说,比如,我们可以在本地运行(localhost),但把它作为一个真正的email服务器来部署是不够的。有很多文档介绍如何将James配置为MTA或者结合SendMail使用,而且在一些邮件列表中你也可以找到相关的支持。
当把所有的文件解压缩到James目录中时,我们的文件结构应该如列表1所示,为了让整个结构更简洁清晰,我去掉了一些javadoc文档和src文件夹以及JavaMail webapp的演示文件夹。


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


这里假定你已经安装了JDK1.4,并且有一个独立的James文件夹。在James的配置声明中提到James在JDK1.3中会出现问题,所有建议你使用JDK.1.3.1或更高的版本,原则上在JVM1.4下是不会有问题的。
我们首先要作的是启动James,因为只有运行一次服务器配置文件才会被解压缩。在James的bin目录下你会看到一个运行教本。当你运行这个教本时你应该看到和下面类似的结果。

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的组合键关闭应用程序,这时你会得到一个退出的消息。严格讲James的关闭路径是由远程控制接口来完成的。这里我使用了Ctrl+C组合键的方式,在部署阶段你应该使用一个关闭命令来完成。
第一次关闭James之后你会在james-2.1-2/apps/james/SAR-INF路径下发现一个名为config.xml的文件,你应该仔细看看这个文件。你需要注意的第一件事是改变administrator帐号,它缺省的被设置为root密码也是root。你可以不管它,但是在部署为产品时这样作显然是不明智的。下一个需要改变的通常是DNS服务器的地址,如果你要把James部署为一个完全的邮件服务器这是必须的。你也可以不管它,因为我们可以在localhost下进行开发,但是一定要注意配置DNS是非常重要的。对于我们的开发阶段来说默认设置也可以使用,但了解这些是非常重要的,更详细的配置信息可以查看james-2.1-2/doc/中的说明。

在开始下异步之前,需要先增加一些用户,执行命令 telnet localhost 4555,你可以通过root用户登录。登录之后我们将增加一个新的用户,增加用户的命令是adduser,它接收两个参数:用户名和密码。我们将增加red,green,blue三个用户,每个用户的密码和用户名相同(在实际运行中这样的用户名和密码当然是不好的,但为了开发简单这里就这样设置了)。增加完用户之后你可以使用listusers命令来查看登注册的用户最后使用quit来退出控制台。整个会话过程看起来跟列表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非常简单直接。很明显,如果你希望你的James服务器更加安全你需要对其参数进行配置,不过这并不麻烦。实际上,使用邮件服务器的关键更多的在于在多用户和多服务器的情况下正确的配置DNS。这是今后我们要讲到的,不过那也同样不是什么负责的过程。
通过JavaMail测试James
为了验证James,我将快速的开发一个简单的类,它将模拟标准的email客户端实现发送邮件并列出收件箱中内容的功能。我们将开发两个类,一个类是如列表4所示的,它可以用于测试任何负责的应用,在文章的第二部分当我们开发自己的James应用时还会用到它。

列表4:模拟email客户端的基本功能。
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的调试标志。它可以在控制台打印出客户端与服务器之间的交互信息,这样你可以对它们的内部运行一目了然。另外两个参数是用户名和主机名称,这里假设着可以通过用户名和主机名得到Email地址。我们还创建了一个PasswordAuthentication对象,它可以通过getPasswordAuthentication()方法获得,这个对象实现了Authenticator接口。
这两个构造函数通过指定的用户名和主机配置了JavaMail的属性,并且明确指定了我们要使用的协议。一旦我们获得了Properties对象,我们就可以调用静态的Session方法getInstance()来获得一个有效的Session引用,这个引用存在在一个本地变量中。一旦构造函数被调用我们就可以向指定的主机发送或接收邮件了。
sendMessage()方法非常简单,它创建一个包含特定标题和内容的MimeMessage,然后通过JavaMail 的静态方法 send()把这个消息发送到Transport类,为了更容易的看到内部运行情况,我们同样把它打印到控制台上。
checkInbox()方法需要完成更多工作,因为它要列出消息,以便可以任意的删除这些消息,它同样可以在不看消息的情况下就删除消息。实际上要获得这些消耗,我们需要获得存储在会话中的对象,连接到服务器然后打开收件箱文件夹。
现在我们有了可以重用的MailClient类,我们可以准备开始测试本机上的James服务器了。列表5中的JamesConfigTest类创建了3个MailClient实例,每个对于一个我们已经创建的用户(red,green,blue)。在执行这个测试之前必须保证这3个用户已经在邮件服务器上创建好了。

列表5:测试邮件服务器的类JamesConfigTest
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() 方法清除了每一个邮箱,之后等待半分钟以确保邮件服务器删除了信息。之后我们会从red和green给blue发送一封邮件,并通过blue帐号检测这个邮件。当你运行JamesConfigTest时,你应该看到类似列表6的输出信息。
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 APIs以及前面提到的它在James中发挥的作用,我们还会大概的讲讲James提供的附加功能。
Matchers
James提供了很多标准的匹配器,每一个都实现了Matcher API如列表7所示,它提供了标准的退出MTA的功能,以及其他一些有用的扩展。这个接口非常简单,它包含了一系列有关生命周期的方法,init() 和 destroy()还有一对簿记方法:getMatcherInfo() 和 getMatcherConfig(),以及main方法,match()方法,整个方法用于操作Mail对象。Mail实例提供了访问容器状态、邮件消息和元数据的过程。

列表7:Matcher接口
public interface Matcher
{
  void init(MatcherConfig config);
  void destroy();
  String getMatcherInfo();
  MatcherConfig getMatcherConfig();
  Collection match(Mail mail);
}


一个Matcher负责解析一系列的收件人信息,并返回一个字符串对象集合,整个集合包含了由mailet处理的收集人信息。结合Matcher的解析功能和mailet的处理功能你可以开发一个负责的应用来处理一个邮件信息。
James的matcher提供的部分功能可以帮你完成很多事情,你不必开发自己的Matcher。如果你要开发自己的Matcher有几件事情是非常重要的,在很多情况下你需要的功能已经实现了,下面是对此的描述:
Matcher 描述
All 匹配所有的email被处理并且返回所有的收件人。
HasHeader 匹配一个指定的header。
HasAttachment 如果信息由多个部分。
SubjectStartsWith 信息的主题以特定的文本开头。
SubjectIs 信息有一个特定的主题。
HostIs 信息来自一个特定的主机。
HostIsLocal 信息来自 localhost
UserIs 信息来自一个特定的用户
SenderIs 来自一个特定的发件人。
SenderInFakeDomain 匹配主机地址无法解析的发件人。
SizeGreaterThan 匹配信息容量超出指定范围的。
Recipients 匹配收件人的信息来自一个指定的列表
RecipientsLocal 匹配本地收件人的信息。
IsSingleRecipient 匹配只有收件人信息的信息。
RemoteAddrInNetwork 匹配信息来自特定的IP列表或域的。
RemoteAddrNotInNetwork 匹配信息不是来自特定的IP列表或域的。
RelayLimit 匹配信息通过指定数量服务器发送而来的。
InSpammerBlackList 匹配信息来自mail-abuse.org
NESSpamCheck 匹配从Netscape Mail Server来的垃圾信息。
HasHabeasWarrantMark Matches mail with a Habeas Warrant
FetchedFrom Matches the X-fetched-from header used by FetchPOP
CommandForListserv 匹配来自已经列出的服务器中的命令。
如你所见,你可以完成很多匹配操作而无需自己重写,从最简单的匹配header主题等到高级内容比如匹配是否为来自列表服务器的命令。
Mailets
James的很多功能都是通过实现Mailet API(列表8)实现的,对于使用Servlet开发的人来说这段代码很眼熟。和Matcher API一样Mailet提供了两个生命周期方法init ()和destroy(),二个附加方法用于返回信息,第一个是getMailetInfo()方法,它返回一个字符串对象,这个字符串包含有关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应用如下表所示:
Mailet 描述
Null 结束email消息的处理。
AddHeader 给信息加一个文本页眉。
AddFooter 给信息加一个文本页脚。
Forward 将信息发给一组收件人。
Redirect 提供可配置的转发服务。
ToProcessor 将email转发到指定的处理器。
ToRepository 将信息的拷贝放到指定的目录中。
NotifySender 将信息作为附件发送给最初的发件人。
NotifyPostmaster 将信息作为附件发送给管理员。
RemoteDelivery 控制 SMTP 主机的发送。
LocalDelivery 将信息发送到本地的邮箱中。
JDBCAlias 使用JDBC 数据源进行别名翻译。
JDBCVirtualUserTable 使用JDBC 数据源进行更负责的别名翻译。
UseHeaderRecipient 从消息头中重建收件人。
ServerTime 发送一个服务器时间戳信息。
PostmasterAlias 通过postmaster@<domain>把信息转发给到一个特定的地址。
AddHabeasWarrantMark Adds a Habeas Warrant mark to the message
AvalonListserv 提供基本的列表服务器的功能。
AvalonListservManager 传输列表服务器控制命令。
如你所见,很多功能已经有James的Mailet实现了,包括复杂的列表服务器支持、别名、存储和路由等功能。
附加功能
James提供的很多功能已经超出了本文的范围,但是这里我还是简单的说一下,以便你可以了解James的强大。首先是它对NNTP的支持,这可以是James作为一个新闻服务器。James还实现了FetchPOP协议来支持以邮件为基础的远程控制特性。RemoteManager和SpoolManager提供了一个抽象,它允许多种存储类型和目前支持的控制。对于开发而言,它主要依赖与以文件为基础的SpoolManager,比如对于部分或完全以数据库为中心的方案也是支持的。
James提供的接口和服务允许用户别有效的控制,对邮件列表也提供了有效的支持。实际上邮件列表是James提供的主要功能之一,也是大多数管理员选择James作为邮件服务器的原因。

接下来是什么?
James的底层被设计为平滑的易于开发的。Email应用程序也许限制了你的想像空间。在文章的后半部分,我们将开发一个简单的应用程序,它允许用户把邮件发送到指定的James地址。用户可以先草拟一个邮件它将发给收件人,直到用户给其他特定用户发出一个取消的信息来关闭它。它模拟了通常由邮件客户端实现的功能,不过它存在地理位置的限制。因为,一旦你关闭了邮件客户端,这些功能将不作工作。对于目前在邮件服务器上实现这些功能,我们仍然可以从一个地方检查我们的邮件,如果我们的计划改变我们可以简单的修改“away”信息来作适当的回复。

http://www.chinaitpower.com/A200508/2005-08-10/189273.html

相关内容

    暂无相关文章