带附件的 SOAP 最初是作为 SOAP 1.1 的扩展提出的,得到了主流 SOAP 工具箱的支持。尽管 W3C 正式发布的 SOAP 1.2 还不支持附件,但是正努力实现在不远的将来(理想情况下)把它包含进来。
Web 服务与二进制数据
我毫不怀疑 XML 在应用程序集成中取得的成功,源自于对文本性编码的依赖(与二进制协议相对而言,如 CORBA ——一种面向对象的 RPC 协议,RMI ―― Java 专用的 RPC 标准)。优先选择文本性编码有几种原因,但最重要的可能是因为它容易调试,而且如果必要的话容易完成专门的实现。
对文本性编码的依赖仍然有不利的一面,XML 对引入二进制数据没有提供有效的解决方案。按照 W3C XML Schema 规范,二进制数据应该采用 base 64 或者十六进制编码。不幸的是,base 64 编码的数据比未编码的数据大 50%,而十六进制编码的数据是原来数据的两倍长。对于小段的二进制数据这种代价还可以接受,但对于较大的数据集显然是个问题。
二进制数据在许多应用程序中都很有用。比如:
1:安全应用程序需要密码、散列、证书以及加密数据本身。
2:多媒体应用程序处理图片、音乐和视频。
3:在一些应用程序中,数据的 XML 表示被认为效率太低,比如 CAD/CAM。
4:XML之前的成千上万种文件格式:字处理、电子表格、字体、向量图形、系谱等等。
尽管为这些文件格式创建 XML 版本是可能的(如用于向量图形的 SVG),但二进制数据已经存在了很长时间并且可能仍然非常普及。
最后还有 XML 自身的问题。在一个 XML 文档中包括另一个 XML 文档不是很简单的事(语法正确的解决方法依赖于 CDATA 节和字符转义)。
为了解决这些应用程序的需要,Web 服务必须有效地支持二进制数据。提出的解决方案是带有附件的 SOAP,该协议的核心是从 XML 有效负载中去掉二进制信息将其直接作为 multipart/related MIME 内容放在 HTTP 请求中。
在设计使用二进制数据的 Web 服务时,可以选的方法有:
1:如果数据集很小,可以考虑在 XML 载荷中使用 base 64 编码,对于小的数据集这样做的代价不构成问题。
2:如果数据集很大,使用附件是唯一可行的选择。
清单 1 是一个带有 base 64 编码参数的 SOAP 请求。注意其中的 address 元素。清单 1. base 64 编码的参数
POST /ws/retrieve HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml multipart/related, text/*
Host: localhost:8080
SOAPAction: ""
Content-Length: 540
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ps:retrieve
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ps="http://psol.com/2004/ws/retrieve">
<address xsi:type="xsd:base64Binary">d3d3Lm1hcmNoYWwuY29t</address>
</ps:retrieve>
</soapenv:Body>
</soapenv:Envelope>
实现附件
Java 开发人员可以通过 JAX-RPC(基于 XML 的 RPC 的 Java API)和 SAAJ(用于 Java 的带附件 SOAP API)使用附件。不要让缩写词 SAAJ 欺骗了您:JAX-RPC 支持附件。JAX-RPC 和 SAAJ 的区别在于抽象的层次而不是功能。
JAX-RPC 是一种高层次的 API,比 SAAJ 更抽象。它在 RMI 层背后隐藏了大部分面向 SOAP 协议的问题。开发人员处理的是 Java 对象,预处理程序将其转成 SOAP 节点。JAX-RPC 使用java.awt.Image 和 javax.activation.DataHandler 类表示附件。
SAAJ 更接近于协议。使用 SAAJ 创建 SOAP 消息和 JAX-RPC 相比要做更多的工作(而且没有提供到 WSDL 的自动链接),因此多数情况您可能更愿意使用 JAX-RPC。但是为了说明附件到底是如何工作的,由于它的底层特性 SAAJ 更加合适。清单 2 是一个带有附件的 SOAP 请求。该请求要求服务器改变一个图片的大小,因为图片很大,使用附件更有效。清单 2. 附件参数
POST /ws/resize HTTP/1.0
Content-Type: multipart/related; type="text/xml";
start="<EB6FC7EDE9EF4E510F641C481A9FF1F3>";
boundary="----=_Part_0_7145370.1075485514903"
Accept: application/soap+xml, multipart/related, text/*
Host: localhost:8080
SOAPAction: ""
Content-Length: 1506005
------=_Part_0_7145370.1075485514903
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: binary
Content-Id: <EB6FC7EDE9EF4E510F641C481A9FF1F3>
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ps:resize
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ps="http://psol.com/2004/ws/resize"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<source href="cid:E1A97E9D40359F85CA19D1B8A7C52AA3"/>
<percent>20</percent>
</ps:resize>
</soapenv:Body>
</soapenv:Envelope>
------=_Part_0_7145370.1075485514903
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
Content-Id: <E1A97E9D40359F85CA19D1B8A7C52AA3>
note: binary data deleted...
------=_Part_0_7145370.1075485514903--
清单 3 示范了该 SOAP 请求的创建。该请求要求服务器改变图像的大小。过程如下:
1:通过工厂创建 SOAP 连接和 SOAP 消息对象。
2:从消息对象中检索消息体(中间步骤:检索 SOAP 部分和信封)。
3:创建一个新的 XML 元素表示请求并设置编码方式。
4:创建附件并使用 DataHandler 对象初始化。
5:创建另外的元素表示两个参数(source 和 percent)。
6:通过添加 href 属性把附件与第一个元素关联。附件通过 cid(content-id)URL 引用。
7:直接把第二个参数的值设成文本并调用服务。
服务使用改变了大小的图像(同样作为附件)作为应答。检索返回的图像之前可以测试 SOAP 错误码(表示一个错误)。如果没有错误,则作为文件检索附件并处理。
清单 3. 使用 SAAJ
public File resize(String endPoint,File file)
{
SOAPConnection connection =
SOAPConnectionFactory.newInstance().createConnection();
SOAPMessage message = MessageFactory.newInstance().createMessage();
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPBody body = envelope.getBody();
SOAPBodyElement operation =
body.addBodyElement(
envelope.createName("resize",
"ps",
"http://psol.com/2004/ws/resize"));
operation.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
DataHandler dh = new DataHandler(new FileDataSource(file));
AttachmentPart attachment = message.createAttachmentPart(dh);
SOAPElement source = operation.addChildElement("source",""),
percent = operation.addChildElement("percent","");
message.addAttachmentPart(attachment);
source.addAttribute(envelope.createName("href"),
"cid:" + attachment.getContentId());
width.addTextNode("20");
SOAPMessage result = connection.call(message,endPoint);
part = result.getSOAPPart();
envelope = part.getEnvelope();
body = envelope.getBody();
if(!body.hasFault())
{
Iterator iterator = result.getAttachments();
if(iterator.hasNext())
{
dh = ((AttachmentPart)iterator.next()).getDataHandler();
String fname = dh.getName();
if(null != fname)
return new File(fname);
}
}
return null;
}
注意,清单 3 清楚地表明附件是在 XML 消息的 外部!为了提高效率这是必需的。
谈到效率,看一看清单 4,这是 清单 3 更常见的 JAX-RPC 版本(也短得多)。JAX-RPC 预处理程序生成一个存根程序,极大简化了编码。您把 DataHandler 对象作为参数传递,JAX-RPC 自动生成附件。
清单 4. 更有效的 JAX-RPC
public File resize(File file)
throws ServiceException, RemoteException
{
AttachmentService service = new AttachmentServiceLocator();
AttachmentTip port = service.getAttachmentTip(); // get stub
DataHandler dh = new DataHandler(new FileDataSource(file));
DataHandler result = port.resize(dh,20);
return new File(result.getName());
}
结束语
选择是一件好事,而 SOAP 为您处理二进制数据提供了选择:您可以在 XML有效负载中使用 base 64编码――对于小的数据集这种方法很好,也可以向请求中附加大的没有编码的二进制文件。