如何处理SOAP里的附件
[Date:2010-07-31] | From: Author: | [Font:Big Mid Small] |
Web 服务已被业界广泛接受用来解决复杂的问题和跨多个平台与系统的分布式过程。Web 服务通过使用基于标准的协议如 SOAP、WSDL 和 UDDI 以及标准组的开发工作实现了这一点。这些标准仍在不断改进,为所需的全部领域巩固解决方案。在这些解决方案尚不完善的领域,您仍需要解决如何在服务间传输定制数据类型,比如数据结构数组,以及如何处理附件来传输二进制和其它复杂的数据文件。
在本文中,我将说明目前支持处理 Web 服务中的定制数据编码和附件的协议和工具。我还将提出一个简单的个案研究,它演示如何使用现有的工具和标准来创建文件上传和下载 Web 服务。在我谈论到这些主题之前,让我们回顾一下 SOAP 和 WSDL 在工作时如何处理数据编码。
SOAP 和数据类型
一条 SOAP 消息包含一个 XML 文档定义,该定义可以用于在分散式、分布式环境中在对等端之间交换有一定结构和类型的信息。SOAP 消息通常是按照 SOAP 规范的第 5 部分的定义被编码。但是,并没有约束不准使用其它的编码模式如“XML 模式”(XML Schema)或 RDF,只要应用程序能够理解它们即可。例如,WSDL 中指定的 literal 编码方法使用 XML 模式进行编码。SOAP 规范的第 5 部分被创建用来弥补“XML 模式”的类型系统的早期版本中的缺陷,特别是 SOAP 所需的功能,例如数组和其它复杂的类型系统。但是,关于既然 XML 模式已经发展到支持 SOAP 所需的大多数功能,为什么还需要一个备用的特定编码模式这个问题已经在业界和标准工作组织之间引起了很大争议。
SOAP 消息交换可以被模型化为远程过程调用(Remote Procedure Call)(RPC),在这种模型中有一个定义完好的消息交换方法,它基于定义完好的方法调用说明(也即方法名称和参数)及其返回值/异常。SOAP 消息也可以看作被交换的文档(Document),其中被交换数据的语义只在应用程序级(发送方和接收方)被了解。
WSDL(Web services Definition Language,Web 服务定义语言)
WSDL 描述了一组 SOAP 消息,以及如何在 SOAP 处理器间交换这些消息。这使得公司能够描述它们的服务,也使得客户能够按标准方法消费服务,而不必对有关较低级别的交换协议(绑定),如 SOAP,了解很多。服务的这种高级抽象限制了人类的交互并使得能够为 Web 服务自动生成代理。(这些代理可以是静态的或者动态的)。WSDL 允许面向文档和面向 RPC 的消息描述。
WSDL 中有两种消息编码可用:literal 和 SOAP 编码。Literal 编码意味着将按照“XML 模式”构建消息。SOAP 编码使用 SOAP 规范的第 5 部分指定的规则。WSDL 中的另一个重要的概念是消息传递格式(message transfer format),使用这种格式,可以将消息作为“文档”或 RPC 参数传递。文档传递格式意味着消息部件是在消息主体中,而 RPC 意味着消息部件只是带有特定说明的调用的参数,包在 XML 元素中。这些传递格式符合 SOAP 消息交换的 SOAP 语义。
记住这些概念,我可以讨论 SOAP 和 Web 服务的一些有趣的方面,包括定制类型映射和附件支持。
类型映射
SOAP 中的类型映射被用于确定如何通过将本机语言对象组织或者重新格式化为 XML 来传递它们,这样就可以在 SOAP 消息中传送它们。这些类型映射被存储在客户机和服务器上的映射注册表中。大多数工具箱都有一些预定义的用于基本数据类型、集合、有时候甚至是 MIME 附件的转译程序(translator)。为传送定制类对象,您将需要创建新的序列化器和反序列化器,还需要在客户机和服务器中都注册新的类型映射。这种类型映射基于 SOAP 的 encodingStyle 功能。
通常每个类型映射都可能包含下面的信息:
- encodingStyle — 一个描述要使用的编码风格的 URI;例如,http://schemas.xmlsoap.org/soap/encoding/
- XML 元素的 Qname — 为特定类型定义提供的 URI,例如 http://www.w3.org/2001/XMLSchema:int
- “本机”语言类(从其中进行编码或解码至其中)
- 充当序列化器的“本机”语言类名称
- 充当反序列化器的“本机”语言类名称
现有的 SOAP 工具箱大多数都支持基于 SOAP 编码模式和 XML 模式的复杂类型映射。
Apache SOAP 2.2 的定制类型映射支持
为处理定制类型编码,必须在服务器和客户机中都定义新的类型映射。使用 Apache SOAP 2.2 工具箱有两种方法在服务器端注册类型映射:
- 使用部署描述符。
- 用新的类型映射重设缺省的映射注册表。新注册表必须是 SOAPMappingRegistry 的一个子类。
Apache 工具箱中还有两种方法在客户端注册类型映射:
- 创建一个 SOAPMappingRegistry 实例并使用 mapTypes() 方法添加新类型。
- 用所有预注册的映射创建 SOAPMappingRegistry 的一个子类。
为帮助开发者,Apache SOAP 还提供了一个“Java Bean 序列化器/反序列化器”(Java Bean Serializer/Deserializer)类,它使得所有按 JavaBeans 技术标准构建的类都能够被序列化或反序列化而不必写任何定制代码。这个“JavaBeans 序列化器/反序列化器”类,org.apache.soap.encoding.soapenc.BeanSerializer 可以在类型映射注册表中作为序列化器和反序列化器使用。
开发者还可以尝试创建定制序列化器和反序列化器(它们是通过分别实现 org.apache.soap.util.xml.Serializer 和 org.apache.soap.util.xml.Deserializer 创建的)进行定制处理。
在下面的示例中我将说明如何进行这种定制编码。
SOAP 协议和附件
您可能经常需要和各种附件(如图像、图画、xml 文档等)一起发送 SOAP 消息。这些数据通常是特殊的二进制格式。带附件的 SOAP 规范详细说明了使用“MIME multipart/related” 媒体类型和 URI 模式引用 MIME 部件。并不是所有的 Web 服务工具箱都提供 SOAP 附件支持。Microsoft 已经提出了另一个基于 DIME 的附件解决方案(请参阅参考资料)。
现在我将说明如何构造带附件的 SOAP 消息以及如何从 SOAP 消息引用附件。
SOAP 消息包
SOAP 消息包包含 XML 格式的主 SOAP 消息以及 SOAP 信封中未定义但与消息有关的任意数据格式(例如 gif、jpg 和 xml 等)的其它实体。
如图 1 所示,SOAP 消息包是用 MIME 的 Multipart/related 媒体类型构建的,每个部件都嵌入 MIME 边界(在 Context-Type 报头中定义)。每个 MIME 部件都有报头信息比如 Context-Type(它指定嵌入这个 MIME 部件的数据的类型)、Content Transfer-encoding(指定用于这个 MIME 部件的编码)、Content-ID 和/或 Content-Location(作为从 MIME 包的任何地方引用这些内容的标识符)。MIME 消息的根部件包含 SOAP 信封,Content-Type 被设置为 text/xml。
图 1. SOAP 消息包
SOAP 对附件的引用
SOAP 报头和 SOAP 消息的主体都可以引用消息包中的其它实体。根据 SOAP 1.1 编码规则,SOAP 的“href”属性(请参阅图 1)可用于引用任何资源。
任何资源都可以使用 Content-ID 或者 Content-Location 引用。如果您正在使用 Content-ID 作为标识符,那么模式属性(例如 href)必须使用如图 1 所示的 URL 模式 CID(根据 rfc 2153)。如果是使用 Content-Location 作为标识符,那么必须根据带附件的 SOAP 规范中指定的规则进行解析,其中解析是基于下列要素之一:
- 绝对 URI 引用 — 绝对 URL 是在 SOAP 信封中的 Content-Location 和“href”属性中指定的。
- 相对 URI 引用 — MIME 消息主报头的 Content-Location 中指定了一个基础(base)URL,对所有的相对 URL 都是使用这个基础 URL 进行引用。
- 不带基础 URI 的相对 URI — 主报头的 Content-Location 中没指定基础 URI,但使用消息的基础 URL,并且对所有的相对 URL 都是使用这个基础 URL 进行引用。
通常由 SOAP 处理器决定是否需要解析 URI。而且,消息包中可能有在 SOAP 信封中没有 URI 引用的附件。
HTTP 绑定
如果 HTTP 绑定被 SOAP 用来发送附件,就需要修改 HTTP 报头使其包含来自 SOAP MIME 包报头的 Content-Type 信息。HTTP 报头中不包含来自 MIME 包报头的其它报头信息。Apache 忽略了报头信息比如 Content-Transfer-Encoding 和 MIME Version。所有的 MIME 部件都包含 SOAP 信封部件和其它附件,构成 HTTP 主体。另一方面,对于 SMTP 绑定来说,所有的多部件 MIME 报头都会被作为 SMTP 报头的一部分存储。这样,SOAP 处理器和应用程序就应该知道这些报头与不同种类的 SOAP 绑定的不兼容性。
WSDL 支持描述带附件的 Web 服务
如下面的 WSDL 文档所示,binding 元素的 input 和/或 output 元素被扩展,并且包住了带有不同 MIME 部件的 MIME:multipart-related,其中一个 MIME 部件用于 SOAP 主体,其它的用于附件。每个带附件的 MIME 部件也都可以指定其内容类型。
清单 1:为 MIME 扩展 WSDL 绑定
<binding name="DocManagementService_Binding" ... /> <operation name="SubmitArrayOfDocuments"> <soap:operation soapAction="http://www.DocManagementService.com/SubmitArrayOfdata"/> <input> <mime:multipartRelated> <mime:part> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocManagement-service" parts="SubmitArrayOfDocuments_inType1 SubmitArrayOfDocuments_inType2" use="encoded"/> </mime:part> <mime:part> <mime:content part="SubmitArrayOfDocuments_inType3" type="*/*"/> </mime:part> </mime:multipartRelated> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocManagement-service" use="encoded"/> </output> </operation> </binding> |
Apache SOAP 2.2 工具箱支持带附件的 SOAP
根据 W3C 的“带附件的 SOAP 注解”(SOAP with Attachments Note),Apache SOAP 工具箱版本 2.2 支持带附件的 SOAP。根据所需的服务类型和细粒度控制,工具箱中有不同的技术可用来处理附件:
- 有一些内建序列化器/反序列化器使用 javax.activation.Datasource 对象和 javax.activation.DataHandler 对象处理附件。这主要用于基于 RPC 的客户机/服务器调用。
- 为获得对附件处理的更细粒度控制,客户端类如 org.apache.soap.messaging.Message、org.apache.soap.rpc.Call 和 org.apache.soap.rpc.Response 以及服务器端的 SoapContext 类提供了一些方法(addBodyPart(..)、findBodyPart(..)、getBodyPart(..)......)。这些方法允许向 SOAP 消息添加或检索一个 javax.mail.internet.MimeBodyPart。
有许多与使用带附件的 SOAP 有关的问题:
- 带附件的 SOAP 规范基于“MIME Multipart/related”。在 SOAP 社区有关于“MIME Application/Multiplexed”的使用的讨论,使用它可以在主 XML 文档中交叉存取二进制内容。这使得应用程序不需立刻将所有数据(包括附件)都加载到内存,有助于更好地伸缩。
- 有一些工具箱支持带附件的 SOAP 规范标准。Apache SOAP 2.2、WASP 也在其中。
- 目前大多数 SOAP 工具箱都不支持流模型(streaming model)来处理以附件或者消息的形式过来的大量数据。这导致了 SOAP 处理器的可伸缩性和性能问题。
构建这个示例
我使用 Web Services ToolKit 2.4.2 和 Apache SOAP toolkit 2.2 为 WebSphere Application Server 4.0 环境构建这个实现。WSTK 更新的版本现在已经存在,但这段代码应该是与之兼容的。您可以下载 ZIP 文件(DocumentManager.zip),它包含源代码、.ear(企业应用程序归档(ent erprise application archive))、.war(文档管理器 web 模块(document manager web module))和其它的文件(DD.xml 和 .wsdl 文件)以及安装这个 ZIP 文件的必需信息(请参阅“setup.doc”)。它还包含样本客户机源和类文件。(请参阅参考资料。)
一次简单的实现研究
对于这次实现研究,我将创建一个基于 Web 的应用程序使最终用户能够从 Web 服务器上传和下载他们的文件。该应用程序提供一个基于 Web 服务的调用模型。这个 Web 服务应用程序将使用户能够向服务器上传任意数量的文件,获得对这些文件的引用,还可以使用这些引用下载他们想要的文件。
通过发送多个附件和高级 WSDL 文档构造来支持 MIME 附件,这个示例将有助于说明定制类型映射。它还将有助于描述如何将定制数据作为数组来发送和检索。这个简单的应用程序可以增强为全球基于 Web 的文件管理应用程序。但是,这个示例没有考虑更复杂的安全性、国际化、隐私和性能方面的问题,在大规模的应用程序中,这些问题都是必须考虑的。
前端客户机和后端服务器的服务描述(WSDL)包含关于从“文档”管理器应用程序公开的服务的信息。已经扩展了 WSDL 绑定使其支持 multipart/related 类型的 MIME 格式。后端 WSDL 文件显示在清单 2 中,前端文件显示在清单 3 中。
清单 2:DocumentManagement-Interface.wsdl
<?xml version="1.0" encoding="UTF-8"?> <definitions name="DocumentManagementService_Interface" targetNamespace= "http://www.DocumentManagementService.com/DocumentManagementService_interface" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns= "http://www.DocumentManagementService.com/DocumentManagementService_interface" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:types= "http://www.DocumentManagementService.com/DocumentManagementService_interface/types/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" > <types> <xsd:schema targetNamespace= "http://www.DocumentManagementService.com/DocumentManagementService_interface/types/" xmlns="http://www.w3.org/2001/XMLSchema/"> <xsd:complexType name="User"> <xsd:sequence> <xsd:element name="userName" type="xsd:string"/> <xsd:element name="password" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="DocumentInformation"> <xsd:sequence> <xsd:element name="userDefinedDocName" type="xsd:string"/> <xsd:element name="localPathInfo" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="ArrayOfDocumentInformation"> <xsd:complexContent> <xsd:restriction base="SOAP-ENC:Array"> <xsd:sequence> <element minOccurs="0" maxOccurs="unbounded" name="docInfo" type=" DocumentInformation"/> </xsd:sequence> <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="DocumentInformation[]"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="DocumentReference"> <xsd:sequence> <xsd:element name="docCreateDateTime" type="xsd:datetime"/> <xsd:element name="docRefType" type="xsd:string"/> <xsd:element name="serverDocPath" type="xsd:string"/> <xsd:element name="localPathInfo" type="xsd:string"/> <xsd:element name="userDefinedDocName" type="xsd:string"/> <xsd:element name="docRef" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="ArrayOfDocumentReference"> <xsd:complexContent> <xsd:restriction base="SOAP-ENC:Array"> <xsd:sequence> <element minOccurs="0" maxOccurs="unbounded" name="docRef" type="DocumentReference"/> </xsd:sequence> <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="DocumentReference[]"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="ArrayOfString"> <xsd:complexContent> <xsd:restriction base="SOAP-ENC:Array"> <xsd:sequence> <element maxOccurs="unbounded" name="docs" type="xsd:string"/> </xsd:sequence> <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[]"/> </xsd:restriction> </xsd:complexContent> </xsd:complexType> </xsd:schema> </types> <message name="InSubmitArrayOfDocumentsRequest"> <part name="SubmitArrayOfDocuments_inType1" type="types:User"/> <part name="SubmitArrayOfDocuments_inType2" type="types:ArrayOfDocumentInformation"/> <part name="SubmitArrayOfDocuments_inType3" type="types:ArrayOfString"/> </message> <message name="OutSubmitArrayOfDocumentsResponse"> <part name="SubmitArrayOfDocuments_outType" type= "types:ArrayOfDocumentReference"/> </message> <message name="InFetchDocumentsRequest"> <part name="FetchDocuments_inType1" type="types:User"/> <part name="FetchDocuments_inType2" type= "types:ArrayOfDocumentReference"/> </message> <message name="OutFetchDocumentsResponse"> <part name=" FetchDocuments_outType1" type="xsd:string"/> </message> <message name="InPingDocumentMangerRequest"> <part name="PingDocumentManger_inType1" type="xsd:string"/> </message> <message name="OutPingDocumentMangerResponse"> <part name="PingDocumentManger_outType1" type="xsd:string"/> </message> <portType name="DocumentManager_portType"> <operation name="SubmitArrayOfDocuments"> <input message="tns:InSubmitArrayOfDocumentsRequest"/> <output message="tns:OutSubmitArrayOfDocumentsResponse"/> </operation> <operation name="FetchDocuments"> <input message="tns:InFetchDocumentsRequest"/> <output message="tns:OutFetchDocumentsResponse"/> </operation> <operation name="PingDocumentManger"> <input message="tns:InPingDocumentMangerRequest"/> <output message="tns:OutPingDocumentMangerResponse"/> </operation> </portType> <binding name="DocumentManagementService_Binding_Rpc" type= "tns:DocumentManager_portType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="SubmitArrayOfDocuments"> <soap:operation soapAction="http://www.DocManagementService.com/SubmitArrayOfData"/> <input> <mime:multipartRelated> <mime:part> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocumentManagement-service" parts="SubmitArrayOfDocuments_inType1 SubmitArrayOfDocuments_inType2" use="encoded"/> </mime:part> <mime:part> <mime:content part="SubmitArrayOfDocuments_inType3" type="text/html"/> </mime:part> </mime:multipartRelated> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocumentManagement-service" use="encoded"/> </output> </operation> <operation name="FetchDocuments"> <soap:operation soapAction="http://www.DocManagementService.com/FetchDocuments"/> <input> <mime:multipartRelated> <mime:part> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocManagement-service" parts="FetchDocuments_inType1 FetchDocuments_inType2" use="encoded"/> </mime:part> </mime:multipartRelated> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocManagement-service" use="encoded"/> </output> </operation> <operation name="PingDocumentManger"> <soap:operation soapAction= "http://www.DocumentManagementService.com/PingDocumentManger"/> <input> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocumentManagement-service" use="encoded"/> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:DocumentManagement-service" use="encoded"/> </output> </operation> </binding> </definitions> |
Using Google to translate:
Google translator