级别: 初级 Nicholas Gallardo (nlgallar@us.ibm.com), 软件工程师, IBM Russell Butek (butek@us.ibm.com), 软件工程师, IBM
2004 年 10 月 01 日 带附件的 SOAP 规范(请参看
参考资料)定义了怎样随 SOAP 消息发送二进制附件。但是某些时候您可能不想使用附件来发送二进制数据。例如,微软的 .Net Web 服务引擎不支持带附件的 SOAP 消息(Sw/A),所以如果您想协同使用 .Net,您必须使用附件以外的方法来发送二进制数据。学习一种新的方法来更改现存带附件的 Web 服务来发送二进制数据到另外一个 Web 服务,而且该 Web 服务不支持附件。
除了使用附件的 SOAP 消息(Sw/A)外,还有很多其他方法来交互二进制数据。我们简单地提到了几个复杂的方法,然后详细地介绍一个最简单的方法。
DIME 解决方案
如果您的 Web 服务实现支持 DIME,那么您可以编写一个封装器 WSDL,它的绑定可以处理 DIME 协议,而不是 Sw/A 协议。不过,使用这个方法有两个比较大的问题:
- 在 W3C 的 DIME 注释在 2002 年底终止了,所以现在已没有任何关于 DIME 的官方规范了。而没有一个官方规范,很可能除了微软以外,其他人都不在支持这个协议。
- 现在没有一个 DIME 的 WSDL 官方绑定。而没有一个官方绑定,很可能一个给定的绑定不会被所有提供商所支持,而且这样的话,DIME 绑定将不能协同操作。
MTOM 解决方案
SOAP 消息传输优化机制(SOAP Message Transmission Optimization Mechanism,MTOM)是另外一种二进制数据附件协议,而且它很有希望被所有的 Web 服务提供商所支持。但是到编写这篇文章为止,MTOM 规范还没有制订完成(参看
参考资料),所以在有协同操作能力的 MTOM 实现开始在市场出现时,可能需要一年,甚至两年。
非附件解决方案
交互二进制数据最简单的方法就是在 XML 中使用字节流,而且作为
xsd:hexBinary 类型或
xsd:base64Binary 类型。简单说,就是您把附件信息转换为非附件形式的字节数据。使用附件的方式是把附件的原始数据放在消息的附件部分,而且与 SOAP 消息相分离,与之不同的是,使用非附件的方式就是把附件的原始数据直接放在 SOAP 消息中。使用这种方法的缺点就是,因为原始数据放在了 SOAP 消息中,为此 XML 解析器
必须扫描这些数据,即使它从来不会被使用到(例如,在一个 SOAP 处理中间体,类似于一个路由器)。所以它不是一个非常有效的解决方案,但它确实是一个最简单的方法。
一个详细的示例
假设您有一个遗留的 Web 服务,它有如
清单 1所示的接口。
清单 1. 附件的遗留 API。
package com.ibm.developerWorks.attachment;
public interface ImageBag extends java.rmi.Remote {
public void sendImage(java.awt.Image i);
public java.awt.Image getImage();
} |
这个接口包含
java.awt.Image s。假设它们是 jpeg 图像。根据 JAX-RPC 协议规范,这些图像将映射到
image/jpeg 类型的附件。但是您不想使用附件,所以您下一步要做的就是创建一个封装应用程序,通过它来委托调用原始的应用程序。这个封装器程序的 API(参看
清单 2)包含了二进制数组,而不是图像。这个封装器程序的 WSDL 包含了
xsd:hexBinary 数据类型。(您可以从这个企业应用程序的 EAR 文件中同时找到原始 Web 服务和封装器 Web 服务的 WSDL 文件,对于这个 EAR 文件,您可以点击这篇文章的顶部或底部的
源代码图标来下载一个 ZIP 文件,然后从这个 Zip 文件中摘取出 EAR 文件。 )
清单 2. 封装器 API
package com.ibm.developerWorks.attachment;
public interface ImageBagWithNoAttachments extends java.rmi.Remote {
public void sendImage(byte[] i);
public byte[] getImage();
} |
到现在为止,我们感觉很好。但现在您要理解更困难的部分:封装器的实际实现。在委托方法调用到真正的服务之前,封装器实现必须在
byte[] 和
java.awt.Image 之间转换。请参看
清单 3 来获得封装器 API 的完整实现。这是一个可能的 J2SE 1.4 解决方案。这个转换相对有点复杂,而且这篇文章的主要目的也不是来详细介绍怎样在
byte[] 和
java.awt.Image 之间进行转换,我们把这断代码放在这里仅仅是作为您的参考信息(FYI)。
清单 3. 封装器 API 的实现
package com.ibm.developerWorks.attachment;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
public class ImageBagWithNoAttachmentsSoapBindingImpl
extends Component implements ImageBagWithNoAttachments{
private ImageBag imageBag = new ImageBagSoapBindingImpl();
public void sendImage(byte[] i) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(i);
ImageIO.setUseCache(false);
imageBag.sendImage(ImageIO.read(bais));
}
catch (Exception e) {
e.printStackTrace();
}
}
public byte[] getImage() {
try {
Image image = imageBag.getImage();
Iterator iter = ImageIO.getImageWritersByMIMEType("image/jpeg");
ImageWriter writer = iter.hasNext() ? (ImageWriter) iter.next() : null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
writer.setOutput(ios);
BufferedImage rendImage = null;
if (image instanceof BufferedImage) {
rendImage = (BufferedImage) image;
} else {
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(image, 0);
tracker.waitForAll();
rendImage = new BufferedImage(image.getWidth(null),
image.getHeight(null), 1);
Graphics g = rendImage.createGraphics();
g.drawImage(image, 0, 0, null);
}
writer.write(new IIOImage(rendImage, null, null));
writer.dispose();
return baos.toByteArray();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
} |
 |
关于使用 xsd:hexBinary、CDATA、以及 .NET 时的注意事项
当您不想让 XML 解析器来解析某些二进制数据时,经常可以使用 CDATA 来封装这些二进制数据,所以有些系统会使用 CDATA 来封装 xsd:hexBinary 数据。而 .NET 不能很好地处理 CDATA 数据块。而 5.1.1 版本之前的 WebSphere Web 服务,却经常会把大的 hexBinary 数据(大于 2K)封装到一个 CDATA 数据块中, 所以,即使使用这里提供的方法,那些早期的 WebSphere Web 服务也不能像这篇文章所描述的技巧那样跟 .Net 进行协同工作,除非您要传输的图像非常的小,即小于 2K。
|
|
客户端的情况非常类似于服务器端的情况。您想在
byte[] 和
java.awt.Image 之间进行转换。所有客户端类似于服务器端的代码。 (请查看随这篇文章带的 Zip 文件(单击文章顶部或底部的
源代码图标)来获得实际的客户端代码。)
其他 JAX-RPC 支持的所有附件类型都会映射到相应的 Java 类型,而且您可以把它转换为
byte[] 数据(通常是一个字节流),或者转换回来。所以每一个 Java 类型的实现都将不同于这个实例,不过基本思想都相同。
一个客户端样例
既然把一个二进制附件转换为一个嵌入的二进制数据的主要原因是,.Net 不支持带附件的 SOAP 消息(Sw/A),为此我们为这个服务选择给您一个 C# 客户端。在这种情况下,我们基于 .NET Framework SDK version 1.1 构造一个客户端。
正如上面提到的服务实现,我们转换
java.awt.Image 对象为一个
byte[] 数组。在这种情况下,我们相反把
byte[] 转换为
System.Drawing.Image 对象。正如在 Java 中的情况,我们有许多方法来进行转换,这仅仅是一个样例。
在
清单 4 中 C# 客户端代码从文件系统装载一个图像,然后通过
sendImage(byte[] i) 操作把它发送给服务器。一旦发送出去,客户端使用
getImage() 操作获得相同的图像,并且使用不同的名称把它保存到文件系统。
清单 4. C# 客户端实现
using System;
using System.Drawing;
using System.IO;
using System.Text;
using System.Web.Services;
namespace ImageClient
{
class ImageClient
{
[STAThread]
static void Main(string[] args)
{
if (args.Length < 4) {
Console.WriteLine("Insufficient argument list: <host> " +
"<port> <sendImageName> <newImageName>");
}
else {
string host = args[0];
string port = args[1];
string sendImageName = args[2];
string newImageName = args[3];
ImageBagWithNoAttachmentsService service =
new ImageBagWithNoAttachmentsService();
service.Url = "http://" + host + ":" + port +
"/AttachmentWar/services/ImageBagWithNoAttachments";
// Create a cookie container so that the session will be saved
// and we can retreive the image.
service.CookieContainer = new System.Net.CookieContainer();
ImageClient client = new ImageClient();
// Turn the image into a byte[] and send it.
Console.WriteLine("Sending data to the server.");
service.sendImage(client.createByteArray(sendImageName));
Console.WriteLine("");
// Get the byte[] from the service and turn it into an image.
Console.WriteLine("Retreiving image data from the server.");
client.saveAsImage(service.getImage(), newImageName);
}
}
public byte[] createByteArray(string imageName)
{
FileInfo fileInfo = new FileInfo(imageName);
FileStream fileStream = fileInfo.OpenRead();
byte[] byteArray = new byte[fileInfo.Length];
int bytesRead = fileStream.Read(byteArray, 0, byteArray.Length);
Console.WriteLine("{0} bytes have been read from {1}",
bytesRead.ToString(), imageName);
return byteArray;
}
public void saveAsImage(byte[] bytes, string imageName)
{
MemoryStream memStream = new MemoryStream(bytes);
System.Drawing.Image image =
System.Drawing.Image.FromStream(memStream);
image.Save(imageName);
Console.WriteLine("{0} was created successfully.", imageName);
}
}
}
|
注释:在
清单 4 中的
ImageBagWithNoAttachmentsService
对象代表了 .Net 客户端代理。这个对象是使用 wsdl.exe 工具创建的,wsdl.exe 包含在 .Net ramework SDK 1.1 中。下面的
参考资料部分包含了更多关于怎样生成这个代理的信息。
总结
除了使用带附件的 SOAP(Sw/A)以外,您还有许多方法可以用来发送二进制数据。在这篇文章中,我们介绍了最简单的一种方法技巧:使用
xsd:hexBinary 来插入二进制数据到 SOAP 消息中。我们使用
xsd:hexBinary 封装了一个服务,在这个服务中我们把
java.awt.Image 转换为了字节数据放到 SOAP 消息中。
下载 | 名字 | 大小 | 下载方法 |
|---|
| ws-tip-noattachcode.zip | 28 KB | HTTP |
参考资料
作者简介  | |  | Nicholas Gallardo 是 IBM 公司的软件工程师,现在参与开发 IBM WebSphere Web 服务引擎,而且主要针对引擎的实用性和协同性。Nicholas 在 Austin 和 Texas 开始开发了两个不同的技术后于 2001 年加入 IBM。加入 IBM 时的工作主要包括 WebSphere JNDI 实现,而且开始也在 Tivoli 部门工作过。 |
 | |  | Russell Butek 是 IBM Web 服务咨询师,而且也是 IBM WebSphere Web 服务引擎的一个开发人员。他也是 JAX-RPC Java Specification Request (JSR) 专家组的 IBM 代表。他参与了 Apache 的 AXIS SOAP 引擎的实现,并推动了 AXIS 1.0 来遵守 JAX-RPC 1.0 规范。之前,他也是 IBM CORBA ORB 的一个开发人员,而且作为多个 OMG 工作组的 IBM 代表,包括可移植拦截器任务组(而且是该组的组长)、OMG 核心工作组、以及协同操作工作组。 |
对本文的评价
|