首页 > 开发 > Xml > 正文

可靠的 XML Web Service (2)

2020-02-03 14:30:19
字体:
来源:转载
供稿:网友
标头的作用
在查看代码之前,我们需要了解一下 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 不会真的成为服务背后的可靠性层。<微笑>



发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表