使用 Web Services Enhancements 发送带有附件的 SOAP 消息
Web Consultant
2003年2月
适用于:
Microsoft® ASP.NET WebMethods
Web Services Enhancements 1.0 for Microsoft® .NET
WS-Attachments 规范
DIME 规范
Microsoft® SOAP Toolkit 3.0
摘要:介绍 Web Services Enhancements 1.0 for Microsoft .NET 支持使用 DIME 和 WS-Attachments 规范来发送附件的方法。
目录
WS-Attachments 是一种推荐的 Web 服务标准,它能利用 DIME 消息封装协议来帮助在 SOAP 消息中发送附件。本文将介绍 Web Services Enhancements (WSE) 1.0 for Microsoft .NET 支持使用 DIME 和 WS-Attachments 规范来发送附件的方法。
简介
WS-Attachments 规定了一种使用直接 Internet 消息封装 (DIME) 发送和接收带有附件的 SOAP 消息的方法,附件的形式可以是二进制文件、XML 片断,还可以是其他 SOAP 消息。根据 Microsoft 和 IBM 向 Internet 工程任务组 (IETF) 提交的规范,DIME 用于以类似于 MIME 的方法封装 SOAP 消息及其相关附件。象 SOAP 一样,DIME 消息可以使用标准传输协议(如 HTTP、TCP 和 UDP)来发送。DIME 支持对数据进行流处理。DIME 甚至可以在没有 SOAP 时使用,但此时 DIME 描述消息内容的能力会受到影响。
Microsoft 在 Web Services Enhancements (WSE) 的第一版中支持 DIME 和 WS-Attachments,以便使用 Microsoft .NET 开发安全、稳定、可缩放的 Web 服务。本文主要讨论 DIME 的实现而不是协议本身。关于 DIME 协议的进一步讨论,请参阅 MDSN Magazine 12 月号中我写的关于 DIME 的文章(英文)。
DIME 并不严格要求与 SOAP 一起使用,DIME 的推动力主要是更高效地传输 SOAP 消息的附件,这对于需要包含大型二进制文件(如媒体文件或二进制数据文件)的 Web 服务尤其有用。当然,没有 DIME,也可以在 SOAP 消息中发送数据。例如,如果希望在 SOAP 消息中向发出请求的客户端发送大型媒体文件,可以将这些二进制附件编码为 Base64 XML,并将它们包含在 SOAP 消息的正文中。但是,当附件非常大时,处理起来相当不方便;如果进行了数字签名,甚至可能无法实现。同样,当需要发送编码方式不同于主 SOAP 消息的其他 SOAP 消息、XML 文档或 XML 片断时,情况就会变得非常复杂。在有些情况下,能够向 SOAP 消息附加二进制数据非常重要。
优化 DIME 以与 SOAP 消息一起使用。依靠已经存在于 SOAP 消息中的元数据,DIME 分析器无需从 DIME 消息本身中读取大量元数据,从而减轻了负担。这就使得 DIME 消息的分析更快,效率更高。有关 DIME 优于 MIME 的详细信息,请参阅 Matt Powell 的文章 Understanding DIME and WS-Attachments(英文)。
Web Services Enhancements 是什么?
为了实现 Web 服务在企业中的互操作性,XML Web Service 的主要供应商(包括 Microsoft、IBM 和 Verisign)提出了新的规范,用以改进关键 Web 服务领域(如安全性、消息传递的可靠性和发送附件)的互操作性。为了支持这些新提出的标准,Microsoft 发布了 Web Services Enhancements (WSE) 1.0,它包括一组用于实现这些新协议的类和一组由 Microsoft ASP.NET 提供的过滤器,这些过滤器用来截取传入和传出的 SOAP 消息,以及解释或生成 SOAP 标头以支持所需的功能。WSE 1.0 支持以下规范:
- WS-Security
- Web Services Security Addendum
- DIME
- WS-Attachments
- WS-Routing
- WS-Referral
有关详细信息,请参阅 Web Services Enhancements for Microsoft .NET(英文)。
在 Web Services Enhancements 中使用 DIME
WSE v1.0 使用 ASP.NET 来支持带 SOAP 附件的 DIME,并可以从 I/O 流读取 DIME 消息或将 DIME 消息写入 I/O 流。本节介绍在 ASP.NET 中向 SOAP 消息添加二进制附件的详细过程,随后讨论 WSE 对 DIME 流的支持。
WSE 运行时实现 DIME 的方法
对于带有附件的 SOAP 消息,WSE 运行时实现了一个 DIME 兼容的消息分析器,用于转换传入的 DIME 消息的记录,并从第一个 DIME 记录中提取主 SOAP 消息,从后续的附件对象记录中提取封装的文件。当主 SOAP 消息从 DIME 中提取出来之后,它被传递到 WSE 消息管道中,并由一系列输入过滤器根据 WSE 支持的其他标头对其进行处理。下图显示了 WSE 运行时和 ASP.NET 处理传入的 DIME 消息的方法。
图 1:WSE 运行时和 ASP.NET 处理传入的 DIME 消息
对于带附件的出站 SOAP 响应消息,WSE 中的 DIME 消息分析器构建一个新的 DIME 消息,其中第一个 DIME 记录是出站 SOAP 响应,而所有指定的附件都被封装在后续的 DIME 记录中。下图显示了 WSE 运行时组装 DIME 消息的方法。
图 2:WSE 运行时组装 DIME 消息
WSE 运行时通过丰富的 API 来进行编程控制并支持 DIME。Microsoft.Web.Services.Dime 命名空间中的对象支持 DIME。对于由 ASP.NET 提供的基于 SOAP 的 Web 服务,Microsoft.Web.Services 命名空间中的SoapContext 类通过为给定的 SOAP 消息指定高级 Web 服务协议的使用和属性来控制 WSE 运行时的行为。在 WSE 编程模型中,当收到 SOAP 请求信息时,运行时为接收到的请求生成一个 HttpSoapContext 对象。HttpSoapContext.RequestContext 属性访问消息的 SoapContext 对象并枚举包含在消息的 WSE 专用标头元素中的信息。
如果传入的 DIME 消息符合 WS-Attachments 规范,WSE 运行时就把它们作为带附件的 SOAP 消息来处理,并提取主 SOAP 消息及其所有附件。对于每个附件,都会向 DimeAttachmentCollection 添加一个 DimeAttachment 对象,这样就可以通过SoapContext.Attachments 属性访问这些附件。与此类似,可以向传出消息的 SoapContext 对象的 DimeAttachmentCollection 中添加附件,以便 WSE 运行时将它们包含在传出消息的 DIME 记录中。
接下来,让我们看一下创建带附件的 DIME 消息作为 Web 服务响应的过程。
为 DIME 配置 Web Services Enhancements
要在 DIME 中使用附件,即使在 ASP.NET Web 服务器上安装了 WSE,仍然需要对 ASP.NET 应用程序进行一些额外的 DIME 相关配置。在 Microsoft® Visual Studio® .NET 中创建新的 ASP.NET Web 服务项目之后,需要把对 Microsoft.Web.Services.dll 程序集的引用添加到项目中。还要向 soapExtensionTypes 元素添加一个新类型,方法是在 Web.config 文件中为项目添加一个 add 元素,如下所示:
<configuration> <system.web> ... <webServices> <soapExtensionTypes> <add type= "Microsoft.Web.Services.WebServicesExtension, Microsoft.Web.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" /> </soapExtensionTypes> </webServices> </system.web> </configuration>
在本例中,type 属性值不能包含分隔符或空格。如果 webServices 和 soapExtensionTypes 元素不存在,还必须将它们添加到 Web.config 文件中。WSE 设置工具是一个不受支持的 Visual Studio 外接程序,可用来方便地配置使用 WSE 的 Web 服务项目。通过安装该工具,就可以很容易地配置基于 WSE 的项目,使其使用 DIME。
向使用 WSE 的 SOAP 消息添加附件
假设有一个名为 ImageDimeService 的 Web 服务类,它具有一个返回二进制 JPEG 图像文件的公有方法。在服务的 .aspx 文件的内含代码页面中,应当使用 using 指令为这些命名空间创建别名,如下所示:
using System.Web.Services using Microsoft.Web.Services using Microsoft.Web.Services.Dime
在本例中,ImageDimeService 类的定义如下:
[WebService(Namespace="http://example.com/dime/", Description="Web 服务使用 DIME 返回一个或多个 JPEG 文件。")] public class ImageService : System.Web.Services.WebService { ...
下面的 GetImage 方法实现 WebMethod,返回所请求的 JPEG 文件:
[WebMethod] public void GetImage(string[] imageNameCollection) { // 获取 SoapContext 作为响应消息 SoapContext myContext = HttpSoapContext.ResponseContext; // 创建一个数组,用于返回相关 DIME 附件的 URI。 string[] retUri= new string[imageNameCollection.Length]; int i = 0; // 迭代器 // 为由 imageID 数组中的值指定的每个文件 // 创建一个 DimeAttachment 对象。 Foreach (string imageName in imageNameCollection) { // 字符串,表示附件的文件名和路径。 string filePath = "C:\\images\\" + imageName + ".jpg"; // 使用文件名来创建新的 DIME 附件, // 并通过 MIME 媒体类型 image\jpeg // 来指定附件编码。 DimeAttachment dimeImage = new DimeAttachment( "image/jpeg", TypeFormatEnum.MediaType, filePath); // 生成对附件对象的基于 GUID 的 URI 引用, // 并将其指定给 DIME 记录的 ID 属性。 dimeImage.Id = "uri:" + Guid.NewGuid().ToString(); // 将新的 DimeAttachment 对象添加到 SoapContext 对象中。 myContext.Attachments.Add(dimeImage); // 将生成的 URI 添加到返回的数组中。 retUri[i] = dimeImage.Id; i++; } // 返回与附件的 ID 值匹配的 URI // 数组。 return retUri; }
在 WebMethod 方法中,从客户端发出的 SOAP 请求消息包含一组图像名称。对于集合中的每一个名称,Web 服务都要从 C:\images 文件夹中检索关联的 JPEG 文件并将图像添加到 ResponseContext 的 DimeAttachmentCollection 中。当 WebMethod 返回时,WSE 运行时将响应消息作为主 DIME 记录,并将每个 DimeAttachment 作为一个后续记录,这些记录的顺序与它们被添加到 DimeAttachmentCollection 中的顺序相同。
从 DIME 消息提取附件
为了让使用 Web 服务的客户应用程序接收并处理 DIME 消息,客户端也必须安装 WSE。要使 Visual Studio 中的 Microsoft .NET 客户应用程序能够支持 WSE DIME,必须添加对 Microsoft.Web.Services.dll 程序集的引用。此外,向基于 DIME 的 Web 服务添加了 Web 引用后,必须修改 References.cs 文件中的代理类,以便从 WSE 中的Microsoft.Web.Services.WebServicesClientProtocol 类继承。例如,上面例子中生成的 ImageService Web 服务的客户端 Web 服务代理需要按以下方式修改:
public class ImageService : Microsoft.Web.Services.WebServicesClientProtocol
如果客户端已经安装了 WSE 设置工具,Add Web Reference(添加 Web 引用)将自动生成一个单独的、以“Wse”结尾的代理类,该类是从相应的 WSE 类继承得到的。在这种情况下,将 ImageServiceWse 用作服务代理即可。
下面的示例显示了使用 ImageService Web 服务检索多个 JPEG 文件作为 DIME 附件的方法。
try { // 调用 Web 方法来传递图像名称数组, // 并从返回的数组中捕获 URI 值, // 以便通过 ID 来引用附件。 string[] retUri = myService.GetImage(imageNameCollection); } catch (Exception ex) { // 处理任何异常错误。 } // 检查响应消息是否包含任何附件。 if (myService.ResponseSoapContext.Attachments.Count > 0) { // 显示每个附加的 JPEG 图像文件。 for (int i=0; i<myService.ResponseSoapContext.Attachments.Count;i++) { // 将附加的图像转换成新的位图对象,以供显示。 Bitmap myImage = new Bitmap(myService.ResponseSoapContext.Attachments[i].Stream); // 对图像执行一些操作。 } // 如果未返回任何图像则警告用户。 MessageBox("No images were returned"); }
在本例中,使用 DimeAttachment.Stream 属性,将每个 JPEG 附件作为数据流从内存写入新的位图对象中。
WSE 中的高级 DIME 支持
WSE 可以方便地将附件与传出消息封装在一起,而 WSE 对 DIME 的支持还提供了一些更高级的功能,本节将对这些功能进行讨论。
将大附件分块
正如您在 MSDN Magazine 中我写的关于 DIME 的文章(英文)中看到的一样,DIME 规范支持将大附件分成多个记录,这对处理超大型附件非常有用。分块的原理是将大附件分成块,这样,在将附件写入单个 DIME 记录时,就无需将整个附件放入缓冲区。WSE 允许您使用 DimeAttachment.ChunkSize 属性以字节为单位指定附件的块大小。但是,由于 ASP.NET WebMethods 不支持流,因此在 ASP.NET 中使用 WSE 时,整个 DIME 消息都会被缓存到内存中,这就限制了实现时分块的有效性。使用 DIME 传输大附件的更好方法是通过适当的传输方法对 DIME 进行流处理。
使用 DimeReader 和 DimeWriter 对数据进行流处理
DIME 是一种基于消息的格式,DIME 记录被序列化和可以分块的事实使它能够通过数据包级传输协议(如 TCP 和 UDP)高效使用。WSE 1.0 支持 DIME 消息和 System.IO.Stream 对象(如 NetworkStream)之间的双向流处理,而不受基于消息的协议(如 HTTP)的限制。当作为 DIME 流处理时,WSE 运行时将传出的 DIME 记录分块,这样就无需定义被进行流处理的对象大小。当到达流结尾处时,写入最后的记录块。下面的示例从一个文件(如大型二进制图像文件)生成输入流,将该流写到一系列的分块 DIME 记录中,并将 DIME 消息写到一个 Stream 对象中,这个对象可以是通过 TCP 传输的 NetworkStream:
// 将二进制输入流采样转换成指定 MIME 媒体类型的 // DIME 流。 static void WriteToDime(Stream inputStr,Stream dimeStr,string mediaType) { // 创建写入器,用于将 DIME 消息写入 dimeStream DimeWriter myDW = new DimeWriter(dimeStr); // 创建 GUID,用作 DIME 记录 ID。 Guid guid = Guid.NewGuid(); string id = string.Format("uuid:{0}", guid.ToString()); // 创建新的 DIME 记录,其 MIME 媒体类型由 // mediaType 指定,并且用 contentLength 值 -1 来指定分块。 DimeRecord myRecord = myDW.LastRecord(id, mediaType, TypeFormatEnum.MediaType, -1); // 指定很小的记录块:4KB。 myRecord.ChunkSize = 4096; // 使用 BinaryWriter 将流写入 DimeRecord。 BinaryWriter myWriter = new BinaryWriter(myRecord.BodyStream); // 将传入流中的内容写入 BinaryWriter。 int size = 4096; byte[] bytes = new byte[4096]; int numBytes; while((numBytes = inputStr.Read(bytes, 0, size)) > 0) { myWriter.Write(bytes, 0, numBytes); } // 清理 myDW.Close(); }
值得注意的是,基于 SOAP 的 DIME 实现是将整个 DIME 消息读入内存,而对 DIME 进行流处理时,您可以使用 DimeRecord.ChunkSize 来限制需要使用的内存量。
以下方法实现了相反的过程,即读取 DIME 流,并将记录内容提取到二进制流中。
static void ReadFromDime(Stream dimeStr, Stream outStr) { // 创建读取器,用于读取流式 DIME 消息。 DimeReader myDR = new DimeReader(dimeStr); // 创建 DimeRecord,以便(仅)读取当前的 DIME 记录。 DimeRecord myRecord = myDR.ReadRecord(); // 创建读取器,以便将记录内容读到其中。 BinaryReader myReader = new BinaryReader(myRecord.BodyStream); // 将内容写到输出流中。 int size = 4096; byte[] bytes = new byte[4096]; int numBytes; while((numBytes = myReader.Read(bytes, 0, size)) > 0) { outStr.Write(bytes, 0, numBytes); } // 清理 myReader.Close(); myDR.Close(); }
对 DIME 使用 WSE 时的问题
和 1.0 版产品一样,WSE 在支持 DIME、WS-Attachments 和相关规范的方法上,存在一些限制。这些问题包括:
保护附件
根据 WS-Security 规范(英文),WSE 对保护 SOAP 消息提供了全面的支持,但对以 DIME 消息形式发送的 SOAP 消息附件没有提供保护。如果告诉 WSE 对主 SOAP 消息进行签名和加密,SOAP 消息将通过 WSE SecurityOutputFilter 传输,并在此过程中得到适当的保护。只有通过各种过滤器传递,SOAP 消息才能封装为带附件的 DIME 消息,也就是说附件本身并未被保护,因为它们可以被任意 DIME 分析器读取。
如果需要保护 DIME 消息中的附件,则应该使用一种 .NET 框架支持的加密机制,该机制由 System.Security.Cryptography 命名空间提供。此外,由于当前的 DIME 规范没有定义对 DIME 消息标头进行签名的方法,WSE 无法确定 DIME 消息是否被篡改。尤其是当使用 href 来表示特定的附件 ID 时,WSE 本身无法验证是否有人修改了 ID 或是将引用指向了完全不同的地方。因此,当使用 DIME 来传输敏感数据时,应该始终对流进行保护或使用安全传输。
引用附件
WSE 支持 DIME 记录的 ID 字段,可以使用 DimeAttachment.Id 属性来设置这个字段。它允许您使用友好的 ID 字符串值而不是整数索引值来分配和访问附件。例如,通过指定 ["Tom"] 而不是数组索引 [0],就可以访问 ID 为 Tom 的附件,如下所示:
myService.ResponseSoapContext.Attachment["Tom"];
从技术上讲,ID 应当是一个 URI,但 WSE 对此没有强制要求。
WS-Attachments 规范规定了通过 DIME 记录中包含的 ID 值从主 SOAP 消息引用附件。DIME 分析器读取主 SOAP 消息,然后通过只分析记录标头直到找到所需的记录,来快速定位附件,从而改进了 DIME 分析器的性能。但是,在 WSE 的最初版本中,Microsoft 认为附件是独立的,这就意味着 WSE 运行时仅按接收的顺序提取各个附件,并将它们作为对象保留在内存中,以供需要时访问。在上面的示例中,通过为集合中的每个附件指定一个 URI ID 值来引用附件,然后返回消息正文中这些 URI 值的集合。
DIME 不支持 WSDL
尽管 WSDL Extension for SOAP in DIME 规范(英文)详细介绍了一种以 Web 服务描述语言 (WSDL) 来支持 DIME 的方法,但是,WSE 没有更新 ASP.NET 中的 WSDL 生成功能和 Wsdl.exe 工具。这就意味着为了公布 Web 服务支持 DIME 的事实,必须根据这个新的规范手动编辑并发布 WSDL 文件。尽管最好使用 WSDL 描述 DIME 功能,但由于可以方便地检查传入消息的 SoapContext 对象来查看是否包含附件,因此并不真正需要完整的描述。
SOAP Toolkit 3.0 互操作性
由于 WSE 和 Microsoft® SOAP Toolkit 3.0 都支持 WS-Attachments 规范,因此可以创建包含带附件的 SOAP 消息的 DIME 消息,并且可以在这两个运行时之间读取消息。事实上,SOAP Toolkit 实现的 WS-Attachments 规范更完整,它支持从主 SOAP 消息引用附件。SOAP Toolkit 3.0 包括一个经过更新的 WSDL 生成器工具 (Wsdlgen3.exe),可生成符合 WSDL Extension for SOAP in DIME 规范(英文)的 WSDL 文件。WSE 不支持对基于 DIME 的 Web 服务的这些描述。
在这种互操作性中,需要注意的是通过 SOAP Toolkit 提供的高级 API 来使用带附件的基于 WSE 的 Web 服务。因为高级 API 使用 WSDL 文件来生成必要的连接对象以封送客户端和 Web 服务之间的通信,所以您需要根据 WSDL 扩展规范的定义,为基于 WSE 的 Web 服务手动修改并发布 WSDL 文件,以向 <wsdl:input> 和 <wsdl:output> 元素添加丢失的 <dime:message> 子元素。但是,SOAP Toolkit 3.0 只能处理符合 WS-Attachments 规范(英文)的 DIME 消息,也就是说,不能象 WSE 那样对数据进行流处理。
小结
Web Services Enhancements for Microsoft .NET 为发送和接收 SOAP 消息及其附件提供了完备的解决方案。除了支持 DIME 以外,WSE 运行时还提供进程过滤器管道,可使您方便地处理 DIME 消息的主 SOAP 消息中支持 WSE 的标头。WSE 还支持不基于 SOAP 的 DIME 实现,可让您使用 DIME 将数据作为一组 DIME 记录来进行流处理。