前言

之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有当你用到再去看相关内容时才会掌握的更加牢固,理解的更加透彻吧,下面我们首先来补补关于http协议中断点续传的知识。

http协议知识恶补

当请求一个html页面时我们会看到请求页面如下:

第一眼看到上面Accept中的参数时我是懵逼的,之前也就看看缓存cookie等常见的头信息,于是借此机会也学习下这部分内容。

我们知道Accept是指客户端允许请求返回的内容类型,那为何这里面参数有如此之多呢?在学习WebAPi时,我们在服务端未进行过滤时既可以返回xml,也可以返回json,此时如上图一样,text/html未匹配上,接着匹配xml类型,匹配后则进行相应格式内容返回,所以客户端接受如此多类型内容,也是为了服务端那边未设置特定内容响应,此时则根据客户端设置的内容进行最合适的匹配。

那么问题来了,上面的q是啥玩意?

q(quality)

上面给出了客户端能够接受响应的内容类型,自然就有最合适的匹配,此时就用到了q这个参数,在此我将q翻译为quality即权重的意思,应该是比较合适的,它用来表示我们期待接受内容偏爱的程度即所占的权重。它的范围是0-1,其默认值为1,这就类似质检部门对产品合格判断的一种介质。例如当我们需要返回视频资源时,我们客户端设置为如下:

  1. Accept: audio/*; q=0.2, audio/basic

此时我们将上述翻译如下:

  1. audio/basic; q=
  2. audio/*; q=0.2

我们更加期待返回的是audio/basic类型的资源,因为其权重为1大于audio/*类型的资源,若为匹配到则继续匹配下一个资源,audio/*则表示属于audio类型的所有子类型资源。

接下来,我们再来看一个例子:

  1. Accept: text/plain; q=0.5, text/html,text/x-dvi; q=0.8, text/x-c

此时我们则可以翻译为如下:

  1. Accept:
  2. text/html;q=1或者 text/x-c;q=
  3. text/x-dvi; q=0.8
  4. text/plain; q=0.5

倾向于返回text/html或者text/x-c类型资源,若都不存在,则返回权重为0.8的text/x-dvi,最终还是不存在则返回text/plain。

Accept-Ranges

在响应头中添加此字段允许服务端来显示表明对资源范围的接受。如果服务端接受一个字节范围的资源的请求则此时变成如下:

  1. Accept-Ranges: bytes

如果服务端不接受任何范围的请求资源此时则在响应头添加如下来告诉客户端不要发送范围请求的资源:

  1. Accept-Ranges: none

Content-Range

当在响应头中添加接受字节范围的资源时,此时若客户端请求资源文件比较大时即只是返回部分数据时,此时则返回状态码为206的部分内容,在Content-Range响应头信息中实时显示当前数据的进度。比如如下:

  1. //开始500个字节数据
  2. Content-Range: bytes -/
  3.  
  4. //第二个500个字节数据
  5. Content-Range: bytes -/
  6.  
  7. //除了开始500个字节之外的数据
  8. Content-Range: bytes -/
  9.  
  10. //最后500个字节数据(表示数据最终传输完毕)
  11. Content-Range: bytes -/

如果客户端请求资源到达所给资源的界限此时则返回416的状态码。

注意:当请求资源为字节范围请求时,不要在响应头中使用 multipart/byteranges 类型的content-type。

断点续传场景

当正在下载时出于其他任何原因此时下载中断,那么下载用户只能重新下载,这样的体验想必是比较痛苦的,最烦躁的是如果用户是在移动端下载大文件时,居然下载中断了,接下来又得重新下载,此时想必用户会放弃下载。此时断点续传则应运而生。 断点续传则需要用到上述Accept-Ranges和Content-Range将其添加到响应头中。例如如下:

  1. HEAD http://localhost/api/files/get?filename=blog_backup.zip
  2. User-Agent: IIS
  3. Host: localhost
  4.  
  5. HTTP/1.1 OK
  6. Content-Length:
  7. Content-Type: application/octet-stream
  8. Accept-Ranges: bytes
  9. Server: Microsoft-IIS/10.0
  10. Content-Disposition: attachment; filename=blog_backup.zip
  1. HEAD http://localhost/api/files/get?filename=blog_backup.zip
  2. User-Agent: IIS
  3. Host: localhost
  4. Range: bytes=-
  5.  
  6. HTTP/1.1 Partial Content
  7. Content-Length:
  8. Content-Type: application/octet-stream
  9. Content-Range: bytes -/
  10. Accept-Ranges: bytes
  11. Server: Microsoft-IIS/10.0
  12. Content-Disposition: attachment; filename=blog_backup.zip

接下来我们来实现简单的下载以及断点续传下载对比看看效果。

在webapi中提供了一系列方便我们调用的api,比如 ContentDispositionHeaderValue 来设置附件而不像在webform中手动在响应头中进行拼接。以及返回的MimeType类型 MediaTypeHeaderValue 。首先我们看看最普通的下载。

普通下载

普通的下载无非就是获取到文件的标识再打开下载的文件夹,最后得到文件流返回到响应的HttpContent对象中以及设置附件即可。我们看看如下代码还是比较简单的,这种相对比较简单的下载想必我们大家定是信手拈来。

  1. //响应的MimeType类型
  2. private const string MimeType = "application/octet-stream";
  3.  
  4. //配置文件中配置的文件所在路径
  5. private const string AppSettingDirPath = "DownloadDir";
  6.  
  7. //将配置文件中取得的路径赋给此变量
  8. private readonly string DirFilePath;
  9.  
  10. this.DirFilePath = ConfigurationManager.AppSettings[AppSettingDirPath];

接下来就是最重要的下载逻辑了,如下:

  1. public HttpResponseMessage Download(string fileName)
  2. {
  3. var fullFilePath = Path.Combine(this.DirFilePath, fileName);
  4.  
  5. if (!File.Exists(fullFilePath))
  6. {
  7. throw new HttpResponseException(HttpStatusCode.NotFound);
  8. }
  9.  
  10. FileStream fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  11.  
  12. var response = new HttpResponseMessage();
  13.  
  14. response.Content = new StreamContent(fileStream);
  15.  
  16. response.Content.Headers.ContentDisposition
  17. = new ContentDispositionHeaderValue("attachment") { FileName = fileName };
  18.  
  19. response.Content.Headers.ContentType
  20. = new MediaTypeHeaderValue(MimeType);
  21.  
  22. response.Content.Headers.ContentLength
  23. = fileStream.Length;
  24.  
  25. return response;
  26. }

那么问题来了,我们可不可以在获取文件流返回到HttpContent之前是不是应该首先将文件流放入到缓冲流中然后再返回呢?如下:

  1. var bufferStream = new BufferedStream(fileStream);
  2. response.Content = new StreamContent(bufferStream);

我们想着是不是将文件流率先放入到缓冲流中效果是否更佳呢?刚开始我也是这样想来着,但是经过查证资料发现:

为了得到更好的性能,在文件流中已经包含有缓冲流的缓冲逻辑,对于用缓冲流来包裹文件流的情况没有任何好处,还有一点就是在.NET Framework中没有任何一个流需要用到缓冲流,但是,但是有一种情况除外则是若我们自定义实现流且默认没有实现缓冲的逻辑情况下需要用到缓冲流,资料来源于:Filestream and BufferedStream

上述也算是涨知识了。继续回到我们的话题,此时我们下载一个文件则看到如下图所示:

因为未实现断点续传,此时我们通过右键可以看到无法暂停,如下:

我们继续往下走,接下来来实现断点续传看看:

断点续传下载

在WebAPi提供了Range属性其返回对象为 RangeHeaderValue 里面有存在每个范围的集合如下:

  1. // 摘要:
  2. // Gets the ranges specified from the System.Net.Http.Headers.RangeHeaderValue
  3. // object.
  4. //
  5. // 返回结果:
  6. // Returns System.Collections.Generic.ICollection<T>.The ranges from the System.Net.Http.Headers.RangeHeaderValue
  7. // object.
  8. public ICollection<RangeItemHeaderValue> Ranges { get; }

这是为利用多线程下载而提供,这里我们仅仅实现一个范围的下载。我们通过判断这个对象的值是否为null来实现断点续传。

  1. if (Request.Headers.Range == null ||
  2. Request.Headers.Range.Ranges.Count == ||
  3. Request.Headers.Range.Ranges.FirstOrDefault().From.Value == )
  4. {
  5. var sourceStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
  6.  
  7. response = new HttpResponseMessage(HttpStatusCode.OK);
  8. response.Content = new StreamContent(sourceStream);
  9. response.Headers.AcceptRanges.Add("bytes");//告诉客户端接受资源为字节
  10. response.Content.Headers.ContentLength = sourceStream.Length;
  11. response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);
  12. response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
  13. {
  14. FileName = fileName
  15. };
  16. }

获取当前已经下载字节数,接着继续进行剩下字节下载。

  1. else
  2. {
  3. var item = Request.Headers.Range.Ranges.FirstOrDefault();
  4. if (item != null && item.From.HasValue)
  5. {
  6. response = this.GetPartialContent(fileName, item.From.Value);
  7. }
  8. }

剩余字节数下载

  1. private HttpResponseMessage GetPartialContent(string fileName, long partial)
  2. {
  3. var response = new HttpResponseMessage();
  4. var fullFilePath = Path.Combine(this.DirFilePath, fileName);
  5. FileInfo fileInfo = new FileInfo(fullFilePath);
  6. long startByte = partial;
  7. var memoryStream = new MemoryStream();
  8. var buffer = new byte[];
  9. using (var fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
  10. {
  11. var bytesRead = ;
  12. fileStream.Seek(startByte, SeekOrigin.Begin);
  13. int length = Convert.ToInt32((fileInfo.Length - ) - startByte) + ;
  14.  
  15. while (length > && bytesRead > )
  16. {
  17. bytesRead = fileStream.Read(buffer, , Math.Min(length, buffer.Length));
  18. memoryStream.Write(buffer, , bytesRead);
  19. length -= bytesRead;
  20. }
  21. response.Content = new StreamContent(memoryStream);
  22. }
  23. response.Headers.AcceptRanges.Add("bytes");
  24. response.StatusCode = HttpStatusCode.PartialContent;
  25. response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);
  26. response.Content.Headers.ContentLength = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read).Length;
  27. response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
  28. {
  29. FileName = fileName
  30. };
  31. return response;
  32. }

接下来我们看看演示结果:

从上面演示我们看出目前已经实现了断点续传,浏览器下载管理器出现了暂停的按钮,但是当暂停后无法继续进行后续下载,在这里存在问题,我们下节再进行后续讲解。同时当返回HttpContent发现居然还有一个可以返回的HttpContent即 PushStreamContent ,此时我们可以将剩余部分字节下载进行如下修改:

  1. Action<Stream, HttpContent, TransportContext> pushContentAction = (outputStream, content, context) =>
  2. {
  3. try
  4. {
  5. var buffer = new byte[];
  6. using (var fileStream = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
  7. {
  8. var bytesRead = ;
  9. fileStream.Seek(startByte, SeekOrigin.Begin);
  10. int length = Convert.ToInt32((fileInfo.Length - ) - startByte) + ;
  11.  
  12. while (length > && bytesRead > )
  13. {
  14. bytesRead = fileStream.Read(buffer, , Math.Min(length, buffer.Length));
  15. outputStream.Write(buffer, , bytesRead);
  16. length -= bytesRead;
  17. }
  18.  
  19. }
  20. }
  21. catch (HttpException ex)
  22. {
  23. throw ex;
  24. }
  25. finally
  26. {
  27. outputStream.Close();
  28. }
  29. };
  30.  
  31. response.Content = new PushStreamContent(pushContentAction, new MediaTypeHeaderValue(MimeType));
  32. response.StatusCode = HttpStatusCode.PartialContent;
  33. response.Headers.AcceptRanges.Add("bytes");
  34. response.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeType);
  35. response.Content.Headers.ContentLength = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read).Length;
  36. response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
  37. {
  38. FileName = fileName
  39. };
  40. return response;

如上所做也可行,返回StreamContent不就ok了吗,为何还出现一个PushStreamContent呢?这又是一个遗留问题!

总结

本节我们讲述了在webapi中普通下载以及断点续传下载,对于断点续传下载当暂停后无法继续进行下载,暂时还存在一定问题,对于返回的内容既可以为StreamContent,也可以是PushStreamContent,这二者有何区别呢?二者的应用场景是什么呢?这又是一个问题,关于此二者我们下节再讲,webapi一个很轻量的服务框架,你值得拥有,see u。

ASP.NET WebAPi之断点续传下载(上)的更多相关文章

  1. ASP.NET WebAPi之断点续传下载(中)

    前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...

  2. ASP.NET WebAPi之断点续传下载(下)

    前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...

  3. NET WebAPi之断点续传下载1

    ASP.NET WebAPi之断点续传下载(上)   前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫 ...

  4. NET WebAPi之断点续传下载(下)

    NET WebAPi之断点续传下载(下) 前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载 ...

  5. ASP.NET如何实现断点续传的上传、下载功能?

    1 背景 用户本地有一份txt或者csv文件,无论是从业务数据库导出.还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工.挖掘和共创应用的时候,首先要将本地文件上传至ODPS,普通的小文件通 ...

  6. 在ASP.NET中支持断点续传下载大文件(ZT)

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag         客户端每次提交 ...

  7. 在ASP.NET中支持断点续传下载大文件

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag        客户端每次提交下 ...

  8. ASP.NET WebAPI数据传输安全HTTPS实战项目演练

    一.课程介绍 HTTPS是互联网 Web 大势所趋,各大网站都已陆续部署了 HTTPS .  全站HTTPS时代,加密用户与网站间的交互访问,在客户端浏览器和Web服务器之间建立安全加密通道,一般情况 ...

  9. Asp.Net Core WebApi 和Asp.Net WebApi上传文件

    public class UpLoadController : ControllerBase { private readonly IHostingEnvironment _hostingEnviro ...

随机推荐

  1. 关于H5框架之Bootstrap的小知识

    浏览器支持 旧的浏览器可能无法很好的支持 Bootstrap 支持 Internet Explorer 8 及更高版本的 IE 浏览器 CSS源码研究 我们不是在head里面引入了下面这些文件么 &l ...

  2. git上传代码到osc@git

    1.get an account 2.get a ssh-key 3.git setting git config --global user.name "...." git co ...

  3. bootstrap之clearfix

    bootstrap之clearfix 在bootstrap辅助类中有那么一类,是这么定义的: 利用clearfix样式清除浮动,但是前提条件是在超小型屏幕上能显示才行(因为其是用visible-xs样 ...

  4. C#详解format函数,各种格式化

    一.String Format for Double Digits after decimal point This example formats double to string with fix ...

  5. 大BOSS随时都会到来

    郑昀(微博:http://weibo.com/yunzheng) 去年在上市前后,我不止一次跟大家说过如下内容: 我们这帮兄弟第一精通业务,第二有丰富的战斗经验和规范,你们都是中流砥柱,都要带兵打仗. ...

  6. 浏览器的兼容问题 判断IE方法

    下面是一些判断ie的常用方法: <!-[if IE 6]> 此处是IE6才会执行的代码 <![endif]-> 还可以给他加个条件,比如判断IE6以下的浏览器: <!-[ ...

  7. Centos 7 安装 设置 IP地址,DNS,主机名,防火墙,端口,SELinux (实测+笔记)

    环境: 系统硬件:vmware vsphere (CPU:2*4核,内存2G,双网卡) 系统版本:CentOS-7.0-1406-x86_64-DVD.iso 安装步骤: 1.虚拟系统安装 1.1 使 ...

  8. iOS开发查看手机app本地存储的文件

    开发过程中,有时会在本地存储一些文件,但是我们不确定有没有存上,可以通过以下方法来查看测试手机上本地存储的文件: 1.选择xcode上面的window下面的Devices 2.先在左边选中你当前的设备 ...

  9. MyBatis的经典案例

    1.首先我们先了解Mybatis的一些jar包 ---和项目框架 2.接下来就看看mybatis的配置文件(mybatis-config.xml) <?xml version="1.0 ...

  10. Java概念性问题

    一.变量命名的五个要素 由字母.数字.“_”和“$” 组成 首字母不能为数字 大小写敏感 不能使用Java的保留字和关键字 可以使用中文命名,但是不建议 二.java的基本数据类型 整数类型:byte ...