科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件在ASP.NET中跟踪和恢复大文件下载

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

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在Web应用程序中处理大文件下载的问题一直出了名的困难,因此对于大多数站点来说

作者:陶刚编译 来源:天极网 2007年11月7日

关键字: Windows

  • 评论
  • 分享微博
  • 分享邮件
示例代码

  我们知道了客户端和服务器如何交换头信息以保证可恢复的下载,把这些知识与文件块流的思想结合起来,你就可以给自己的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示例文件的路径硬编码到代码中了。如果把这段代码应用于你自己的应用程序,需要修改它来打开被请求的文件。

' 使用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 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(见下载代码)。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章