内容


Web 服务编程技巧和窍门

实现隐式和显式 SOAP 消息头

学习 SOAP 消息头之间的区别以及如何创建自己的 SOAP 消息头

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Web 服务编程技巧和窍门

敬请期待该系列的后续内容。

此内容是该系列的一部分:Web 服务编程技巧和窍门

敬请期待该系列的后续内容。

SOAP 规范描述了 SOAP 信封可以包括一个可选的消息头部分。该消息头用来传输并不属于实际消息的有效载荷部分的数据。WSDL 规范定义了如何将 SOAP 消息头数据声明为 Web 服务定义的一部分。在 WSDL 定义中有两种定义 SOAP 消息头的方式:显式隐式消息头 。

SOAP 消息头的样式

SOAP 消息头的典型应用是用来传送上下文的数据。例如,如果消息中包括数字签名,那么此签名将最有可能在 SOAP 消息头中传送。另一个例子是用于 Web 服务,这些服务支持与客户端之间进行某些形式的会话。一旦建立了这样的会话,它们就要求应该将特定的标识符与每个请求一起发送。 WS-AtomicTransaction 规范(参阅 参考文献) 同时还描述了一种非常类似的机制, 这种机制用于在多个 Web 服务之间运行交互的协调性序列。

WSDL 规范提供了两种不同的识别 SOAP 消息头字段用法的方法。在显式消息头中,用户将消息头的所有信息添加给服务的 portType 了。它作为附加的参数显示给客户端。这种样式的优点在于客户端能够直接将所有的信息传送给该服务。其不足之处就是它经常将服务的外部接口和与它的业务意图毫不相干的信息群集在一起。

下面是使用隐式消息头的好处:消息头信息并不是 portType 的一部分,因此不会影响服务的功能性接口。另一方面,隐式消息头很难作为标题以编程的方式处理。

在更加深入了解有关编程方面的详细信息之前, 我们来看一看这些不同的样式是如何定义的。

WSDL 中 SOAP 消息头的绑定类型

描述 SOAP 头不同演示的最简单方式就是从实例开始讲述。下面清单 1 中的 WSDL 摘录是摘自以前的解释 SOAP 消息头用法的一篇文章:

清单 1. WSDL 中 SOAP 消息头的绑定
<wsdl:definitions targetNamespace="http://soapheader.ibm.com" ...>
 <wsdl:types ...>
  <schema elementFormDefault="qualified" ...>
	...
      <element name="quote_timestamp" type="xsd:dateTime" />
  </schema>
 </wsdl:types>
<wsdl:binding name="StockServiceSoapBinding" type="intf:StockService">
      <wsdlsoap:binding style="document" transport=
      "http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name="getLastSellPrice">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="getLastSellPriceRequest">
            <wsdlsoap:header message="intf:getLastSellPriceRequest" part=
            "request_header" use="literal"/>
            <wsdlsoap:body parts="parameters" use="literal"/>
         </wsdl:input>
         <wsdl:output name="getLastSellPriceResponse">
            <wsdlsoap:body use="literal"/>
         </wsdl:output>
      </wsdl:operation>
   </wsdl:binding>
...
</wsdl:definitions>

您能够看到在 WSDL 文件的绑定部分中特别的位置上使用了一个名为 <wsdlsoap:header> 的元素。它包含在 <wsdl:input> 元素中,该元素告诉用户在该处存在 SOAP 消息头片断,可作为操作的部分请求消息。<wsdlsoap:header> 元素的内容能够识别在消息头中传送的消息部分。

这样做显得非常简洁易懂,但这是显式消息头还是隐式消息头?显然,从上面的摘录来看,不能准确区分。它其实可以是两种方式的任意一种,这是因为:消息头绑定定义了消息 intf:getLastSellPriceRequest 中名为 request_header 的部分,而它又是 SOAP 信封的消息头部分。这种消息头样式依赖于此消息部分是否被用于 Web 服务的 portType 中。让我们详细地研究一下这两种情况。

显式消息头

如果消息头是服务 <portType> 的一部分,那么就可以调用消息头定义显式。换句话说,名为 request_header 的消息部分必需在 portType 中使用,如 清单 2 所示。

清单 2. WSDL 中的显式 SOAP 消息头
<wsdl:message name="getLastSellPriceRequest">
      <wsdl:part element="intf:getLastSellPrice" name="parameters"/>
      <wsdl:part name="request_header" element="intf:quote_timestamp"/>
   </wsdl:message>
   <wsdl:message name="getLastSellPriceResponse">
      <wsdl:part element="intf:getLastSellPriceResponse" name="parameters"/>
   </wsdl:message>
   <wsdl:portType name="StockService">
      <wsdl:operation name="getLastSellPrice">
         <wsdl:input message="intf:getLastSellPriceRequest" name=
         "getLastSellPriceRequest"/>
         <wsdl:output message="intf:getLastSellPriceResponse" name=
         "getLastSellPriceResponse"/>
      </wsdl:operation>
   </wsdl:portType>

请注意名为 getLastSellPriceRequest 的消息包括两部分。一部分加入到 SOAP 请求消息的消息体部分,另一部分加入到消息头中。清单 3 显示了 WSDL 文件的相关部分,WSDL 文件显示了这两个部分:

清单 3. WSDL - SOAP 绑定中的显式 SOAP 头
<wsdl:input name="getLastSellPriceRequest">
      <wsdlsoap:header message="intf:getLastSellPriceRequest" part=
      "request_header" use="literal"/>
      <wsdlsoap:body parts="parameters" use="literal"/>
</wsdl:input>

<portType> 元素定义了 Web 服务的外部接口。它定义了哪些数据要作为请求消息的一部分发送。如果这些请求数据在该请求消息的 SOAP 消息头部分中传送,那么用户就可以调用这个显式消息头。该操作同样分别适用于部分(或者全部)的响应消息被定义为头元素的情况。

隐式消息头

这种情况要简单些,是这样吗?如果在消息头中传送的消息部分没有在 <portType> 元素中显示,那么它就是一个隐式 消息头。清单 4 显示了这种消息头:

清单 4. WSDL 中的隐式 SOAP 头
<wsdl:message name="getLastSellPriceRequest">
      <wsdl:part element="intf:getLastSellPrice" name="parameters"/>
   </wsdl:message>
   
   <wsdl:message name="getLastSellPriceResponse">
      <wsdl:part element="intf:getLastSellPriceResponse" name="parameters"/>
   </wsdl:message>
   <wsdl:message name="getLastSellPriceRequestHeader">
      <wsdl:part name="request_header" element="intf:quote_timestamp"/>
   </wsdl:message>
   <wsdl:portType name="StockService">
      <wsdl:operation name="getLastSellPrice">
         <wsdl:input message="intf:getLastSellPriceRequest" name=
         "getLastSellPriceRequest"/>
         <wsdl:output message="intf:getLastSellPriceResponse" name=
         "getLastSellPriceResponse"/>
      </wsdl:operation>
   </wsdl:portType>

该清单显示了所定义的三个消息,其中只有两个用于 <portType>。第三个名为 getLastSellPriceRequestHeader 根本就不予使用。现在假定 SOAP 绑定如 清单 5 所示:

清单 5. WSDL - SOAP 绑定中的隐式 SOAP 消息头
<wsdl:input name="getLastSellPriceRequest">
      <wsdlsoap:header message="intf:getLastSellPriceRequestHeader" part=
      "request_header" use="literal"/>
      <wsdlsoap:body parts="parameters" use="literal"/>
</wsdl:input>

<wsdlsoap:header> 元素再次引用到了消息部分,它并没有在 <portType> 中使用。因此,该消息头是一个隐式消息头。

SOAP 消息表示

请注意每种 SOAP 消息头的电线传输格式都是相同的:在消息的 SOAP 消息头部分传输信息。换句话说,查看 SOAP 消息时无法判断出它在相关的 WSDL 定义中是定义为隐式还是定义为显式消息头。清单 6 显示了样本 Web 服务的 SOAP 消息:

清单 6. 带有消息头的 SOAP 消息
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" ...>
	<soapenv:Header>
	     <p677:quote_timestamp xmlns:p677=
	     "http://soapheader.ibm.com">2005-01-03T06:00:00.000Z</p677:quote_timestamp>
	</soapenv:Header>
	<soapenv:Body>
		<p677:getLastSellPrice xmlns:p677="http://soapheader.ibm.com">
			<p677:ticker>IBM</p677:ticker>
		</p677:getLastSellPrice>
	</soapenv:Body>
</soapenv:Envelope>

显式和隐式消息头的 JAX-RPC 映射

既然用户已经掌握了两种消息头样式的定义,那么这对用户在使用 JAX-RPC 开发 Web 服务有什么意义呢?JAX-RPC 规范讨论了关于所谓的隐式和显式服务上下文方面的知识。尽管服务上下文有可能映射到 SOAP 消息头定义中,也可能没有映射到 SOAP 消息头定义中,但是在此用户可以认为它们是一回事。规范因此描述了显式的服务上下文如何映射到服务的远程接口中,而隐式的上下文通常却不能这样做。

坦白地说,这表明显式消息头(或者服务上下文)相对容易处理,因为它们都映射到 Web 服务的 Service Endpoint Interface (SEI) 中了。SOAP 消息头中传递的消息部分成了 SEI 的附加参数。例如,用于 WSDL 并且带有上述显式定义的 SEI 如 清单 7 所示:

清单 7. 显式消息头的服务端点接口
public interface StockServiceExplicit_PortType extends java.rmi.Remote {
    public com.ibm.soapheader.GetLastSellPriceResponse 
      getLastSellPrice(com.ibm.soapheader.GetLastSellPrice parameters, 
          java.util.Calendar request_header) 
              throws java.rmi.RemoteException;
}

运行的时候,将传入的值作为 request_header 参数输入到请求消息的 SOAP 消息头中。

现在宁可能会猜测隐式消息头实例中接口的形式。这里根本不存在任何对消息头部分的引用!这种工具完全忽视了这方面并生成了如清单 8 所示的 SEI :

清单 8. 隐式消息头的服务端点接口
public interface StockServiceImplicit_PortType extends java.rmi.Remote {
 public float getLastSellPrice(java.lang.String ticker) throws java.rmi.RemoteException;
}

虽然本文没有深入探讨这个话题,但是在本实例中,输入和输出的参数不能像以往一样封装到新类中,注意到这一点是很有意义的。

Java 编程中的隐式消息头处理

那么用户如何处理将隐式消息头定义在 JAX-RPC 中的 Web 服务呢?规范中并没有说明,因为只有那些包括在 portType 中的元素才能生成到服务端点接口中去。

不仅如此,不同的 JAX-RPC 厂商可能提供不同层次的支持。处理隐式消息头的一种方法就是使用 JAX-RPC 处理程序来管理消息头的上下文。处理程序能充分的访问客户端和服务器端的 SOAP 消息,包括消息头。但是用户如何才能将正确的内容从客户端应用程序传送到处理程序呢?可以使用 javax.xml.rpc.Stub 接口上的 _setProperty() 将信息传送到客户端代理对象。处理程序可以在那里使用 javax.xml.rpc.handler.SOAPMessageContext.getProperty() 方法检索信息。本文的实例向用户显示了这种机制(点击本文上部或者底部的 code 图标下载 EAR 文件)。

在服务器端,还可以使用 JAX-RPC 处理程序处理隐式消息头。除此之外,可以将 SOAP 消息头信息通过 javax.xml.rpc.server.ServiceLifecycle 接口传送给服务实现。我在该系列以前的技巧中解释了如何使用该接口。这里再次涉及到了复杂实例的代码样本。

结束语

在 Web 服务中有两种不同的定义 SOAP 消息头字段用法的方法,即隐式消息头和显式消息头。两者的不同之处在 WSDL 定义中可以轻易体现出来。尽管它看起来是一个很小的设计细节,但它对生成的 JAX-RPC Java 代码有极大影响。特别是在隐式消息头实例中,必需编写(或者生成)额外的代码来处理不属于 portType 部分的消息头信息。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=58288
ArticleTitle=Web 服务编程技巧和窍门: 实现隐式和显式 SOAP 消息头
publish-date=03012005