级别: 初级 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é 聯繫。 |
对本文的评价
|