知识大全 ASP.NET中大文件下载的跟踪和恢复

Posted 文件

篇首语:有上不去的天,没过不去的关。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 ASP.NET中大文件下载的跟踪和恢复相关的知识,希望对你有一定的参考价值。

ASP.NET中大文件下载的跟踪和恢复  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  在Web应用程序中处理大文件下载的问题一直出了名的困难 因此对于大多数站点来说 如果用户的下载被中断了 它们只能说悲哀降临到用户的身上了 但是我们现在不必这样了 因为你可以使自己的ASP NET应用程序有能力支持可恢复(继续)的大文件下载 使用本文提供的方法的时候 你可以跟踪下载的过程 这样你就可以处理动态建立的文件——而且要达到这个目标根本不需要旧式的ISAPI动态链接库和非受控的(unmanaged)C++代码

  为客户端提供从互联网上下载文件的服务最容易了 对吗?仅仅只需要把可下载的文件复制到你的Web应用程序目录中 发布链接并让IIS完成所有相关的工作 但是 文件服务不应该比脖子上的疼痛还要多(还要麻烦) 你不希望整个世界都能访问自己的数据 你不希望服务器被数百个静态文件塞满了 你甚至于希望下载临时文件——只有当客户端开始下载后的空闲时间才建立这些文件

  不幸的是 使用IIS对下载请求的默认的响应是不可能达到这些效果的 因此在一般情况下 为了获得对下载过程的控制权 开发者需要链接到一个定制的 aspx页面 在这个页面中它们检查用户凭证(credential) 建立可以下载的文件并使用下面的代码把该文件推送给客户端

  Response WriteFile

  Response End()

  而这就是出现真正麻烦的地方

  有什么问题?

  WriteFile方法看起来非常完美 它使文件的二进制数据流向客户端 但是直到最近我们才知道 WriteFile方法是一个出名的内存占用狂 它把整个文件载入服务器的RAM中来提供服务(实际上它甚至于会占用文件两倍大小的空间) 对于大文件 这会引起服务内存问题 并且可能重复ASP NET过程 但是在 年 月微软发布了一个补丁解决了这个问题 这个补丁现在是 NET Framework 补丁包(SP )的一部分

  这个补丁引入了TransmitFile方法 它把一个磁盘文件读入到较小的内存缓冲区之后就开始传输该文件 尽管这个方案解决了内存和循环的问题 但是它仍然不能令人满意 你不能控制响应的生命周期 你无法知道下载是否正确地完成了 你没有办法知道下载是否被中断了 并且(如果你建立了临时文件)你也不知道是否应该 以及什么时候可以删除这些文件 更糟的是 如果下载的确失败了 TransmitFile方法又从客户端下次尝试的文件头部开始下载

  其中一种可能的解决方案——实现后台智能传输服务(BITS)对于多数站点来说是不可行的 因为这会毁掉维持客户端浏览器和操作系统独立性而作出的努力

  令人满意的解决方案的基础还是来自微软用于解决WriteFile引起的内存混乱问题的第一次尝试(见知识库文章 ) 那篇文章演示了智能的大块数据下载过程 它从文件流中读取数据 在服务器把字节块发送给客户端之前 它使用Response IsClientConnected属性检查客户端是否仍然保持着连接 如果仍然保持连接 它就继续发送流字节 否则就停止 以防止服务器发送不必要的数据

  这就是我们采用的方法 特别是在下载临时文件的时候 在IsClientConnected返回False的情况下 你就知道下载过程被中断了 你应该保存文件 反之 当这个过程成功完成的时候 你就删除临时文件 此外 为了恢复中断了的下载 你需要做的工作是从上次下载尝试过程中客户端连接失败的文件点开始下载

  HTTP协议和头信息(Header)支持

  HTTP协议支持可以用于处理被中断下载的头信息 使用少量的HTTP头信息 你可以增强自己的下载过程 使它完全遵循HTTP协议规范 这个规范与ranges一起提供恢复被中断的下载所需要的一切信息

  下面是它的工作方式 首先 如果服务器支持客户端断点续传 它就在初始的响应中发送Accept Ranges头信息 服务器还发送一个实体标签(entity tag)头信息(ETag) 它包含一个唯一的标识字符串

  下面的代码显示了IIS发送给客户端的用于响应一个初始下载请求的一些头信息 它向客户端传递了被请求的文件的详细信息  HTTP/ OKConnection: closeDate: Tue Oct : : GMTAccept Ranges: bytesLast Modified: Sun Sep : : GMTETag: febb cfd c : Cache Control: privateContent Type: application/x zip pressedContent Length:

    在接收这些头信息之后 如果下载被中断了 IE浏览器在后来的下载请求中会把Etag值和Range头信息发送回服务器 下面的代码显示了尝试恢复被中断下载时IE发送给服务器的一些头信息  GET HTTP/ Range: bytes= Unless Modified Since: Sun Sep : : GMTIf Range: febb cfd c :

  这些头信息表明IE缓存了IIS提供的实体标签 并在If Range头信息中把它发送回服务器了 这是确保下载从准确相同的文件恢复的一种途径 不幸的是 并非所有的浏览器的工作方式都相同 客户端发送的用于验证文件的其它HTTP头信息可能是If Match If Unmodified Since或者Unless Modified Since 很明显 该规范对于客户端软件必须支持哪些头信息 或者必须使用哪些头信息没有明确的规定 因此 有些客户端根本就没有使用头信息 而IE只使用If Range和Unless Modified Since 你最好用代码检查这些信息 采用这种方式的时候 你的应用程序可以在非常高的层次遵循HTTP规范 并可以使用多种浏览器 Range头信息指明了被请求的字节范围——在例子中它是服务器应该恢复文件流的起始点

  当IIS接收到恢复下载的请求类型时 它发回包含下面的头信息的响应信息  HTTP/ Partial ContentContent Range: bytes / Accept Ranges: bytesLast Modified: Sun Sep : : GMTETag: febb cfd c : Cache Control: privateContent Type: application/x zip pressedContent Length:

  请注意上面的代码与最初的下载请求的HTTP响应有点差别——恢复下载的请求是 而最初下载的请求是 这表明通过线路传递进来的内容是部分文件 这一次Content Range头信息指出了被传递字节的精确数量和位置

  IE对于这些头信息是很挑剔的 如果最初的响应没有包含Etag头信息 IE永远不会尝试恢复下载 我测试过的其它客户端不使用ETag头信息 它们简单得依赖于文件名 请求范围 并使用Last Modified头信息(如果它们试图验证该文件)

  深入了解HTTP协议

  前面的部分中显示的头信息对于使恢复下载的解决方案运行来说是足够的 但是它没有完全覆蓋HTTP规范

  在单个请求中 Range头信息可以询问多个范围 这种特性称为 多部分范围(multipart ranges) 请不要与分段下载(segmented downloading)混淆 几乎所有的下载工具都使用分段下载来提高下载速度 这些工具声称通过打开两个或多个并发的连接(每个连接请求文件的不同范围)提高了下载速度

  多部分范围的想法并没有开启多个连接 但是它可以使客户端软件可以在单个请求/响应周期中请求某个文件的最前面的十个和最后面的十个字节

  诚实地说 我从来都没有找到使用这种特性软件片断 但是我拒绝在代码声明中写入 它并不是完全的HTTP兼容的 略去这个特性必定会触犯墨菲法则(Murphy s Law) 无论如何 多部分范围还是被用于电子邮件传输中 把头信息 普通文本和附件分开

  示例代码

  我们知道了客户端和服务器如何交换头信息以保证可恢复的下载 把这些知识与文件块流的思想结合起来 你就可以给自己的ASP NET应用程序增加可靠的下载管理能力了

  获取下载过程的控制权的方法是从客户端截取下载请求 读取头信息并适当地响应 在 NET之前 你必须编写ISAPI(Internet服务器API)应用程序来实现这种功能 但是 NET框架组件提供了一个IHttpHandler接口 在类中实现的时候 它允许你仅仅使用 NET代码就能够截取和处理请求 这意味着你的应用程序对于下载过程有完全控制权和响应性 再也不会涉及或使用IIS的自动化函数

  示例代码在HttpHandler vb文件中包含了一个自定义的HttpHandler类(ZIPHandler) ZipHandler实现了IHandler接口 并且处理对所有 zip文件的请求

  为了测试示例代码 你需要在IIS中建立一个新的虚拟目录 并把源文件复制到那儿 在该目录中建立一个叫做download zip的文件(请注意IIS和ASP NET不能处理大于 GB的下载 因此要确保你的文件没有超过该限制) 配置你的IIS虚拟目录 通过aspnet_isapi dll映射 zip扩展名

  HttpHandler类 ZIPHandler

  在ASP NET中映射了 zip扩展名之后 客户端每次向服务器请求 zip文件的时候 IIS调用ZipHandler类的ProcessRequest方法(见下载代码)

  ProcessRequest方法首先建立自定义的FileInformation类(见下载代码)的一个实例 它封装了下载的状态(例如进行中 被中断了等等) 示例把download zip示例文件的路径硬编码到代码中了 如果把这段代码应用于你自己的应用程序 需要修改它来打开被请求的文件   使用objRequest检测请求了哪个文件 用该文件打开objFile 例如objFile = New Download FileInformation(<完整文件名>)objFile = New Download FileInformation( _objContext Server MapPath( ~/download zip ))

    接下来 程序使用描述的HTTP头信息(如果请求提供了头信息)执行一系列的验证检查 它把每种检查都封装在小型私有函数中 如果验证成功的话就返回True 如果某个验证检查失败了 响应会立即终止 并发送适当的StatusCode值  If Not objRequest HttpMethod Equals(HTTP_METHOD_GET) Or NotobjRequest HttpMethod Equals(HTTP_METHOD_HEAD) Then  目前只支持GET和HEAD方法 objResponse StatusCode = 没有执行ElseIf Not objFile Exists Then  无法找到被请求的文件 objResponse StatusCode = 没有找到ElseIf objFile Length > Int MaxValue Then  文件太大了 objResponse StatusCode = 请求实体太大ElseIf Not ParseRequestHeaderRange(objRequest alRequestedRangesBegin alRequestedRangesend _objFile Length bIsRangeRequest) Then  Range请求中包含无用的实体 objResponse StatusCode = 无用的请求ElseIf Not CheckIfModifiedSince(objRequest objFile) Then  实体没有被修改过 objResponse StatusCode = 没有被修改过ElseIf Not CheckIfUnmodifiedSince(objRequest objFile) Then  实体在上次被请求的日期之后被修改过 objResponse StatusCode = 预处理失败ElseIf Not CheckIfMatch(objRequest objFile) Then  实体与请求不匹配 objResponse StatusCode = 预处理失败ElseIf Not CheckIfNoneMatch(objRequest objResponse objFile) Then  实体的确与none match请求匹配   响应代码位于CheckIfNoneMatch函数中Else  初步检查成功

  这些初步检查的函数中的ParseRequestHeaderRange(见下载代码)检查客户端是否请求了文件范围(这意味着是一个局部下载) 如果被请求的范围是无效的(无效范围指超越文件大小或包含不合理数字的范围数值) 该方法把bIsRangeRequest设置为True 如果请求了范围 CheckIfRange方法会验证IfRange头信息

  如果被请求的范围是有效的 代码会计算响应信息的大小 如果客户端请求了多个范围 响应信息大小的数值会包含多部分头部信息长度的数值

  如果不能确定某个发送的头部信息值 程序将把这个下载请求作为最初请求而不是部分下载来处理 从文件的顶部开始发送一个新的下载流

  If bIsRangeRequest AndAlso CheckIfRange(objRequest objFile) Then  这是范围请求  如果Range数组包含多个实体 它还是一个多部分范围请求 bMultipart = CBool(alRequestedRangesBegin GetUpperBound( )> )  进入每个范围来获取整个响应长度 For iLoop = alRequestedRangesBegin GetLowerBound( ) To alRequestedRangesBegin GetUpperBound( )   内容的长度(这个范围的)  iResponseContentLength += Convert ToInt (alRequestedRangesend( _iLoop) alRequestedRangesBegin(iLoop)) +   If bMultipart Then    如果是多部分范围请求 计算出将发送的中间头信息的长度   iResponseContentLength += MULTIPART_BOUNDARY Length   iResponseContentLength += objFile ContentType Length   iResponseContentLength += alRequestedRangesBegin(iLoop) ToString Length   iResponseContentLength += alRequestedRangesend(iLoop) ToString Length   iResponseContentLength += objFile Length ToString Length    是多部分下载中换行和其它必要的字符的长度   iResponseContentLength +=   End If Next iLoop

  If bMultipart Then   如果是多部分范围请求    我们还必须计算出将发送的最后一个中间头信息的长度  iResponseContentLength +=MULTIPART_BOUNDARY Length   是破折号和换行符的长度  iResponseContentLength +=  Else   不是多部分下载 因此我们必须说明初始HTTP头信息的响应范围  objResponse AppendHeader( HTTP_HEADER_CONTENT_RANGE bytes & _  alRequestedRangesBegin( ) ToString & & _  alRequestedRangesend( ) ToString & / & _  objFile Length ToString)   End If   范围响应  objResponse StatusCode = 局部响应 Else   这不是范围请求 或者被请求的范围实体ID与当前的实体ID不匹配    因此开始新的下载   指明文件完成部分的大小等于内容的长度  iResponseContentLength =Convert ToInt (objFile Length)   返回正常的OK状态  objResponse StatusCode =  End If  接下来服务器必须发送几个重要的响应头信息 例如内容长度 Etag 和文件的内容类型   把内容长度写入响应 objResponse AppendHeader( HTTP_HEADER_CONTENT_LENGTH iResponseContentLength ToString)  把最后修改日期写入响应 objResponse AppendHeader( HTTP_HEADER_LAST_MODIFIED objFile LastWriteTimeUTC ToString( r ))  告诉客户端软件我们接受了范围请求 objResponse AppendHeader( HTTP_HEADER_ACCEPT_RANGES HTTP_HEADER_ACCEPT_RANGES_BYTES)  把文件的实体标签写入响应(用引号括起来) objResponse AppendHeader(HTTP_HEADER_ENTITY_TAG & objFile EntityTag & )  把内容类型写入响应 If bMultipart Then   多部分消息有这种特殊的类型   在例子中文件实际的mime类型在以后才写入响应  objResponse ContentType = MULTIPART_CONTENTTYPE Else   单个部分消息拥有的文件内容类型  objResponse ContentType = objFile ContentTypeEnd If

  下载所需要的一切都准备好了 可以开始下载文件了 你将使用FileStream对象从文件中读取字节块 把FileInformation实例objFile的State属性设置为fsDownloadInProgress 只要客户端保持连接 服务器就从文件中读取字节块并发送给客户端 对于多部分下载 这段代码会发送特定的头信息 如果客户端中断连接 服务器就把文件状态设置为fsDownloadBroken 如果服务器完成了被请求范围的发送过程 它会把状态设置为fsDownloadFinished(见下载代码)

  FileInformation辅助类

  在ZIPHandler部分中你会发现 FileInformation是一个辅助类 它封装了下载状态信息(例如下载中 中断等等)

  为了建立FileInformation的实例 你需要把被请求文件的路径传递给该类的构造函数  Public Sub New(ByVal sPath As String) m_objFile = New System IO FileInfo(sPath)End Sub

  FileInformation使用System IO FileInfo对象来获取文件的信息 这些信息是作为该对象的属性暴露的(例如文件是否存在 文件全名 大小等等) 这个类还暴露了一个DownloadState枚举 它描述了下载请求的多种状态

 <Flags()> Enum DownloadState  Clear 没有下载过程 文件可能在维护 fsClear =   Locked 动态建立的文件不能被更改 fsLocked =   In Progress 文件被锁定了 下载过程正在进行 fsDownloadInProgress =   Broken 文件被锁定了 下载过程正在进行 但是被取消了 fsDownloadBroken =   Finished 文件被锁定了 下载过程完成了 fsDownloadFinished = End Enum    FileInformation还提供了EntityTag属性值 示例代码中的这个值是硬编码的 这是由于示例代码只使用了一个下载文件 并且该文件不会被改变 但是对于实际应用程序来说 你会提供多个文件 甚至于动态地建立文件 你的代码必须为每个文件提供一个唯一的EntityTag值 此外 每次改变或修改该文件的时候 这个值也必须改变 这使客户端软件能够验证它们已经下载的字节块是否仍然是最新的 下面是示例代码中返回硬编码EntityTag值的部分  Public ReadOnly Property EntityTag() As String  EntityTag用于对客户端的初始( )响应 以及来自客户端的恢复请求 Get   为文件建立唯一的字符串    注意 只要文件没有发生改变 该唯一码就必须保留    但是 如果文件的确改变了或者被修改了 这个码必须改变   Return MyExampleFileID  End GetEnd Property

  一个简单的和大致足够安全的EntityTag可能由文件名和文件最后被修改的日期组成 无论使用什么方法 你都必须确保这个值是真的是唯一的 不会与其它文件的EntityTag混淆 我希望在自己的应用程序中按照客户 顾客和邮编索引来动态地替被建立的文件命名 并把用作EntityTag的GUID存储在数据库中

  ZipFileHandler类读取和设置公共的State属性 在完成下载以后 它把State设置为fsDownloadFinished 这个时候你就可以删除临时文件了 这儿一般需要调用Save方法来维持状态  Public Property State() As DownloadState Get  Return m_nState End Get Set(ByVal nState As DownloadState)  m_nState = nState   可选操作 这个时候你可以自动地删除文件    如果状态被设置为Finished 你就再也不需要这个文件了    If nState =DownloadState fsDownloadFinished Then    Clear()   Else    Save()   End If  Save() End SetEnd Property

    在文件状态发生改变的任何时候ZipFileHandler都应该调用Save方法 保存文件的状态 这样在以后才能显示给用户 你还可以用它来保存你自己建立的EntityTag 请不要把文件的状态和EntityTag值保存在Application Session或Cache中——你必须跨越所有的这些这些对象的生命周期来保存信息  Private Sub Save()  把该文件下载的状态保存到数据库或XML文件中   当然 如果你并没有动态地建立文件 就不需要保存这个状态 End Sub

  前面提到 示例代码只处理一个已有的文件(download zip) 但是你可以进一步增强这个程序 根据需要建立被请求的文件

  测试示例代码的时候 你的本地系统或LAN可能太快了 以至于无法中断下载过程 因此我推荐你使用慢速LAN连接(在IIS中减少站点的带宽是一种模拟的方法)或者把服务器放到互联网上

  在客户端上下载文件仍然很艰难 ISP操作的不对的或配置错误的Web缓冲服务器都可能使大文件下载过程失败 包括下载状况恶化或早期对话终结 如果文件大小超过了 MB 你就应该鼓励顾客使用第三方下载管理软件 尽管某些最新的浏览器内建了基本的下载管理器

cha138/Article/program/net/201311/12311

相关参考

知识大全 ASP.NET MVC和代码隐藏文件

ASP.NETMVC和代码隐藏文件  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  ASPNETM

知识大全 加密和解密ASP.NET配置文件(Web.config)

加密和解密ASP.NET配置文件(Web.config)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下

知识大全 ASP.NET文件下载函数使用浅析

ASP.NET文件下载函数使用浅析  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  ASPNET文

知识大全 asp.net下载文件

  SystemIOFileInfofile=newSystemIOFileInfo(pstrFilePath);  if(fileExists)    ResponseClear();  Respo

知识大全 ASP.NET下文件批量下载应用

ASP.NET下文件批量下载应用  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  场景描述  在B

知识大全 ASP.NET 从服务器下载文件

ASP.NET从服务器下载文件  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  stringfil

知识大全 ASP.NET中文件上传下载方法集合

ASP.NET中文件上传下载方法集合  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  文件的上传下

知识大全 asp.net显示下载提示的下载网页程序

cha138/Article/program/net/201311/14277

知识大全 Asp.NET大文件上传组件---提取文件内容

Asp.NET大文件上传组件---提取文件内容  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  为

知识大全 ASP.NET 简单下载的实现

ASP.NET简单下载的实现  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  简单页面下载通过Re