2002-09-16 13:19
可靠的XML Web Service
Eric Schmidt
Microsoft
Corporation,XML Core Services 组,项目经理
2001 年 12 月 11
日
在 PDC 上,我谈论了有关可靠的 XML Web Service(Web 服务)的话题,这个话题源于过去一年来的多次交流。在有关建立 XML Web Service 的众多常见问题中,可靠性问题是开发人员实现分散式 Web 服务所面临的五个最重要的问题之一。如果分开来讲,这个问题并不是太难,因此,本月我准备谈一谈建立可靠的 XML Web Service 这一棘手的问题。
概述
Global XML Web Services Architecture(GXA
[英文])最突出的一面就是可以使用可合成处理协议扩展该体系结构。这些协议主要通过 SOAP
标头实现,可以提供包括安全性、加密、路由和可靠性的广泛服务。当您开始构建基于 GXA 的应用程序时,您将发现 GXA
实质上是一种消息处理体系结构,它通过基于标准的编码技术 (SOAP) 在系统和服务之间提供协同工作能力。到目前为止,大部分实现工作都集中在 SOAP 1.1 和
WSDL 兼容服务上,因此 Web 服务实现方案可以与多种语言和操作系统协同工作。
这是一个了不起的概念。任何两个系统之间都能够进行交流,只要它们能够分析 XML 并理解 SOAP 规范的规则。但是,简单的消息交换并不能满足复杂的业务应用程序的需要。真正的应用程序(不管其内部域体系结构如何)均需要标准化的服务,例如处于 Web 服务消息处理层上的安全性、授权和可靠性。在 Global XML Web Services Architecture(具体地说就是 SOAP、SOAP 模块和基础结构协议)的创建和实现背后有一个巨大的动力。随着今年十月份四项新规范(WS-Routing、WS-Referral、WS-Licensing 和 WS-Security)的发布,我们已经开始着手下一代 XML Web Service 实现工作。尽管发布了这么多的新规范,但仍有两个领域尚无公共规范,即事务处理和可靠的消息处理,这主要是因为这些基础结构协议依赖于底层 SOAP 模块。
本专栏主要从 GXA 环境的角度讨论可靠性和可靠的消息处理的含义。而且我还要花一些时间探讨通过在 .NET 框架中扩展现有 Web 服务类来开发可靠性协议需要做些什么。本专栏有两个主要目的:
让读者了解可靠性概念,为以后各种规范的实施做好准备。请注意,本文不是规范,而只是一篇文章,旨在引发读者思考下面要讨论的问题。
说明 .NET 框架中 Web 服务和 SOAP 类强大的、基于标准的功能。
XML Web Service 的可靠性
我们把问题分开来讲。我们前面讲过,GXA
服务实现方案属于消息处理服务,它们需要在分散式环境中发送和接收基于标准的编码消息。在 Web 服务实现方案中发送 SOAP 消息的主要传输协议是
HTTP,它易于实现和管理,但本身不可靠。我们无需深入探讨 HTTP 不可靠的具体原因,但只要知道 HTTP
没有基于标准的服务来保证终点或服务器能够接收到请求就足够了。尽管内置的网络层设备可以在发生一般灾难性故障(例如未找到资源)时产生错误,但是却没有机制可以确保客户端能够以可靠的方式接收请求或响应。
通常是通过简单的重新发送操作处理 HTTP 故障,但在业务处理环境中这既不利于提高效率也没有效果。它导致不必要的通信量,并增加了重复交易的风险。
目前市场上有多种能够更有效解决此问题的消息处理技术,包括传输协议(如 HTTPR)、企业基础结构(如 MSMQ 和 MQ Series)以及业务处理协议(如 ebXML)。尽管每种技术针对特定的实现方案各有优点,但都不能以可在所有传输协议上跨域应用的可扩展方式解决可靠性问题;而且,在消息交换和处理方面的功能层次上也不尽相同。
面对所有这些问题,我决定总结一个需求列表,看一看在 Web 服务环境中实现可靠性原型需要做哪些工作。
以下是我自己创建的可靠性层的主要需求列表:
基于标准并应用于消息协议层
确认发送
有序发送
对称对话
加快异步处理
基于标准
该协议必须由现有的基于标准的技术组成。而且,该协议还应该对 SOAP 1.1
规范进行扩展,然后应用到消息编码层而不是传输层。这样,消息才能够在所有可用的机制(从 HTTP 到某些专用套接字实现方案)上进行传输。
确认发送
为了有章可循,该协议必须采用某种发送确认机制,也就是说,使用该协议发送的消息应当从处理器接收一个且只有一个关于该消息状态的确认信息。
有序发送
有序发送引入了对话概念,即客户端和服务器可以交换消息和确认(确认也是可唯一标识的对话的一部分)。收到消息后,将检查消息以进行排序,确保处理器收到一组有序的消息。
对称对话
建立在对话机制之上的协议,还必须确保消息和确认的对称性。必须确保每条消息只被处理一次,并且只生成一个确认。
加快异步处理
这是需求列表中最重要的一点,所以留在最后说明。HTTP 是基于同步请求响应模型的,适用于处理任务量小或运行时间短的简单应用程序。Web
服务实现方案有一个不太高明的小秘密,即用户不需要从处理的角度了解服务是如何在后端实现的。也就是说,Web
服务实现方案处理一个请求可能只需要三秒钟,也可能会花上三个小时。这导致消息处理体系结构效率低下,且无法扩缩。我们需要的是一个能够加快异步消息处理体系结构的处理模型。但是要注意,异步模型实现方案要比紧耦合的请求响应实现方案复杂得多,因为它需要更多的基础结构。
我来解释一下。在使用标准 HTTP 的同步消息处理模型中,客户端在向服务器发送请求后、从服务器接收到响应之前一直保持停滞状态。在这段时间内,可能会发生许多灾难性事件:
连接可能会因外部原因而断开,从而丢失请求或响应。
服务器可能会因脱机或过载而超时。
服务器进程可能取决于下行服务,而这种服务的响应时间无法控制。
同步模型
图 1:同步模型
不管问题是与网络有关还是与应用程序有关,要确保可靠性都需要实现某种附加协议驱动的基础结构。在本次讨论中,我想着重讲述消息是如何在传输层上进行处理的。传输层是独立于应用程序的关键部分,使用它可以实现可靠性层,并将消息的最终处理过程分离出来。更具体一点来说,每一条消息(不管针对哪种应用程序)都需要从网络层上读取,并分配至适当的应用程序资源。我们可以在这里添加一个发送可靠性确认和执行持续存储的附加协议,这样应用程序资源就可以选择处理该消息的时间和方式。而且,这个新协议可以帮助我们分离或加快异步处理模型。下面将解释如何完成此过程。
在下面的异步模型中,某一请求被发送至 SOAP 服务器。服务器从网络层上读取该消息流,并立即向客户端返回 HTTP 202 响应。此进程仅就向服务器发送消息的时间而言是同步的,这样可以减少与连接有关的问题。到达服务器后,消息将被传送到可靠性层,在这里进行检查以验证消息是否过期、重复和有序。然后,消息被持续存储(在关系数据库中),并向客户端发送有关其状态的确认。最后,消息将被分配至正确的应用程序功能。
异步模型
图 2:异步模型
在 HTTP 环境中,您可以控制向客户端发送响应的时间。通过控制向客户端发送响应的时间,您可以将下行处理影响通信可靠性的风险降到最低。在 SOAP 中,这是通过单向消息传递实现的。它指示底层 SOAP 处理器立刻向客户端发送 HTTP 202 响应,通知客户端已收到消息,并已成功地将消息分配给正确的资源进行处理。之后,处理器向客户端发送有关该消息状态的响应。本文稍后将对这种模型的优点进行详细介绍。
建立可靠性层
明确了上述要求之后,我们来讨论如何使用 .NET 框架为 Web 服务实现方案建立可靠性协议。根据上述要求,我建立了一个小型
API,以便提供可用的实现方案。
协议:ericRP
第一个问题是定义如何分解可靠性协议 (ericRP)。以下是该协议的关键之处:
该协议是用于教学的原型。
该协议主要是通过扩展
SOAP 消息处理层在 SOAP 处理层上执行的。
SOAP 标头用于对处理层所需信息进行编码。
该协议要求实现方案具有某种方式的持续存储,以便记录消息。本实现方案使用的是 Microsoft SQL
Server 2000。
注意:不管采取哪种方式在 SOAP 环境中实现可靠性层,除了 SOAP
分析器之外,都还需要其他基础结构。
该协议支持对话概念,也就是说可以对多条消息进行排序,从而保证有序的发送。
该协议的全部实现方案都由 ericRP 名称空间限定。
ericRP 基于两方对话方案,即两个服务可以通过 XML Web Services 体系结构(HTTP、SOAP 和
WSDL)进行对话。
客户端负责消息的所有更正。(本文后面有详细论述)
服务器只负责基于特定标准发送确认。
服务器不记录收到的过期消息。
服务器不记录收到的无序消息。
服务器不记录收到的重复消息。
处理 API
在这个原型中,我建立了六个主要的类和一个小型数据库。我将类称为处理 API。Web 服务客户端和服务器将使用这些类监控和更正使用 ericRP
可靠性协议的消息。所有的类都属于 ericRP 名称空间:
Client.ConversationManager:由客户端使用,创建 Web
服务消息关联和消息监控的对话环境。
Client.RPClientTrace:由 Web
服务客户端使用,这些客户端的方法对出站消息执行 ericRP 可靠性协议。
Server.ConversationManager:由 Web 服务服务器使用,记录并处理入站消息。
Server.RPServerTrace:由 Web 服务服务器使用,这些服务器的方法对入站消息执行 ericRP
可靠性协议。
ReliabilityInfo:具有双重作用。它可以由
Client.ConversationManager 使用,为记录提供可靠性信息;也可以由 Web 服务客户端代理使用,为出站消息创建必要的 SOAP
标头信息。
Acknowledgment:由 Server.ConversationManager
使用,向客户端发送确认。
ericRP 的工作原理
在查看代码之前,我想先从用户的角度说明该协议的工作原理。例如,我有一个简单的 Web 服务代理类,通过它可以向 Web
服务发送订单消息。打算使用 API 的客户端需要执行以下操作:
首先,创建 Client.ConversationManager 类的实例并开始一个新对话。例如:
private void begin()
{
rpClient = new
ericRP.Client.ConversationManager();
rpClient.MessageSent += _
new
ericRP.Client.ConversationManager.MessageSentEventHandler(process);
rpClient.ConversationStarted += new _
ericRP.Client.ConversationManager.ConversationStartedHandler(constarted);
rpClient.BeginConversation();
}
rpClient 变量在类级别内有效,稍后会用到。我还设置了一些事件处理程序。
下一步,使用订单代理并配合 ReliabilityInfo 类,发送一条可靠的信息。先创建 PurchaseOrderProxy 的实例,就象通常为 Web 服务客户端所做的操作一样。再创建 ReliabiltiyInfo 类的实例,将 ConversationManager 传送给构造函数,然后设置可靠性属性。需要特别注意的属性是 MaxRetry、ExpireDate 和 AckURL。MaxRetry 和 ExpireDate 用于限制消息的活动,防止它无限制地发送;Web 服务将在向客户端发送接收确认时使用 AckURL。设置完这些属性后,即可设置代理的 ReliableHeader 属性并调用所需的方法。
private void sendMessage()
{
ClientProxies.PurchaseOrderProxy po = new
ClientProxies.PurchaseOrderProxy();
ericRP.ReliabilityInfo rInfo = new
ericRP.ReliabilityInfo(rpClient);
rInfo.Status =
ReliabilityInfo.MessageStatus.New;
rInfo.SendDate =
System.DateTime.Now;
rInfo.ExpireDate =
System.DateTime.Now.AddHours(4);
rInfo.MaxRetry =
5;
rInfo.AckURL = "http://localhost:8082/ericRPAck/POAck.asmx";
po.ReliableHeader = rInfo;
po.SubmitMessage("非常希望他们得到此订单!");
}
这是为了说明该功能而编写的一段客户端测试程序的屏幕快照。注意,我们一共发送了五条消息。第三条消息在到达目的地之前已过期,按照 ericRP 协议,这条消息将被丢弃,服务器不对其进行处理。第四条消息是无序消息,因为服务器并没有收到有效的第三条消息。在重新发送第三条消息之前,任何后续消息都是无序的。如果重新查询 Client.ConversationManager,您将发现第五条消息也是无序的。
图 3:客户端测试程序
标头的作用
在查看代码之前,我们需要了解一下 SOAP
主题,即标头。SOAP 1.1 规范中谈论最少的内容之一就是 SOAP 标头。标头提供了一种扩展消息处理体系结构的简单方法。SOAP 1.1
规范中提到:标头在实现与消息主体没有特定关系的处理规则(例如验证和事务管理)时非常有用。对任何类型的消息来说,SOAP
标头都是以独立方式对可靠性信息进行编码的完美解决方案。规范中还概述了实施和处理这些标头的标准和规则。
下面我们来看看如何实现包含可靠性信息的 SOAP 标头。首先要为标头确定架构。这很重要,因为它正是最终用户在支持该标头的 Web 服务的 WSDL 文件中看到的实际效果。对于此实现方案,我直接将处理 API 映射到 SOAP 标头。
我来解释一下。在我的处理 API 中有一个名为 ReliabilityInfo 的类。对此类进行实例化时,它将在运行时变成动态对象。也就是说,您可以从可靠性的角度设置确定如何处理出站消息的属性。此对象还可以在运行时序列化为 SOAP 标头。下面是该类及其成员的一个快照。为了清楚起见,我删除了具体的实现和私有成员。
[XmlRootAttribute(ElementName="ReliableHeader", _
Namespace="http://ericRP/ReliableHeader/2001/",
IsNullable=false)]
public class ReliabilityInfo :
SoapHeader
{
public string
Destination{}
public string
ConversationId{}
public int
MessageId{}
public MessageStatus
Status{}
public DateTime
SendDate{}
public DateTime
ExpireDate{}
public string AckURL{}
public
enum MessageStatus{}
[XmlIgnore]
public int
MaxRetry{}
[XmlIgnore]
public string
Text{}
}
序列化为出站 SOAP 消息时,标头如下所示:
<soap:Header>
<ReliableHeader xmlns="http://ericRP/ReliableHeader/2001/">
<ConversationId>b9e029e1-af0f-42cb-83b0-7888f9e3ffc4</ConversationId>
<MessageId>1</MessageId>
<Status>新</Status>
<SendDate>2001-11-06T14:59:02.1021226-08:00</SendDate>
<ExpireDate>2001-11-06T18:59:02.1021226-08:00</ExpireDate>
<AckURL>http://localhost:8082/ericRPAck/POAck.asmx</AckURL>
</ReliableHeader>
</soap:Header>
这是通过 .NET 框架中两个非常重要的类实现的。基本 XML 序列化块使用 System.Xml.Serialization 名称空间类来进行处理。这两个类一个是 XmlRootAttribute 属性类,我使用它告诉序列化程序将文档碎片的根称为 ReliableHeader,并将它与名称空间相关联。此后,所有公共成员也同样会被序列化,除非您告诉序列化程序忽略该成员。我使用的另一个重要名称空间是 System.Web.Services.Protocols。具体地说,即 SoapHeader 类。从该类进行继承时,它会自动将序列化的 XML 加入到 SOAP 消息中。本文稍后将论述如何将标头加入消息。
这非常强大,因为我不仅可以使用类型明确的、编译好的对象作为标头的基础,而且在这些类的成员内部还有特定的实现。
综述
好啦,上面只是对可靠性进行了简单的论述,并阐述了我自己的规范。下面让我们看一看它的代码。
扩展 Web 服务客户端代理
第一步是重新设置现有的 .NET Web 服务客户端代理。记住,此协议可以在任何 SOAP 处理引擎中实现。我选择了
.NET 框架,因为它易于使用、基于标准且可扩展。
选取一个现有的 Web 服务客户端代理(例如 PurchaseOrderProxy),然后添加以下代码:
该类中必须有名为 ReliableHeader 的公共成员,并且其类型必须为
ericRP.ReliabilityInfo。此成员将在运行时通过调用客户端进行设置。稍后将使用此成员为出站消息提供标头信息,并提供在跟踪过程中应用了该标头的消息的状态信息。例如:
public
class PurchaseOrderProxy :
System.Web.Services.Protocols.SoapHttpClientProtocol
{
public ReliabilityInfo
ReliableHeader;
//为了清楚起见,此处省略了其他代码
}
在 Web 服务客户端代理中调用的方法必须使用以下属性进行批注:
[SoapHeader("ReliableHeader",
Required=true)]
在序列化过程中,此属性将在运行时把适当的标头值加入到出站 SOAP 消息中。此处的 SubmitMessage 方法使用 SoapHeader 属性进行标记。注意,ReliableHeader 是在步骤 1 中实现的成员,而且是必须的。也就是说,如果不在运行时设置此成员,将产生异常。例如:
[SoapHeader("ReliableHeader", Required=true)]
public void
SubmitMessage(object message)
{
this.Invoke("SubmitMessage", new object[]
{message});
}
在 Web
服务客户端代理中调用的方法必须包含以下属性:
[ericRP.Client.RPClientTrace.TraceExtension()]
此属性表示该方法支持自定义 SOAP 扩展。在消息被序列化之前和之后、且在消息被发送至底层传输机制之前,这种 SOAP 扩展将在运行时被调用。我通常是在消息从客户端计算机发送出去之前,对它进行一些简单的跟踪和记录。稍后再查看此扩展的实现情况。例如:
[ericRP.Client.RPClientTrace.TraceExtension()]
[SoapHeaderAttribute("ReliableHeader",
Required=true)]
public void SubmitMessage(object message)
{
this.Invoke("SubmitMessage", new object[]
{message});
}
该类必须从 ericRP.Client.RPClientTrace.IClientTrace
实现。这种接口实现方案提供了基本的跟踪功能,以检查调用程序是否支持特定的跟踪协议。在后面的跟踪功能中可以看到此代码。例如:
public class
PurchaseOrderProxy :
System.Web.Services.Protocols.SoapHttpClientProtocol,
ericRP.Client.RPClientTrace.IClientTrace
{
public ReliabilityInfo
ReliableHeader;
}
最后,该类必须实现 IClientTrace.GetReliabilityInfo 函数,该函数是
IClientTrace 接口要求的唯一函数。这是一个简单的机制,客户端可以使用它在运行时将消息的状态信息发送到跟踪服务。例如:
public class
PurchaseOrderProxy :
System.Web.Services.Protocols.SoapHttpClientProtocol,
ericRP.Client.RPClientTrace.IClientTrace
{
public ReliabilityInfo
ReliableHeader;
ericRP.ReliabilityInfo
ericRP.Client.RPClientTrace.IClientTrace.GetReliabilityInfo()
{
return ReliableHeader;
}
}
看起来代码可能很多,但实际上却很简单。事实上,如果时间再多一些,我可以创建从 SoapHttpClientProtocol 继承的新类并明确加以实现,但这种方式对于服务器端来说会更有趣。
扩展 Web 服务服务器存根
下一步,我们来扩展现有的 Web 服务服务器存根。选取一个现有的 .NET Web 服务类(例如
ProcessPurchaseOrder),然后添加以下代码:
该类中必须有一个类型为 ericRP.ReliabilityInfo 的公共成员
ReliableHeader。稍后将使用此成员为入站消息提供反序列化标头信息,并提供在跟踪过程中应用了该标头的消息的状态信息。例如:
public
class ProcessPurchaseOrder :
System.Web.Services.WebService
{
public ReliabilityInfo
ReliableHeader;
}
在 Web 服务存根中调用的方法必须使用以下属性进行批注:
[SoapHeader("ReliableHeader",
Required=true)]
在反序列化过程中,此属性将在运行时对入站 SOAP 消息中适当的标头值进行反序列化。此处的 SubmitMessage 方法使用 SoapHeader 属性进行标记。例如:
[WebMethod]
[SoapHeader("ReliableHeader", Required=true)]
public void
SubmitMessage(object message)
{
//为了清楚起见,此处省略了一些代码
}
在 Web
服务存根中调用的方法必须使用以下属性进行批注:
[ericRP.Server.RPServerTrace.TraceExtension()]
此属性表示该方法支持自定义 SOAP 扩展。稍后再查看此扩展的实现情况。
注意:此扩展与客户端扩展不同。
例如:
[SoapHeader("ReliableHeader",
Required=true)]
[ericRP.Server.RPServerTrace.TraceExtension()]
public void
SubmitMessage(object message)
{
//为了清楚起见,此处省略了一些代码
}
在 Web 服务存根中调用的方法必须使用以下属性进行批注:
[SoapDocumentMethod(OneWay=true)]
此属性表示被调用的函数不返回值。更具体地说,即一旦消息被反序列化,此属性就会强制 Web 服务向客户端返回 HTTP 202 响应。对于分离消息的最终处理来说,这不失为一个有效的机制,否则,客户端就不得不同步地等待服务返回响应。例如:
[WebMethod]
[SoapDocumentMethod(OneWay=true)]
[SoapHeader("ReliableHeader",
Required=true)]
[ericRP.Server.RPServerTrace.TraceExtension()]
public void
SubmitMessage(object message)
{
//为了清楚起见,此处省略了一些代码
}
该类必须从 ericRP.Server.RPServerTrace.IServerTrace
实现。这种接口实现方案提供了基本的跟踪功能,以检查调用程序是否支持特定的跟踪协议。在后面的跟踪功能中可以看到此代码。例如:
public class
ProcessPurchaseOrder :
System.Web.Services.WebService,
ericRP.Server.RPServerTrace.IServerTrace
{
public ReliabilityInfo
ReliableHeader;
}
最后,该类必须实现 IServerTrace.GetReliabilityInfo 函数。这是 IServerTrace
接口要求的唯一函数。例如:
public class ProcessPurchaseOrder :
System.Web.Services.WebService,
ericRP.Server.RPServerTrace.IServerTrace
{
public ReliabilityInfo ReliableHeader;
ericRP.ReliabilityInfo
ericRP.Server.RPServerTrace.IServerTrace.GetReliabilityInfo()
{
return
ReliableHeader;
}
}
好啦,现有的 Web 服务客户端和服务器已准备就绪。下面我们来看看 SoapExtension 是如何工作的。
查看 RPClientTrance 和
RPServerTrace
为了实现可靠性处理,我决定使用
SoapExtension。SoapExtension 是一个可继承的基类,使用它可以跟踪 SOAP
消息的出站序列化和入站反序列化。正是在这个跟踪过程中,对消息进行记录并检查其状态。记得前面讲过,Web 服务客户端代理方法实现
[ericRP.Client.RPClientTrace.TraceExtension()]。当调用该方法时,此属性将在 SOAP
消息出站序列化时调用以下代码。
需要特别指出的主要函数是 ProcessMessage。发送出站消息时,ProcessMessage 将提供有关该消息序列化之前和之后的全部状态信息。这时,将检查谁在调用并将 Client 属性的类级别成员与当前消息分离。然后检查消息是否处于 AfterSerialize 状态。如果已经序列化,则可以在消息被发送至服务器之前进行记录。通过名为 ProcessOutgoingMessageText 的自定义函数,首先进行一些流交换以免破坏底层消息流。然后检查客户端是否支持 IClientTrace 接口。如果客户端支持该接口,则表明它们也支持可靠性协议。通过接口检查功能,可以调用 GetReliabilityInfo 以便将当前消息返回可应用于该消息的 ConversationManager,然后设置一些属性并调用 LogMessage。LogMessage 将当前消息信息写入本地数据库,然后发送事件,通知客户端已记录该消息。
public class RPClientTrace : SoapExtension
{
public override void
ProcessMessage(SoapMessage message)
{
SoapClientMessage
tmpMsg = (SoapClientMessage)message;
_client = tmpMsg.Client;
if (message.Stage == SoapMessageStage.AfterSerialize)
{
ProcessOutgoingMessageText(message);
}
}
public void ProcessOutgoingMessageText(SoapMessage message)
{
newStream.Position = 0;
TextReader reader =
new StreamReader(newStream);
StringBuilder strMessage = new
StringBuilder();
strMessage.Append(reader.ReadToEnd());
newStream.Position =
0;
Copy(newStream, oldStream);
if(_client is Client.RPClientTrace.IClientTrace)
{
try
{
Client.RPClientTrace.IClientTrace _ptrClient = _
(Client.RPClientTrace.IClientTrace)_client;
ReliabilityInfo rInfo =
_ptrClient.GetReliabilityInfo();
rInfo.Text =
strMessage.ToString();
rInfo.Destination = message.Url;
rInfo.Manager.LogMessage(rInfo);
}
catch(Exception e)
{
throw e;
}
}
}
}
服务器端的情况有点复杂,因为服务器需要为入站消息执行额外的操作。被调用的 Web 方法用于实现 [ericRP.Server.RPServerTrace.TraceExtension()]。以下代码将在入站消息反序列化之前和之后被调用:
public class RPServerTrace : SoapExtension
{
public override void
ProcessMessage(SoapMessage message)
{
try
{
switch (message.Stage)
{
case
SoapMessageStage.BeforeDeserialize:
ReadIncomingMessageText(message);
break;
case SoapMessageStage.AfterDeserialize:
SoapServerMessage tmpMsg =
(SoapServerMessage)message;
_server =
tmpMsg.Server;
if(_server is
Server.RPServerTrace.IServerTrace)
{
Server.RPServerTrace.IServerTrace _ptrServer =
_
(Server.RPServerTrace.IServerTrace)_server;
ReliabilityInfo rInfo =
_ptrServer.GetReliabilityInfo();
ericRP.ReliabilityInfo tempInfo =
(ericRP.ReliabilityInfo)message.Headers[0];
tempInfo.Text =
_tempMessage;
Server.ConversationManager manager = new
Server.ConversationManager();
manager.ProcessMessage(tempInfo);
}
break;
}
}
进行流交换和接口检查后,将在服务器对话管理器上调用 ProcessInboundMessage。ProcessInboundMessage 用于检查核心消息的状态。消息限制程序即在此使用。首先检查消息是否过期,然后检查其是否重复,最后检查其是否有序。如果满足所有三个条件,则将记录该消息并向客户端发送确认;如果不能满足任一条件,则将更改消息的状态并向客户端发送确认。
public void ProcessInboundMessage(ericRP.ReliabilityInfo
rInfo)
{
try
{
if(IsExpired(rInfo))
{
rInfo.Status =
ericRP.ReliabilityInfo.MessageStatus.Expired;
SendAck(rInfo);
}
else
if(IsDulplicate(rInfo))
{
rInfo.Status =
ericRP.ReliabilityInfo.MessageStatus.Duplicate;
SendAck(rInfo);
}
else
if(IsNotOrdered(rInfo))
{
rInfo.Status =
ericRP.ReliabilityInfo.MessageStatus.NotOrdered;
SendAck(rInfo);
}
else
{
rInfo.Status =
ericRP.ReliabilityInfo.MessageStatus.Success;
LogMessage(rInfo);
SendAck(rInfo);
}
}
catch(Exception
e)
{
throw e;
}
}
}
要使本示例在生产环境中可行,还需要做大量的实施工作。更重要的是,API 应当基于一个以后将会发布的公共标准。本文的主要目的就是引发读者思考问题,了解一下 .NET 框架中的主要 Web 服务类,我想这两个目的都已经达到。如果您正在寻求可靠异步消息处理的成熟可用的实现方案,建议您看一看 Microsoft BizTalk? Server 2000。
最后,您可以使用类似的跟踪功能执行所有类型的操作,例如加密、客户分配和通知。记住,这项附加的功能增加了 Web 服务所需的处理基础结构。
展望
无论是在规范还是在实现方面,XML Web Service 的未来都是光明的。Microsoft 将以协作的、标准驱动的方式工作,确保 XML
Web Service 成为编写松散耦合的分散式应用程序的最佳体系结构。可靠的消息处理和事务规范将在以后发布。尽管还有大量的实现工作要做,但您现在就可以开始使用
SOAP 和 WSDL 构建您的框架。与公共规范进程越接近,以后的改动工作就越容易进行。SOAP 规范就是这种概念的一个显著例子。世界各地的开发人员都可以跟踪
SOAP 规范的发展进程,因此可以毫不费力地对自己的实现方案进行相应地调整。
希望我能有一个水晶球,为您准确地描绘未来 XML Web Service 的基础结构;希望我们目前依赖的所有公用服务,例如安全性、事务、存储、查询、路由、进程协调等等,将来都能够直接映射至 Web 服务体系结构;而且 GXA 实现方案可以渗透到核心开发语言、应用程序框架、业务处理框架和企业基础结构中。到那时,我们就能够真正地以无缝方式将任意两种服务耦合在一起。但愿 ericRP 不会真的成为服务背后的可靠性层。<微笑>