首页 > 编程语言 >C# HTTP 断点续传

C# HTTP 断点续传

时间:2023-11-13 11:37:07浏览次数:27  
标签:断点续传 HTTP C# bytes startBytes Content httpContext Response

在IIS中,磁盘路径对应的文件是可以直接下载的,而原生的IIS并不需要额外的配置就可以进行断点续传。而在小猪的项目中使用到的文件下载地址不对应磁盘路径的文件地址,而是需要验证用户是否有权限进行下载然后使用使用fileresult提供文件下载。这样整个下载过程都需要自己动手写代码完成。为了使客户端的体验更佳,所以必须要提供断点续传的功能。

断点续传的原理

其实断点续传的原理很简单,就是在 Http 的请求上和一般的下载有所不同而已。

打个比方,浏览器请求服务器上的一个文时,所发出的请求如下:
假设服务器域名为 wwww.smallerpig.com,文件名为 down.zip。

200 

Content-Length=106786028  Accept-Ranges=bytes   Date=Mon, 30 Apr 2001 12:56:11 GMT   ETag=W/"02ca57e173c11:95b"  Content-Type=application/octet-stream   Server=Microsoft-IIS/5.0  Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

服务器收到请求后,按要求寻找请求的文件,提取文件的信息,然后返回给浏览器,返回信息如下:

200  Content-Length=106786028  Accept-Ranges=bytes   Date=Mon, 30 Apr 2001 12:56:11 GMT   ETag=W/"02ca57e173c11:95b"  Content-Type=application/octet-stream   Server=Microsoft-IIS/5.0  Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web 服务器的时候要多加一条信息 -- 从哪里开始。
下面是用自己编的一个"浏览器"来传递请求信息给 Web 服务器,要求从 2000070 字节开始。

GET /down.zip HTTP/1.0  User-Agent: NetFox   RANGE: bytes=2000070-   Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

仔细看一下就会发现多了一行 RANGE: bytes=2000070-
这一行的意思就是告诉服务器 down.zip 这个文件从 2000070 字节开始传,前面的字节不用传了。
服务器收到这个请求以后,返回的信息如下:

206  Content-Length=106786028  Content-Range=bytes 2000070-106786027/106786028  Date=Mon, 30 Apr 2001 12:55:20 GMT   ETag=W/"02ca57e173c11:95b"  Content-Type=application/octet-stream   Server=Microsoft-IIS/5.0  Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT

和前面服务器返回的信息比较一下,就会发现增加了一行:

Content-Range=bytes 2000070-106786027/106786028

返回的代码也改为 206 了,而不再是 200 了。

知道了以上原理,就可以进行断点续传的编程了。

C#实现断点续传

/// <summary> /// 支持断点续传 /// </summary> /// <param name="httpContext"></param> /// <param name="filePath"></param> /// <param name="speed"></param> /// <returns></returns> public static bool DownloadFile(HttpContext httpContext, string filePath, long speed) {     bool ret = true;     try     {         #region--验证:HttpMethod,请求的文件是否存在         switch (httpContext.Request.HttpMethod.ToUpper())         { //目前只支持GET和HEAD方法               case "GET":             case "HEAD":                 break;             default:                 httpContext.Response.StatusCode = 501;                 return false;         }         if (!System.IO.File.Exists(filePath))         {             httpContext.Response.StatusCode = 404;             return false;         }         #endregion              #region 定义局部变量         long startBytes = 0;         int packSize = 1024 * 40; //分块读取,每块40K bytes           string fileName = Path.GetFileName(filePath);         FileStream myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);         BinaryReader br = new BinaryReader(myFile);         long fileLength = myFile.Length;              int sleep = (int)Math.Ceiling(1000.0 * packSize / speed);//毫秒数:读取下一数据块的时间间隔           string lastUpdateTiemStr = System.IO.File.GetLastWriteTimeUtc(filePath).ToString("r");         string eTag = HttpUtility.UrlEncode(fileName, Encoding.UTF8) + lastUpdateTiemStr;//便于恢复下载时提取请求头;           #endregion              #region--验证:文件是否太大,是否是续传,且在上次被请求的日期之后是否被修         if (myFile.Length > Int32.MaxValue)         {//-------文件太大了-------               httpContext.Response.StatusCode = 413;//请求实体太大               return false;         }              if (httpContext.Request.Headers["If-Range"] != null)//对应响应头ETag:文件名+文件最后修改时间           {             //----------上次被请求的日期之后被修改过--------------               if (httpContext.Request.Headers["If-Range"].Replace("\"", "") != eTag)             {//文件修改过                   httpContext.Response.StatusCode = 412;//预处理失败                   return false;             }         }         #endregion              try         {             #region -------添加重要响应头、解析请求头、相关验证-------------------             httpContext.Response.Clear();             httpContext.Response.Buffer = false;             httpContext.Response.AddHeader("Content-MD5",Common.ASE.GetMD5Hash(filePath));//用于验证文件             httpContext.Response.AddHeader("Accept-Ranges", "bytes");//重要:续传必须               httpContext.Response.AppendHeader("ETag", "\"" + eTag + "\"");//重要:续传必须               httpContext.Response.AppendHeader("Last-Modified", lastUpdateTiemStr);//把最后修改日期写入响应                               httpContext.Response.ContentType = "application/octet-stream";//MIME类型:匹配任意文件类型               httpContext.Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fileName, Encoding.UTF8).Replace("+", "%20"));             httpContext.Response.AddHeader("Content-Length", (fileLength - startBytes).ToString());             httpContext.Response.AddHeader("Connection", "Keep-Alive");             httpContext.Response.ContentEncoding = Encoding.UTF8;             if (httpContext.Request.Headers["Range"] != null)             {//------如果是续传请求,则获取续传的起始位置,即已经下载到客户端的字节数------                   httpContext.Response.StatusCode = 206;//重要:续传必须,表示局部范围响应。初始下载时默认为200                   string[] range = httpContext.Request.Headers["Range"].Split(new char[] { '=', '-' });//"bytes=1474560-"                   startBytes = Convert.ToInt64(range[1]);//已经下载的字节数,即本次下载的开始位置                     if (startBytes < 0 || startBytes >= fileLength)                 {//无效的起始位置                       return false;                 }             }             if (startBytes > 0)             {//------如果是续传请求,告诉客户端本次的开始字节数,总长度,以便客户端将续传数据追加到startBytes位置后----------                   httpContext.Response.AddHeader("Content-Range", string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength));             }             #endregion                  #region -------向客户端发送数据块-------------------             br.BaseStream.Seek(startBytes, SeekOrigin.Begin);             int maxCount = (int)Math.Ceiling((fileLength - startBytes + 0.0) / packSize);//分块下载,剩余部分可分成的块数               for (int i = 0; i < maxCount && httpContext.Response.IsClientConnected; i++)             {//客户端中断连接,则暂停                   httpContext.Response.BinaryWrite(br.ReadBytes(packSize));                 httpContext.Response.Flush();                 if (sleep > 1) Thread.Sleep(sleep);             }             #endregion         }         catch         {             ret = false;         }         finally         {             br.Close();             myFile.Close();         }     }     catch     {         ret = false;     }     return ret; }
 

参考文章:http://blog.ncmem.com/wordpress/2023/11/13/c-http-%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/

欢迎入群一起讨论

 

 

标签:断点续传,HTTP,C#,bytes,startBytes,Content,httpContext,Response
From: https://www.cnblogs.com/songsu/p/17828754.html

相关文章

  • 浅尝poc编写工具+漏洞验证xpoc
    一、快速编写poc工具为了方便打点找到了一个在线编写poc的网站:https://poc.xray.cool/ 可配合xpoc批量利用。在线版(个人觉得本地版好用)  本地版(可验证是否有效):①windows下载链接:https://ctstack-oss.oss-cn-beijing.aliyuncs.com/tool/c39865a939edf5d7f4a37017c......
  • 怎么在window上自动获取crash dump
     保存如下内容到opendump.bat,已管理员权限打开文件@echooffecho正在启用Dump...regadd"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\WindowsErrorReporting\LocalDumps"regadd"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\WindowsErrorReporting\Loc......
  • 无涯教程-Dart - 包(Package)
    包Packages是一种封装机制,每种语言都有一种管理外部程序包的机制,如Java的Maven或Gradle,.NET的Nuget,Node.js的npm等,Dart的程序包管理器是pub 包元数据在文件pubsec.yaml中定义,YAML是另一种标签语言的缩写,pub工具可用于下载应用程序所需的所有各种库。每个Dart应用程序都......
  • LeetCode-283移动0
    给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。题解双指针:初始化双指针i、j;当前指针j所指位置为0时,i不动,j++;指针j所指位置不为0时,将j所指位置指向i指针位置,i++,j++;当指针j......
  • cmd网络命令(七)
    上文介绍了ping命令,继续tracert和arptracertTracert命令就是个路由跟踪命令,是一个检测路由节点数的一个网络命令。举例说,如果我访问百度域名www.baidu.com,使用这个命令进行跟踪检测,该命令就可以列出从您自己的上网IP地址到访问www.baidu.com网站IP所经过的所有服务器(路由)。这就是路......
  • A Latent Hidden Markov Model for Process Data读文献笔记
    【个人笔记】:笔记(ALatentHiddenMarkovModelforProcessData)\SummaryResponseprocessdatafromcomputer-basedproblem-solvingitemsdescriberespondents'problem-solvingprocessesassequencesofactions.Suchdataprovideavaluablesourcefor......
  • 神经网络入门篇:详解多样本向量化(Vectorizing across multiple examples)
    多样本向量化与上篇博客相联系的来理解逻辑回归是将各个训练样本组合成矩阵,对矩阵的各列进行计算。神经网络是通过对逻辑回归中的等式简单的变形,让神经网络计算出输出值。这种计算是所有的训练样本同时进行的,以下是实现它具体的步骤:图1.4.1上篇博客中得到的四个等式。它们......
  • 查看 IP 地址和 MAC 地址映射
    一、运行WIN+R打开命令提示窗 二、ARP(地址解析协议)-----------------------------------拓展:ARP命令使用方法----------------------------------ARP缓存中包含一个或多个表,它们用于存储IP地址及其经过解析的MAC地址。ARP命令用于查询本机ARP缓存中IP地址-->MAC地址的对......
  • 归并排序C++实现
    把归并排序看作二叉树的后序遍历的确是一种好的思路,使代码的逻辑更加清晰。但是具体实现还是遇到了很多的困难。有太多报错,自己也觉得有些莫名其妙。但是事后看来,大多数还是自己粗心所致。以后都可以采用这个框架来处理需要归并排序的问题。classSolution{public:vect......
  • CF17E Palisection
    改进了一下@\(\bf{\color{black}\text{唐}\color{red}\text{一文}}\)大佬的做法。tags:\(\text{strings}\)\(\color{red}*2900\)洛谷CF给出一个字符串\(s\),求\(s\)有多少对相交的回文子串(不考虑顺序)。\(|s|\le2\times10^6\)。\(2.00\,\text{s}\,/\,1......