Web 服务协议已经从支持带有简单参数的非常简单的请求,发展到对现代的面向对象语言的完整支持。XML-RPC 看来是 Web 服务的早期形式之一,仅仅支持简单类型 ―― 字符串、整数、布尔值等等。SOAP 向前迈出了一步,有了用于对象的编码规则。最近的发展 ―― 在二进制数据方面的改进 ―― 来自带附件的 SOAP。
带附件的 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编码――对于小的数据集这种方法很好,也可以向请求中附加大的没有编码的二进制文件。