级别: 初级 Boris Lublinsky (boris.lublinsky@cna.com), 企业架构师, CNA Insurance
2005 年 3 月 18 日 开发一种实现,它提供了对于任意类型附件部署的灵活的发送及接收机制。作者向您展示了基于 JAX-RPC 处理程序和 SAAJ 的附件处理的实现。他也展示了提出的解决方案是如何同附件支持的 .NET 实现互操作。
引言
附件是扩展 Web 服务的功能的非常重要的元素。典型的具有附件的 Web 服务的应用程序是:
- Web 服务的命令模式的实现,SOAP 消息在此定义了命令及附件,它们被包含在支持的 XML 文档中。这种解决方案在 Web 服务的实现中变得越来越普及了,如果所有的数据都被定义成一个非常大的 schema(例如,请见 Web 服务的 ACORD 提议)。
- 发送图片及文档来支持 Web 服务的请求。
处理附件的最简单的解决方案是 base-64 数据编码,它允许直接将附件放置在 SOAP 消息中。将需要被附带上的数据编码,转换成字符串表示,并将其放置在 XML 有效载荷的 SOAP 体中。这使用了所有可能的传送,这些传送被认为在不同实现中具有很好的互操作性,并且确保使用少量的数据。但是,base-64 编码有其局限性。它是低效的;通过使用每字节仅 6 位的编码,它添加了大约消息长度的三分之一。它还引发了对接收到的消息的处理问题,因为事实上通常不会将 XML 分析器设计成能够处理非常大的字符串。
更好的解决方案(尤其是当附件变大的时候)是使用附件的消息。在此,消息本身包含多个部分,其中之一是 SOAP 消息,其它部门包含额外的(附件)信息。不幸的是,对于附件的消息,目前在行业中没有太多协议。事实上,目前在市场上存在多个标准(对于这些标准请见参考资料):
- Multipurpose Internet Mail Extensions(MIME)是电子邮件所使用的标准并且被 Web 服务的 Java 实现所广泛采用。
- Direct Internet Message Encapsulation(DIME)是微软和 IBM 及附件的唯一实现所提出的新标准,可以从微软中获取它作为 Web Services Extensions(WSE)框架的部份。
- Web Service with attachments(WS-Attachment)基于 DIME,目前作为互联网草案被提交。
- SOAP Message Transmission Optimization Mechanism(MTOM)描述了抽象特征及其具体实现,用于优化转换及 SOAP 消息的线路格式,它目前是 W3C 提议。
更糟的情形是,虽然 Web 服务定义语言(Web Services Definition Language,WSDL)支持附件定义,但是依照所用的 MIME 或 DIME 将它们定义成不同内容。WSDL 定义也是相当局限的,它不允许指定任意类型的附件部署。每个可能的附件都必须明确定义成 WSDL 服务定义的一部分。
另一方面,SOAP with Attachments API for Java(SAAJ——请见参考资料)允许直接操作作为 SOAP 消息的部分的附件。与 JAX-RPC 处理程序——用于 JAX-RPC 扩展的标准机制相结合,使用 SAAJ 获得对于附件处理的通用支持。
本文中,我向您展示了通用的 JAX-RPC 处理程序的实现,它具备发送并接收任意附件类型的部署的能力。我首先说明了支持 Web 服务实现和基于 IBM® WebSphere® Application Server(Application Server)的客户端的附件处理程序的创建及用法,然后扩展了该实现以支持 .Net 客户端,它基于添加到 Microsoft 的 Microsoft® Visual Studio .NET 和 Microsoft .NET 框架的 Web Services Enhancements(WSE)。
提出的实现的体系结构
消息处理是 JAX-RPC 规范的一个最强大的功能。它们提供了对于 Web 服务终端(客户端和服务器)的额外的消息处理工具,将其作为基本服务实现逻辑的扩展。处理程序可以管理编码和解码,登录和审核等等。我将消息处理程序作为这个提出的实现的基础,如图 1 所示:
图 1. 整个实现的体系结构
图 1 中提出的解决方案如下运作:
- 服务客户创建了附件容器,其中包含服务实现所必须的所有附件,并将其附加到 SOAP 消息上下文中。
- 客户请求处理程序作为客户请求的管道执行的一部分被调用。该方法检查 SOAP 消息上下文来获取附件容器。如果找到该容器,将其内容复制到请求 SOAP 消息中。
- SOAP 消息被传递到服务实现管道中。
- 服务请求处理程序作为服务请求的管道执行的一部分被调用。此方法检查到来的消息是否包含附件,如果它包含附件,就将附件重新打包到附件容器中,该附件容器被附加到 SOAP 消息上下文中。
- Web 服务实现检查 SOAP 消息上下文来获取附件容器,如果找到一个,就将其用于处理请求。
- 作为执行的一部分,服务实现创建了响应附件容器,并将其添加到 SOAP 消息上下文中。
- 服务响应处理程序作为服务应答的管道执行的一部分被调用。该方法检查 SOAP 消息上下文来获取附件容器。如果找到该容器,将其内容复制到应答 SOAP 消息中。
- 然后将 SOAP 消息传递到服务客户端管道中。
- 客户端响应处理程序作为客户端响应的管道执行的一部分被调用。此方法检查到来的消息是否包含附件,如果它包含附件,就将附件重新打包到附件容器中,该附件容器被附加到 SOAP 消息上下文中。
- Web 服务客户端实现检查 SOAP 消息上下文来获取附件容器,如果找到一个,就将其用于处理应答。
本文的下面部分讨论并提供了提出的实现的详细信息。
实现基本服务
为了保持实现的简单化,我首先创建一个非常简单的 Echo Service。我使用 WebSphere Studio Application Developer Integration Edition V5.1(Application Developer)的生成功能。生成支持 Web 服务的实现及客户端程序的最简单的方法是从 Java bean 启动,实现该服务(清单 1),然后生成将该 bean 配置成服务以及从客户端访问该服务所需的全部构件。
清单 1. Echo Service bean
package com.cna.services;
public class EchoService{
public String echo(String request) {
System.out.println("Inside echo service");
// Return
return request;
}
}
|
基于生成的 Java 代码,可以实现该服务的简单测试客户端,如下所示(清单 2):
清单 2. 简单的服务测试客户端
package com.cna.service.tester;
import com.cna.services.EchoService;
import com.cna.services.EchoServiceProxy;
public class serviceTester {
public void testServices(){
EchoServiceProxy portProxy = new EchoServiceProxy();
EchoService port = portProxy.getEchoService();
String reply = null;
try{
reply = port.echo("my echo");
}
catch(Exception e){
System.out.println("Error invoking service");
e.printStackTrace();
}
System.out.println("Done invoking service " + reply);
}
}
|
可以创建简单的 JSP(清单 3)来从 Web 页面调用该测试(清单 2):
清单 3. 用于服务测试调用的简单 JSP
<![CDATA[
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding=
"ISO-8859-1"%>
<jsp:useBean id="serviceTester" scope="session" class=
"com.cna.service.tester.serviceTester" />
<META http-equiv="Content-Type"
content="text/html; charset=ISO-8859-1">
<META name="GENERATOR" content="IBM WebSphere Studio">
<META http-equiv="Content-Style-Type" content="text/css">
<LINK href="../../theme/Master.css" rel="stylesheet" type="text/css">
<TITLE>serviceTester.jsp</TITLE>
</HEAD>
<BODY>
<P>Testing Service ... </P>
<% serviceTester.testServices(); %>
<P>Done </P>
</BODY>
</HTML>
]]>
|
在处理程序与实现之间传递信息
提出的解决方案的基础之一是,处理程序具有在服务实现与服务调用客户端之间传递信息的能力。由于在处理程序实现与服务实现或服务客户端实现之间不存在直接的链接,所以建立这种信息传递的唯一方式是通过 SOAPMessageContext 的特性。不幸的是,在客户端与服务实现端对于该上下文(在 Application Server V5.1 中)的访问的支持是不同的。
服务实现访问 SOAPMessageContext
在 JAX-RPC 实现的当前版本中,不存在直接的方式来访问 SOAPMessageContext。幸运的是,这种功能可以通过 javax.xml.rpc.server.ServiceLifecycle 接口获得,该接口是 JAX-RPC 规范的一部分。由于服务实现源于 ServiceLifecycle 接口,所以可以在 ServiceLifecycle 接口的初始化方法中获取 SOAPMessageContext,然后由服务实现来使用。清单 4 展示了简单的实现,它使得 SOAPMessageContext 在扩展该类的服务实现中是可用的。
清单 4. ServiceLifecycle 接口实现
package com.cna.service.context;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.server.ServiceLifecycle;
import javax.xml.rpc.server.ServletEndpointContext;
public class CfContextHandler implements ServiceLifecycle{
private MessageContext context;
public void init(Object ctx){
System.out.println("Inside Service init method");
// Initialize message context for the service use
ServletEndpointContext sc = (ServletEndpointContext)ctx;
if(ctx != null)
context = sc.getMessageContext();
else
context = null;
}
public void destroy(){
System.out.println("Inside Service destroy method");
}
public MessageContext getContext() {
return context;
}
}
|
服务客户访问 SOAPMessageContext
在服务客户端,也不存在访问 SOAPMessageContext 的直接方式。唯一的方法是通过设置或获取 javax.xml.rpc.Stub 接口的属性,该接口被定义成 JAX RPC 标准的一部分。由 Application Developer 生成的每个端口实现类(清单 2)都可以被输入 Stub 接口中,这样来设置并获取 Stub 的属性。
Application Server 和 JAX-RPC 实现的一个局限是设置在 Stub 上的属性先于服务调用被复制到 SOAPMessageContext 中,但是在服务调用中对于 SOAPMessageContext 的更改没有被复制回 Stub 属性中。这意味着在服务调用中服务客户看不到对 SOAPMessageContext 的变更。避免这种局限性的简单方法是在服务调用之前将容器作为属性(必须被设置成服务调用的一部分的参数)。这样,取代了在 SOAPMessageContext 中设置新的属性,而在服务客户可以直接获得的容器中设置值。
实现附件处理程序
为了实现附件处理程序,您需要为多个附件定义容器类。所有附件都基于两个主要内容:
- 附件标识符:唯一地确定了容器中特有的附件。该标识符也可以用于引用 SOAP 消息体中的附件。
- 附件数据源:实现 javax.activation.DataSource 接口并代表附件的内容。
Java 1.4 提供了对于数据源接口的一些标准实现——FileDataSource 类实现了简单的数据源对象,该对象封装了文件以及 URLDataSource 类,此类提供将 URL 对象包装成数据源接口的对象。额外的数据源对象可以被创建成所需的类型。基于以上所述,可以将附件容器(清单 5)实现成包含附件数据源的映射,并以附件标识符唯一识别。
清单 5. 附件容器类
package com.cna.service.attachments;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.activation.DataSource;
import com.ibm.ws.webservices.engine.attachments.Attachments;
public class CfAttachmentsContainer implements Serializable{
private Map map = null;
public CfAttachmentsContainer(){
map = new HashMap();
}
public void addAttachment(DataSource source, String ID){
map.put(ID,source);
}
public Map getAttachments(){
return map;
}
public DataSource getAttachment(String ID){
return (DataSource)map.get(ID);
}
}
|
额外的类,双向的附件容器(清单 6),也需用于支持服务客户。
清单 6. 双向的附件容器
package com.cna.service.attachments;
public class CfTwoWayAttchmentsContainer {
private CfAttachmentsContainer requestContainer = null;
private CfAttachmentsContainer responseContainer = null;
public CfAttachmentsContainer getRequestContainer() {
return requestContainer;
}
public CfAttachmentsContainer getResponseContainer() {
return responseContainer;
}
public void setRequestContainer(CfAttachmentsContainer container) {
requestContainer = container;
}
public void setResponseContainer(CfAttachmentsContainer container) {
responseContainer = container;
}
}
|
基于上面设计的附件容器,附件处理程序的基本功能是在带有附件的 SOAP 消息与附件容器之间进行双向转换。由于附件处理程序在 javax.xml.soap.SOAPMessage 类上操作,所以您可以使用 SAAJ 来直接操作 SOAP 消息的内容。在清单 7 中展示了实现转换操作的基本的处理程序。
清单 7. 基本的附件处理程序
package com.cna.service.attachments.handler;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import javax.xml.rpc.handler.GenericHandler;
import javax.xml.rpc.handler.MessageContext;
import javax.xml.rpc.handler.soap.SOAPMessageContext;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPMessage;
import com.cna.service.attachments.CfAttachmentsContainer;
public class CfAttachmentHandler extends GenericHandler {
public static String HANDLER_REQUEST_ATTACHMENT_PROPERTY = "ReqAttachments";
public static String HANDLER_RESPONSE_ATTACHMENT_PROPERTY = "RespAttachments";
public static String HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY = "Attachments";
// Metod Required by Generic Handler
public QName[] getHeaders() {
return null;
}
protected void addContainerToMessage(MessageContext mc,CfAttachmentsContainer aContainer){
try{
SOAPMessageContext smc = (SOAPMessageContext) mc;
SOAPMessage sMessage = smc.getMessage();
// Go through all attachments
Map attachments = aContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
AttachmentPart attachment = sMessage.createAttachmentPart();
DataHandler dh = new DataHandler(aContainer.getAttachment(ID));
attachment.setDataHandler(dh);
attachment.setContentId(ID);
sMessage.addAttachmentPart(attachment);
}
}
catch(Exception e){
System.out.println("Error appending attachments on the consumer");
e.printStackTrace();
}
}
protected CfAttachmentsContainer extractContainerFromMessage(MessageContext mc){
try{
SOAPMessageContext smc = (SOAPMessageContext) mc;
SOAPMessage sMessage = smc.getMessage();
Iterator attachments = sMessage.getAttachments();
if(!attachments.hasNext())
return null;
System.out.println("Storing Attachments to container");
CfAttachmentsContainer aContainer = new CfAttachmentsContainer();
// Process all attachments
while(attachments.hasNext()){
AttachmentPart attachment = (AttachmentPart)attachments.next();
DataHandler dh = attachment.getDataHandler();
aContainer.addAttachment(dh.getDataSource(),attachment.getContentId());
}
return aContainer;
}
catch(Exception e){
System.out.println("Error retrieving attachments on the consumer");
e.printStackTrace();
return null;
}
}
}
|
用于客户端(清单 8)及服务器端(清单 9)的特有的附件处理程序通过实现由 JAX-RPC 管道调用的 handleRequest 和 handleResponse 方法扩展了基本的附件头类(清单 7)。
清单 8. 客户端附件处理程序
package com.cna.service.attachments.handler;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.CfTwoWayAttchmentsContainer;
public class CfClientAttachmentHandler extends CfAttachmentHandler{
// Request handler. Takes container and adds it to the message
public boolean handleRequest(MessageContext mc){
System.out.println("Inside Client Hand?e Request");
CfTwoWayAttchmentsContainer tContainer =
(CfTwoWayAttchmentsContainer)mc.getProperty(
HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY);
if(tContainer != null){
CfAttachmentsContainer aContainer = tContainer.getRequestContainer();
if(aContainer != null)
addContainerToMessage(mc, aContainer);
}
return true;
}
public boolean handleResponse(MessageContext mc){
System.out.println("Inside Client Handle Response");
CfTwoWayAttchmentsContainer tContainer =
(CfTwoWayAttchmentsContainer) mc.getProperty(
HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY);
if(tContainer != null){
CfAttachmentsContainer aContainer = extractContainerFromMessage(mc);
if(aContainer != null)
tContainer.setResponseContainer(aContainer);
}
return true;
}
}
|
清单 9. 服务器端附件处理程序
package com.cna.service.attachments.handler;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
public class CFServerAttachmentHandler extends CfAttachmentHandler {
public boolean handleRequest(MessageContext mc){
System.out.println("Inside server Handle Request");
CfAttachmentsContainer aContainer = extractContainerFromMessage(mc);
if(aContainer != null)
mc.setProperty(HANDLER_REQUEST_ATTACHMENT_PROPERTY, aContainer);
return true;
}
public boolean handleResponse(MessageContext mc){
System.out.println("Inside Server Handle Response");
CfAttachmentsContainer aContainer =
(CfAttachmentsContainer)mc.getProperty(HANDLER_RESPONSE_ATTACHMENT_PROPERTY);
if(aContainer != null)
addContainerToMessage(mc, aContainer);
return true;
}
}
|
注意到服务器端处理程序使用 CfAttachmentsContainer 和两个特有的属性:一个用于入站的附件,另一个用于出站的附件。客户端处理程序也使用了容器——CfTwoWayAttchmentsContainer 和单一的属性。该容器用于引入的和输出的附件,并且被置于服务调用之前。
将其放置在一起
为了将其放置在一起,您需要更改服务和客户端实现来发送并接收附件。您需确保服务实现扩展 CfContextHandler 类(清单 4),以便它能够访问 SOAPMessageContext。
清单 10. 完成服务的实现
package com.cna.services;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataSource;
import javax.xml.rpc.handler.MessageContext;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.handler.CfAttachmentHandler;
import com.cna.service.context.CfContextHandler;
import com.cna.services.datasources.CfDataSourceReader;
import com.cna.services.datasources.CfOctetDataSource;
import com.cna.services.datasources.CfPlainTextDataSource;
public class EchoService extends CfContextHandler{
private static String attachment1 = "WAS Server weird attachment";
private static String attachment2 = "WAS Server other weird attachment";
private DataSource ds1 = null;
private DataSource ds2 = null;
public EchoService(){
ds1 = new CfPlainTextDataSource("attachment1",attachment1);
ds2 = new CfOctetDataSource ("attachment2",attachment2.getBytes());
}
public String echo(String request) {
System.out.println("Inside echo service");
// Process incoming attachments
MessageContext context = getContext();
CfAttachmentsContainer reqContainer = null;
if(context != null)
reqContainer =
(CfAttachmentsContainer)context.getProperty(
CfAttachmentHandler. HANDLER_REQUEST_ATTACHMENT_PROPERTY);
System.out.println("Got attachments container " + reqContainer);
if(reqContainer != null){
Map attachments = reqContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
System.out.println("Got attachments " + ID);
DataSource ds = reqContainer.getAttachment(ID);
System.out.println(
"Attachment value is " + CfDataSourceReader.convertToString(ds));
}
}
// Create outgoing attachments
if(context != null){
CfAttachmentsContainer repContainer = new CfAttachmentsContainer();
repContainer.addAttachment(ds1,"firstAttchment");
repContainer.addAttachment(ds2,"secondAttchment");
context.setProperty(
CfAttachmentHandler.HANDLER_RESPONSE_ATTACHMENT_PROPERTY,repContainer);
}
// Return
return request;
}
}
|
清单 11. 完成客户端的实现
package com.cna.service.tester;
import java.util.Iterator;
import java.util.Map;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.rpc.Stub;
import javax.xml.soap.AttachmentPart;
import com.cna.service.attachments.CfAttachmentsContainer;
import com.cna.service.attachments.CfTwoWayAttchmentsContainer;
import com.cna.service.attachments.handler.CfAttachmentHandler;
import com.cna.services.EchoService;
import com.cna.services.EchoServiceProxy;
import com.cna.services.datasources.CfDataSourceReader;
import com.cna.services.datasources.CfOctetDataSource;
import com.cna.services.datasources.CfPlainTextDataSource;
public class serviceTester {
private static String attachment1 = "Client weird attachment";
private static String attachment2 = "Client other weird attachment";
private DataSource ds1 = null;
private DataSource ds2 = null;
public serviceTester(){
ds1 = new CfPlainTextDataSource("attachment1",attachment1);
ds2 = new CfOctetDataSource("attachment2",attachment2.getBytes());
}
public void testServices(){
CfTwoWayAttchmentsContainer tContainer = new CfTwoWayAttchmentsContainer();
CfAttachmentsContainer reqContainer = new CfAttachmentsContainer();
reqContainer.addAttachment(ds1,"firstAttchment");
reqContainer.addAttachment(ds2,"secondAttchment");
tContainer.setRequestContainer(reqContainer);
EchoServiceProxy portProxy = new EchoServiceProxy();
EchoService port = portProxy.getEchoService();
Stub stub = (Stub)port;
stub._setProperty(
CfAttachmentHandler.HANDLER_REQUEST_RESPONSE_ATTACHMENT_PROPERTY,tContainer);
String reply = null;
try{
reply = port.echo("my echo");
}
catch(Exception e){
System.out.println("Error invoking service");
e.printStackTrace();
}
System.out.println("Done invoking service " + reply);
CfAttachmentsContainer repContainer = tContainer.getResponseContainer();
System.out.println("Got attachments container " + repContainer);
if(repContainer != null){
Map attachments = repContainer.getAttachments();
Iterator aIterator = attachments.keySet().iterator();
while(aIterator.hasNext()){
String ID = (String)aIterator.next();
System.out.println("Got attachments " + ID);
DataSource ds = repContainer.getAttachment(ID);
System.out.println(
"Attachment value is " + CfDataSourceReader.convertToString(ds));
}
}
}
}
|
最后,客户端和服务器端的调用管道必须被配置成包含上面定义的(清单 8 和清单 9)处理程序。
当调用测试的 JSP(清单 3)时生成了下面的输出:
清单 12. 测试服务调用的结果
SystemOut O Inside Client Handle Request
SystemOut O Inside server Handle Request
SystemOut O Storing Attachments to cont?iner
SystemOut O Inside Service init method
SystemOut O Inside echo service
SystemOut O Got attachments container
com.cna.service.attachments.CfAttachmentsContainer@71764583
SystemOut O Got attachments secondAttchment
SystemOut O Attachment value is Client other weird attachment
SystemOut O Got attachments firstAttchment
SystemOut O Attachment value is Client weird attachment
SystemOut O Inside Service destroy method
SystemOut O Inside Server Handle Response
SystemOut O Inside Client Handle Response
SystemOut O Storing Attachments to container
SystemOut O Done invoking service my echo
SystemOut O Got attachments container
com.cna.service.attachments.CfAttachmentsContainer@22e60582
SystemOut O Got attachments secondAttchment
SystemOut O Attachment value is WAS Server other weird attachment
SystemOut O Got attachments firstAttchment
SystemOut O Attachment value is WAS Server weird attachment
|

 |

|
扩展处理程序以支持 MIME 和 DIME
虽然没有在 Application Server 实现中说明或归档,但是 SOAP 消息是基于支持 MIME 和 DIME 附件的 com.ibm.ws.webservices.engine.Message 类的。对于输入的消息,它检查 HTTP 内容头并直接构建 SOAP 消息(不管附件是何类型)。类似于 AXIS 实现,该类可以查询输入消息的附件类型(清单 13)。
清单 13. 查询附件类型
import com.ibm.ws.webservices.engine.Message;
import com.ibm.ws.webservices.engine.attachments.Attachments;
....................................................................
Message messageImpl = (Message)sMessage;
if(messageImpl.getAttachmentsImpl().getSendType() == Attachments.SEND_TYPE_DIME)
aContainer.setSendType(CfAttachmentsContainer.DIME_ATTACHMENTS);
else
aContainer.setSendType(CfAttachmentsContainer.MIME_ATTACHMENTS);
|
当发送附件时,Application Server 默认采用 MIME 附件,但是允许在附件发向的对象中覆盖该格式,如清单 14 所示:
清单 14. 覆盖附件类型
import com.ibm.ws.webservices.engine.Message;
import com.ibm.ws.webservices.engine.attachments.Attachments;
....................................................................
if(aContainer.getSendType() == CfAttachmentsContainer.DIME_ATTACHMENTS){
Message messageImpl = (Message)sMessage;
messageImpl.getAttachmentsImpl().setSendType(Attachments.SEND_TYPE_DIME);
}
|
为了合并上述功能,我修改了附件容器类(清单 5)来添加变量,它包含发送或接收附件的类型以及该变量的设置及获取方法。我也合并代码来查询输入消息的类型(清单 13)并明确地将附件类型(清单 14)设置到基本的处理程序(清单 7)的实现中。
实现 .Net 服务客户
支持 .NET 框架的附件的实现由 WSE(添加到 .NET 1.1 上的)提供,它可以从 Microsoft Web 站点上免费获得。我使用该包的最新版本,WSE 2.0。WSE 2.0 提供了处理附件的非常简单的模型:
- 个别附件——依照附件类型和流创建 DimeAttachment 类,它表示附件的内容。
- 为了创建请求消息的附件,可以从服务代理中获取 RequestSoapContext。该上下文包含附件容器,可以添加输出的附件。由 WSE 提供的标准处理程序负责将这些附件与输出消息打包。
- 当应答时,WSE 提供的处理程序解析了输入的消息并将输入的附件存储到 ResponseSoapContext 的附件容器中。然后将该上下文作为服务代理的属性。
完整的创建 .Net 客户端所需的步骤如下:
- 下载并安装 WSE 2.0。
- 在 Visual Studio 2003 中创建新的 C# 控制台项目。
- 添加资源 WSE 2.0。
- 添加 Web 资源,引入 echo Service 的 WSDL。完成之后,Visual Studio 生成了两个代理:EchoServiceService(正常的 Web 服务代理)和 EchoServiceServiceWse(WSE 支持的 Web 服务代理)。我在该实现中使用 EchoServiceServiceWse。
实际的实现是相当简单的,如清单 15 所示:
清单 15. .Net 客户端的实现
using System;
using WasEchoServiceTester.WASEchoService;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Dime;
using System.Web;
using System.Web.Services;
using System.IO;
using System.Text;
using System.Xml;
namespace WasEchoServiceTester
{
class WASEchoClient
{
private static string request = "echo request";
private static string aString = "Microsoft Client weird attachment";
private static string aString1 = "Microsoft Client other weird attachment";
[STAThread]
static void Main(string[] args)
{
// create service proxy
EchoServiceServiceWse eService = new EchoServiceServiceWse();
// Create attachments
SoapContext outSOAPContext = eService.RequestSoapContext;
UTF8Encoding encoder = new UTF8Encoding();
byte[] bytes = encoder.GetBytes(aString);
MemoryStream aStream = new MemoryStream(bytes);
DimeAttachment outAttachment =
new DimeAttachment("text/plain",TypeFormat.None,aStream);
outAttachment.Id = "attachment1";
outSOAPContext.Attachments.Add(outAttachment);
bytes = encoder.GetBytes(aString1);
aStream = new MemoryStream(bytes);
outAttachment = new DimeAttachment("text/plain",TypeFormat.None,aStream);
outAttachment.Id = "attachment2";
outSOAPContext.Attachments.Add(outAttachment);
// Invoke service
string reply = eService.echo(request);
System.Console.Out.WriteLine("Returned from service
with result " + reply);
// Process attachments
SoapContext inSOAPContext = eService.ResponseSoapContext;
int nAttachments = inSOAPContext.Attachments.Count;
if (nAttachments > 0){
for(int i = 0; i < nAttachments;i ++){
String attID = inSOAPContext.Attachments[i].Id;
System.Console.Out.WriteLine("Processing attachment " + attID);
Stream inStream = inSOAPContext.Attachments[i].Stream;
int sLengt = (int)inStream.Length;
byte[] sBytes = new byte[sLengt];
int sByte = 0;
while(sLengt > 0){
int n = inStream.Read(sBytes, sByte, sLengt);
if (n==0)
break;
sByte += n;
sLengt -= n;
}
inStream.Close();
Decoder decoder = encoder.GetDecoder();
int charCount = decoder.GetCharCount(sBytes, 0, sBytes.Length);
Char[] chars = new Char[charCount];
decoder.GetChars(sBytes, 0, sBytes.Length, chars, 0);
string attachmentValue = new string(chars);
System.Console.Write(attachmentValue + "\n");
}
}
}
}
}
|
依赖 Application Server 服务(清单 10)的 .Net 客户端(清单 15)的测试产生如下结果:
清单 16. 在 Application Server 上执行的 Echo 结果
SystemOut O Inside server Handle Request
SystemOut O Storing Attachments to container
SystemOut O Inside Service init method
SystemOut O Inside echo service
SystemOut O Got attachments container
com.cna.service.attachments.CfAttachmentsContainer@4a5b5070
SystemOut O Got attachments attachment2
SystemOut O Attachment value is Microsoft Client other weird attachment
SystemOut O Got attachments attachment1
SystemOut O Attachment value is Microsoft Client weird attachment
SystemOut O Inside Service destroy method
SystemOut O Inside Server Handle Response
|
清单 17. 在 .Net 客户端执行的结果
Returned from service with result echo request
Processing attachment secondAttchment
WAS Server other weird attachment
Processing attachment firstAttchment
WAS Server weird attachment
|
结束语
本文提出了附件处理的非常灵活的解决方案,用于输入及输出消息。该解决方案用于处理任意类型的附件的任意部署。
致谢
作者感谢他的 CNA 同事,尤其是 James Mckune 和 Frank Marascom 对于本文的建议及代码的提供。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Source code for the article | ws-jaxhandlecode.zip | 77 KB | HTTP |
|---|
参考资料
关于作者  | |  | Boris Lublinsky 是 CNA 保险的一名企业架构师,他主要负责设计和实现 CNA 的整体战略,构建应用框架和实现面向服务的架构。在此之前,作为 Inventa Technologies 的技术主管,他负责并亲自参与了 EAI 与 B2B 的集成实现,以及大规模 Web 应用程序的开发。他同时还是 Platinum Technology 和 SSA 的技术架构师,参与了基于组件的系统开发和设计。Boris 在软件工程和技术架构领域有超过 20 年的工作经验。 |
对本文的评价
|