引言
Web服务是计算世界的新宠,它有望解决我们的所有计算问题,包括平台差异以及与旧系统间的互操作性等问题。几乎每篇相关文章都提到Web服务可以在各种协议上工作,但它们却只具体讨论了SOAP/HTTP。HTTP较轻易理解,所以最常被定义和实现,但显然它并不是Web服务的唯一选择。
本系列的文章将讨论在Web服务托管于WebSphere®ApplicationServer的情况下,如何通过实现简单邮件传输协议(SimpleMailTransferProtocol,SMTP)上的SOAP使Web服务可用。本文展示了一些支持不同的“服务质量”并利用现有基础结构的不同方法。本文将不讨论Web服务的创建,只讨论为什么为Web服务的接口使用SMTP上的SOAP绑定而不用更常用的HTTP/HTPPS上的SOAP。
本系列假设您已经对SOAP和Web服务技术有了大致的了解,所以就不再向您提供比较熟悉的显示“面向服务的体系结构(Service-OrientedArchitecture)”的三角图。对HTTP和SMTP有一定程度的了解也会有所帮助,但却不是必要的。
这一系列文章中的示例使用的是IBMWebSphereStudioApplicationDeveloper,它使用内置的WebSphereApplicationServer,但任何遵守J2EE的应用程序服务器(包括Tomcat)也应该可行。
为什么选用SMTP?
HTTP上的SOAP之所以如此常用,原因有以下几个:
HTTP协议无处不在-它随处可见。
HTTP协议与防火墙兼容性很好,它只使用一些大家熟悉的端口,而且防火墙几乎总是配置为答应HTTP协议通过。
HTTP协议使用HTTPS的“安全套接字层(SecureSocketLayer)”进行加密,并使用各种证书类型进行认证,很轻易保护。
这些原因中的一部分也适用于SMTP协议。电子邮件和Web浏览一样普遍-我们许多人都有多个可供天天查对的电子邮件帐户。SMTP使用的是一个大家都熟悉的端口,所以很轻易设置答应它通过的防火墙,而几乎每个防火墙都被配置为答应该协议通过。加密没有这么普遍,但通过PGP或其他方式的数字签名还是很轻易设置的。
此外,SMTP协议是异步的。调用者可以通过电子邮件发送请求,而且假如目标服务器宕机了,那么为了确保该电子邮件的发送,任何中间服务器都将重发好几次。另一方面,假如目标服务器在请求时不可用,那么HTTP协议也将失败。
选用POP还是原始SMTP?
SMTP协议提供了两种获取电子邮件的方法:
实现一个SMTP服务器,自己实现对基础协议的支持。
使用一个现有的“邮局”协议,使用这种协议时电子邮件存储在一个服务器上,并可由某个进程在适当的时机接收。
毫无疑问,您对这两种方法都很熟悉,因为您的电子邮件程序把这两种方法都用上了。传出的邮件被配置为使用一个SMTP服务器,直接把消息发送给理解该协议的服务器。收到的电子邮件通常是用一个邮局协议-邮局协议(PostOfficeProtocol,POP)或因特网消息访问协议(InternetMessageAccessProtocol,IMAP)-处理的,在您连接到因特网上检索到这封电子邮件之前,它会留在您的ISP的服务器上。
SMTP协议或者邮局协议都可以用作Web服务分派器接口,接收请求并将它们发送给目标Web服务。我们将分别展示两者的实现,但首先我们来讨论一下它们各自的优缺点。为简单起见,我们选择POP作为我们的中间服务器协议,因为它比IMAP协议更常见也更简单。
此外还有其他SOAP/SMTP实现,比如ApacheSOAP就是其中一个。然而,这种方法是先获得一个传入的SMTP请求,然后调用一个servlet接收HTTP协议上的请求,并将该请求传递给服务的实现。这一系列的文章将实现直接的SMTP,同时不会为将请求传递给目标服务而进行要求另一次网络跳转(即便是在同一台机器上)的协议切换。
SMTP
SMTP协议的直接实现要求我们在标准SMTP端口(25)上安装一个侦听器,然后在调用者连接到该端口时执行该协议指定的交互。假如调用者不是通过中间机器而是直接连到我们的服务器上,那么Web服务客户机就可以确定消息已被接收;否则它会收到一个通信错误,并可以立即对这次通信失败采取相应的措施。然而,若服务器宕机,就无法再进行纯异步的请求。
由于在多个服务器间分配电子邮件负载的机制得到了广泛的理解,所以可伸缩性是很轻易实现的。只要这多个服务器能处理请求,那么任何通过服务器多个实例之间的连接的适当路由都会成功,同时还提供工作负载治理和故障转移。
由于SMTP实现将侦听每个连接在它端口上的客户机,所以任何发送至服务器的电子邮件都会被处理,而不管目标用户(请想一下电子邮件中的To:域)是谁。因此,分派器例程能够轻松地为各个用户路由请求。
POP
POP协议直到服务器上的电子邮件被请求时才释放它们。客户机程序必须用显式方法将它们删除。您的电子邮件程序或许被配置为检索后删除电子邮件,但根据该协议这只是对服务器的一个单独请求。分派器将周期性地轮询POP服务器来检索每封电子邮件,向目标Web服务发送请求,然后在这些请求被处理后将它们删除。由于没有一个直接的机制来告诉调用者电子邮件已被接收或已经发生了通信故障,因此这个过程是纯异步的。
对于纯SMTP实现来说,可伸缩性的实现更为复杂,因为必须在分派器服务器之间做一些协调工作以避免一个请求的多服务情况。
POP协议要求一个明确的用户标识和密码来连接和检索电子邮件。假如您想为许多不同的用户标识处理请求,就需要向POP服务器轮询多个用户标识。若服务器不仅要处理Web服务请求,还要处理为客户机提供的响应消息,或者要根据目标用户标识做出不同的路由决定,这么做就显得尤为重要。
设计时的注重事项
在实现这样一个框架时总是有许多注重事项。首先,尽量使前两种实现(POP和原始SMTP)有更多的共性很重要。在多个地方做类似的事情是个不好的习惯。其次,您应当尽量灵活,答应部署多个能对请求起作用的对象,并在将请求传送到实际的Web服务实现前尽可能做一下认证或其他一些检查工作。灵活性的另一方面是使扩展—比如为另一个协议(例如IMAP)添加支持—轻易进行。
您还要尽量多利用其他实现。为此,这一系列文章将实现在Java应用程序服务器的Web容器内运行的代码。首先,您在支持HTTPS协议的同时最好也支持SMTP协议,因此,将两种实现放到同一个运行时内是有意义的。使用应用程序服务器也使伸缩、配置、部署和运行时支持变得更加轻易,因为您可以利用现有的基础设施而不必自己新创建什么东西。
这种利用一部分是使用JavaMail以便能与SMTP顺利交互。JavaMail是J2EE的一部分,所以它将自动被包含在任何遵守J2EE的应用程序服务器中。
由于对任何代码而言,确保无错误才是要害,所以设计时可测性很重要,包括实现几个JUnit类来支持类的自动测试。
您可能还想让分派器尽可能快地处理请求,不要等一个请求处理完后才去处理另一个请求,这样就要为每个请求启动一个新线程。还是为了简单起见,您可以只创建一个新线程,而不必使用一个ThreadPool来使开销达到最少,就象在一个业界水平的实现中一样。
两个SOAP供给商的故事
有趣的是,对于一个SOAP供给商来说有多种选择,并且它们都是Apache项目的一部分!
最常见的是ApacheSOAP,它提供一个servlet(RPCRouter)来接收传入的SOAP/HTTP请求,假如需要的话对它们进行解码,然后将它们传递给目标Java对象。这是最初的实现,但不幸的是它受到了一个主要问题的困扰:由于它使用DOM来处理XML,所以速度比较慢。处理SOAP中使用的XML可能是最慢的,而且可能是Web服务请求中最集中的处理部分。由于这个和其他几个原因,已经发起了另一个倡导,并且这个倡导已经成了Apache项目Axis的一部分。它对SOAP消息使用SAX解析,这就为XML提供了一个简单而又更加快捷的接口。Axis项目的工作正在进行中,即将发行代码的beta测试版。
这个项目使用Axis是因为它速度更快。从理论上讲,由于我们将对请求作异步处理,因此性能在这里并不那么重要。但这并不意味着我们就可以心安理得地慢下来,或者能接受DOM解析器的额外处理要求。我们只要解析XML就可以让服务器败下阵来,更不要说Web服务自身的处理要求了。
高级设计
高级设计使用的是启动时加载(load-at-startup)servlet。这些servlet启动使用原始SMTP绑定到端口25并等待传入的电子邮件的守护线程,或者周期性地向POP服务器轮询等待中的电子邮件的守护线程。图1显示了POPservlet、轮询守护程序、处理器以及处理程序之间的一个对象交互图:
图1.对象交互图
守护线程将根据它们正处理的协议做不同的工作。对于原始SMTP,该线程将获得一个连接请求,然后将套接字传递给一个处理器(Processor)类来处理SMTP协议并检索电子邮件。对于POP实现,该线程将周期性地轮询POP服务器,并将各个电子邮件传递给特定于POP的处理器类。
我们的处理器类实现一个MailProcessor接口,并可以继续一个AbstractProcessor类,AbstractProcessor包含我们所需的每个电子邮件协议都有的公共函数。这些类获得电子邮件并将它们传递给一串处理程序(Handler)对象。
处理程序对象链可以被配置为执行日志记录、将服务请求传递给SOAP引擎或处理响应等独立的任务。我们的处理程序类可以继续AbstractHandler,也包含公共函数。
结束语
本文介绍了许多关于我们的SOAP/SMTP实现的背景知识,包括SOAP/SMTP的优点、各种方法的优缺点和高级设计。下一篇文章将讨论实际的实现,包括怎样开始实现以及如何让该实现在WebSphereStudioIDE中运行。