针对 SOAP 编码的参数

 

蒂姆·埃瓦尔德
Microsoft Corporation

2002 年 10 月

适用于:
   Web 服务规范 (SOAP、WSDL)

总结: 本文解释了为什么 SOAP 编码(也称为“第 5 部分编码”)是 SOAP 过去一个阴影,在 Web 服务的未来中没有一席之地。 ) (11 个打印页

目录

简介
SOAP 和 Web 服务的演变
问题的核心
具体示例
解决编码问题
未来

简介

SOAP 是基本 Web 服务协议堆栈的基石。 SOAP 规范将 XML 消息的使用正式化为通信手段。 它定义了一个扩展性模型、一种表示协议和应用程序故障的方法、通过 HTTP 发送消息的规则,以及将 RPC 调用映射到 SOAP 消息的准则。 使用标准方法来执行这些操作非常有用。 如果没有,每个想要通过 HTTP 发送 XML 消息的开发人员必须创建自己的临时解决方案来解决这些问题,这使得互操作性相当困难。 虽然 SOAP 规范提供的大部分内容都是好的,但有一件事是不好的:SOAP 编码。 SOAP 编码(有时称为“第 5 节编码”,在定义 SOAP 1.1 规范的部分之后)是 SOAP 过去的阴影,在 Web 服务的未来中没有位置。 本文从一些历史开始解释原因。

SOAP 和 Web 服务的演变

编写第一个 SOAP 规范时,Web 服务背后的概念仍处于起步阶段。 人员计划使用 SOAP 来更好地将分布式对象技术(如 DCOM、CORBA 和 RMI)与本机 Internet 技术(如 XML 和 HTTP)集成。 目标是生成管道,生成和使用基于 XML 的消息,而不是分别 () NDR、CDR 和 JRMP 等每种技术所青睐的各种二进制消息格式。

为了使分布式应用程序中的客户端和服务器能够生成和使用消息,他们需要知道这些消息的外观。 大多数分布式对象系统依赖于编译的代理/存根/主干代码和元数据 (的二进制表示形式(例如 COM 类型库、CORBA 接口存储库或 Java .class 文件) )来提供信息。 SOAP 没有更改这一点。 SOAP 规范的作者假定应用程序开发人员将确保客户端和服务器具有正确处理 SOAP 消息所需的任何信息。

但是,SOAP 作者意识到,如果他们不打算定义描述消息的常用方式,他们至少应该为如何将常见的面向对象的编程构造映射到 XML 提供一些指导。 他们无法使用 XML 架构 (XSD) 来解决此问题;它仍然远未完成。 因此,他们基于非类型化结构的图定义了数据模型。 然后,他们编写了 SOAP 编码规则,该规则说明了如何将 SOAP 数据模型的实例序列化为 SOAP 消息。 由 SOAP 实现者将自己的技术映射到 SOAP 数据模型。

随着 SOAP 在行业中的牵引力不断增强,新的要求也出现了。 开发人员希望下载 SOAP 服务器消息格式的说明,以便他们可以生成客户端来与他们对话。 由于想要提供此类说明的服务器无法对用于生成客户端的技术做出任何假设,因此使用现有元数据格式(如类型库)公开消息说明不起作用。 解决方案是使用与 SOAP 本身一样可移植的通用元数据格式,即 WSDL。 WSDL 描述了 Web 服务使用 portTypes 支持的行为。 portType 是操作的集合。 操作根据消息进行定义。 消息根据 XML 架构进行定义。 今天,SOAP 和 WSDL 在大多数人的脑海中有着密不可分的联系:它们与 UDDI 一起定义基本的 Web 服务构建基块。

问题的核心

WSDL 的作者意识到 XSD 在描述 SOAP 消息方面发挥着重要作用,因此接受它 (尽管他们为替代项和) 敞开了大门。 意识到已有实现 SOAP 编码方案的工具包,WSDL 规范的作者也不得不接受它。 他们想出的解决方案是根据 XML 架构构造定义消息,然后根据需要允许绑定对其应用 SOAP 编码。

绑定定义了调用 portType 定义的操作所需的具体详细信息。 (这不是一个新想法。例如,COM 类通常通过 vtable 绑定和 IDispatch 绑定公开其方法。同样,CORBA 类的方法通常通过静态存根或动态调用接口提供。) 创建将 portType 的操作映射到通过 HTTP 发送的 SOAP 消息的 WSDL 绑定时,必须指定 SOAP 消息是包含构造操作所使用的架构的文本实例还是编码实例。 如果选择“literal”,则表示 XML 架构构造 WSDL 定义引用是 SOAP 消息正文中显示内容的具体规范。 如果选择“已编码”,则表示 XML 架构构造 WSDL 定义引用的是 SOAP 消息正文中将显示的内容的抽象规范;这些可以通过应用 SOAP 编码定义的规则来具体化。 (WSDL 规范也允许其他编码方案,但很少使用替代方法。)

这让我们来到问题的核心。 如前所述,SOAP 编码方案将 SOAP 数据模型序列化为 XML。 当一个将信息表示为非类型化结构的图,而另一个将信息表示为类型化元素的树时,SOAP 数据模型的编码方案如何应用于抽象 XML 架构定义? 遗憾的是,无论是定义 SOAP 编码) 的 SOAP 规范 (,还是将 SOAP 规范应用于 XML 架构定义的 WSDL 规范 () 回答此问题。 事实上,没有任何规范可以描述这一点的含义及其工作原理。 这是一个问题。

具体示例

到目前为止,我的论点是非常理论性的,所以让我提供一个示例,以帮助使其更实用。 对于名为 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,其中包含 Distance 运算。 它还定义了使用 SOAP 编码的 Geometry portType 的绑定。

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

 <wsdl:types>
   <xsd:schema targetNamespace=
           "https://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="https://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=
         "https://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body soap:encodingStyle=
         "https://schemas.xmlsoap.org/soap/encoding/">
    <ns:Distance xmlns:ns=
         "https://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 类型的架构定义匹配,这似乎很清楚。 每个元素都有一个包含两个元素的序列, 即 xy,其值为整数。 但这一结论没有充分基于。 虽然 SOAP 数据模型使用 XML 架构简单类型 (xsd:int (例如) )来描述单个值(如 xy),但它不使用 XML 架构复杂类型来描述结构化数据 (这就是为什么我所说的 SOAP 数据模型基于非类型化结构) 。 如果 SOAP 数据模型不使用 XML 架构复杂类型,并且 SOAP 编码基于 SOAP 数据模型,那么最好认为 p1p2 是复杂类型 Point 的 SOAP 编码实例?

你可能认为我在分裂头发 - 毕竟, p1p2 看起来很像 。 因此,现在,请考虑在调用 Distance 操作时将同一实例作为两个参数传递的客户端,如:

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

下面是客户端的序列化请求消息。 在这种情况下,客户端对“多引用访问器”使用 SOAP 编码方案,即单个 Point 实例 (一个) 作为 Distancep1p2 参数引用。

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

在这种情况下,很明显 ,p1p2Point 类型的架构定义不匹配。 事实上,他们甚至不接近。 它们都有一个未定义的 href 属性,两者都没有必需的子元素 xyDistance 操作的输入消息的定义表明 p1p2Point 的实例,但显然,在 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="https://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:tns="https://www.gotdotnet.com/team/tewald/sample"
 targetNamespace=
           "https://www.gotdotnet.com/team/tewald/sample">

 <wsdl:types>
   <xsd:schema targetNamespace=
           "https://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="https://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 操作的任何歧义, p1p2Point 的实例。

此方法是否仍使用 SOAP 编码? 这取决于你的观点。 它基本上与 SOAP 编码执行的操作相同,但不尝试将 SOAP 编码规则直接应用于架构,这是错误的。 主要优点是 WSDL 中包含的 XML 架构定义准确地描述了 portType 和绑定所需的消息。 这使得实现在处理消息之前检查消息是否正确的服务要简单得多。

我们需要采取几个步骤来获得对此方法的广泛支持。 首先,我们需要定义标准的全局属性,用于表示对序列化图中节点的引用。 在我的示例中,我定义了本地 IDhref 属性作为 Point 类型的一部分。 这种方法的问题在于,在序列化图中使用的每个类型都必须定义具有相同语义的等效属性。 更糟的是,工具必须假定具有 idhref 属性的任何类型都希望它们用于描述图形。 如果我们创建一些全局属性并定义引用它们的类型,则无需为每个类型定义属性,并且工具包只需识别一对属性。 (WS-I 基本配置文件工作组 已经对此进行了工作,作为其帮助解决 Web 服务互操作性问题的一部分,尽管在撰写本文时尚未发布该工作。)

一旦存在标准全局属性,工具包实施者就可以采用它们。 基本上,他们必须做出两项更改。 首先,必须更新其 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 服务堆栈将大大更易于实现和使用。