[WCF 4.0新特性] 路由服务[原理篇]_WCF在一个典型的服务调用场景中,具有两个基本的角色,即服务的消费者和服务的提供者。从消息交换的角度讲前者一般是消息的最初发送者,而后者则是消息的最终接收者。在很多情况下,由于网络环境的局限,消息的最初发送者和最终接收者不能直接进行消息交换,这就需要一个辅助实现消息路由的中介服务,这就是我们接下来要介绍的路由服务。


在一个典型的服务调用场景中,具有两个基本的角色,即服务的消费者和服务的提供者。从消息交换的角度讲前者一般是消息的最初发送者,而后者则是消息的最终接收者。在很多情况下,由于网络环境的局限,消息的最初发送者和最终接收者不能直接进行消息交换,这就需要一个辅助实现消息路由的中介服务,这就是我们接下来要介绍的路由服务。


目录
一、路由服务就是一个WCF服务
      路由服务契约的定义
      路由服务契约的定义
二、基于消息内容的路由策略
      RoutingBehavior服务行为
      消息筛选器
      筛选器表


一、路由服务就是一个WCF服务

路由服务,就其本质而言就是一个WCF服务,具体的消息路由操作实现在该服务的某个操作之中。和其他一般的服务比较,并没有太多不同之处。在使用路由服务之前,我们也需要像一般的服务一样对其进行寄宿,并为之指定一个基于某种绑定的终结点。对于需要被路由的服务的客户端,除了需要将路由服务的地址作为其消息发送的物理地址之外,它依然像普通的方式一样对目标服务进行调用。

路由服务的定义

既然路由服务本质上就是一个WCF服务,它肯定就对应着某个实现相应服务契约的类型,这个类型就是RoutingService。下面的代码片断给出了RoutingService的成员定义。


1: [AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
2: [ServiceBehavior(AddressFilterMode=AddressFilterMode.Any,
3:                  InstanceContextMode=InstanceContextMode.PerSession,
4:                  UseSynchronizationContext=false,
5:                  ValidateMustUnderstand=false)]
6: public sealed class RoutingService : ISimplexDatagramRouter,
7:                                      ISimplexSessionRouter,
8:                                      IRequestReplyRouter,
9:                                      IDuplexSessionRouter,
10:                                      IDisposable
11: {
12:     //ISimplexDatagramRouter
13:     [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
14:     IAsyncResult ISimplexDatagramRouter.BeginProcessMessage(Message message, AsyncCallback callback, object state);
15:     void ISimplexDatagramRouter.EndProcessMessage(IAsyncResult result);
16:
17:     //ISimplexSessionRouter
18:     [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
19:     IAsyncResult ISimplexSessionRouter.BeginProcessMessage(Message message, AsyncCallback callback, object state);
20:     void ISimplexSessionRouter.EndProcessMessage(IAsyncResult result);
21:
22:     //IRequestReplyRouter
23:     [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
24:     IAsyncResult IRequestReplyRouter.BeginProcessRequest(Message message, AsyncCallback callback, object state);
25:     Message IRequestReplyRouter.EndProcessRequest(IAsyncResult result);
26:
27:     //IDuplexSessionRouter
28:     [OperationBehavior(Impersonation=ImpersonationOption.Allowed)]
29:     IAsyncResult IDuplexSessionRouter.BeginProcessMessage(Message message, AsyncCallback callback, object state);
30:     void IDuplexSessionRouter.EndProcessMessage(IAsyncResult result);
31:
32:     void IDisposable.Dispose();
33: }


我们根据其定义来简单地分析一下表示路由服务的RoutingService。首先,RoutingService实现了四个服务契约接口,分别是ISimplexDatagramRouter​、ISimplexSessionRouter​、IRequestReplyRouter​和IDuplexSessionRouter。在这里我们从RoutingService对它们的实现不难看出,这四个契约接口以异步模式定义了一个唯一的操作。其中定义在IRequestReplyRouter中的叫做ProcessRequest,定义在其他三个的则叫做ProcessMessage。实际上ProcessRequest/ProcessMessage就是真正实现消息路由的服务操作。

其次,RoutingService分别用于实现该操作方法的BeginProcessMessage方法上应用了OperationBehaviorAttribute特性并将Impersonation设置为ImpersonationOption.Allowed,意味着允许身份模拟(关于身份模式,可以参阅《模拟在WCF中的应用》)。

最后,在类型级别应用了AspNetCompatibilityRequirementsAttribute特性并将RequirementsMode属性设置为AspNetCompatibilityRequirementsMode.Allowed,意味着路由服务允许与ASP.NET兼容(关于ASP.NET兼容模式请参阅《WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘》)。同时应用了ServiceBehaviorAttribute特性将AddressFilterMode设置为AddressFilterMode.Any,这意味着WCF会关闭基于地址的消息筛选机制。这一点对于路由服务非常重要,因为它允许路由服务处理携带的目标地址(WS-Addressing的<To>报头)与本终结点不一致的请求消息。而在很多情况下,请求消息携带的目标地址一般都是目标服务的地址,而不是作为中介的路由服务的地址。

路由服务契约的定义

RoutingService显式实现的四个服务契约接口具有相似的操作方法的定义,其主要的目的在于对不同消息交换模式和会话的支持。我们知道,针对某个服务操作的调用可以通过三种不同的消息交换模式来实现,即数据报/单向(Datagram/One-Way)、请求/回复(Request/Reply)和双工(Duplex)。而RoutingService实现的契约接口ISimplexDatagramRouter/ISimplexSessionRouter、IRequestReplyRouter和IDuplexSessionRouter就是分别基于这三种不同的消息交换模式来定义的。其中,ISimplexDatagramRouter和ISimplexSessionRouter不同之处在于其会话模式的定义,前者采用默认的会话模式(SessionMode.Allowed),后者则强制使用会话(SessionMode.Required)。而基于双工消息交换模式的IDuplexSessionRouter,除了也将会话模式设置为SessionMode.Required之外,还定义了回调的类型IDuplexRouterCallback。下面的代码片断提供了对这四种契约接口的定义。


1: [ServiceContract(Namespace = "...",
2:                  SessionMode = SessionMode.Allowed)]
3: public interface ISimplexDatagramRouter
4: {
5:     [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")]
6:     IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
7:     void EndProcessMessage(IAsyncResult result);
8: }
9: [ServiceContract(Namespace = "...",
10:     SessionMode = SessionMode.Required)]
11: public interface ISimplexSessionRouter
12: {
13:     [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")]
14:     IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
15:     void EndProcessMessage(IAsyncResult result);
16: }
17: [ServiceContract(Namespace = "...",
18:     SessionMode = SessionMode.Allowed)]
19: public interface IRequestReplyRouter
20: {
21:     [GenericTransactionFlow(TransactionFlowOption.Allowed)]
22:     [OperationContract(AsyncPattern = true, IsOneWay = false, Action = "*", ReplyAction = "*")]
23:     IAsyncResult BeginProcessRequest(Message message, AsyncCallback callback, object state);
24:     Message EndProcessRequest(IAsyncResult result);
25: }
26: [ServiceContract(Namespace = "...",
27:     SessionMode = SessionMode.Required, CallbackContract = typeof(IDuplexRouterCallback))]
28: public interface IDuplexSessionRouter
29: {
30:     [GenericTransactionFlow(TransactionFlowOption.Allowed)]
31:     [OperationContract(AsyncPattern = true, IsOneWay = true, Action = "*")]
32:     IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
33:     void EndProcessMessage(IAsyncResult result);
34: }


四个不同的路由服务契约实际上均定义一个唯一的实现消息路由的操作ProcessMessage/ ProcessRequest操作。只不过这个操作是以异步模式(AsyncPattern = true)定义的,所以体现为BeginProcessMessage/BeginProcessRequest和EndProcessMessage/EndProcessRequest两个操作方法的组合。由于操作旨在实现对请求消息(或者回复消息)的路由,所以操作接受一个代表路由消息的类型为Message的输入参数。而对于IRequestReplyRouter接口的路由操作具有一个Message类型的返回值,代表被路由的回复消息。

由于ISimplexDatagramRouter/ISimplexSessionRouter专门针对数据报模式的消息交换,所以应用在操作方法上的OperationContractAttribute特性将IsOneWay属性设置为True,这一点我们很好理解。值得一提的是,对于面向双工消息交换模式的IDuplexSessionRouter接口来说,应用在操作方法上的OperationContractAttribute特性同样将此属性设置为True。我们知道,所谓的双工消息交换模式实际上可以看作是多次基于简单模式(数据报和请求/回复模式)的消息交换的组合。如果忽略服务端对客户端的回调,单独来看双工模式下服务调用采用的消息交换模式,它可以是单向的,也可以是基于请求/回复模式的。由于IDuplexSessionRouter的路由操作是单向的,那么最终的回复消息是如何被路由道客户端的呢?实际上,在这种情况下,不论是针对服务端回调客户端的消息,还是最终调用完成后的回复消息,都是通过路由服务对客户端的回调来实现消息的路由的。

此外,针对定义在不同服务契约接口中的路由操作,它们都具有一个相同的特性:应用在它们上面的OperationContractAttribute特性的Action属性值均被设置为“*”。通过OperationContractAttribute特性定义的服务操作的Action属性最终用于辅助实现对目标操作的选择。具体来说,服务端运行时正式通过请求消息WS-Addressing的这个<action>报头的值来选择当前操作列表中Action的值与此一致的操作。但是对于路由服务来说,请求消息的<action>报头的值一般是决定以真正的目标操作的,在这里情况下,路由服务的运行时是不可能根据请求消息正确地选择路由操作来处理该消息的。如果不能正确地选择出目标操作来处理请求消息,WCF的服务端运行时就会退而求其次地选择一个“备用”的操作。而当你将在定义服务契约的时候,通过OperationContractAttribute特性将Action属性设为“*”,这个操作就成为了这样一个备用操作。所以说,将路由操作的Action属性设置为“*”最终的目的在于:在操作选择阶段能够正确地选择该操作来处理请求消息以实现对该消息的路由。

二、基于消息内容的路由策略

路由服务以一个“中介服务”的形式提供消息路由的功能。具体来说,服务的客户端将于原本应该发送给目标服务的基于某个操作的调用消息转发给路由服务,而路由服务将接收到的消息作为输入,转而调用目标服务。路由服务对消息的接收和转发机制如下图所示。

[WCF 4.0新特性] 路由服务[原理篇]_RoutingService_02

所以,定义在路由服务中的路由操作ProcessRequest/ProcessMessage需要做的就是根据接收到的消息解析并调用真正的目标服务。至于目标服务的调用以实现对消息的路由,这和普通的服务调用并没有本质的区别。一般来说,路由服务的配置中具有用于调用目标服务的所有的客户端终结点的设置。所以需要解决的核心问题是:如果通过接收到的消息找到用于调用相应服务的终结点。

提到针对请求消息对终结点的选择,你或许会具有某种时曾相识之感。由于不同的终结点可以共享同一个监听地址,所以一个信道分发器(一个信道分发器使用一个独立的信道监听器进行请求监听)具有多个终结点分发器(一个终结点分发器对应于一个终结点)。当信道分发起接收到请求消息的时候,也需要根据这个消息选择正确的终结点分发器。

信道分发器对终结点的选择是通过“消息筛选”机制来实现的。具体来说,每个终结点分发器具有两个消息筛选器(MessageFilter),其中一个叫做地址筛选器(AddressFilter),另一个叫做契约筛选器(ContractFilter)。消息筛选器以请求作为输入,并返回一个布尔类型的值,如果返回值为True,则代表对应的终结点分发器适合用于处理接收到的请求消息。。

[WCF 4.0新特性] 路由服务[原理篇]_RoutingService_03

路由服务借用了原本用在信道分发器基于请求消息选择终结点分发器的消息筛选机制来实现根据被路由的消息来选择用于向目标服务路由消息的客户端终结点。具体的实现是这样的:路由服务维护者一个叫做筛选器表(FilterTable)的数据结构,该表的每一个元素代表一个消息筛选器和一个客户端终结点之间的映射关系,而该终结点直接指向某个具体的目标服务。

当路由服务接收到请求消息选择目标服务的时候,只需按照先后顺序或者优先级别遍历筛选器表中的每个消息筛选器,并以请求消息作为输入调用之,直到返回结构为True。而最终的这个消息筛选器对应的终结点就指向本次理由请求的目标服务。路由服务采用的消息筛选机制大体上上图所示。

RoutingBehavior服务行为

上面介绍的这套基于消息消息筛选的终结点选择机制最终是通过服务行为RoutingBehavior来实现的,所以我们寄宿路由服务RoutingService的时候,除了为终结点选择适合的绑定和满足相应消息交换模式的服务契约之外,还需在此服务上应用RoutingBehavior服务行为。下面的代码片断显示了RoutingBehavior类型的主要成员定义。


1: public sealed class RoutingBehavior : IServiceBehavior
2: {
3:     //其他成员
4:     public RoutingBehavior(RoutingConfiguration routingConfiguration);
5:     void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters);
6:     void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
7:     void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
8: }


从RoutingBehavior的定义我们可以看出,在调用构造函数创建RoutingBehavior的时候,需要指定一个RoutingConfiguration对象作为其参数。从其类型名称,我们知道该类是对路由策略信息的封装,下面的代码片断列出了RoutingConfiguration的核心属性的定义。

上面我们已经介绍过了,路由服务采用基于消息筛选机制的路由策略,而整个路由策略的实施依赖一个筛选器表,作为核心的筛选器表通过RoutingConfiguration的属性FilterTable表示。此外,RoutingConfiguration还具有两个布尔类型的属性SoapProcessingEnabled和RouteOnHeadersOnly。前者表示是否按照SOAP消息的方式进行路由处理,而后者则表式路由的处理是否仅仅需要使用到报头信息。SoapProcessingEnabled和RouteOnHeadersOnly两属性默认为True。


1: public sealed class RoutingConfiguration
2: {
3:     //其他成员
4:     public MessageFilterTable<IEnumerable<ServiceEndpoint>> FilterTable { get; }
5:     public bool RouteOnHeadersOnly { get;  set; }
6:     public bool SoapProcessingEnabled { get; set; }
7: }


在大部分情况下,我们通过配置的方式将RoutingBehavior行为应用到路由服务上,该行为对应的行为配置元素名称为<routing>。在下面的配置中,我为寄宿的路由服务RoutingService添加了一个基于WS2007HttpBinding的终结点,该终结点采用IRequestReplyRouter作为其服务契约。该服务应用了一个名称为routingBehavior的服务行为,而RoutingBehavior行为的配置就包含其中。<routing>配置元素的routeOnHeadersOnly和soapProcessingEnabled分别对应着RoutingConfiguration的同名属性,而filterTableName则表示配置的筛选器表的名称。


1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3:     <system.serviceModel>
4:         <behaviors>
5:             <serviceBehaviors>
6:                 <behavior name="routingBehavior">
7:                     <routing filterTableName="greetingFilterTable" routeOnHeadersOnly="true" soapProcessingEnabled="true" />
8:                 </behavior>
9:             </serviceBehaviors>
10:         </behaviors>
11:         <services>
12:             <service behaviorConfiguration="routingBehavior" name="System.ServiceModel.Routing.RoutingService">
13:                 <endpoint binding="ws2007HttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter" />
14:             </service>
15:         </services>
16: </system.serviceModel>
17: ...
18: </configuration>


接下来我们会具体地介绍RoutingBehavior配置中引用的筛选器表,以及具体的筛选器如何进行配置。

消息筛选器

对于WCF来说,消息筛选器旨在实现对给定消息的评估以判断它是否满足某个预先指定的条件,比如消息携带的AS-Addressing报头是否和预先指定的一致。WCF中所有的消息筛选器都继承自抽象类MessageFilter,而具体的消息评估操作实现在两个Match方法中。


1: public abstract class MessageFilter
2: {
3:     //其他成员
4:     public abstract bool Match(Message message);
5:     public abstract bool Match(MessageBuffer buffer);
6: }


WCF定义了一系列的系统定义的消息筛选器,如果我们在消息筛选过程中需要特殊的消息评估逻辑,我们还可以通过继承这个MessageFilter抽象类自定义消息筛选器。 我们经常使用的包括如下的六种:


  • ActionMessageFilter:该筛选器包含一组预先指定的表示Action的字符串,判断给定的消息的WS-Addressing <Action>报头是否是其中之一;
  • EndpointAddressMessageFilter:预先指定一个EndpoingAddress对象,判断给定消息的WS-Addressing <To>报头的值是否与之匹配;
  • PrefixEndpointAddressMessageFilter:预先指定一个EndpoingAddress对象,判断给定消息的WS-Addressing <To>报头的值于指定的地址是否具有相同的前缀;
  • EndpointNameMessageFilter:预先指定一个表示终结点名称的字符串,判断给定消息的是否具有一个名称为“System.ServiceModel.Routing.EndpointNameMessageFilter.Name”的属性,并且属性值与指定的值一致;
  • XPathMessageFilter:预先指定一个XPath格式的字符串,判断表示消息的XML是否满足基于该XPath的查询;
  • MatchAllMessageFilter:不管消息的内容是什么,都会匹配成功。


此外,WCF还为我们定义了一个特殊的消息筛选器StrictAndMessageFilter。它本身并不进行具体的消息评估的工作,具体的消息评估工作由它所包含的两个具体的消息筛选器来完成。只要当这两个消息筛选器评估结构均返回True时,该StrictAndMessageFilter才返回True,否则返回False。

供路由服务使用的所有消息筛选器均配置在WCF配置节的<routing>/<filters>节点下。该节点下表示单个消息筛选器配置元素的<filter>具有三个基本的配置属性:name、filterType和filterData,分别表示消息筛选器的名称、类型和创建筛选器需要的参数信息。为对于上述的六种消息筛选器,WCF为它们的类型分别定义了别名,分别是Action、EndpointAddress、Endpoint、XPath和MatchAll。在进行配置的时候只需要对filterType属性设置相应的类型别名即可。在下面配置片断中,我定义了6中消息筛选器,它们分别对应着上述的6种类型。


1: <configuration>
2:   <system.serviceModel>
3:     <routing>
4:       <filters>
5:         <filter name="ActionMessageFilter1" filterType="Action" filterData="http://namespace/contract/operation" />
6:         <filter name="EndpointAddressMessageFilter1" filterType="EndpointAddress" filterData="http://host/vdir/s.svc/b" />
7:         <filter name="PrefixEndpointAddressMessageFilter1" filterType="EndpointAddressPrefix" filterData="http://host/" />
8:         <filter name="EndpointNameMessageFilter" filterType="Endpoint" filterData="SvcEndpoint" />
9:          <filter name="XPathMessageFilter1" filterType="XPath" filterData="//ns:element" />
10:          <filter name="MatchAllMessageFilter1" filterType="MatchAll" />
11:        </filters>
12:       <routing>
13:   </system.serviceModel>
14: </configuration>


对于StrictAndMessageFilter的配置来说,它也具有相应的类型别名And。由于它是通过额外的两个消息筛选器来完成具体的消息评估,在配置中这两个消息筛选器通过属性filter1和filter2来表示。在下面的配置中,我们定义了三个消息筛选器,而前两个是为第三个StrictAndMessageFilter1服务的。


1: <configuration>
2:   <system.serviceModel>
3:     <routing>
4:       <filters>
5:         <filter name="filter1" ... />
6:         <filter name="filter2" ... />
7:         <filter name="StrictAndMessageFilter1" filterType="And" filter1="filter1" filter2="filter2" />
8:        </filters>
9:       <routing>
10:   </system.serviceModel>
11: </configuration>


上面介绍的都是系统定义的消息筛选器的配置,对于自定义的消息筛选器又该是如何配置呢?比如说,我定义了如下一个MyMessageFilger,并且它具有包含两个字符串参数的构造函数。


1: namespace Artech.CustomMessageFilters
2: {
3:     public class MyMessageFilger : MessageFilter
4:     {
5:         public MyMessageFilger(string arg1, string arg2)
6:         {
7:            //省略实现
8:  }
9:         public override bool Match(Message message)
10:         {
11:             //省略实现
12:         }
13:         public override bool Match(MessageBuffer buffer)
14:         {
15:             //省略实现
16:         }
17:     }
18: }


那么我们需要通过下面的方式对这个自定义消息筛选器进行配置。首先需要将filterType设置为“Custom”,并通过customType指定消息筛选器的类型,传入构造函数的参数通过filterData进行设置,参数值通过逗号分隔。


1: <configuration>
2:   <system.serviceModel>
3:     <routing>
4:       <filters>
5:         <filter name="MyMessageFilter1" filterType="Custom"
6:                 customType="Artech.CustomMessageFilters.MyMessageFilter, Artech.CustomMessageFilters"
7:                 filterData="argValue1, argValue2"/>
8:        </filters>
9:       <routing>
10:   </system.serviceModel>
11: </configuration>


筛选器表

上面配置的具名消息筛选器最终是为了创建用于定义路由策略的筛选器表服务的。筛选器表配置在<routing>/<filterTables>配置节下,表示具体筛选器配置元素的<filterTable>具有一个必须的配置属性name表示筛选器的名称,而之前我们介绍的配置在RoutingBehavior服务行为上的筛选器表名就是指的这个。

<filterTable>下的<add>节点表示具体的消息筛选器和指向目标服务的客户端中节点之间的映射关系。其中filterName和endpointName属性是对配置的消息筛选器和客户端终结点的引用。除此之外,我们还可以通过可选的priority属性配置匹配的级别。筛选器表的具体配置可以参考下面


1: <configuration>
2:     <system.serviceModel>
3:       <client>
4:         <endpoint name="endpoint1" .../>
5:          endpoint name="endpoint2" .../>
6:       </client>
7:       <routing>
8:         <filters>
9:           <filter name ="filter1" .../>
10:           <filter name ="filter2" .../>
11:         </filters>
12:         <filterTables>
13:           <filterTable name="myFilterTable">
14:             <add filterName="filter1" endpointName="endpoint1" priority="" />
15:             <add filterName="filter2" endpointName="endpoint2" priority=""/>
16:           </filterTable>
17:         </filterTables>
18:       </routing>
19:     </system.serviceModel>
20: </configuration>