IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

Web 服务编程技巧与窍门: 在 JAX-RPC 中使用 SOAP Headers

创建及处理在 SOAP 消息的 Header 部分传输的信息

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Andre Tost, 解決方案設計師, IBM 

2003 年 10 月 01 日

在本文中,Andre Tost 检验了用来创建及处理在 SOAP 消息的 Header 部分传输的数据的多种方法。尤其是他着重考虑了在使用 JAX-RPC 标准的应用程序中怎么做,因为绝大多数 Java 应用程序都是使用 JAX-RPC 标准提供并使用 Web 服务的。

SOAP Header

正如任何好的消息协议,SOAP 也定义了一个消息 Header 的概念,即它是任何 SOAP 消息的一个可选部分。如果它存在,Header 部分包括了 SOAP 消息的一些信息,或者是要发送的消息的上下文关系,也可以是那些最基本的消息创造者认为应该放在 Header 部分而不是消息 Body 部分的信息。显然,不管发送者还是接受者都应该遵守它的格式。

我可以用整篇文章来讨论什么样的数据应该放在 Header 部分,而不是 Body 部分。最常用的例子就是安全信息,而且事实上,现在已经有一个叫做 WS-Security 的规范描述了怎样在 SOAP Header 中保存用户标识及密码信息或者其他鉴定证书。

不过在本文中,我将重点放在怎样对这些进行编程及怎样处理 SOAP Header 的各个字段(从客户端和服务器端两方面)。为了保证这个示例尽可能的简单,我将使用一个古老而经典的“Stockquote”示例,也就是该Web服务返回一个股票报价。





回页首


SOAP Headers 及 WSDL

正如上面提到的,如果两个应用程序交换带 Headers 的 SOAP 消息,它们必须在 Header 的数据格式上达成一致。自然而然,这些格式描述也将放在该 Web 服务的 WSDL 定义中。您必须做出的一个决定是这些保存在 SOAP Header 中的数据是否是您的功能或业务接口的一部分。换句话说,它是不是您的端口类型(port type)的 Header 信息部分(也就是包含在一个操作(operation)的输入输出信息)?在本文中我们将假设它是。这意味着它不仅定义在 SOAP 绑定(binding)中,也定义在端口类型(port type)部分,即定义在<getLastSellPriceRequest>消息中。

WSDL 允许您分别定义用于输入和/或输出消息的 SOAP Header 字段。在本例中,我们将加上您想随请求消息发送一个时间信息字段。还要这里的重点不是您要发什么及为什么要发,而是该怎样发。

清单1显示了 WSDL 定义文件的一个部分(您能在 参考资料部分找到完整示例的下载链接)。


清单1. 带 SOAP Header 字段的 WSDL 定义范例
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://soapheader.ibm.com" ...>
  <wsdl:types>
    <schema ...>
      <element name="getLastSellPrice"> 
         <complexType> 
           <sequence>
            <element name="ticker" nillable="true" type="xsd:string" />
           </sequence>
         </complexType>
      </element>
      <element name="getLastSellPriceResponse">
         <complexType>
           <sequence>
             <element name="getLastSellPriceReturn" type="xsd:float" />
           </sequence>
         </complexType>
      </element>
      <element name="quote_timestamp" type="xsd:dateTime" />
  </schema>
  </wsdl:types>
  <wsdl:message name="getLastSellPriceRequest">
    <wsdl:part name="parameters" 
               element="intf:getLastSellPrice"/>
   
         <wsdl:part name="request_header" 
               element="intf:quote_timestamp"/>
  </wsdl:message>
  <wsdl:message name="getLastSellPriceResponse">
    <wsdl:part name="parameters"  
               element="intf:getLastSellPriceResponse"/>
  </wsdl:message>
  <wsdl:portType name="StockService">
    <wsdl:operation name="getLastSellPrice">
      <wsdl:input name="getLastSellPriceRequest" 
                  message="intf:getLastSellPriceRequest"/>
      <wsdl:output name="getLastSellPriceResponse" 
                   message="intf:getLastSellPriceResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <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 use="literal" parts="parameters"/>
      </wsdl:input>
      <wsdl:output name="getLastSellPriceResponse">
        <wsdlsoap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="StockServiceService">
    ...
  </wsdl:service>
</wsdl:definitions>
      

在这个 WSDL 定义中您最感兴趣的部分已经用粗体字着重标出来了。<input> 元素中的 SOAP 绑定(bindings)包括了 <header> 及 <body> 两个元素,它们定义了要在 Header 及 Body 中传输的消息。在本例中,Header 部分包括了时间信息字段。。





回页首


服务实现

现在您可能想对上面描述的服务提供一个编程实现。这里我们选择 JAX-RPC 作为服务器端的编程模型,以便您有一个实现该服务的标准化 Java 应用程序。在映射 Header 信息时有好几种选择,下面我们针对每一种选择进行更详细的讲解。

JAX-RPC 消息处理器


JAX-RPC 规范描述了消息处理器的概念。一个服务器端消息处理器在它到达 Web 服务前能解释请求消息,或在它被发送回请求者时能解释响应消息。在一个服务中可以调用多个消息处理器,这个也叫做“处理器链”。在一个服务中哪一个处理器要被调用是可配置的,但您也可以在程序中设置它。在将来的另外一篇文章中我将对 JAX-RPC 处理器作更详细的解释,并给出一些示例。

SOAP Header 经常包含“超出范围(out-of-band)”的信息。这些信息不是一个服务的业务功能的直接部分,所以它不应该传送到服务实现中。JAX-RPC 消息处理器能从传入消息中分离出 SOAP Header 字段,或者添加新的 Header 到一个传出消息中,而甚至不需要实际的服务实现知道它。

清单2显示了一个消息处理器从传入消息中检索并除去 SOAP Header 字段的示例代码(您可以在 参考资料部分找到完整源代码的链接)。

清单2. JAX-RPC 消息处理器示例
public class SOAPHeaderHandler extends javax.xml.rpc.handler.GenericHandler {
   ...
   public boolean handleRequest(MessageContext arg) {
 if (arg instanceof SOAPMessageContext) {
  SOAPMessageContext context = (SOAPMessageContext)arg;
  try {
   SOAPHeader header = 
           context.getMessage().getSOAPPart().getEnvelope().getHeader();
   Iterator headers = 
                        header.extractHeaderElements
                        ("http://schemas.xmlsoap.org/soap/actor/next");
   while (headers.hasNext()) {
    SOAPHeaderElement he = 
                                 (SOAPHeaderElement)headers.next();
    // process the retrieved header element here
   } 
  } catch (SOAPException x) {
   // insert error handling here
  }
 }
 return true;
   }
}

在服务端点接口(Service Endpoint Interface,SEI)中的参数


处理 SOAP Header 字段的另外一个选择是简单地把它添加到 Web 服务的服务端点接口(SEI)。这允许您直接在服务实现中处理 SOAP Header。

严格地说,WSDL 定义中的 Header 信息怎样映射到一个服务端点接口(SEI)的参数中依赖于您的 JAX-RPC 实现,因为 JAX-RPC 规范没有定义它的具体实现。例如,您上面的 WSDL 示例的服务端点接口(SEI)可能看起来像清单 3(由 WebSphere Studio Application Developer 5.1 生成)。

清单 3. 服务端点接口(SEI)范例
public interface StockService_SEI extends java.rmi.Remote {
  public float getLastSellPrice(
                 String ticker, 
                 java.util.Calendar request_Header) 
       throws java.rmi.RemoteException;
}

最有可能的是,如果 Header 作为消息部分定义在了端口类型(port type)中了,您的 JAX-RPC 实现将生成 Header 信息作为服务端点接口(SEI)的一部分。在上面 清单 1的示例中,“请求 Header”部分包含在了<getLastSellPriceRequest>消息中,因此也映射到了服务端点接口(SEI)的一个参数。

ServiceLifecycle


这种选择也提供了服务实现类的 Header 信息。不过它没有显示断点接口,但它必须明确地从消息中提取。您可以把这个看出前面两个方法的混合体:SOAP Header 的内容是从传过来的 MessageContext 中提取,MessageContext 不是在消息处理器中,而是在服务实现类中。

要访问 SOAP 消息,您的服务实现类必须实现 javax.xml.rpc.server.ServiceLifecycle 接口。这个接口包括了一个叫做 init() 的方法,它被 JAX-RPC 引擎用来传入一个上下文(context)对象给服务实现类实例。在实际的服务方法中,您可以使用类似之前描述消息处理器提取 Header 内容的代码。 清单4显示了我们的示例。

清单4. ServiceLifecycle 接口
public class StockService implements 
                      StockService_SEI, javax.xml.rpc.server.ServiceLifecycle
{
   ServletEndpointContext ctx;
   public float getLastSellPrice(String ticker, java.util.Calendar request_Header) {
 float f = 0.0f;
 if (ctx != null) {
  ServletContext servletContext = ctx.getServletContext();
            SOAPMessageContext mc = 
                   (SOAPMessageContext)ctx.getMessageContext();
  // process SOAP header as shown in the message handler
 }
 // further processing...
 return f;
   }
 
   public void init(Object arg0) throws ServiceException {
 if (javax.xml.rpc.server.ServletEndpointContext.class.isInstance(arg0))
  ctx = (ServletEndpointContext) arg0;
   }





回页首


客户端处理

在客户端的 SOAP 处理器的处理与前面讨论的服务器端的处理没有什么大的不同,您可以直接使用前面两种选择。
跟服务器端一样,JAX-RPC 也为客户端定义了消息处理器的概念 ,而且它们一般定义在客户端的 Web 服务部署描述符中。其他选项是用来作为参数添加 SOAP Header 字段到客户端 stub,以便它们能作为调用的一部分直接传送。如果 Header 字段已经定义在输出消息中,它们能作为调用结果而分别返回。





回页首


总结

JAX-RPC 应用程序可以用不同的方法来创建和处理 SOAP Header 字段。它们可以是服务的业务接口的一部分,或者被消息处理器“隐藏”或处理。无论哪一种情况,服务契约,也就是 WSDL 定义,必须定义任何要使用到的 SOAP Header 字段。特定的 JAX-RPC 引擎将支持上面描述的所有或部分选择。



参考资料



关于作者

André Tost 是 WebSphere Business Development 小組的一位解決方案設計師,他在這個小組幫助 IBM 的戰略聯盟夥伴把他們的應用程式和 WebSphere 整合在一起。他的工作重點是貫穿整個 WebSphere 產品系列的 WebService技術。在開始從事他目前的任務之前,他有十年時間是在 IBM 軟體發展工作中擔任各種開發和架構方面的角色,最近是在從事 WebSphere Business Components 產品。他出生於德國,目前在美國明尼蘇達州的羅切斯特居住和工作。在業餘時間,他喜歡和他的家人在一起,只要有可能就去踢球或者看球。您可以通過 atost@us.ibm.com 與 André 聯繫。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款