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

developerWorks 中国  >  SOA and Web services  >

Web 服务编程技巧与窍门: 不使用附件形式来发送二进制数据

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 初级

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.zip28 KBHTTP
关于下载方法的信息


参考资料



作者简介

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 核心工作组、以及协同操作工作组。




对本文的评价










回页首


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