CSDN博客

img xuandme000

关于 SOAP 编码的论点

发表于2004/9/28 14:30:00  1283人阅读

关于 SOAP 编码的论点

发布日期: 4/1/2004 | 更新日期: 4/1/2004

Tim Ewald

Microsoft Corporation

2002 年 10 月

摘要:本文阐述了 SOAP 历史所遗留下来的 SOAP 编码(又称“Section 5 编码”)为什么在 Web 服务的未来发展中没有立足之地 

简介 简介
SOAP 的发展历程和 Web 服务 SOAP 的发展历程和 Web 服务
问题的核心 问题的核心
具体示例 具体示例
解决编码问题 解决编码问题
未来 未来

简介

SOAP 是基本 Web 服务协议栈的基石。 SOAP 规范正式确认将 XML 消息作为一种通信方法来使用。 它定义了一种扩展性模型、一种表示协议和应用程序错误的方法、通过 HTTP 发送消息的多个规则以及将 RPC 调用映射到 SOAP 消息的多个原则。 使用标准的方法来进行这些操作是很有益的。 否则,每个想要通过 HTTP 发送 XML 消息的开发人员将不得不针对这些问题创建自己的特定解决方案,从而使互操作性很难以实现。 虽然 SOAP 规范的大多数内容对我们来说都是好的,但是其中有一项却不然: SOAP 编码。 SOAP 编码(根据该编码方法的定义在 SOAP 1.1 规范中所处的部分,有时被称为“Section 5 编码”)是 SOAP 历史所遗留的一个问题,它在 Web 服务的未来中没有立足之地。 本文将解释其中的原因,让我们首先回顾一下历史。

SOAP 的发展历程和 Web 服务

在编写第一个 SOAP 规范时,Web 服务背后的各种概念尚处在萌芽阶段。 人们计划利用 SOAP 将分布式对象技术(例如,DCOM、CORBA 和 RMI)与本机的 Internet 技术(例如,XML 和 HTTP)更好地集成起来。 目标是要建立一种管道,以便创建并使用基于 XML 的消息,而不使用由不同的技术(分别是 NDR、CDR 和 JRMP)支持的各种二进制消息格式。

为了让分布式应用程序中的客户端和服务器创建并使用消息,它们需要知道这些消息应该是什么样子。 大多数分布式对象系统依赖于编译的 proxy/stub/skeleton 和元数据的二进制表示形式(例如,COM 类型库、CORBA 接口库或 Java .class 文件)的组合来提供这些信息。 SOAP 没有改变这一点。 SOAP 规范的作者们假定应用程序开发人员将确保客户端和服务器已经具有正确处理 SOAP 消息所需的任何信息。

然而,SOAP 作者们认识到如果自己不打算定义一种通用方法来对消息进行描述,那么至少也应就如何将通用的面向对象的编程结构映射到 XML 提供一些指导。 他们不能使用 XML 架构 (XSD) 来解决这个问题,因为那时 XSD 还远未完成。 于是,他们基于非类型化结构的图定义了一种数据模型。 然后,他们编写了 SOAP 编码规则,其中解释了如何将 SOAP 数据模型的实例序列化为 SOAP 消息。 而将具体技术映射到 SOAP 数据模型的工作则留给实现者来完成。

随着 SOAP 日渐获得业界的重视,一个新的要求出现了。 开发人员想要下载有关 SOAP 服务器的消息格式的说明,以便能够创建客户端来与其对话。 因为想要提供这种说明的服务器不能对用于创建客户端的技术进行任何假设,所以通过现有的元数据格式(例如,类型库)来公开消息说明的做法是行不通的。 此问题的解决方案就是使用一种跟 SOAP 自身一样可移植的通用元数据格式,也就是 WSDL。 WSDL 利用 portType 来说明某项 Web 服务所支持的行为。 PortType 是操作的集合。 操作是以消息的形式定义的。 消息是以 XML 架构的形式定义的。 如今,在大多数人看来,SOAP 和 WSDL 相当紧密地联系在一起;它们连同 UDDI 一起确立了基本的 Web 服务构造块。

问题的核心

WSDL 的作者认识到 XSD 在说明 SOAP 消息方面扮演了重要角色,因此采用了 XSD(但他们也允许使用其他替代方案)。 他们还意识到有些工具包已经实现了 SOAP 编码方案,所以觉得也有必要采用 SOAP 编码。 他们提出的解决方案就是根据 XML 架构结构来定义消息,然后,如果需要的话,允许利用绑定向这些消息应用 SOAP 编码。

绑定定义用户在调用由某个 portType 定义的操作时需要知道的一些具体细节。 (这并不是一种新概念。 例如,COM 类就经常通过 vtable 绑定和 IDispatch 绑定来公开其方法。 类似地,通常也可以通过静态 stub 或动态调用接口来提供 CORBA 类的方法。) 当创建可将某个 portType 的操作映射到通过 HTTP 发送的 SOAP 消息的 WSDL 绑定时,您必须指明 SOAP 消息中所包含的该 portType 的操作所使用的架构结构的实例是文字的还是编码的。 如果您选择“文字的”,就意味着 WSDL 定义所引用的 XML 架构结构是 SOAP 消息主体中将显示的内容的具体规范。 如果您选择“编码形式”,则意味着 WSDL 定义所引用的 XML 架构结构是 SOAP 消息正文中将显示的内容的抽象规范;通过应用由 SOAP 编码定义的规则可将这些抽象规范变为具体规范。 (WSDL 规范也允许其他编码方案,但这些替代方案即使得到过使用,那也是很罕见的。)

这就给我们揭示了问题的核心。 正如我前面所解释的,SOAP 编码方案将 SOAP 数据模型序列化为 XML。 SOAP 数据模型以非类型化结构图来表示信息,而 XML 架构以类型化元素树来表示信息,那么如何才能将 SOAP 数据模型的编码方案应用到抽象的 XML 架构定义呢? 但是,SOAP 规范(定义了 SOAP 编码)和 WSDL 规范(将 SOAP 编码应用到 XML 架构定义)都没有回答这个问题。 实际上,也没有任何规范说明这意味着什么以及如何实施。 而这就是问题所在。

具体示例

到目前为止,我的论点一直是很理论化的,现在让我给出一个示例以便更实际地加以说明。 考虑以下用于一个名为 Distance 操作的伪代码,该代码用来测量两点之间的距离。

class Point
{
  public Point() {}
  public Point(int x, int y) { this.x = x; this.y = y; }
  public int x;
  public int y;
}

float Distance(Point p1, Point p2)
{
  ???… // apply the Pythagorean Theorem
}

以下是一个 WSDL 文档,它描述一个名为 Geometry 的 portType,该 portType 包含 Distance 操作。 它还定义一个针对Geometry portType 的绑定,该绑定使用 SOAP 编码。

<wsdl:definitions
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:tns="http://www.gotdotnet.com/team/tewald/sample"
 targetNamespace=
           "http://www.gotdotnet.com/team/tewald/sample">

 <wsdl:types>
   <xsd:schema targetNamespace=
           "http://www.gotdotnet.com/team/tewald/sample">

     <!-- Point type used by Distance operation -->

     <xsd:complexType name="Point">
       <xsd:sequence>
         <xsd:element name="x" type="xsd:int" />
         <xsd:element name="y" type="xsd:int" />
       </xsd:sequence>
     </xsd:complexType>

   </xsd:schema>
 </wsdl:types>

 <!-- RPC style message definitions -->

 <wsdl:message name="DistanceInput">
   <wsdl:part name="p1" type="tns:Point" />
   <wsdl:part name="p2" type="tns:Point" />
 </wsdl:message>

 <wsdl:message name="DistanceOutput">
   <wsdl:part name="result" type="xsd:float" />
 </wsdl:message>

 <!-- Geometry portType -->

 <wsdl:portType name="Geometry">
   <wsdl:operation name="Distance">
     <wsdl:input message="tns:DistanceInput" />
     <wsdl:output message="tns:DistanceOutput" />
   </wsdl:operation>
 </wsdl:portType>

 <!-- Binding for Geometry portType that
      uses SOAP encoding -->

 <wsdl:binding name="GeometryBinding" type="tns:Geometry">
   <soap:binding style="rpc"
    transport="http://schemas.xmlsoap.org/soap/http" 
   <wsdl:operation name="Distance">
     <soap:operation soapAction="" style="rpc" />
     <wsdl:input message="tns:DistanceInput">
       <soap:body
        namespace="http://www.gotdotnet/team/tewald/sample"
        use="encoded" />
     </wsdl:input>
     <wsdl:output message="tns:DistanceOutput">
       <soap:body
        namespace="http://www.gotdotnet/team/tewald/sample"
        use="encoded" />
     </wsdl:output>
   </wsdl:operation>
 </wsdl:portType>

</wsdl:definitions>

假设您想要实现一项可公开此 portType 和绑定的服务。 想要您的实现检查其从客户端收到的消息是否与 WSDL 所指定的格式匹配。 如果不匹配,您可以丢弃这些消息并返回一个错误,而不用进行任何其他操作。 那么,正确的消息是由哪些内容组成的呢?

考虑这样一种情况,客户端将两个不同的 Point 实例作为参数传递给Distance 操作,如下所示:

Point one = new Point(10, 20);
Point two = new Point(100, 200);
float f = proxy.Distance(one, two);

以下是该客户端的已序列化的请求消息。

<soap:Envelope xmlns:soap=
         "http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body soap:encodingStyle=
         "http://schemas.xmlsoap.org/soap/encoding/">
    <ns:Distance xmlns:ns=
         "http://www.gotdotnet.com/team/tewald/samples">
      <p1>
        <x>10</x>
        <y>20</y>
      </p1>
      <p2>
        <x>100</x>
        <y>200</y>
      </p2>
    </ns:Distance>
  </soap:Body>
</soap:Envelope>

乍一看来,好像很清楚,p1p2 (指定为 ns:Distance 的形参)这两个实例都与 WSDL 文档中 Point 类型的架构定义匹配。 它们各自都有一个具有两个元素(x y )的序列,这两个元素的值都是整数。 但是这个结论并没有充分的依据。 虽然 SOAP 数据模型使用 XML 架构简单类型(例如,xsd:int)来说明单个值(例如,xy),但是它并不使用 XML 架构复杂类型来说明结构化数据(正因如此,所以我说 SOAP 数据模型是基于非类型化结构的)。 如果 SOAP 数据模型不使用 XML 架构复杂类型而 SOAP 编码是基于 SOAP 数据模型的,那么下结论说 p1p2 就是复杂类型 Point 的 SOAP 编码的实例,这样说是否是明智之举吗?

您可能认为我正在作无益的分析 — 毕竟,p1p2 看起来确实极像 Point。 那么现在考虑这样一种情况,客户端在调用 Distance 操作时传递同一个Point 实例作为这两个参数,如下所示:

Point one = new Point(10, 20);
float f = proxy.Distance(one, one);

以下是该客户端的已序列化的请求消息。 在这种情况下,该客户端使用 SOAP 编码方案作为“多引用访问器”,就是说,单个 Point 实例 (one) 被引用为Distance 的两个参数(p1 p2)。

<soap:Envelope xmlns:soap=
         "http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body soap:encodingStyle=
         "http://schemas.xmlsoap.org/soap/encoding/">
    <ns:Distance xmlns:ns=
         "http://www.gotdotnet.com/team/tewald/samples">
      <p1 HREF="#id1"/>
      <p2 HREF="#id1"/>
    </ns:Distance>
    <ns:Point id="id1"
     xmlns:ns="http://www.gotdotnet.com/team/tewald/samples">
      <x>10</x>
      <y>20</y>
    </ns:Point>
  </soap:Body>
</soap:Envelope>

在这种情况下,很明显,p1p2 并不与 Point 类型的架构定义匹配。 实际上,它们相差甚远。 它们各自具有一个未定义的 href 属性,而都不具有必要的子元素xyDistance 操作的输入消息的定义表明 p1p2 都是 Point 的实例,但是很清楚,在 SOAP 编码面前,有些情况并非如此。

那么,结果会怎样呢? 元素 p1p2 有时看起来像 Point 的已序列化的实例,有时又不像。从理论上讲,SOAP 编码方案应该告诉我们 p1p2 应该是什么样子的,除非没有确定的方法将 SOAP 编码方案应用到通过 XML 架构指定的类型(例如,Point)。 如果缺少这样一种确定的方法,那么很难实现这样一种服务器 — 公开 Geometry portType 和绑定并确保该服务器收到的所有消息都正确。

解决编码问题

目前,您可能会认为这可不是您的问题,因为您并不是从头开始实现 Web 服务。 相反,您使用 Web 服务工具包并依靠它来替您处理这些细节。 但这样并不能解决这个问题;只不过将问题从自己身上推到工具包实现者的身上,他们必须设法搞清楚应该怎么办。迄今为止,他们在提供对 SOAP 编码的支持方面做的还不错(尽管还存在我前面所述的问题),但代价是高昂的。 大多数工具包针对文字的和编码的绑定分别使用不同的代码路径;从本质上说,就是实现两个封送层,一个用于文字的绑定,另一个用于编码的绑定。 那些曾经耗时解决此问题的开发人员普遍认为他们需要一个更好的解决方案。 从某种角度来讲,我们必须调和 SOAP 编码和 XML 架构。

令人庆幸的是,我们有一个相当直接的解决方案。 SOAP 编码规则定义了 SOAP 数据模型到 XML 消息的转换。 将 SOAP 编码应用到 XML 架构中的定义则没有什么意义,因为这二者之间没有任何确定的关系。 但是如果编写一种架构来说明 SOAP 编码所产生的 XML 消息,结果会怎样呢?

以下是针对 Geometry portType 改写后的 WSDL 定义,其中就使用了这种方法。

<wsdl:definitions
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:tns="http://www.gotdotnet.com/team/tewald/sample"
 targetNamespace=
           "http://www.gotdotnet.com/team/tewald/sample">

 <wsdl:types>
   <xsd:schema targetNamespace=
           "http://www.gotdotnet.com/team/tewald/sample">

     <!-- Point type used by Distance operation,
          note use optional attributes and
          element content -->

     <xsd:complexType name="Point">
       <xsd:sequence minOccurs="0">
         <xsd:element name="x" type="xsd:int" />
         <xsd:element name="y" type="xsd:int" />
       </xsd:sequence>
       <xsd:attribute name="id"
            type="xsd:ID" use="optional" />
       <xsd:attribute name="href"
            type="xsd:anyURI" use="optional" />
     </xsd:complexType>

   </xsd:schema>
 </wsdl:types>

 <!-- RPC style message definitions -->

 <wsdl:message name="DistanceInput">
   <wsdl:part name="p1" type="tns:Point" />
   <wsdl:part name="p2" type="tns:Point" />
 </wsdl:message>

 <wsdl:message name="DistanceOutput">
   <wsdl:part name="result" type="xsd:float" />
 </wsdl:message>

 <!-- Geometry portType -->

 <wsdl:portType name="Geometry">
   <wsdl:operation name="Distance">
     <wsdl:input message="tns:DistanceInput" />
     <wsdl:output message="tns:DistanceOutput" />
   </wsdl:operation>
 </wsdl:portType>

 <!-- Binding for Geometry portType that
      uses SOAP encoding -->

 <wsdl:binding name="GeometryBinding" type="tns:Geometry">
   <soap:binding style="rpc"
    transport="http://schemas.xmlsoap.org/soap/http" 
   <wsdl:operation name="Distance">
     <soap:operation soapAction="" style="rpc" />
     <wsdl:input message="tns:DistanceInput">
       <soap:body
        namespace="http://www.gotdotnet/team/tewald/sample"
        use="literal" />
     </wsdl:input>
     <wsdl:output message="tns:DistanceOutput">
       <soap:body
        namespace="http://www.gotdotnet/team/tewald/sample"
        use="literal" />
     </wsdl:output>
   </wsdl:operation>
 </wsdl:portType>

</wsdl:definitions>

Point 类型更新的定义中包括对两个新属性 idhref 的声明,正如该类型的元素内容一样,这两个属性是可选的。 对 Point 所作的这些更改允许在对多引用数据进行序列化时使用实例(相当于 SOAP 编码所产生的那些实例)。 一个 Point 实例可以包括一个 id 属性与 xy 子元素,或者包含一个 href 属性,但不带子元素。 前者表示 Point 的一个实例;而后者表示对 Point 的引用。 因为 XML 架构定义已经进行了等效于 SOAP 编码的操作,从而没有必要向其应用编码规则,所以 Geometry portType 的绑定已被更新为使用文字的架构类型(encodingStyle 属性已被删除),从而消除关于 Distance 操作的参数 p1 p2 是否为 Point 的实例的任何多义性。

这种方法仍然使用 SOAP 编码吗? 这取决于您怎么看。 它基本上执行与 SOAP 编码一样的操作,但并不尝试将 SOAP 编码规则直接应用到架构(这种直接应用是不好的)。 关键的好处在于,WSDL 中所包含的 XML 架构定义准确地说明了 portType 和绑定所需的消息。 这样一来,实现一种可在处理各消息之前检查消息是否正确的服务要简单得多。

要使这种方法获得广泛支持,我们需要采取几个步骤。 首先,我们需要定义一些标准的全局属性,以便表示对序列化图中节点的引用。 在我的示例中,我定义本地 idhref 属性作为 Point 类型的一部分。 这种方法的问题在于,在序列化图中可能使用的每种类型都必须利用完全相同的语义来定义等效的属性。 更糟的是,工具将不得不假定任何具有 id href 属性的类型都计划用于说明一个图。 如果创建一些全局属性并定义一些引用这些属性的类型,我们就不必针对每个类型定义其属性,而且工具包将只需识别一对属性。 (作为其帮助解决 Web 服务互操作性问题的努力的一部分,WS-I 基本概要工作组已经针对这一点完成了一些工作,但在撰写本文时这些工作尚未公开。)

只要存在标准的全局属性,工具包实现者就可以采用这些属性。 基本上,他们必须进行两处修改。 首先,他们必须更新其 WSDL 工具,以便产生并使用包含可能用于表示序列化图节点的任何类型的参考属性的 XML 架构。 然后,他们必须更新其运行时管道,以便将图序列化和反序列化为能使用这些属性的 XML 消息。

未来

包括我自己在内的很多人都相信 SOAP 编码的废止是不可避免的。 W3C XML 协议工作组关于 SOAP 1.2 规范的当前草案将对 SOAP 编码的支持变为一个可选项(就是说,工具包可以声明 SOAP 1.2 遵从性,而不用支持 SOAP 编码),WS-I 基本概要工作组关于其互操作性指南的当前草案不允许在 SOAP 1.1 中使用 SOAP 编码,而 W3C Web 服务说明工作组在其 WSDL 1.2 规范的最新工作草案中选择不再支持编码。

这些更改要反映在工具包中将需要一段时间,首先我们必须确定一种架构友好的方法以便对图进行序列化。 然后必须对工具包进行更新。 这将花费一些时间,但这种等待是值得的。 最后,Web 服务栈的实现和使用将会简单得多。

阅读全文
0 0

相关文章推荐

img
取 消
img