webdn
 
  首页     免费截图     VIP会员区     广告Banner     技术文档     网站模板  
网站截图,网站每日新 !
虚拟主机
网页模板

首页 >> WebDN 文档 >> 网络编程 >> ASP.net 技术资料 >> Page 22 >> 在ASP.NET中跟踪和恢复大文件下载

 

在ASP.NET中跟踪和恢复大文件下载

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

   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头信息(如果请求提供了头信息)执幸幌盗械难橹ぜ觳椤K衙恐旨觳槎挤庾霸谛⌒退接泻校绻橹こ晒Φ幕熬头祷豑rue。如果某个验证检查失败了,响应会立即终止,并发送适当的StatusCode值。
  
  
  
  If Not objRequest.HttpMethod.Equals(HTTP_METHOD_GET) Or Not
  objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD) Then
   ' 目前只支持GET和HEAD方法
   objResponse.StatusCode = 501 ' 没有执行
  ElseIf Not objFile.Exists Then
   ' 无法找到被请求的文件
   objResponse.StatusCode = 404 ' 没有找到
  ElseIf objFile.Length > Int32.MaxValue Then
   ' 文件太大了
   objResponse.StatusCode = 413 ' 请求实体太大
  ElseIf Not ParseRequestHeaderRange(objRequest, alRequestedRangesBegin, alRequestedRangesend, _
  objFile.Length, bIsRangeRequest) Then
   ' Range请求中包含无用的实体
   objResponse.StatusCode = 400 ' 无用的请求
  ElseIf Not CheckIfModifiedSince(objRequest,objFile) Then
   ' 实体没有被修改过
   objResponse.StatusCode = 304 ' 没有被修改过
  ElseIf Not CheckIfUnmodifiedSince(objRequest,objFile) Then
   ' 实体在上次被请求的日期之后被修改过
   objResponse.StatusCode = 412 ' 预处理失败
  ElseIf Not CheckIfMatch(objRequest, objFile) Then
   ' 实体与请求不匹配
   objResponse.StatusCode = 412 ' 预处理失败
  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(0)>0)
   ' 进入每个范围来获取整个响应长度
   For iLoop = alRequestedRangesBegin.GetLowerBound(0) To alRequestedRangesBegin.GetUpperBound(0)
    ' 内容的长度(这个范围的)
    iResponseContentLength += Convert.ToInt32(alRequestedRangesend( _
  iLoop) - alRequestedRangesBegin(iLoop)) + 1
    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
     ' 49是多部分下载中换行和其它必要的字符的长度
     iResponseContentLength += 49
    End If
   Next iLoop
  
   If bMultipart Then
    ' 如果是多部分范围请求,
    ' 我们还必须计算出将发送的最后一个中间头信息的长度
    iResponseContentLength +=MULTIPART_BOUNDARY.Length
    ' 8 是破折号和换行符的长度
    iResponseContentLength += 8
   Else
    ' 不是多部分下载,因此我们必须说明初始HTTP头信息的响应范围
    objResponse.AppendHeader( HTTP_HEADER_CONTENT_RANGE, "bytes " & _
    alRequestedRangesBegin(0).ToString & "-" & _
    alRequestedRangesend(0).ToString & "/" & _
    objFile.Length.ToString)
    'End If
    ' 范围响应
    objResponse.StatusCode = 206 ' 局部响应
   Else
    ' 这不是范围请求,或者被请求的范围实体ID与当前的实体ID不匹配,
    ' 因此开始新的下载
    ' 指明文件完成部分的大小等于内容的长度
    iResponseContentLength =Convert.ToInt32(objFile.Length)
    ' 返回正常的OK状态
    objResponse.StatusCode = 200
   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.ContentType
  End 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枚举,它描述了下载请求的多种状态:
  
  Enum DownloadState
   ' Clear:没有下载过程,文件可能在维护
   fsClear = 1
   ' Locked:动态建立的文件不能被更改
   fsLocked = 2
   ' In Progress:文件被锁定了,下载过程正在进行
   fsDownloadInProgress = 6
   ' Broken:文件被锁定了,下载过程正在进行,但是被取消了
   fsDownloadBroken = 10
   ' Finished:文件被锁定了,下载过程完成了
   fsDownloadFinished = 18
  End Enum
  
    FileInformation还提供了EntityTag属性值。示例代码中的这个值是硬编码的,这是由于示例代码只使用了一个下载文件,并且该文件不会被改变,但是对于实际应用程序来说,你会提供多个文件,甚至于动态地建立文件,你的代码必须为每个文件提供一个唯一的EntityTag值。此外,每次改变或修改该文件的时候,这个值也必须改变。这使客户端软件能够验证它们已经下载的字节块是否仍然是最新的。下面是示例代码中返回硬编码EntityTag值的部分:
  
  Public ReadOnly Property EntityTag() As String
   ' EntityTag用于对客户端的初始(200)响应,以及来自客户端的恢复请求
   Get
    ' 为文件建立唯一的字符串。
    ' 注意,只要文件没有发生改变,该唯一码就必须保留。
    ' 但是,如果文件的确改变了或者被修改了,这个码必须改变。
    Return "MyExampleFileID"
   End Get
  End 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 Set
  End Property
  
    在文件状态发生改变的任何时候ZipFileHandler都应该调用Save方法,保存文件的状态,这样在以后才能显示给用户。你还可以用它来保存你自己建立的EntityTag。请不要把文件的状态和EntityTag值保存在Application、Session或Cache中--你必须跨越所有的这些这些对象的生命周期来保存信息。
  
  Private Sub Save()
   ' 把该文件下载的状态保存到数据库或XML文件中。
   ' 当然,如果你并没有动态地建立文件,就不需要保存这个状态。
  End Sub
  
    前面提到,示例代码只处理一个已有的文件(download.zip),但是你可以进一步增强这个程序,根据需要建立被请求的文件。
  
    测试示例代码的时候,你的本地系统或LAN可能太快了,以至于无法中断下载过程,因此我推荐你使用慢速LAN连接(在IIS中减少站点的带宽是一种模拟的方法)或者把服务器放到互联网上。
  
    在客户端上下载文件仍然很艰难。ISP操作的不对的或配置错误的Web缓冲服务器都可能使大文件下载过程失败,包括下载状况恶化或早期对话终结。如果文件大小超过了255MB,你就应该鼓励顾客使用第三方下载管理软件,尽管某些最新的浏览器内建了基本的下载管理器。
  
   如果你希望进一步扩展示例代码,查阅一下HTTP规范是有益的。你可以为下载建立MD5校验值,使用Content-MD5头信息添加它们,提供一种验证下载文件完整性的途径。示例代码除了GET和HEAD之外没有涉及到其它的HTTP方法。



电话咨询:010-60520722 QQ咨询:3792656   |   583696287   |   66733350 关于WebDN  |  站点地图  |  联系我们  |  支付方式  |  友情链接
© 2004-2008 WebDN.com 版权所有. 沪ICP备05040479号