带有附件的soap分析
带有附件的SOAP信息并没有给SOAP增加新的特征。 确切的说,它定义了如何利用在SOAP信息中MIME类型来定义附件, 并且还定义了如何引用在SOAP体(SOAP Body)中的那些附件。
MIME类型的复合块/关联(multipart/related)特性能定义由多部分组成的一个文档。带有附件的SOAP信息一定要符合这样的复合块/关联(multipart/related)的MIME类型。下面的例子展示了一个复合块/关联的 SOAP 信息,它被绑定到到 HTTP 协议,带有两个附件:
POST /propertyListing HTTP/1.1
Host: www.realproperties.com
Content-Type: Multipart/Related; boundary=MIME_boundary; type=text/XML; start="<property1234@realhouses.com>"
Content-Length: NNNN
--MIME_boundary
Content-Type: text/xml; charset=UTF-8
Content-Transfer-Encoding: 8bit
Content-ID: <property1234.xml@realhouses.com>
<?xml version='1.0'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<realProperty:propertyListing id="property_432"
xmlns:realProperty="http://schemas.realhouses.com/listingSubmission">
<listingAgency>Really Nice Homes, Inc.</listingAgency>
<listingType>Add</listingType>
<propertyAddress>
<street>1234 Main St</street>
<city>Pleasantville</city>
<state>CA</state>
<zip>94323</zip>
</propertyAddress>
<listPrice>
250000
</listPrice>
<frontImage href="property1234_front.jpeg@realhouses.com"/>
<interiorImage href="property1234_interior.jpeg@realhouses.com"/>
</realProperty:propertyListing>
</SOAP-ENV: Body>
</SOAP-ENV: Envelope>
--MIME_boundary
Content-Type: image/jpeg
Content-ID:
....JPEG DATA .....
--MIME_boundary
Content-Type: image/jpeg
Content-ID:
....JPEG DATA .....
--MIME_boundary--
上述的复合块信息包含一系列的MIME头和相关的数据。文件的底层是SOAP体(SOAP Body)。 因为SOAP体只包含XML数据,整个信息的MIME类型是本文/xml(text/xml)类型 。 在SOAP封套(SOAP envelope)后面是二个附件,每个附件都包含一个连同信息一起发送的图像文件。
用内容ID(Content ID)来识别每一个附件。W3C 备忘录允许用内容ID或内容位置来引用附件,但是它优先选择前者。这样的一个内容ID作为统一资源标志符URI(Uniform Resource Identifier)引用给附件;SOAP 1.1的编码规则定义了如何通过URI来引用SOAP信息里面的任何资源,不仅仅是引用XML( 参考SOAP1.1第5节资源)。当SOAP处理机处理信息时,它会解析这些URI引用。在上述的例子中,SOAP处理器把元素frontImage关联到内容ID为property1234_front.jpeg@realhouses.com的数据段中。
创建并发送带有附件的soap信息
SAAJ能让你创建并编辑SOAP信息的任何部份, 包括附件。 大多数的SAAJ以抽象类和接口为基础,所以每个供应商都能实现它自己的SAAJ产品。Sun Microsystems公司的参考实现附在JWSDP包(Java Web Services Developer Pack)中。
因为SOAP信息只是XML文档的一种特殊形式,JAAS在DOM(Document Object Model)API的基础上处理XML。大多数的SOAP信息组件派生自 avax.xml.soap.Node接口, 而这个接口又是org.w3c.dom.Node的子类。SAAJ继承了Node来添加SOAP样式的结构。 举例来说,这个特别的Node, SOAPElement,代表了一个SOAP信息元素。
SAAJ依赖于接口和抽象类的直接结果是:你要通过工厂方法(factory methods)来完成大多数与SOAP相关的工作。 要把你的程序链接到SAAJ API,你首先要创建一个来自工厂方法SOAPConnectionFactory的链接SOAPConnection。要创建和编辑SOAP信息,你可以初始化MessageFactory和SOAPFactory。MessageFactory能让你产生SOAP信息,而 SOAPFactory则提供方法产生SOAP信息的各个部份:
SOAPConnectionFactory spConFactory = SOAPConnectionFactory.newInstance();
SOAPConnection con = spConFactory.createConnection();
SOAPFactory soapFactory = SOAPFactory.newInstance();
把这些工具用在适当的位置,你就可以创建一个 SOAP 信息,在前面的例子中,来自房产代理的客户可以使用这些信息发送项目表更新给一个网站入口。
SAAJ 提供了几个方法来产生一个新的 SOAP 信息。 下面例子演示了用最简单的方法来创建一个有封套(envelope)的空白SOAP信息,这个封套还带有头(heade)和体(body)。如果你在这个信息中不需要SOAP头(SOAP header),那么你可以将这个元素从信息中删除:
SOAPMessage message = factory.createMessage();
SOAPHeader header = message.getSOAPHeader();
header.detachNode();
把 XML 结构加入信息也是直接了当的:
SOAPBody body = message.getSOAPBody();
Name listingElementName = soapFactory.createName(
"propertyListing", "realProperty",
"http://schemas.realhouses.com/listingSubmission");
SOAPBodyElement listingElement = body.addBodyElement(listingElementName);
Name attname = soapFactory.createName("id");
listingElement.addAttribute(attname, "property_1234");
SOAPElement listingAgency = listingElement.addChildElement("listingAgency");
listingAgency.addTextNode("Really Nice Homes, Inc");
SOAPElement listingType = listingElement.addChildElement("listingType");
listingType.addTextNode("add");
SOAPElement propertyAddress = listingElement.addChildElement("propertyAddress");
SOAPElement street = propertyAddress.addChildElement("street");
street.addTextNode("1234 Main St");
SOAPElement city = propertyAddress.addChildElement("city");
city.addTextNode("Pleasantville");
SOAPElement state = propertyAddress.addChildElement("state");
state.addTextNode("CA");
SOAPElement zip = propertyAddress.addChildElement("zip");
zip.addTextNode("94521");
SOAPElement listPrice = listingElement.addChildElement("listPrice");
listPrice.addTextNode("25000");
注意,你要把属性的ID作为一个参数加入到属性列表元素(propertyListing)中。 更进一步的是,你要用QName, 或namespace来限定propertyListing元素。
你可以用几种方法把附件加入到SOAP信息中。 在这一个例子中,你首先要创建元素来指示列表属性的背景图片和前景图片。它们每个都有一个href属性指明附件的内容ID:
String frontImageID = "property1234_front_jpeg@realhouses.com";
SOAPElement frontImRef = listingElement.addChildElement("frontImage");
Name hrefAttName = soapFactory.createName("href");
frontImRef.addAttribute(hrefAttName, frontImageID);
String interiorID = "property1234_interior_jpeg@realhouses.com";
SOAPElement interiorImRef = listingElement.addChildElement("interiorImage");
interiorImRef.addAttribute(hrefAttName, interiorID);
要方便地把需要的图像文件附加在信息中,可以使用JavaBeans Activation Framework架构里面的javax.activation.DataHandler对象。DataHandler能自动地检测传递给它的数据类型,而且它还能自动地分配适当的MIME类型给附件:
URL url = new URL("file:///eXPort/files/pic1.jpg");
DataHandler dataHandler = new DataHandler(url);
AttachmentPart att = message.createAttachmentPart(dataHandler);
att.setContentId(frontImageID);
message.addAttachmentPart(att);
另一种方法,你可以把一个对象,连同正确的MIME类型一起,传递到createAttachmentPart()方法里。这个方法跟第一个方法类似。在内部,SAAJ将会寻找处理器DataContentHandler 来处理这个MIME类型。 如果它不能找一个合适的处理器,createAttachmentPart() 方法将会抛出一个IllegalArgumentException异常:
URL url2 = new URL("file:///export/files/pic2.jpg");
Image im = Toolkit.getDefaultToolkit().createImage(url2);
AttachmentPart att2 = message.createAttachmentPart(im, "image/jpeg");
att2.setContentId(interiorID);
message.addAttachmentPart(att2);
这个方法的缺点集中在:它依赖于AWT中示例UI(用户界面user interface)相关的类。 在一些粗心的(服务器)设置中,这些库并没有被正确的设置。
不管你选择什么方法来产生附件,上述的代码表示了在第一个列表里面的SOAP信息。 既然这只是一个简单的XML信息,你可以用任何Web服务器能够接收的方法来发送这个消息: 一个直接的HTTP连接,JMX(JAVA管理扩展Java Management Extensions),JavASPaces, SMTP(简单邮件传输协议Simple Mail Transfer Protocol),等等。 SAAJ 提供了简单的方法来通过HTTP协议发送和接收SOAP信息。你只需要指定要发送的信息和连接终端的URL即可:
URL end point =
new URL("http://localhost:8080/saajtest/servlet/InvServlet");
SOAPMessage response = connection.call(message, end point);
SOAP连接的call()方法是同步的; 它会挂起直到它接收到回应。 回应以SOAP信息的形式发送过来。
用saaj接收并处理soap 信息
读取和处理一个 SOAP 信息和创建一个这样的信息相似。在这一个例子中, servlet监听进入的SOAP信息,然后使用SAAJ来决定该如何处理它们。下面的代码段演示了用另外一种方法来构造 SOAP信息: 通过传递一组MIME头(MIME headers)和一个输入流到SOAP信息工厂来构造信息。 因此,在创建一个SOAP信息之前,你一定要解析HTTP请求的MIME头:
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
...
MimeHeaders mimeHeaders = new MimeHeaders();
Enumeration en = req.getHeaderNames();
while (en.hasMoreElements()) {
String headerName = (String)en.nextElement();
String headerVal = req.getHeader(headerName);
StringTokenizer tk = new StringTokenizer(headerVal, ",");
while (tk.hasMoreTokens()){
mimeHeaders.addHeader(headerName, tk.nextToken().trim());
}
}
SOAPMessage message =
messageFactory.createMessage(mimeHeaders, req.getInputStream());
通过那个SOAP信息,servlet能处理流入的信息。 要取回并发送公司的名字,比如,你或许首先要获得SOAP体的元素然后获得公司名字相应的元素:
SOAPBody body = message.getSOAPBody();
Name listingElName = soapFactory.createName(
"propertyListing", "realProperty",
"http://schemas.realhouses.com/listingSubmission");
Iterator listings = body.getChildElements(listingElName);
while (listings.hasNext()) {
// The listing and its ID
SOAPElement listing = (SOAPElement)listings.next();
String listingID = listing.getAttribute("id");
// The listing agency name
Iterator ageIt = listing.getChildElements(soapFactory.createName("listingAgency"));
SOAPElement listingAgency = (SOAPElement)ageIt.next();
String agencyName = listingAgency.getValue();
...
}
每个附件相关的内容ID使得Servlet能够从SOAP里取得需要的图片。这一个例子中,已经做了广告的属性的前景图片被保存在文件系统中。附件的MIME内容的类型决定了分配给保存文件的扩展名:
Iterator frontImage = listing.getChildElements(soapFactory.createName("frontImage"));
SOAPElement front = (SOAPElement)frontImage.next();
if (front != null) {
String frontID = front.getAttribute("href");
Iterator attachments = message.getAttachments();
while (attachments.hasNext()) {
AttachmentPart att = (AttachmentPart)attachments.next();
if (att.getContentId().equals(frontID)) {
String contentType = att.getContentType();
String[] parts = contentType.split("/");
String fileExt = "UNK";
if (parts.length > 0) {
fileExt = parts[1];
}
InputStream is = att.getDataHandler().getInputStream();
FileOutputStream os =
new FileOutputStream("/tmp/" + listingID+ "_front." + fileExt);
byte[] buff = new byte[1024];
int read = 0;
while ((read = is.read(buff, 0, buff.length)) != -1) {
os.write(buff, 0, read);
}
os.flush();
os.close();
}
}
}
一旦servlet取得图像并且处理了SOAP信息的XML内容,它就通过HTTP请求输出给客户一个答复。由于客户会在这个回应中寻找MIME头的类型,你必须在HTTP输出流中指定MIME头的值:
SOAPMessage reply = messageFactory.createMessage();
SOAPHeader header = reply.getSOAPHeader();
header.detachNode();
SOAPBody replyBody = reply.getSOAPBody();
SOAPBodyElement bodyElement = replyBody.addBodyElement(
soapFactory.createName("ack"));
bodyElement.addTextNode("OK");
res.setHeader("Content-Type", "text/xml");
OutputStream os = res.getOuputStream();
reply.writeTo(os);
os.flush();
当客户接收到确认信息的时候, 它能够处理这个信息,这跟服务器处理客户信息的方式是一样的。
对用这种信息交换的一个告诫: 客户端和服务器端一定要知道每个信息的结构。 因为一个网络服务提供者总是把他的信息定义在Web服务注册表中,以Web服务描述语言(Web Services Description Language WSDL)文件的形式定义,所以客户端的程序员能检查服务的 WSDL 定义并且根据它构造信息。
在soap中发送二进制的附件? 没有问题
开发者时常感觉用SOAP完成基于XML的远程过程调用过分的复杂。 在这一篇文章中,你已经看到能以简单的方式使用SOAP发送、接收简单的信息,并且这个SOAP信息不仅能包含XML内容,还能包含二进制内容。 由于支持创造和解析SOAP文件的工具的不断增多, 在SOAP上构建的一个简单的文件-交换Web服务是非常方便的。
如这一篇文章所说的, SAAJ没有把构造和解析SOAP信息绑定到任何特定的信息传输通道。这样的无关性能让你自由选择最适合你程序的信息交换方式。比如,如果你需要容错的信息传输,你可能经由JAXM,Java信息服务(JMS)或JavaSpaces来传输SOAP信息。另一方面,如果你不需要招惹麻烦的高可靠性设计,就可能选择通过简单的HTTP连接经发送SOAP信息。
冬临 ,java 爱好者,Matrixxml soap翻译小组成员
进入讨论组讨论。
(出处:http://www.knowsky.com)