EndpointAddress——不只是一个Uri[下篇]

上篇》对AddressHeader在服务端和客户端的作用,以及如何通过配置和编成的方式设置AddressHeader进行了详细介绍。现在我们通过一个实例来演示终结点的地址报头如何影响实现终结点选择的消息筛选机制。这个实例通过为服务端终结点指定地址报头实现针对客户端的授权,让经过许可的客户端才能访问这个服务。具体来说,我们将一个代码序列号的GUID作为终结点的地址报头。对于客户端发送的消息,只有具有相应的报头才能访问服务。[三个实例源代码下载地址:实例1实例2实例3]

一、无地址报头下服务调用(实例1

我们采用计算服务的例子,整个实例的解决方案具有右图所示的3个项目。其中类库项目Service.Interface用于定义契约接口。Service项目是一个控制台应用程序,用于定义服务类型和作为服务的宿主。控制台应用程序Client代码进行服务调用的客户端。在本书后续部分的绝大部分实例都会采用这个结构。

实例演示的目的旨在旨在指导读者编程,或者说明某个方面的原理,所以我会将服务承载的业务功能尽量地简化。所以我们分别在Service.Interface和Service项目中定义了如下所示的契约接口ICalculator和服务类型CalculatorService。ICalculator仅仅具有唯一的表示加法运算的Add操作。

ICalculator:

   1: using System.ServiceModel;
   2: namespace Artech.WcfServices.Service.Interface
   3: {
   4:     [ServiceContract(Name = "CalculatorService", Namespace ="http://www.artech.com/")]
   5:     public interface ICalculator
   6:     {
   7:         [OperationContract]
   8:         double Add(double x, double y);
   9:     }
  10: }

CalculatorService:

   1: using Artech.WcfServices.Service.Interface;
   2: namespace Artech.WcfServices.Service
   3: {
   4:     public class CalculatorService : ICalculator
   5:     {
   6:         public double Add(double x, double y)
   7:         {
   8:             return x + y;
   9:         }
  10:     }
  11: }

服务CalculatorService通过控制台程序Service进行寄宿。下面是服务寄宿代码和相应的配置。从配置可以看到,服务唯一的终结点具有一个作为地址报头的<sn>元素,它的值代表服务的序列号。

服务寄宿程序:

   1: using System;
   2: using System.ServiceModel;
   3: namespace Artech.WcfServices.Service
   4: {
   5:     class Program
   6:     {
   7:         static void Main(string[] args)
   8:         {
   9:             using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
  10:             {
  11:                 host.Open();
  12:                 Console.Read();
  13:             }
  14:         }
  15:     }
  16: }

配置:

   1: <configuration>
   2:     <system.serviceModel>
   3:         <services>
   4:             <service name="Artech.WcfServices.Service.CalculatorService">
   5:               <endpoint address="http://127.0.0.1:3721/calculatorservice"
   6:                         binding="ws2007HttpBinding"                        
   7:                         contract="Artech.WcfServices.Service.Interface.ICalculator">
   8:                 <headers>
   9:                   <sn xmlns="http://www.artech.com/">
  10:                        {DDA095DA-93CA-49EF-BE01-EF5B47179FD0}
  11:                      </sn>
  12:                 </headers>
  13:               </endpoint>
  14:             </service>
  15:         </services>
  16:     </system.serviceModel>
  17: </configuration>

客户端通过ChannelFactory<TChannel>创建的服务代理进行服务调用。下面是进行服务调用的程序和客户端配置。

服务调用程序:

   1: using System;
   2: using System.ServiceModel;
   3: using Artech.WcfServices.Service.Interface;
   4: namespace Artech.WcfServices.Client
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
  11:             {
  12:                 ICalculator calculator = channelFactory.CreateChannel();
  13:                 Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2,calculator.Add(1,2));
  14:             }
  15:             Console.Read();
  16:         }
  17:     }
  18: }

配置:

   1: <configuration>
   2:   <system.serviceModel>
   3:     <client>
   4:       <endpoint name="calculatorservice"
   5:                address="http://127.0.0.1:3721/calculatorservice"
   6:                binding="ws2007HttpBinding"
   7:                contract="Artech.WcfServices.Service.Interface.ICalculator"/>
   8:       </client>
   9:   </system.serviceModel>
  10: </configuration>

由于进行服务调用的客户端终结点并没有一个相应的表示序列号的<sn>地址报头,在进行服务调用的时候没有显式地将序列号作为报头添加到请求消息中,所以针对服务端来说,这是一个不被许可的客户端。客户端运行后将会抛出如下图所示的EndpointNotFoundException异常。(S201)

二、为请求消息添加地址报头(实例2

假设服务端将作为序列化的GUID分发给经过许可的客户端,那么它就可以将其作为客户端终结点的地址报头定义到配置文件中,也可以在消息发送之前将序列化作为报头添加到请求消息中。第一种方式比较简单,我们来演示第二种方式。我们采用如下的代码进行服务调用,在调用之前将序列号作为报头添加到请求消息的报头列表中。在这种情况下,服务嗲用将会顺利进行。(S202)

   1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice"))
   2: {
   3:     ICalculator calculator = channelFactory.CreateChannel();
   4:      using (OperationContextScope contextScope = new OperationContextScope(calculator as IClientChannel))
   5:     {
   6:         string sn = "{DDA095DA-93CA-49EF-BE01-EF5B47179FD0}";
   7:         string ns = "http://www.artech.com/";
   8:         AddressHeader addressHeader = AddressHeader.CreateAddressHeader("sn", ns, sn);
   9:         MessageHeader messageHeader = addressHeader.ToMessageHeader();
  10:         OperationContext.Current.OutgoingMessageHeaders.Add(messageHeader);
  11:         Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, calculator.Add(1, 2));
  12:     }
  13: }

输出结果:

   1: x + y = 3 when x = 1 and y = 2

之所以在请求消息中不存在于终结点地址报头相匹配的报头会导致抛出EndpointNotFoundException异常,原因在于按照默认的消息筛选机制找不到匹配的终结点。为了解决这个问题,对于客户端来说,可以通过在消息中添加相应的报头满足服务端筛选的条件;而对于服务端来说,则可以改变为了实现终结点的选择而采用消息筛选机制。总之一句话,只要服务端能够根据匹配的终结点就可以抑制EndpointNotFoundException异常的抛出。

三、改变地址筛选策略(实例3

我们可以在服务类型上应用ServiceBehaviorAttribute特性并为AddressFilterMode属性进行相应的设置来改变针对终结点地址的筛选机制。如下面的代码所示,AddressFilterMode属性是一个类型为AddressFilterMode的枚举。三个枚举项(Exact、Prefix和Any)分别代表三种地址匹配的策略,即精确匹配,基于前缀匹配和匹配任意地址。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     [DefaultValue(0)]
   5:     public AddressFilterMode AddressFilterMode { get; set; }
   6: }
   7: public enum AddressFilterMode
   8: {
   9:     Exact,
  10:     Prefix,
  11:     Any
  12: }

其中Exact和Prefix都需要进行地址报头的匹配,而Any则不需要。从应用在AddressFilterMode的DefaultValueAttribute特性可以看出,该属性的默认值是Exact,所以在默认的情况下采用的是针对地址的精确匹配。那么如果我们在CalculatorService上应用ServiceBehaviorAttribute特性并将AddressFilterMode设置为Any,即使请求消息中不具有相关的报头,服务调用也会成功。(S203)

   1: [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
   2: public class CalculatorService : ICalculator
   3: {
   4:     //省略成员
   5: }

本例虽然名为通过“通过地址报头实现对客户端的授权”,其实在真正的应用中我们不会通过这样的方式对服务授权。因为终结点的地址报头是元数据的一部分,客户端在获取服务发布的元数据时会将地址报头一并获取。

EndpointAddress——不只是一个Uri[上篇] EndpointAddress——不只是一个Uri[下篇]

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术博客

设计模式之四(抽象工厂模式第一回合)

首先关于抽象工厂模式的学习,我们需要慢慢的,由浅入深的进入。不能单刀直入,否则可能达不到预期学明白的目标。

1121
来自专栏Java3y

过滤器第一篇【介绍、入门、简单应用】

什么是过滤器 过滤器是Servlet的高级特性之一,也别把它想得那么高深,只不过是实现Filter接口的Java类罢了! 首先,我们来看看过滤器究竟Web容器的...

2725
来自专栏编程思想之路

WiFiAp探究实录--功能实现与源码分析

Android虐我千百遍,我待Android如初恋。 ——————编辑于2017-08-02——————— wifi热点说的是wifiAp相...

1.6K9
来自专栏技术博文

URI与URL的区别

Web上可用的每种资源 - HTML文档、图像、视频片段、程序等 - 由一个通过通用资源标志符(Universal Resource Identifier, 简...

3256
来自专栏玩转JavaEE

SpringBoot+Vue前后端分离,使用SpringSecurity完美处理权限问题(二)

当前后端分离时,权限问题的处理也和我们传统的处理方式有一点差异。笔者前几天刚好在负责一个项目的权限管理模块,现在权限管理模块已经做完了,我想通过5-6篇文章,来...

1K9
来自专栏alexqdjay

分布式共享Session之SpringSession源码细节

3265
来自专栏Java后端生活

JavaWeb(十六)Listener监听器

监听器:专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。

1644
来自专栏别先生

基于jsp+servlet图书管理系统之后台用户信息插入操作

前奏:   刚开始接触博客园写博客,就是写写平时学的基础知识,慢慢发现大神写的博客思路很清晰,知识很丰富,非常又价值,反思自己写的,顿时感觉非常low,有相当长...

3766
来自专栏服务端技术杂谈

Motan源码阅读--ShutDownHook使用

任何一个中间件系统,都需要有个“平滑部署,平滑下线”的功能。 如果基于Java开发,往往采用ShutDownHook去做这件事情。 比如我们在tomcat关闭时...

1122
来自专栏黑泽君的专栏

day53_BOS项目_05

定区可以将取派员、分区、客户信息关联到一起。 页面:WEB-INF/pages/base/decidedzone.jsp

864

扫码关注云+社区

领取腾讯云代金券