在Web应用程序中处理大文件下载的问题一直出了名的困难,因此对于大多数站点来说,如果用户的下载被中断了,它们只能说悲哀降临到用户的身上了。但是我们现在不必这样了,因为你可以使自己的ASP.NET应用程序有能力支持可恢复(继续)的大文件下载。使用本文提供的方法的时候,你可以跟踪下载的过程,这样你就可以处理动态建立的文件--而且要达到这个目标根本不需要旧式的ISAPI动态链接库和非受控的(unmanaged)C++代码。 为客户端提供从互联网上下载文件的服务最容易了,对吗?仅仅只需要把可下载的文件复制到你的Web应用程序目录中,发布链接并让IIS完成所有相关的工作。但是,文件服务不应该比脖子上的疼痛还要多(还要麻烦),你不希望整个世界都能访问自己的数据,你不希望服务器被数百个静态文件塞满了,你甚至于希望下载临时文件--只有当客户端开始下载后的空闲时间才建立这些文件。 不幸的是,使用IIS对下载请求的默认的响应是不可能达到这些效果的。因此在一般情况下,为了获得对下载过程的控制权,开发者需要链接到一个定制的.aspx页面,在这个页面中它们检查用户凭证(credential)、建立可以下载的文件并使用下面的代码把该文件推送给客户端:
而这就是出现真正麻烦的地方。 有什么问题? WriteFile方法看起来非常完美,它使文件的二进制数据流向客户端。但是直到最近我们才知道,WriteFile方法是一个出名的内存占用狂,它把整个文件载入服务器的RAM中来提供服务(实际上它甚至于会占用文件两倍大小的空间)。对于大文件,这会引起服务内存问题,并且可能重复ASP.NET过程。但是在2004年6月微软发布了一个补丁解决了这个问题。这个补丁现在是.NET Framework 1.1补丁包(SP1)的一部分。 这个补丁引入了TransmitFile方法,它把一个磁盘文件读入到较小的内存缓冲区之后就开始传输该文件。尽管这个方案解决了内存和循环的问题,但是它仍然不能令人满意。你不能控制响应的生命周期。你无法知道下载是否正确地完成了,你没有办法知道下载是否被中断了,并且(如果你建立了临时文件)你也不知道是否应该、以及什么时候可以删除这些文件。更糟的是,如果下载的确失败了,TransmitFile方法又从客户端下次尝试的文件头部开始下载。 其中一种可能的解决方案--实现后台智能传输服务(BITS)对于多数站点来说是不可行的,因为这会毁掉维持客户端浏览器和操作系统独立性而作出的努力。 令人满意的解决方案的基础还是来自微软用于解决WriteFile引起的内存混乱问题的第一次尝试(见知识库文章812406)。那篇文章演示了智能的大块数据下载过程,它从文件流中读取数据。在服务器把字节块发送给客户端之前,它使用Response.IsClientConnected属性检查客户端是否仍然保持着连接。如果仍然保持连接,它就继续发送流字节,否则就停止,以防止服务器发送不必要的数据。 这就是我们采用的方法,特别是在下载临时文件的时候。在IsClientConnected返回False的情况下,你就知道下载过程被中断了,你应该保存文件;反之,当这个过程成功完成的时候,你就删除临时文件。此外,为了恢复中断了的下载,你需要做的工作是从上次下载尝试过程中客户端连接失败的文件点开始下载。 HTTP协议和头信息(Header)支持 HTTP协议支持可以用于处理被中断下载的头信息。使用少量的HTTP头信息,你可以增强自己的下载过程,使它完全遵循HTTP协议规范。这个规范与ranges一起提供恢复被中断的下载所需要的一切信息。 下面是它的工作方式。首先,如果服务器支持客户端断点续传,它就在初始的响应中发送Accept-Ranges头信息。服务器还发送一个实体标签(entity tag)头信息(ETag),它包含一个唯一的标识字符串。 下面的代码显示了IIS发送给客户端的用于响应一个初始下载请求的一些头信息,它向客户端传递了被请求的文件的详细信息。
在接收这些头信息之后,如果下载被中断了,IE浏览器在后来的下载请求中会把Etag值和Range头信息发送回服务器。下面的代码显示了尝试恢复被中断下载时IE发送给服务器的一些头信息。
这些头信息表明IE缓存了IIS提供的实体标签,并在If-Range头信息中把它发送回服务器了,这是确保下载从准确相同的文件恢复的一种途径。不幸的是,并非所有的浏览器的工作方式都相同。客户端发送的用于验证文件的其它HTTP头信息可能是If-Match、If-Unmodified-Since或者Unless-Modified-Since。很明显,该规范对于客户端软件必须支持哪些头信息,或者必须使用哪些头信息没有明确的规定。因此,有些客户端根本就没有使用头信息,而IE只使用If-Range和Unless-Modified-Since。你最好用代码检查这些信息。采用这种方式的时候,你的应用程序可以在非常高的层次遵循HTTP规范,并可以使用多种浏览器。Range头信息指明了被请求的字节范围--在例子中它是服务器应该恢复文件流的起始点。 当IIS接收到恢复下载的请求类型时,它发回包含下面的头信息的响应信息:
示例代码
我们知道了客户端和服务器如何交换头信息以保证可恢复的下载,把这些知识与文件块流的思想结合起来,你就可以给自己的ASP.NET应用程序增加可靠的下载管理能力了。 获取下载过程的控制权的方法是从客户端截取下载请求、读取头信息并适当地响应。在.NET之前,你必须编写ISAPI(Internet服务器API)应用程序来实现这种功能,但是.NET框架组件提供了一个IHttpHandler接口,在类中实现的时候,它允许你仅仅使用.NET代码就能够截取和处理请求。这意味着你的应用程序对于下载过程有完全控制权和响应性,再也不会涉及或使用IIS的自动化函数。 示例代码在HttpHandler.vb文件中包含了一个自定义的HttpHandler类(ZIPHandler)。ZipHandler实现了IhttpHandler接口,并且处理对所有.zip文件的请求。 为了测试示例代码,你需要在IIS中建立一个新的虚拟目录,并把源文件复制到那儿。在该目录中建立一个叫做download.zip的文件(请注意IIS和ASP.NET不能处理大于2GB的下载,因此要确保你的文件没有超过该限制)。配置你的IIS虚拟目录,通过aspnet_isapi.dll映射.zip扩展名。 HttpHandler类:ZIPHandler 在ASP.NET中映射了.zip扩展名之后,客户端每次向服务器请求.zip文件的时候,IIS调用ZipHandler类的ProcessRequest方法(见下载代码)。 ProcessRequest方法首先建立自定义的FileInformation类(见下载代码)的一个实例,它封装了下载的状态(例如进行中、被中断了等等)。示例把download.zip示例文件的路径硬编码到代码中了。如果把这段代码应用于你自己的应用程序,需要修改它来打开被请求的文件。
FileInformation辅助类
FileInformation使用System.IO.FileInfo对象来获取文件的信息,这些信息是作为该对象的属性暴露的(例如文件是否存在、文件全名、大小等等)。这个类还暴露了一个DownloadState枚举,它描述了下载请求的多种状态:
FileInformation还提供了EntityTag属性值。示例代码中的这个值是硬编码的,这是由于示例代码只使用了一个下载文件,并且该文件不会被改变,但是对于实际应用程序来说,你会提供多个文件,甚至于动态地建立文件,你的代码必须为每个文件提供一个唯一的EntityTag值。此外,每次改变或修改该文件的时候,这个值也必须改变。这使客户端软件能够验证它们已经下载的字节块是否仍然是最新的。下面是示例代码中返回硬编码EntityTag值的部分:
一个简单的和大致足够安全的EntityTag可能由文件名和文件最后被修改的日期组成。无论使用什么方法,你都必须确保这个值是真的是唯一的,不会与其它文件的EntityTag混淆。我希望在自己的应用程序中按照客户、顾客和邮编索引来动态地替被建立的文件命名,并把用作EntityTag的GUID存储在数据库中。 ZipFileHandler类读取和设置公共的State属性。在完成下载以后,它把State设置为fsDownloadFinished。这个时候你就可以删除临时文件了。这儿一般需要调用Save方法来维持状态。
在文件状态发生改变的任何时候ZipFileHandler都应该调用Save方法,保存文件的状态,这样在以后才能显示给用户。你还可以用它来保存你自己建立的EntityTag。请不要把文件的状态和EntityTag值保存在Application、Session或Cache中--你必须跨越所有的这些这些对象的生命周期来保存信息。
前面提到,示例代码只处理一个已有的文件(download.zip),但是你可以进一步增强这个程序,根据需要建立被请求的文件。 测试示例代码的时候,你的本地系统或LAN可能太快了,以至于无法中断下载过程,因此我推荐你使用慢速LAN连接(在IIS中减少站点的带宽是一种模拟的方法)或者把服务器放到互联网上。 在客户端上下载文件仍然很艰难。ISP操作的不对的或配置错误的Web缓冲服务器都可能使大文件下载过程失败,包括下载状况恶化或早期对话终结。如果文件大小超过了255MB,你就应该鼓励顾客使用第三方下载管理软件,尽管某些最新的浏览器内建了基本的下载管理器。 如果你希望进一步扩展示例代码,查阅一下HTTP规范是有益的。你可以为下载建立MD5校验值,使用Content-MD5头信息添加它们,提供一种验证下载文件完整性的途径。示例代码除了GET和HEAD之外没有涉及到其它的HTTP方法。 | ||||||||||||
|
跟踪和恢复大文件下载
最新推荐文章于 2022-11-24 15:45:28 发布